@fluid-app/fluid-cli-member-site 0.1.29 → 0.1.30
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 +21 -18
- package/dist/index.mjs +0 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["field","delay","refused","defaultDeps","isRecord","PAGE_SLUG_PATTERN","vocabulary.fields","vocabulary.families","vocabulary.modes","vocabulary.maxEntries","vocabulary.maxBytes","defaultDeps","defaultDeps","defaultDeps","settingTypesJson.types","(pathScopes as { entries: PathScope[] }).entries","defaultDeps"],"sources":["../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/api/site-api.ts","../src/api/errors.ts","../src/api/http.ts","../src/api/credential.ts","../src/commands/run.ts","../src/commands/create.ts","../src/dev/local-guard.ts","../src/dev/port-preflight.ts","../src/dev/preview-proxy.ts","../src/dev/watcher.ts","../src/tree/binding.ts","../src/tree/folder.ts","../src/commands/bound-site.ts","../src/tree/local-tree.ts","../src/tree/pages-config.ts","../src/tree/access-vocabulary.json","../src/tree/access-declaration.ts","../src/tree/push-plan.ts","../src/commands/publish.ts","../src/commands/push.ts","../src/commands/dev.ts","../src/display.ts","../../../platform/theme-schema/src/setting-types.json","../../../platform/theme-schema/src/types.ts","../../../platform/theme-schema/src/validate-settings.ts","../../../platform/theme-schema/src/validate-blocks.ts","../../../platform/theme-schema/src/validate.ts","../../../platform/theme-schema/src/sections.ts","../../../platform/theme-schema/src/liquid-blocks.ts","../src/lint/path-scopes.json","../src/lint/fluid-fetch.ts","../src/lint/lint-folder.ts","../src/commands/lint.ts","../src/commands/list.ts","../src/commands/pull.ts","../src/commands/member-site.ts","../src/index.ts"],"sourcesContent":["/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, else the field errors in its `error.details`, with its `error.message`\n * as `error.message`, when the body has that, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * Rails sends two error envelopes. `ControllerAction#error_response` puts the\n * bag under `errors`; `Api::Response.error` puts it under `error.details`,\n * beside `error.message`. Both unwrap to the bag, `errors` first.\n *\n * `!= null` rather than a truthiness check so an explicitly empty `errors`\n * (`\"\"`, `0`, `false`) is preserved instead of silently falling back to the\n * whole envelope.\n * `details` is taken whatever its value, `null` included, so data from an\n * `Api::Response` error is never the envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n if (body.errors != null) {\n return body.errors as ApiErrorData;\n }\n\n const error = toApiErrorBody(body.error);\n if (error && error.details !== undefined) {\n return withErrorMessage(toFieldErrorDetails(error.details), error.message);\n }\n\n return body as ApiErrorData;\n}\n\n/**\n * `Api::Response` errors carry diagnostics such as `{ type: \"not_provisioned\" }`\n * or `{ violations: [{ type, description }] }` in `details` as readily as\n * field errors, and sometimes both. A field error is a list of message\n * strings, at any depth, so only those entries make up the bag; an `errors`\n * wrapper inside `details` is unwrapped as the envelope's own is. Details with\n * no field error unwrap to an empty bag. The diagnostics stay on\n * `ApiError.body`.\n */\nfunction toFieldErrorDetails(details: unknown): ApiErrorData {\n if (details === null) {\n return null;\n }\n\n const record = toApiErrorBody(details);\n const bag =\n record && record.errors != null ? toApiErrorBody(record.errors) : record;\n\n return ((bag && pickFieldErrors(bag)) ?? {}) as ApiErrorData;\n}\n\n/**\n * Telling a field error from a diagnostic list such as\n * `{ missing_keys: [\"coupons.create\"] }` by its content cannot be exact, so\n * the bag also carries `error.message` as `{ error: { message } }`, after the\n * field errors. `parseApiErrors` and the inlined `fluidToast` copies render\n * that nested form first and without a field prefix, so the server's message\n * is never hidden by whatever reaches the bag.\n */\nfunction withErrorMessage(\n fieldErrors: ApiErrorData,\n message: unknown,\n): ApiErrorData {\n if (typeof message !== \"string\" || message.trim().length === 0) {\n return fieldErrors;\n }\n\n const bag = toApiErrorBody(fieldErrors) ?? {};\n if (\"error\" in bag) {\n return fieldErrors;\n }\n\n return { ...bag, error: { message } };\n}\n\nfunction pickFieldErrors(\n record: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const picked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(record)) {\n const nested = toApiErrorBody(value);\n const fieldErrors = isMessageList(value)\n ? value\n : nested && pickFieldErrors(nested);\n\n if (fieldErrors) {\n picked[key] = fieldErrors;\n }\n }\n\n return Object.keys(picked).length > 0 ? picked : undefined;\n}\n\nfunction isMessageList(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((message) => typeof message === \"string\")\n );\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n\n /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n priority?: RequestInit[\"priority\"];\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\n );\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]. The OpenAPI-declared\n // param name is sometimes already bracketed (e.g. \"product_ids[]\",\n // \"filters[integration_class][]\") to match Rails' own convention;\n // appending another \"[]\" in that case would double-bracket the wire\n // format (key[][]=1), which Rails does not parse as the array.\n const arrayKey = key.endsWith(\"[]\") ? key : `${key}[]`;\n value.forEach((item) => queryString.append(arrayKey, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n const headerRequestId = getRequestIdFromHeaders(response.headers);\n\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n priority,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import type { HttpClient } from \"./http.js\";\nimport type {\n CreatePageInput,\n CreateSiteInput,\n ListSitesOptions,\n OwnerKind,\n Site,\n SiteApi,\n SiteFile,\n SiteFileEntry,\n SitePage,\n UpdatePageInput,\n WriteFileInput,\n} from \"./types.js\";\n\n/** What a response or a request body carries. */\ntype Resource = \"site\" | \"sites\" | \"file\" | \"files\" | \"page\" | \"pages\";\n\n/**\n * How one API wraps its payloads. The member API answers `{ data }` and takes\n * flat bodies. The company API names the payload after the resource\n * (`member_site`, `member_site_files`) and nests a body under the same name.\n */\ninterface Envelope {\n payload(response: unknown, resource: Resource): unknown;\n body(resource: \"site\" | \"file\" | \"page\", fields: object): object;\n}\n\ninterface Dialect {\n readonly ownerKind: OwnerKind;\n /** Path of the site collection, with no trailing slash. */\n readonly basePath: string;\n readonly envelope: Envelope;\n}\n\nfunction field(response: unknown, key: string): unknown {\n return response && typeof response === \"object\"\n ? (response as Record<string, unknown>)[key]\n : undefined;\n}\n\nconst MEMBER_DIALECT: Dialect = {\n ownerKind: \"member\",\n basePath: \"/api/member/v2026-10/sites\",\n envelope: {\n payload: (response) => field(response, \"data\"),\n body: (_resource, fields) => fields,\n },\n};\n\nconst COMPANY_KEYS: Record<Resource, string> = {\n site: \"member_site\",\n sites: \"member_sites\",\n file: \"member_site_file\",\n files: \"member_site_files\",\n page: \"member_site_page\",\n pages: \"member_site_pages\",\n};\n\nconst COMPANY_DIALECT: Dialect = {\n ownerKind: \"company\",\n basePath: \"/api/company/v2026-10/member-sites\",\n envelope: {\n payload: (response, resource) => field(response, COMPANY_KEYS[resource]),\n body: (resource, fields) => ({ [COMPANY_KEYS[resource]]: fields }),\n },\n};\n\n/** The cursor a list response names for its next page, if any. */\nfunction nextCursor(response: unknown): string | null {\n const meta = field(response, \"meta\");\n const pagination = field(meta, \"pagination\");\n const cursor = field(pagination, \"next_cursor\");\n return typeof cursor === \"string\" && cursor.length > 0 ? cursor : null;\n}\n\n/**\n * A file's path is part of the URL. Each segment is escaped and the slashes\n * between them are kept, as the routes take `files/*path`.\n */\nfunction filePath(path: string): string {\n return path.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nfunction createSiteApi(http: HttpClient, dialect: Dialect): SiteApi {\n const { basePath, envelope } = dialect;\n const siteUrl = (siteId: string) =>\n `${basePath}/${encodeURIComponent(siteId)}`;\n\n /** Follows `meta.pagination.next_cursor` to the last page. */\n async function listAll<T>(\n url: string,\n resource: \"sites\" | \"files\",\n params: Record<string, unknown> = {},\n ): Promise<T[]> {\n const rows: T[] = [];\n const seen = new Set<string>();\n let cursor: string | null = null;\n do {\n const response: unknown = await http.get(url, {\n ...params,\n ...(cursor ? { page: { cursor } } : {}),\n });\n rows.push(...((envelope.payload(response, resource) as T[]) ?? []));\n const next = nextCursor(response);\n // A server that repeats a cursor would otherwise loop forever.\n cursor = next && !seen.has(next) ? next : null;\n if (cursor) seen.add(cursor);\n } while (cursor);\n return rows;\n }\n\n return {\n ownerKind: dialect.ownerKind,\n\n listSites: (options: ListSitesOptions = {}) =>\n // Only the company list has filters; the member list has none.\n listAll<Site>(\n basePath,\n \"sites\",\n options.ownerKind || options.status\n ? {\n filter: { owner_kind: options.ownerKind, status: options.status },\n }\n : {},\n ),\n\n getSite: async (siteId) =>\n envelope.payload(await http.get(siteUrl(siteId)), \"site\") as Site,\n\n createSite: async (input: CreateSiteInput) =>\n envelope.payload(\n await http.post(basePath, envelope.body(\"site\", input)),\n \"site\",\n ) as Site,\n\n publishSite: async (siteId) =>\n envelope.payload(\n await http.post(`${siteUrl(siteId)}/publish`),\n \"site\",\n ) as Site,\n\n listFiles: (siteId) =>\n listAll<SiteFileEntry>(`${siteUrl(siteId)}/files`, \"files\"),\n\n readFile: async (siteId, path) =>\n envelope.payload(\n await http.get(`${siteUrl(siteId)}/files/${filePath(path)}`),\n \"file\",\n ) as SiteFile,\n\n writeFile: async (siteId, path, input: WriteFileInput) =>\n envelope.payload(\n await http.put(\n `${siteUrl(siteId)}/files/${filePath(path)}`,\n envelope.body(\"file\", input),\n ),\n \"file\",\n ) as SiteFile,\n\n deleteFile: async (siteId, path, options = {}) => {\n await http.delete(\n `${siteUrl(siteId)}/files/${filePath(path)}`,\n options.version === undefined\n ? undefined\n : { params: { version: options.version } },\n );\n },\n\n listPages: async (siteId) =>\n (envelope.payload(\n await http.get(`${siteUrl(siteId)}/pages`),\n \"pages\",\n ) as SitePage[]) ?? [],\n\n createPage: async (siteId, input: CreatePageInput) =>\n envelope.payload(\n await http.post(\n `${siteUrl(siteId)}/pages`,\n envelope.body(\"page\", input),\n ),\n \"page\",\n ) as SitePage,\n\n updatePage: async (siteId, pageId, input: UpdatePageInput) =>\n envelope.payload(\n await http.patch(\n `${siteUrl(siteId)}/pages/${encodeURIComponent(pageId)}`,\n envelope.body(\"page\", input),\n ),\n \"page\",\n ) as SitePage,\n\n deletePage: async (siteId, pageId, options = {}) => {\n await http.delete(\n `${siteUrl(siteId)}/pages/${encodeURIComponent(pageId)}`,\n options.lockVersion === undefined\n ? undefined\n : { params: { lock_version: options.lockVersion } },\n );\n },\n };\n}\n\n/**\n * The path of the collection of sites an owner kind builds, with no trailing\n * slash: the member API for a member's site and the company API for a company\n * site.\n */\nexport function siteCollectionPath(ownerKind: OwnerKind): string {\n return (ownerKind === \"member\" ? MEMBER_DIALECT : COMPANY_DIALECT).basePath;\n}\n\n/** The client for a member's own sites: `/api/member/v2026-10/sites`. */\nexport function createMemberSiteApi(http: HttpClient): SiteApi {\n return createSiteApi(http, MEMBER_DIALECT);\n}\n\n/** The client for company sites and oversight: `/api/company/v2026-10/member-sites`. */\nexport function createCompanySiteApi(http: HttpClient): SiteApi {\n return createSiteApi(http, COMPANY_DIALECT);\n}\n","import { ApiError } from \"@fluid-app/api-client-core\";\nimport type { OwnerKind } from \"./types.js\";\n\n/** A failure with a message that is ready to show. No stack trace follows it. */\nexport class MemberSiteCliError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MemberSiteCliError\";\n }\n}\n\nexport interface ErrorContext {\n /** The owner kind of the site the request was for, when it was for one. */\n ownerKind?: OwnerKind;\n /** The file or page a request wrote, named in a conflict. */\n subject?: string;\n /**\n * What a 409 on the request means when it is not a stale write: the path\n * was to be created and already holds a file, a removal was refused, or the\n * site was changed while it was being published.\n */\n conflict?: \"exists\" | \"delete\" | \"publish\";\n}\n\n/**\n * Which login a site needs. The credential follows the owner: a member's site\n * is built with that member's own login, a company site with an admin's.\n */\nfunction loginNeeded(ownerKind: OwnerKind): string {\n return ownerKind === \"member\"\n ? \"A member site needs the member's own login, and a read-only login or an admin's cannot change it.\"\n : \"A company site needs a signed-in admin who may change themes. A token with no user can only read.\";\n}\n\nfunction field(value: unknown, key: string): unknown {\n return value && typeof value === \"object\"\n ? (value as Record<string, unknown>)[key]\n : undefined;\n}\n\n/** The request id the API puts in `meta`, or the one the client found. */\nfunction requestIdOf(error: ApiError): string | undefined {\n const fromMeta = field(field(error.body, \"meta\"), \"request_uuid\");\n if (typeof fromMeta === \"string\" && fromMeta) return fromMeta;\n return error.requestId;\n}\n\n/** `error.details` as `field: message` lines, for a 422. */\nfunction detailLines(error: ApiError): string[] {\n const details = field(field(error.body, \"error\"), \"details\");\n if (!details || typeof details !== \"object\") return [];\n return Object.entries(details).flatMap(([name, value]) => {\n const messages = (Array.isArray(value) ? value : [value]).filter(\n (m): m is string => typeof m === \"string\" && m.length > 0,\n );\n return messages.map((message) => ` ${name}: ${message}`);\n });\n}\n\n/**\n * The message to show for an API failure. A 404 is the same answer for a\n * disabled feature, a missing site and another owner's site, so it names the\n * first and last and never says which.\n */\nexport function describeApiError(\n error: ApiError,\n context: ErrorContext = {},\n): string {\n const lines: string[] = [];\n const hint = context.ownerKind ? loginNeeded(context.ownerKind) : null;\n\n switch (error.status) {\n case 404:\n lines.push(\n \"Member sites are not enabled for this company or the site is not yours.\",\n );\n break;\n case 401:\n lines.push(\n \"The login was not accepted. Run `fluid login` again, or check FLUID_TOKEN.\",\n );\n if (hint) lines.push(hint);\n break;\n case 403:\n lines.push(`The login may not do this: ${error.message}`);\n if (hint) lines.push(hint);\n break;\n case 409: {\n const subject = context.subject ?? \"The site\";\n lines.push(\n context.conflict === \"exists\"\n ? `${subject} exists on the server: another writer created it since the pull, so it was not replaced. Pull the latest copy and try again.`\n : context.conflict === \"delete\"\n ? `${subject} changed on the server since it was read, so it was not deleted. Pull the latest copy and try again.`\n : context.conflict === \"publish\"\n ? `${subject} was changed on the server while it was being published, so it was not published (a conflict). Run the publish again.`\n : `${subject} changed on the server since it was read. Pull the latest copy and try again.`,\n );\n break;\n }\n case 422:\n lines.push(`The API refused the request: ${error.message}`);\n lines.push(...detailLines(error));\n break;\n default:\n lines.push(error.message);\n }\n\n const requestId = requestIdOf(error);\n if (requestId) lines.push(`Request id: ${requestId}`);\n return lines.join(\"\\n\");\n}\n\n/** The message for any failure a command can meet. */\nexport function describeError(\n error: unknown,\n context: ErrorContext = {},\n): string {\n if (error instanceof ApiError) return describeApiError(error, context);\n if (error instanceof Error) return error.message;\n return String(error);\n}\n","import {\n ApiError,\n createFetchClient,\n type FetchClient,\n type RequestOptions,\n} from \"@fluid-app/api-client-core\";\nimport {\n findProjectConfig,\n getAuthToken,\n getRoleAuthTokenAsync,\n isRoleNotGrantedError,\n readConfig,\n} from \"@fluid-app/fluid-cli\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { MemberSiteCliError } from \"./errors.js\";\n\nexport type HttpClient = FetchClient;\n\n/**\n * The API base the stored login was made for. It follows the same choice of\n * credential `getAuthToken` makes: a token in `FLUID_TOKEN` or\n * `FLUID_API_TOKEN` records no base, then the project's `.fluidrc` profile,\n * then the active profile.\n */\nfunction loginApiBase(): string | null {\n if (process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"]) return null;\n const config = readConfig();\n const named = findProjectConfig(process.cwd())?.profile;\n const profile =\n (named ? config.profiles[named] : undefined) ??\n (config.activeProfile ? config.profiles[config.activeProfile] : undefined);\n return profile?.baseUrl ?? null;\n}\n\n/**\n * Base URL for all API calls: `FLUID_API_BASE`, else the API the stored login\n * was made for, so a staging login talks to staging. With neither there is\n * no base, and no request is made: there is no default, so a command never\n * reaches an API the login was not made for.\n *\n * Throws a MemberSiteCliError when there is no base.\n */\nexport function getApiBase(): string {\n const base = process.env[\"FLUID_API_BASE\"] || loginApiBase();\n if (!base) {\n throw new MemberSiteCliError(\n \"No API base for this login: run `fluid login` or set FLUID_API_BASE.\",\n );\n }\n return base;\n}\n\n/**\n * Whether an API base is, or may be, production. Only a base that is known\n * not to be is not: a local one (`localhost`, a loopback address, that is any of 127.0.0.0/8 or\n * `::1`, and `*.localhost`),\n * a stage one (`*.stage.fluid.app`, as the identity service names them) and a\n * name under the reserved `.test` TLD. Any other base, a custom or unknown\n * host included, is taken for production, so that a command that must not\n * write production data by default errs on the side of refusing.\n */\nexport function isProductionApiBase(apiBase: string): boolean {\n let hostname: string;\n try {\n hostname = new URL(apiBase).hostname.toLowerCase();\n } catch {\n return true;\n }\n const local =\n hostname === \"localhost\" ||\n /^127(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}$/.test(hostname) ||\n hostname === \"[::1]\" ||\n hostname.endsWith(\".localhost\");\n const stage = hostname.endsWith(\".stage.fluid.app\");\n const reserved = hostname.endsWith(\".test\");\n return !(local || stage || reserved);\n}\n\n/**\n * The stored login's token, in the order the core CLI resolves it:\n * FLUID_TOKEN or FLUID_API_TOKEN, then the project's `.fluidrc` profile, then\n * the active profile. This plugin keeps no credential of its own.\n */\nexport function resolveAuthToken(): string | null {\n return getAuthToken() ?? null;\n}\n\n/**\n * The stored login's token narrowed to a rep, which is what the member API\n * accepts from a person who is also an admin: the identity service issues it\n * only for a person who holds rep standing in the company. A login that\n * cannot be narrowed, a literal token (`FLUID_TOKEN`) or a legacy profile,\n * gives the token `resolveAuthToken` gives.\n *\n * Throws a MemberSiteCliError, before any member API request, when the person\n * is not a rep in the company.\n */\nexport async function resolveRepToken(): Promise<string | null> {\n try {\n return (await getRoleAuthTokenAsync([\"rep\"])) ?? null;\n } catch (error) {\n if (!isRoleNotGrantedError(error)) throw error;\n throw new MemberSiteCliError(\n `You are not a rep at ${error.companyName ?? \"this company\"}, so you cannot build a member site there. ` +\n \"Use --as admin for company sites.\",\n );\n }\n}\n\n/**\n * A client for one login. Reads that fail with 502, 503 or 504 are retried\n * twice, as the theme CLI retries them. A write is never retried, because a\n * failed response may follow a successful change, and a 404 never is,\n * because it answers the same for a disabled feature and a missing site.\n */\nexport function createHttpClient(token: string): HttpClient {\n const client = createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () => token,\n });\n const request = async <TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> => {\n for (let attempt = 0; ; attempt++) {\n options.signal?.throwIfAborted();\n try {\n return await client.request<TResponse>(endpoint, options);\n } catch (error) {\n if (\n (options.method ?? \"GET\") !== \"GET\" ||\n !(error instanceof ApiError) ||\n ![502, 503, 504].includes(error.status) ||\n attempt >= 2 ||\n options.signal?.aborted\n ) {\n throw error;\n }\n await delay(500 * 2 ** attempt, undefined, { signal: options.signal });\n }\n }\n };\n return {\n ...client,\n request,\n get: (endpoint, params, options) =>\n request(endpoint, { ...options, method: \"GET\", params }),\n };\n}\n","import { ApiError } from \"@fluid-app/api-client-core\";\nimport { createCompanySiteApi, createMemberSiteApi } from \"./site-api.js\";\nimport { createHttpClient, resolveAuthToken, resolveRepToken } from \"./http.js\";\nimport { MemberSiteCliError } from \"./errors.js\";\nimport type { OwnerKind, Site, SiteApi } from \"./types.js\";\n\n/**\n * Which kind of login holds a token. The credential follows the owner: a\n * member's site is read and written with the member's own login through the\n * member API, and a company site with an admin's login through the company\n * API.\n */\nexport type LoginKind = \"member\" | \"admin\";\n\nexport interface Login {\n token: string;\n /**\n * `null` when the kind is not known before a request. The core CLI stores\n * a token and no record of whose it is, so the kind is known only when the\n * caller states it (`--as`).\n */\n kind: LoginKind | null;\n}\n\n/** The two clients one token can drive. */\nexport interface Clients {\n member: SiteApi;\n company: SiteApi;\n}\n\nexport function parseLoginKind(value: string | undefined): LoginKind | null {\n if (value === undefined) return null;\n if (value === \"member\" || value === \"admin\") return value;\n throw new MemberSiteCliError(\n `--as takes \"member\" or \"admin\", not \"${value}\".`,\n );\n}\n\n/** The login a site of this owner kind needs. */\nexport function loginFor(ownerKind: OwnerKind): LoginKind {\n return ownerKind === \"member\" ? \"member\" : \"admin\";\n}\n\nfunction describeLogin(kind: LoginKind): string {\n return kind === \"member\" ? \"a member login\" : \"an admin login\";\n}\n\n/** Where a login's token comes from. Replaced in tests. */\nexport interface TokenSources {\n /** The stored login's token, as the core CLI resolves it. */\n readonly token: () => string | null;\n /** The same login narrowed to a rep, for the member API. */\n readonly repToken: () => Promise<string | null>;\n}\n\nconst defaultSources: TokenSources = {\n token: resolveAuthToken,\n repToken: resolveRepToken,\n};\n\n/**\n * The login a command acts with, or an error that says to sign in. With\n * `--as member` the token is the stored login's rep session, so an admin who\n * is also a rep builds their own sites with the login they already have;\n * otherwise it is the stored login's token as it is.\n */\nexport async function loadLogin(\n kind: LoginKind | null,\n sources: TokenSources = defaultSources,\n): Promise<Login> {\n const token = kind === \"member\" ? await sources.repToken() : sources.token();\n if (!token) {\n throw new MemberSiteCliError(\n \"Not logged in. Run `fluid login` first (or set FLUID_TOKEN).\",\n );\n }\n return { token, kind };\n}\n\nexport function createClients(token: string): Clients {\n const http = createHttpClient(token);\n return {\n member: createMemberSiteApi(http),\n company: createCompanySiteApi(http),\n };\n}\n\n/**\n * The client that builds a site of this owner kind. When the login's kind is\n * known and is the other one, this stops before any request and names the\n * login the site needs. When it is not known, the server decides, and a\n * refusal is reported with the login the site needs.\n */\nexport function clientForOwner(\n ownerKind: OwnerKind,\n login: Login,\n clients: Clients,\n): SiteApi {\n const needed = loginFor(ownerKind);\n if (login.kind !== null && login.kind !== needed) {\n throw new MemberSiteCliError(\n `A ${ownerKind} site needs ${describeLogin(needed)}, and this is ${describeLogin(login.kind)}. ` +\n (needed === \"admin\"\n ? \"Sign in as an admin with `fluid login`.\"\n : \"Use the member's own token (set FLUID_TOKEN).\"),\n );\n }\n return needed === \"member\" ? clients.member : clients.company;\n}\n\n/** What `list` shows, and which login it listed with. */\nexport interface SiteListing {\n as: LoginKind;\n sites: Site[];\n}\n\nfunction refused(error: unknown): boolean {\n return (\n error instanceof ApiError && (error.status === 401 || error.status === 403)\n );\n}\n\n/**\n * The sites this login can see. An admin lists every site in the company, a\n * member's own sites among them read-only. A member lists their own. A login\n * of unknown kind tries the company API first and falls back to the member\n * API when the company API refuses it, so an admin who is also a member lists\n * the company's sites.\n */\nexport async function listForLogin(\n login: Login,\n clients: Clients,\n): Promise<SiteListing> {\n if (login.kind === \"member\") {\n return { as: \"member\", sites: await clients.member.listSites() };\n }\n if (login.kind === \"admin\") {\n return { as: \"admin\", sites: await clients.company.listSites() };\n }\n try {\n return { as: \"admin\", sites: await clients.company.listSites() };\n } catch (error) {\n if (!refused(error)) throw error;\n }\n try {\n return { as: \"member\", sites: await clients.member.listSites() };\n } catch (error) {\n if (!refused(error)) throw error;\n throw new MemberSiteCliError(\n \"This login can list neither the company's sites nor a member's own. \" +\n \"Sign in as an admin with `fluid login`, or use a member's own token (set FLUID_TOKEN).\",\n );\n }\n}\n\nconst SITE_ID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** A site found by `resolveSite`, and the client that found it. */\nexport interface ResolvedSite {\n site: Site;\n api: SiteApi;\n}\n\n/** The clients a login of this kind may look a site up with, in order. */\nfunction lookupClients(login: Login, clients: Clients): SiteApi[] {\n if (login.kind === \"member\") return [clients.member];\n if (login.kind === \"admin\") return [clients.company];\n return [clients.company, clients.member];\n}\n\nasync function findSite(api: SiteApi, ref: string): Promise<Site | null> {\n if (SITE_ID_PATTERN.test(ref)) return api.getSite(ref);\n const matches = (await api.listSites()).filter((s) => s.handle === ref);\n if (matches.length > 1) {\n throw new MemberSiteCliError(\n `More than one site has the handle \"${ref}\". Use the site's id: ${matches.map((s) => s.id).join(\", \")}.`,\n );\n }\n return matches[0] ?? null;\n}\n\n/**\n * The site a handle or id names, with the client that found it. A login of\n * unknown kind tries the company API first and the member API when the\n * company API refuses it or does not know the site. When every client refuses\n * or answers 404, the last of those is thrown, so a 404 keeps its \"not enabled\n * or not yours\" wording. When any client answered and has no such site, that\n * is the answer instead.\n */\nexport async function resolveSite(\n ref: string,\n login: Login,\n clients: Clients,\n): Promise<ResolvedSite> {\n let failure: ApiError | null = null;\n let answered = false;\n for (const api of lookupClients(login, clients)) {\n try {\n const site = await findSite(api, ref);\n if (site) return { site, api };\n // A client that answered and has no such site is the last word.\n answered = true;\n } catch (error) {\n if (\n !(error instanceof ApiError) ||\n ![401, 403, 404].includes(error.status)\n ) {\n throw error;\n }\n failure = error;\n }\n }\n if (failure && !answered) throw failure;\n throw new MemberSiteCliError(\n `No site \"${ref}\" among the sites this login can see. List them with: fluid member-site list`,\n );\n}\n","import chalk from \"chalk\";\nimport { exitOnInputsError } from \"@fluid-app/fluid-cli\";\nimport { describeError, type ErrorContext } from \"../api/errors.js\";\n\n/**\n * Runs a command's work and reports a failure as a message, with no stack\n * trace and a non-zero exit code.\n */\nexport async function runCommand(\n work: () => Promise<void>,\n context: ErrorContext = {},\n): Promise<void> {\n try {\n await work();\n } catch (error) {\n exitOnInputsError(error);\n console.error(chalk.red(describeError(error, context)));\n process.exitCode = 1;\n }\n}\n","import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n clientForOwner,\n createClients,\n loadLogin,\n parseLoginKind,\n type Clients,\n type Login,\n} from \"../api/credential.js\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport type { OwnerKind, Site } from \"../api/types.js\";\nimport { runCommand } from \"./run.js\";\n\nconst HANDLE_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst HANDLE_MAX_LENGTH = 40;\n\n/** The site's name when `--name` is not given: its handle, in words. */\nexport function nameFromHandle(handle: string): string {\n return handle\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/** Refuses a handle the API would refuse, before any request. */\nexport function assertValidHandle(handle: string): void {\n if (handle.length > HANDLE_MAX_LENGTH || !HANDLE_PATTERN.test(handle)) {\n throw new MemberSiteCliError(\n `\"${handle}\" is not a valid handle. Use lowercase letters, digits and single hyphens, at most ${HANDLE_MAX_LENGTH} characters.`,\n );\n }\n}\n\nexport interface CreateOptions {\n company?: boolean;\n name?: string;\n as?: string;\n}\n\nexport interface CreateDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n}\n\nconst defaultDeps: CreateDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\nexport function ownerKindOf(options: CreateOptions): OwnerKind {\n return options.company ? \"company\" : \"member\";\n}\n\nexport async function runCreate(\n handle: string,\n options: CreateOptions,\n deps: CreateDeps = defaultDeps,\n): Promise<Site> {\n assertValidHandle(handle);\n const name = options.name?.trim() || nameFromHandle(handle);\n const ownerKind = ownerKindOf(options);\n\n const login = await deps.loadLogin(parseLoginKind(options.as));\n const api = clientForOwner(ownerKind, login, deps.createClients(login.token));\n const site = await api.createSite({ name, handle });\n\n deps.log(\n `Created ${site.owner_kind} site ${chalk.cyan(site.handle)} (${site.id}) as a ${site.status}.`,\n );\n return site;\n}\n\nexport function createCreateCommand(): Command {\n return new Command(\"create\")\n .description(\n \"Create a site: yours with a member login, or --company with an admin login\",\n )\n .argument(\n \"<handle>\",\n \"The site's handle: lowercase letters, digits and hyphens\",\n )\n .option(\"--company\", \"Create a company-owned site (needs an admin login)\")\n .option(\"--name <name>\", \"The site's name (default: the handle in words)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .action((handle: string, options: CreateOptions) =>\n runCommand(() => runCreate(handle, options).then(() => undefined), {\n ownerKind: ownerKindOf(options),\n }),\n );\n}\n","import { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\n\n/**\n * The assets a sandboxed preview page loads itself with CORS: a stylesheet\n * and a script (module scripts are fetched with CORS). A font is not one,\n * because the preview serves no font file. A `fetch` has the destination\n * `empty`, and is refused.\n */\nconst ASSET_DESTINATIONS = new Set([\"script\", \"style\"]);\n\n/** The local path a preview serves a site's text assets from. */\nexport const ASSET_ROUTE_PREFIX = \"/files/assets/\";\n\n/** A session's token is this many random bytes, written as hex. */\nconst PREVIEW_TOKEN_BYTES = 16;\n\n/**\n * A secret of this preview session, which the preview puts in the asset paths\n * of the pages it serves (`/files/assets/<token>/<file>`). It is never the\n * CLI's credential and it authorizes nothing at the API: it only tells the\n * preview's own page from any other page, which cannot know it.\n */\nexport function createPreviewToken(): string {\n return randomBytes(PREVIEW_TOKEN_BYTES).toString(\"hex\");\n}\n\n/** Whether `candidate` is the session's token, compared in constant time. */\nexport function isPreviewToken(\n candidate: string | undefined,\n previewToken: string,\n): boolean {\n if (candidate === undefined) return false;\n const given = Buffer.from(candidate);\n const expected = Buffer.from(previewToken);\n return given.length === expected.length && timingSafeEqual(given, expected);\n}\n\nfunction header(req: IncomingMessage, name: string): string | undefined {\n const value = req.headers[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\n/**\n * Whether a path is `/files/assets/<token>/<file>` with this session's token,\n * the path the preview gave its own page.\n */\nexport function hasPreviewTokenPath(\n path: string,\n previewToken: string,\n): boolean {\n if (!path.startsWith(ASSET_ROUTE_PREFIX)) return false;\n return isPreviewToken(\n path.slice(ASSET_ROUTE_PREFIX.length).split(\"/\")[0],\n previewToken,\n );\n}\n\n/**\n * Whether a request comes from the preview's own origin or from the browser's\n * address bar: `Sec-Fetch-Site` says `same-origin` or `none`. A request\n * without the header is not one, because a browser that does not send it\n * cannot be told from a page of another site, and the answer is a draft's\n * asset, which a page of another site could read by loading it as a\n * stylesheet or script without CORS. A sandboxed preview page has an opaque\n * origin, so it is never one: it loads its assets by the path with the token.\n */\nexport function isFirstPartyRequest(req: IncomingMessage): boolean {\n const site = header(req, \"sec-fetch-site\");\n return site === \"same-origin\" || site === \"none\";\n}\n\n/**\n * Whether a request is a CORS request for an asset from the preview's own\n * page: a document with an opaque origin (the sandbox policy it is served\n * with gives it an `Origin: null`) that loads an asset from the path the\n * preview gave it, `/files/assets/<token>/<file>`.\n *\n * `Origin: null` is also what any other sandboxed page sends, so the origin\n * proves nothing, and the token is what tells the preview's page from another\n * one: only a request for a path with this session's token is one. Only\n * `GET` or `HEAD` of such a path, asked for as a script or a stylesheet, is.\n * A `fetch` has the destination `empty`.\n */\nexport function isSandboxedAssetRequest(\n req: IncomingMessage,\n previewToken: string,\n): boolean {\n if (header(req, \"origin\") !== \"null\") return false;\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return false;\n const dest = header(req, \"sec-fetch-dest\");\n if (dest === undefined || !ASSET_DESTINATIONS.has(dest)) return false;\n const path = (req.url ?? \"\").split(/[?#]/)[0] ?? \"\";\n return hasPreviewTokenPath(path, previewToken);\n}\n\n/**\n * Deny DNS rebinding and cross-origin access before using Node's CLI\n * credential. It is the guard of `fluid theme dev` (`isLocalPreviewRequest`)\n * with one addition, because a preview is served with the sandbox policy of a\n * member site frame (no `allow-same-origin`).\n *\n * That policy gives the page an opaque origin, and a request from an opaque\n * origin is `Sec-Fetch-Site: cross-site` whatever its target. So the page's own\n * stylesheet, script and link clicks look cross-site to the server. They are\n * let through by what they are:\n *\n * - the Host is one the server listens on (a rebound DNS name is not);\n * - an `Origin`, when sent, is the server's own, or `null` for an asset that\n * the page loads by the path with the session's token\n * (`isSandboxedAssetRequest`), never another site and never another\n * sandboxed page;\n * - `Sec-Fetch-Site: cross-site` is refused for a request with CORS (it could\n * be read) and let through for a navigation or a request without CORS,\n * whose answer the requester cannot read. The page's `frame-ancestors`\n * policy keeps another site from framing a draft.\n */\nexport function isLocalPreviewRequest(\n req: IncomingMessage,\n host: string,\n previewToken: string,\n): boolean {\n const port = req.socket.localPort;\n const hosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n if ((host === \"0.0.0.0\" || host === \"::\") && req.socket.localAddress) {\n const address = req.socket.localAddress.replace(/^::ffff:/, \"\");\n hosts.add(address.includes(\":\") ? `[${address}]` : address);\n }\n let url: URL;\n try {\n url = new URL(`http://${req.headers.host}`);\n } catch {\n return false;\n }\n if (\n !hosts.has(url.hostname) ||\n Number(url.port || 80) !== port ||\n url.username ||\n url.password ||\n url.pathname !== \"/\"\n ) {\n return false;\n }\n\n const origin = header(req, \"origin\");\n if (origin !== undefined && origin !== url.origin) {\n if (!isSandboxedAssetRequest(req, previewToken)) return false;\n }\n\n if (header(req, \"sec-fetch-site\") !== \"cross-site\") return true;\n if (isSandboxedAssetRequest(req, previewToken)) return true;\n // Without the header a request is taken to have CORS, and so is refused.\n const mode = header(req, \"sec-fetch-mode\");\n return origin === undefined && (mode === \"navigate\" || mode === \"no-cors\");\n}\n","import net from \"node:net\";\n\n/** The port a dev server was asked for is already taken. */\nexport class PortInUseError extends Error {\n constructor(\n public readonly host: string,\n public readonly port: number,\n ) {\n super(\n `Port ${port} on ${host} is already in use. Stop the other server or pass --port <number>.`,\n );\n this.name = \"PortInUseError\";\n }\n}\n\n/**\n * Binds `host:port` and releases it at once. Resolves when the port is free\n * and rejects with `PortInUseError` on `EADDRINUSE` or `EACCES`, so a command\n * fails before it saves anything instead of with a stack trace at `listen`.\n */\nexport function checkPortAvailable(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n reject(\n err.code === \"EADDRINUSE\" || err.code === \"EACCES\"\n ? new PortInUseError(host, port)\n : err,\n );\n });\n server.once(\"listening\", () => {\n server.close(() => resolve());\n });\n server.listen(port, host);\n });\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport { siteCollectionPath } from \"../api/site-api.js\";\nimport type { OwnerKind } from \"../api/types.js\";\nimport {\n ASSET_ROUTE_PREFIX,\n createPreviewToken,\n hasPreviewTokenPath,\n isFirstPartyRequest,\n isPreviewToken,\n isSandboxedAssetRequest,\n} from \"./local-guard.js\";\n\n/** The site a preview shows, from the folder's binding. */\nexport interface PreviewTarget {\n siteId: string;\n handle: string;\n ownerKind: OwnerKind;\n}\n\n/** What one local path asks of the preview route. */\nexport type PreviewRoute =\n | { kind: \"page\"; slug: string | null }\n | { kind: \"file\"; path: string };\n\n/**\n * The policy every frame response carries: the sandbox gives a document an\n * opaque origin, and never the same origin. A preview keeps it. It is used\n * for an answer of the proxy's own, and the server's own policy replaces it\n * when the server sent one.\n */\nexport const PREVIEW_SANDBOX_POLICY =\n \"frame-ancestors 'self'; sandbox allow-scripts allow-forms\";\n\nconst SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst ASSET_NAME = /^[a-z0-9_-]+\\.(css|js|json|txt|html)$/;\n\nconst UPSTREAM_TIMEOUT_MS = 30_000;\n\nfunction decode(segment: string): string | null {\n try {\n return decodeURIComponent(segment);\n } catch {\n return null;\n }\n}\n\n/**\n * The preview route a local path asks for, or null when it names none.\n *\n * - `/` is the home page, `/<slug>` another page, and\n * `/files/assets/<token>/<file>` a text asset, the path a preview rewrites\n * its links to. The token is the session's (`previewToken`), and a path\n * with another names nothing. `/files/assets/<file>` is the same asset,\n * for a request that is not read with CORS.\n * - A page or asset linked at its published address is taken too:\n * `/accounts/_frame/<site id>/assets/<file>` (a link in a stylesheet or\n * script, which the rewrite of a page does not reach) and\n * `/accounts/<handle>(/<slug>)`.\n */\nexport function routeOf(\n pathname: string,\n target: PreviewTarget,\n previewToken?: string,\n): PreviewRoute | null {\n const segments = pathname.split(\"/\").slice(1);\n if (segments.at(-1) === \"\" && segments.length > 1) segments.pop();\n const parts = segments.map(decode);\n if (parts.some((part) => part === null)) return null;\n const [first, second, third, fourth] = parts as string[];\n\n const page = (slug: string | undefined): PreviewRoute | null => {\n if (slug === undefined || slug === \"\") return { kind: \"page\", slug: null };\n return SLUG.test(slug) ? { kind: \"page\", slug } : null;\n };\n const asset = (name: string | undefined): PreviewRoute | null =>\n name !== undefined && ASSET_NAME.test(name)\n ? { kind: \"file\", path: `assets/${name}` }\n : null;\n\n if (parts.length === 1) return page(first);\n if (first === \"files\" && second === \"assets\" && parts.length === 3) {\n return asset(third);\n }\n if (first === \"files\" && second === \"assets\" && parts.length === 4) {\n return previewToken !== undefined && isPreviewToken(third, previewToken)\n ? asset(fourth)\n : null;\n }\n if (first === \"accounts\") {\n if (second === \"_frame\") {\n return third === target.siteId &&\n fourth === \"assets\" &&\n parts.length === 5\n ? asset(parts[4] ?? undefined)\n : null;\n }\n if (second === target.handle && parts.length <= 3) return page(third);\n }\n return null;\n}\n\n/**\n * Points the links of a rendered page at the preview. A page links a site's\n * assets at the published frame (`/accounts/_frame/<site id>/assets/<file>`),\n * which serves a published site only, and its pages at the site's shell\n * address (`/accounts/<handle>(/<slug>)`). Neither opens a draft, so the first\n * becomes `/files/assets/<file>`, read through the preview route, and the\n * second `/` or `/<slug>`, which the preview serves. The asset path carries the\n * session's token, which lets the page read its assets with CORS and no other\n * page.\n */\nexport function rewritePreviewHtml(\n html: string,\n target: PreviewTarget,\n previewToken: string,\n): string {\n const handle = target.handle.replace(/[^a-z0-9-]/g, \"\");\n const shell = new RegExp(\n `(?<![A-Za-z0-9_.:/-])/accounts/${handle}(?![A-Za-z0-9_-])`,\n \"g\",\n );\n return html\n .split(`/accounts/_frame/${target.siteId}/assets/`)\n .join(`${ASSET_ROUTE_PREFIX}${previewToken}/`)\n .replace(shell, (match, offset: number, whole: string) =>\n whole[offset + match.length] === \"/\" ? \"\" : \"/\",\n );\n}\n\n/**\n * Points the links of a stylesheet or script at the preview's asset path, as\n * `rewritePreviewHtml` does for a page: an asset reached by its published\n * address from a sandboxed page is a request from another origin, which only\n * the path with the session's token serves.\n */\nexport function rewritePreviewAsset(\n source: string,\n target: PreviewTarget,\n previewToken: string,\n): string {\n return source\n .split(`/accounts/_frame/${target.siteId}/assets/`)\n .join(`${ASSET_ROUTE_PREFIX}${previewToken}/`);\n}\n\nconst FETCH_DIRECTIVES = new Set([\n \"script-src\",\n \"style-src\",\n \"img-src\",\n \"font-src\",\n \"media-src\",\n]);\n\n/**\n * Adds the preview's own origin to the sources of code, styles, images, fonts\n * and media in the server's policy. The server renders a draft for no origin\n * in particular and names only Fluid's site SDK, so a page shown from here\n * would otherwise have its assets, which this proxy serves, blocked. Every\n * other directive, the closed network included, is kept as the server sent it.\n */\nexport function allowPreviewOrigin(policy: string, origin: string): string {\n return policy\n .split(\";\")\n .map((directive) => {\n const name = directive.trim().split(/\\s+/)[0] ?? \"\";\n return FETCH_DIRECTIVES.has(name)\n ? `${directive.trimEnd()} ${origin}`\n : directive;\n })\n .join(\";\");\n}\n\n/** What the server's answer to a probe says about the preview route. */\nexport type ProbeResult = \"available\" | \"unavailable\";\n\nexport interface PreviewProxyOptions {\n target: PreviewTarget;\n /** The API base the login is for, with or without a trailing slash. */\n apiBase: string;\n /** The CLI's bearer, read when a request is made and sent to the API only. */\n getToken: () => string | Promise<string>;\n}\n\n/**\n * Serves a draft preview by asking the preview route of the member or company\n * API with the CLI's bearer, from this process only. The browser sends no\n * credential and is sent none: no token is in a URL, a header or a page that\n * the proxy answers, and nothing the browser sent goes to the API.\n */\nexport class PreviewProxy {\n private readonly previewUrl: string;\n /** This session's secret, which only the pages the proxy serves hold. */\n readonly previewToken = createPreviewToken();\n\n constructor(private readonly options: PreviewProxyOptions) {\n const { target, apiBase } = options;\n this.previewUrl = `${apiBase.replace(/\\/+$/, \"\")}${siteCollectionPath(\n target.ownerKind,\n )}/${encodeURIComponent(target.siteId)}/preview`;\n }\n\n private async fetchUpstream(route: PreviewRoute): Promise<Response> {\n const path =\n route.kind === \"page\"\n ? `/pages${route.slug === null ? \"\" : `/${encodeURIComponent(route.slug)}`}`\n : `/files/${route.path.split(\"/\").map(encodeURIComponent).join(\"/\")}`;\n return fetch(`${this.previewUrl}${path}`, {\n method: \"GET\",\n headers: {\n authorization: `Bearer ${await this.options.getToken()}`,\n accept: route.kind === \"page\" ? \"text/html\" : \"*/*\",\n \"accept-encoding\": \"identity\",\n \"user-agent\": \"Fluid CLI\",\n },\n // A redirect would carry the bearer to wherever it points.\n redirect: \"manual\",\n signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),\n });\n }\n\n /**\n * Whether the server has the preview route. A route that is there answers\n * every request, a refusal included, with the frame's sandbox policy and\n * `X-Robots-Tag: noindex`, and one that is not answers without them, so the\n * two are told apart by those headers and not by a 404, which a missing page\n * also is. Throws on a login the server did not accept.\n */\n async probe(): Promise<ProbeResult> {\n const response = await this.fetchUpstream({ kind: \"page\", slug: null });\n await response.body?.cancel();\n if (response.status === 401 || response.status === 403) {\n throw new PreviewAuthError(response.status);\n }\n const present =\n /(^|[\\s;])sandbox(\\s|;|$)/.test(\n response.headers.get(\"content-security-policy\") ?? \"\",\n ) && response.headers.get(\"x-robots-tag\")?.includes(\"noindex\") === true;\n return present ? \"available\" : \"unavailable\";\n }\n\n /**\n * Answers a request for a preview path: the page or asset the route names,\n * or a plain-text refusal. Every answer is `private, no-store`, sends no\n * referrer, and keeps the sandbox policy.\n */\n async handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const { target } = this.options;\n const send = (\n status: number,\n type: string,\n body: string | Buffer,\n policy = PREVIEW_SANDBOX_POLICY,\n extra: Record<string, string> = {},\n ) => {\n res.writeHead(status, {\n \"content-type\": type,\n \"content-security-policy\": policy,\n \"cache-control\": \"private, no-store\",\n \"referrer-policy\": \"no-referrer\",\n \"x-content-type-options\": \"nosniff\",\n \"x-robots-tag\": \"noindex\",\n \"content-length\": String(Buffer.byteLength(body)),\n ...extra,\n });\n res.end(req.method === \"HEAD\" ? undefined : body);\n };\n const text = (\n status: number,\n message: string,\n extra: Record<string, string> = {},\n ) => send(status, \"text/plain; charset=utf-8\", message, undefined, extra);\n\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n return text(405, \"The draft preview is read-only.\", {\n allow: \"GET, HEAD\",\n });\n }\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n const route = routeOf(url.pathname, target, this.previewToken);\n if (!route) return text(404, \"No such page or file in this preview.\");\n // An asset path without the session's token is open to a page of another\n // site, which loads it as a stylesheet or script with no CORS and reads\n // what it applies. It is served only to the preview's own origin or to\n // the address bar, and the CLI's bearer is not used for any other.\n if (\n route.kind === \"file\" &&\n !hasPreviewTokenPath(url.pathname, this.previewToken) &&\n !isFirstPartyRequest(req)\n ) {\n return text(403, \"An asset is served only by the path of its preview.\");\n }\n\n let upstream: Response;\n try {\n upstream = await this.fetchUpstream(route);\n } catch {\n return text(\n 502,\n \"The draft preview could not reach the Fluid API. Check your network and try again.\",\n );\n }\n if (upstream.status !== 200) {\n await upstream.body?.cancel();\n // The server's own bodies are never echoed.\n if (upstream.status === 404) {\n return text(\n 404,\n route.kind === \"page\"\n ? \"This site has no draft of that page.\"\n : \"This site has no such text asset.\",\n );\n }\n if (upstream.status === 401 || upstream.status === 403) {\n return text(\n upstream.status,\n \"The draft preview was not authorized. Check your CLI login: a member's own for a member's site, an admin's for a company site.\",\n );\n }\n return text(502, \"The draft preview could not be rendered.\");\n }\n\n const type =\n upstream.headers.get(\"content-type\") ?? \"text/plain; charset=utf-8\";\n const upstreamPolicy = upstream.headers.get(\"content-security-policy\");\n const policy = upstreamPolicy\n ? allowPreviewOrigin(upstreamPolicy, url.origin)\n : PREVIEW_SANDBOX_POLICY;\n const raw = Buffer.from(await upstream.arrayBuffer());\n const rewrite = type.startsWith(\"text/html\")\n ? rewritePreviewHtml\n : /^(text\\/css|(text|application)\\/(x-)?javascript)\\b/.test(type)\n ? rewritePreviewAsset\n : undefined;\n const body = rewrite\n ? Buffer.from(rewrite(raw.toString(\"utf8\"), target, this.previewToken))\n : raw;\n // The page that loads an asset from its opaque origin reads the answer\n // with CORS, so it is let read it, by the path that carries the session's\n // token. No other origin is, and no other sandboxed page.\n send(\n 200,\n type,\n body,\n policy,\n isSandboxedAssetRequest(req, this.previewToken)\n ? { \"access-control-allow-origin\": \"null\", vary: \"Origin\" }\n : {},\n );\n }\n}\n\n/** The server did not accept the login that asked for a preview. */\nexport class PreviewAuthError extends MemberSiteCliError {\n constructor(public readonly status: number) {\n super(\n `The login was not accepted for the draft preview (${status}). Run \\`fluid login\\` again, or check FLUID_TOKEN.`,\n );\n this.name = \"PreviewAuthError\";\n }\n}\n","import path from \"node:path\";\n\nimport chokidar from \"chokidar\";\n\n/**\n * Calls `onChange` when a file in a site folder is added, changed or removed.\n * The binding file and other dotfiles are ignored, so the push that rewrites\n * the binding after each write does not trigger another one. The event says\n * nothing about what changed: a push compares the folder with the binding.\n * Resolves once the folder is being watched, with a function that stops\n * watching: a file that changes after that is reported, so a caller that reads\n * the folder next misses no edit. Rejects, with the watcher closed, when it\n * fails before it is ready, because a folder that is not watched would not be\n * saved, and an error after that is reported to `onError`.\n */\nexport async function watchFolder(\n dir: string,\n onChange: () => void,\n onError: (error: unknown) => void = () => undefined,\n): Promise<() => Promise<void>> {\n const watcher = chokidar.watch(dir, {\n ignoreInitial: true,\n // Folder names are matched whole and inside the site folder only, so an\n // asset named `node_modules.css`, or a site folder under a dot directory,\n // is still watched.\n ignored: (filePath: string) => {\n const parts = path.relative(dir, filePath).split(/[\\\\/]/);\n if (parts.includes(\"node_modules\")) return true;\n return (parts.at(-1) ?? \"\").startsWith(\".\");\n },\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },\n });\n watcher.on(\"add\", onChange);\n watcher.on(\"change\", onChange);\n watcher.on(\"unlink\", onChange);\n let ready = false;\n watcher.on(\"error\", (error) => {\n if (ready) onError(error);\n });\n try {\n await new Promise<void>((resolve, reject) => {\n watcher.once(\"ready\", resolve);\n // A watcher that fails to start never says it is ready.\n watcher.once(\"error\", reject);\n });\n } catch (error) {\n await watcher.close().catch(() => undefined);\n throw error;\n }\n ready = true;\n return () => watcher.close();\n}\n","import { createHash } from \"node:crypto\";\nimport type {\n OwnerKind,\n Site,\n SiteFile,\n SiteFileEntry,\n SitePage,\n} from \"../api/types.js\";\n\n/**\n * The binding file ties a pulled folder to its site. It is local state, never\n * a site file, and no API path starts with a dot.\n */\nexport const BINDING_FILE = \".member-site.json\";\n\n/** The page metadata file. It is not a file of the site's file list. */\nexport const PAGES_CONFIG_PATH = \"config/pages.json\";\n\n/** What the API reported for one file at the pull, and what the pull wrote. */\nexport interface BoundFile {\n /** The row version, which only `config/settings.json` has today. */\n lock_version: number | null;\n updated_at: string | null;\n /**\n * The version the file list reports, to send back on a write or a delete.\n * A binding from a pull before the API reported one has none.\n */\n version?: string;\n /** The SHA-256 of the content written, to tell a local edit from the pulled copy. */\n sha256: string;\n}\n\n/** What a page row holds besides its content, as `config/pages.json` names it. */\nexport interface PageRow {\n title: string;\n position: number;\n published: boolean;\n layout: string | null;\n menu: string | null;\n}\n\nexport interface BoundPage {\n id: string;\n lock_version: number;\n /**\n * The row as the server last had it, so a push can tell which fields a\n * local edit changed. A binding from an earlier pull has none.\n */\n row?: PageRow;\n}\n\nexport interface SiteBinding {\n site_id: string;\n handle: string;\n owner_kind: OwnerKind;\n api_base: string;\n settings: { lock_version: number | null };\n /** The site's default layout, as `config/pages.json` names it. */\n layout?: string | null;\n /** Keyed by slug, the home page as \"/\". */\n pages: Record<string, BoundPage>;\n files: Record<string, BoundFile>;\n /** The SHA-256 of the `config/pages.json` written. */\n pages_config_sha256: string;\n}\n\n/** A page's row as `config/pages.json` holds it. */\nexport function pageRow(page: SitePage): PageRow {\n return {\n title: page.title,\n position: page.position,\n published: page.published,\n layout: page.layout_name ?? null,\n menu: page.menu ?? null,\n };\n}\n\nexport function sha256(content: string): string {\n return createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\n/** One JSON document as it is written to disk: stable, indented, one newline at the end. */\nexport function serializeJson(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/** A file's entry in the list and the content read for it. */\nexport interface PulledFile {\n entry: SiteFileEntry;\n file: SiteFile;\n}\n\nexport interface BindingInput {\n site: Site;\n apiBase: string;\n files: PulledFile[];\n pages: SitePage[];\n pagesConfig: string;\n}\n\n/**\n * The binding for a pull. Each version is what the API gave, null kept as\n * null. A file's own read is newer than the list, so its `lock_version` and\n * `version` win.\n */\nexport function buildBinding(input: BindingInput): SiteBinding {\n const files: Record<string, BoundFile> = {};\n for (const { entry, file } of [...input.files].sort((a, b) =>\n a.entry.path < b.entry.path ? -1 : 1,\n )) {\n const version = file.version ?? entry.version;\n files[entry.path] = {\n lock_version: file.lock_version ?? entry.lock_version ?? null,\n updated_at: entry.updated_at ?? null,\n ...(typeof version === \"string\" ? { version } : {}),\n sha256: sha256(file.content),\n };\n }\n\n const pages: Record<string, BoundPage> = {};\n for (const page of [...input.pages].sort((a, b) =>\n a.slug < b.slug ? -1 : 1,\n )) {\n pages[page.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n }\n\n return {\n site_id: input.site.id,\n handle: input.site.handle,\n owner_kind: input.site.owner_kind,\n api_base: input.apiBase,\n settings: {\n lock_version: files[\"config/settings.json\"]?.lock_version ?? null,\n },\n layout: input.site.layout_name ?? null,\n pages,\n files,\n pages_config_sha256: sha256(input.pagesConfig),\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The binding in a file's text, or null when it is not one. */\nexport function parseBinding(text: string): SiteBinding | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (!isRecord(parsed)) return null;\n const { site_id, handle, owner_kind, files, pages } = parsed;\n if (\n typeof site_id !== \"string\" ||\n typeof handle !== \"string\" ||\n (owner_kind !== \"member\" && owner_kind !== \"company\") ||\n !isRecord(files) ||\n !isRecord(pages)\n ) {\n return null;\n }\n return parsed as unknown as SiteBinding;\n}\n","import { randomBytes } from \"node:crypto\";\nimport {\n lstat,\n mkdir,\n readdir,\n readFile,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\";\nimport {\n withInternalReadsAsync,\n type CommandInputs,\n} from \"@fluid-app/fluid-cli\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport { BINDING_FILE, parseBinding, type SiteBinding } from \"./binding.js\";\n\n/**\n * The absolute file for an API path, which must stay inside the folder. The\n * paths come from the server, so a path that climbs out of the folder is\n * refused rather than written.\n */\nexport function localPath(root: string, apiPath: string): string {\n const segments = apiPath.split(\"/\");\n const unsafe =\n apiPath.length === 0 ||\n apiPath.includes(\"\\\\\") ||\n apiPath.includes(\"\\0\") ||\n segments.some((s) => s === \"\" || s === \".\" || s === \"..\");\n const target = resolve(root, apiPath);\n if (unsafe || !target.startsWith(root + sep)) throw unwritable(apiPath);\n return target;\n}\n\n/**\n * The file for a path the server listed. The binding file is the CLI's own,\n * so a listed file of that name is refused as well.\n */\nexport function listedPath(root: string, apiPath: string): string {\n if (apiPath === BINDING_FILE) throw unwritable(apiPath);\n return localPath(root, apiPath);\n}\n\nfunction unwritable(apiPath: string): MemberSiteCliError {\n return new MemberSiteCliError(\n `The server listed \"${apiPath}\", which cannot be written inside the site's folder.`,\n );\n}\n\nexport interface FolderState {\n /** The folder holds at least one entry. */\n nonEmpty: boolean;\n /** The binding file, when the folder has a readable one. */\n binding: SiteBinding | null;\n}\n\n/** What is in the folder now. A missing folder is empty. */\nexport async function inspectFolder(root: string): Promise<FolderState> {\n let names: string[];\n try {\n names = await readdir(root);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { nonEmpty: false, binding: null };\n }\n if ((error as NodeJS.ErrnoException).code === \"ENOTDIR\") {\n throw new MemberSiteCliError(`${root} is a file, not a folder.`);\n }\n throw error;\n }\n return { nonEmpty: names.length > 0, binding: await readBinding(root) };\n}\n\n/** The binding in a folder, or null when there is none or it is unreadable. */\nexport async function readBinding(\n root: string,\n inputs?: CommandInputs,\n): Promise<SiteBinding | null> {\n const text = inputs\n ? await readLocalVia(inputs, root, BINDING_FILE)\n : await readLocal(root, BINDING_FILE);\n return text === null ? null : parseBinding(text);\n}\n\n/**\n * readLocal with the bytes read through the declared-inputs reader. The\n * symbolic-link and regular-file checks stay, as a fail-safe on the folder\n * itself.\n */\nasync function readLocalVia(\n inputs: CommandInputs,\n root: string,\n apiPath: string,\n): Promise<string | null> {\n const file = await withInternalReadsAsync(async () => {\n const target = await checkedPath(root, apiPath);\n try {\n return (await lstat(target)).isFile() ? target : null;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n });\n return file === null ? null : inputs.readFile(file, \"utf8\");\n}\n\n/**\n * The file for an API path, after a check that no part of its path under the\n * folder is a symbolic link. A write follows a link, so a link in a folder\n * could make a pull read or replace a file outside it. The folder itself may\n * be a link, as it is the one the person chose.\n */\nexport async function checkedPath(\n root: string,\n apiPath: string,\n): Promise<string> {\n const target = localPath(root, apiPath);\n let current = root;\n for (const part of relative(root, target).split(sep)) {\n current = join(current, part);\n let info;\n try {\n info = await lstat(current);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") break;\n if (code === \"ENOTDIR\") throw unwritable(apiPath);\n throw error;\n }\n if (info.isSymbolicLink()) {\n throw new MemberSiteCliError(\n `${apiPath} is, or is inside, a symbolic link, and a pull does not write through one. Remove the link, or pull into another folder.`,\n );\n }\n }\n return target;\n}\n\n/** A file's text, or null when it does not exist or is not a regular file. */\nexport async function readLocal(\n root: string,\n apiPath: string,\n): Promise<string | null> {\n const file = await checkedPath(root, apiPath);\n try {\n if (!(await lstat(file)).isFile()) return null;\n return await readFile(file, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n}\n\n/**\n * Removes a file of the folder. The path is checked first, so a link in it is\n * refused, and a file that is already gone is not an error.\n */\nexport async function removeLocal(\n root: string,\n apiPath: string,\n): Promise<void> {\n const file = await withInternalReadsAsync(() => checkedPath(root, apiPath));\n await rm(file, { force: true });\n}\n\n/**\n * Writes a file through a new file in the same folder that replaces it, so\n * the write never opens an existing path and a link planted after the check\n * is replaced, not followed.\n */\nexport async function writeLocal(\n root: string,\n apiPath: string,\n content: string,\n): Promise<void> {\n const file = await withInternalReadsAsync(() => checkedPath(root, apiPath));\n await mkdir(dirname(file), { recursive: true });\n // A folder made just now is checked again, as a link could stand in its place.\n await withInternalReadsAsync(() => checkedPath(root, apiPath));\n const temp = join(\n dirname(file),\n `.${basename(file)}.${randomBytes(6).toString(\"hex\")}.tmp`,\n );\n try {\n await writeFile(temp, content, { encoding: \"utf8\", flag: \"wx\" });\n await rename(temp, file);\n } catch (error) {\n await rm(temp, { force: true });\n throw error;\n }\n}\n","import { resolve } from \"node:path\";\nimport type { CommandInputs } from \"@fluid-app/fluid-cli\";\nimport {\n clientForOwner,\n parseLoginKind,\n type Clients,\n type Login,\n} from \"../api/credential.js\";\nimport { MemberSiteCliError, type ErrorContext } from \"../api/errors.js\";\nimport { getApiBase } from \"../api/http.js\";\nimport type { SiteApi } from \"../api/types.js\";\nimport { BINDING_FILE, type SiteBinding } from \"../tree/binding.js\";\nimport { readBinding } from \"../tree/folder.js\";\n\n/** What a command that acts on a pulled folder needs from outside. */\nexport interface BoundSiteDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n /** The declared-inputs reader, when the command declared its inputs. */\n inputs?: CommandInputs;\n}\n\n/** A pulled folder, its binding, and the client that builds its site. */\nexport interface BoundSite {\n dir: string;\n /** A copy: a command changes it and saves it. */\n binding: SiteBinding;\n api: SiteApi;\n}\n\n/**\n * Opens a pulled folder for a command that sends requests. The binding names\n * the owner kind, which needs one kind of login, so another is refused here,\n * and so is a folder pulled from another API. No request is made.\n */\nexport async function openBoundSite(\n dirArg: string | undefined,\n as: string | undefined,\n deps: BoundSiteDeps,\n context: ErrorContext,\n): Promise<BoundSite> {\n const dir = resolve(dirArg ?? \".\");\n const stored = await readBinding(dir, deps.inputs);\n if (!stored) {\n throw new MemberSiteCliError(\n `${dir} is not a pulled site: it has no readable ${BINDING_FILE}. Pull the site first: fluid member-site pull <site> [dir]`,\n );\n }\n const binding = structuredClone(stored);\n context.ownerKind = binding.owner_kind;\n\n const login = await deps.loadLogin(parseLoginKind(as));\n const api = clientForOwner(\n binding.owner_kind,\n login,\n deps.createClients(login.token),\n );\n if (binding.api_base !== getApiBase()) {\n throw new MemberSiteCliError(\n `${dir} was pulled from ${binding.api_base}, and this login is for ${getApiBase()}. Use the login it was pulled with, or pull the site again.`,\n );\n }\n return { dir, binding, api };\n}\n","import { lstat, readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { withInternalReadsAsync, type InputTree } from \"@fluid-app/fluid-cli\";\nimport { sha256, type SiteBinding } from \"./binding.js\";\n\n/** Folders whose files are Liquid templates. */\nconst TEMPLATE_FOLDERS = {\n layouts: \"layout\",\n sections: \"section\",\n snippets: \"snippet\",\n} as const;\n\n/** The extensions of a text asset, the ones the file API accepts. */\nconst TEXT_ASSET_PATTERN = /^[a-z0-9_-]+\\.(css|js|json|txt|html)$/;\nconst NAME_PATTERN = /^[a-z0-9_-]+$/;\nconst PAGE_SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\nexport const SETTINGS_PATH = \"config/settings.json\";\nexport const MENUS_PATH = \"config/menus.json\";\nexport const ACCESS_PATH = \"config/access.json\";\n\n/** What a path in the folder is, or why it cannot be one of the site's. */\nexport type PathClass =\n | { kind: \"layout\" | \"section\" | \"snippet\" | \"asset\" }\n | { kind: \"page\"; slug: string }\n | { kind: \"settings\" | \"menus\" | \"pages-config\" | \"access\" }\n | { kind: \"refused\"; reason: string };\n\nconst NOT_TEXT_ASSET = (path: string) =>\n `${path} is not a text asset. A site holds css, js, json, txt and html files; use images and fonts from the media library by URL.`;\n\nfunction refused(reason: string): PathClass {\n return { kind: \"refused\", reason };\n}\n\n/**\n * Classifies a path under the folder by the file API's rules: the folders and\n * config files a site has, names of lowercase letters, digits, hyphens and\n * underscores, and no subfolders.\n */\nexport function classifyPath(path: string): PathClass {\n const segments = path.split(\"/\");\n const [folder, name] = segments;\n if (segments.length === 1 || folder === undefined || name === undefined) {\n return refused(\n `${path} is outside the folders a site holds: layouts, sections, snippets, pages, assets and config.`,\n );\n }\n if (folder === \"config\") {\n if (segments.length > 2) {\n return refused(`${path} is in a subfolder of config.`);\n }\n if (name === \"settings.json\") return { kind: \"settings\" };\n if (name === \"menus.json\") return { kind: \"menus\" };\n if (name === \"pages.json\") return { kind: \"pages-config\" };\n if (name === \"access.json\") return { kind: \"access\" };\n return refused(\n `${path} is not a site file. config/ holds settings.json, menus.json, pages.json and access.json.`,\n );\n }\n if (folder === \"assets\") {\n if (segments.length > 2) return refused(`${path} is in a subfolder.`);\n if (TEXT_ASSET_PATTERN.test(name)) return { kind: \"asset\" };\n return refused(\n /\\.(css|js|json|txt|html)$/.test(name)\n ? `${path}: names use lowercase letters, digits, hyphens and underscores.`\n : NOT_TEXT_ASSET(path),\n );\n }\n const templateKind = (TEMPLATE_FOLDERS as Record<string, string>)[folder];\n if (templateKind === undefined && folder !== \"pages\") {\n return refused(\n `${path} is outside the folders a site holds: layouts, sections, snippets, pages, assets and config.`,\n );\n }\n if (segments.length > 2) return refused(`${path} is in a subfolder.`);\n if (!name.endsWith(\".liquid\")) return refused(`${path} must end in .liquid.`);\n const stem = name.slice(0, -\".liquid\".length);\n if (folder === \"pages\") {\n if (stem === \"index\") return { kind: \"page\", slug: \"/\" };\n if (PAGE_SLUG_PATTERN.test(stem)) return { kind: \"page\", slug: stem };\n return refused(\n `${path}: a page's name uses lowercase letters and digits separated by single hyphens.`,\n );\n }\n if (!NAME_PATTERN.test(stem)) {\n return refused(\n `${path}: names use lowercase letters, digits, hyphens and underscores.`,\n );\n }\n return { kind: templateKind as \"layout\" | \"section\" | \"snippet\" };\n}\n\n/** The text of a file, or null when the bytes are not valid UTF-8 text. */\nfunction decodeText(bytes: Buffer): string | null {\n if (bytes.includes(0)) return null;\n const text = bytes.toString(\"utf8\");\n return Buffer.from(text, \"utf8\").equals(bytes) ? text : null;\n}\n\n/** The local folder as a push sees it. */\nexport interface LocalTree {\n /** The text of each file of the site, keyed by its API path. */\n files: Map<string, string>;\n /** Why a file in the folder cannot be pushed. Nothing is sent while any remain. */\n refusals: string[];\n /** The same reasons with the file each is about. */\n refusedFiles: Array<{ path: string; reason: string }>;\n}\n\ntype Refuse = (path: string, reason: string) => void;\n\n/**\n * The paths under a folder as API paths. Names that start with a dot are\n * skipped, and so are the names in `skip`. A symbolic link is refused, not\n * followed.\n */\nasync function walk(\n root: string,\n prefix: string,\n refuse: Refuse,\n skip: readonly string[] = [],\n): Promise<string[]> {\n const found: string[] = [];\n const entries = await readdir(join(root, prefix), { withFileTypes: true });\n for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {\n if (entry.name.startsWith(\".\") || skip.includes(entry.name)) continue;\n const path = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isSymbolicLink()) {\n refuse(\n path,\n `${path} is a symbolic link, and a push does not follow one.`,\n );\n } else if (entry.isDirectory()) {\n found.push(...(await walk(root, path, refuse, skip)));\n } else if (entry.isFile()) {\n found.push(path);\n }\n }\n return found;\n}\n\n/** Where `readLocalTree` gets the folder's paths and bytes. */\nexport interface TreeSource {\n /** The API paths of the folder's files, refusing what a push must not follow. */\n list(refuse: Refuse): Promise<string[]>;\n /** The bytes of one listed file, or null when it is not a regular file. */\n read(path: string): Promise<Buffer | null>;\n}\n\nfunction diskSource(root: string): TreeSource {\n return {\n list: (refuse) => walk(root, \"\", refuse),\n async read(path) {\n const file = join(root, path);\n return (await lstat(file)).isFile() ? readFile(file) : null;\n },\n };\n}\n\nfunction byPath(a: string, b: string): number {\n const x = a.split(\"/\");\n const y = b.split(\"/\");\n for (let i = 0; i < Math.min(x.length, y.length); i++) {\n if (x[i] !== y[i]) return (x[i] as string) < (y[i] as string) ? -1 : 1;\n }\n return x.length - y.length;\n}\n\n/**\n * A source that lists and reads through a declared input tree. The tree\n * skips symbolic links, and a push deletes remote files missing locally, so a\n * link is still refused here by a walk of the folder itself.\n *\n * root - the folder the tree was opened on\n * tree - the input tree from ctx.inputs\n * skip - the tree's ignored names, which the walk skips too\n *\n * Returns the source.\n */\nexport function inputTreeSource(\n root: string,\n tree: InputTree,\n skip: readonly string[],\n): TreeSource {\n return {\n async list(refuse) {\n await withInternalReadsAsync(() => walk(root, \"\", refuse, skip));\n return tree.list().sort(byPath);\n },\n read: async (path) => tree.readFile(path),\n };\n}\n\n/**\n * Reads the folder's files and refuses what the file API would refuse: a path\n * outside the allowed set, a name outside the allowed characters, an asset\n * that is not text or is empty. A file the binding says the pull wrote and\n * that has not changed is not judged: it came from the server. With no\n * binding every file is judged, as `lint` does.\n */\nexport async function readLocalTree(\n root: string,\n binding: SiteBinding | null,\n source: TreeSource = diskSource(root),\n): Promise<LocalTree> {\n const files = new Map<string, string>();\n const refusals: string[] = [];\n const refusedFiles: LocalTree[\"refusedFiles\"] = [];\n const refuse: Refuse = (path, reason) => {\n refusals.push(reason);\n refusedFiles.push({ path, reason });\n };\n const paths = await source.list(refuse);\n\n for (const path of paths) {\n const verdict = classifyPath(path);\n const pulled = binding?.files[path];\n if (verdict.kind === \"refused\" && !pulled) {\n refuse(path, verdict.reason);\n continue;\n }\n\n const bytes = await source.read(path);\n if (bytes === null) continue;\n const text = decodeText(bytes);\n if (text !== null && pulled?.sha256 === sha256(text)) {\n files.set(path, text);\n continue;\n }\n\n if (verdict.kind === \"refused\") {\n refuse(path, verdict.reason);\n } else if (text === null) {\n refuse(\n path,\n verdict.kind === \"asset\"\n ? NOT_TEXT_ASSET(path)\n : `${path} is not valid UTF-8 text.`,\n );\n } else if (verdict.kind === \"asset\" && text.length === 0) {\n refuse(path, `${path} is empty, and an asset cannot be empty.`);\n } else {\n files.set(path, text);\n }\n }\n return { files, refusals, refusedFiles };\n}\n","import type { Site, SitePage } from \"../api/types.js\";\nimport { pageRow, serializeJson } from \"./binding.js\";\n\n/** The home page's slug. Its template, and so its file, is `index`. */\nconst HOME_SLUG = \"/\";\n\n/** The path of the file that holds a page's content. */\nexport function pageFilePath(slug: string): string {\n return `pages/${slug === HOME_SLUG ? \"index\" : slug}.liquid`;\n}\n\n/**\n * `config/pages.json`: the site's default layout and what each page row holds\n * besides its content. Pages are in position order, then slug, so a second\n * pull of an unchanged site writes the same bytes.\n */\nexport function buildPagesConfig(site: Site, pages: SitePage[]): string {\n const ordered = [...pages].sort(\n (a, b) => a.position - b.position || (a.slug < b.slug ? -1 : 1),\n );\n return serializeJson({\n layout: site.layout_name ?? null,\n pages: ordered.map((page) => ({ slug: page.slug, ...pageRow(page) })),\n });\n}\n","","import vocabulary from \"./access-vocabulary.json\";\n\n/**\n * Reads `config/access.json`, a site's declaration of the viewer data it uses\n * and the actions it takes as the viewer. It mirrors\n * `MemberSites::AccessDeclaration` (apps/rails/app/models/member_sites/\n * access_declaration.rb) rule for rule, so `lint` reports what the file API\n * would refuse. `access-vocabulary.json` holds the lists, and a Rails test\n * holds them equal to the model's.\n *\n * The document is one object with one key, `uses`, a list of unique strings,\n * and no key is repeated at any depth. Each string is a member field\n * (`member.email`) or a scope (`orders: read`).\n */\n\n/** The fields and the families a declaration may name. */\nexport const FIELDS: readonly string[] = vocabulary.fields;\nexport const FAMILIES: readonly string[] = vocabulary.families;\nconst MODES: readonly string[] = vocabulary.modes;\n\n/** What a valid declaration holds, sorted and without duplicates. */\nexport interface AccessDeclaration {\n /** The member fields without the `member.` prefix. */\n fields: string[];\n /** The scopes, such as `orders: read`. */\n scopes: string[];\n}\n\nexport type AccessParse =\n | { declaration: AccessDeclaration; problems: [] }\n | { declaration: null; problems: string[] };\n\nconst FIELD_PREFIX = \"member.\";\n\nfunction entryProblems(entry: unknown): string[] {\n if (typeof entry !== \"string\") {\n return [`${JSON.stringify(entry)} is not a string`];\n }\n if (entry.startsWith(FIELD_PREFIX)) {\n return FIELDS.includes(entry.slice(FIELD_PREFIX.length))\n ? []\n : [`${JSON.stringify(entry)} is not a member field`];\n }\n const separator = entry.indexOf(\": \");\n if (separator < 0) {\n return [\n `${JSON.stringify(entry)} is neither \"member.<field>\" nor \"<family>: read\" or \"<family>: write\"`,\n ];\n }\n const family = entry.slice(0, separator);\n const mode = entry.slice(separator + 2);\n if (family === \"session\") {\n return [\n `\"session: read\" is granted with any other scope and is not declared`,\n ];\n }\n return [\n ...(FAMILIES.includes(family)\n ? []\n : [`${JSON.stringify(entry)} names an unknown family`]),\n ...(MODES.includes(mode)\n ? []\n : [`${JSON.stringify(entry)} has a mode other than read or write`]),\n ];\n}\n\n/**\n * Whether a text of valid JSON repeats a key in any object, which the file API\n * refuses because the value of a key would depend on which copy a parser\n * keeps. It walks the text, so a repeat is seen that `JSON.parse` hides.\n */\nfunction repeatsAKey(text: string): boolean {\n const objects: Array<Set<string> | null> = [];\n let expectKey = false;\n for (let i = 0; i < text.length; i++) {\n const char = text[i] as string;\n if (char === '\"') {\n let end = i + 1;\n while (text[end] !== '\"') end += text[end] === \"\\\\\" ? 2 : 1;\n const scope = objects[objects.length - 1];\n if (expectKey && scope) {\n const key = JSON.parse(text.slice(i, end + 1)) as string;\n if (scope.has(key)) return true;\n scope.add(key);\n expectKey = false;\n }\n i = end;\n } else if (char === \"{\") {\n objects.push(new Set());\n expectKey = true;\n } else if (char === \"[\") {\n objects.push(null);\n expectKey = false;\n } else if (char === \"}\" || char === \"]\") {\n objects.pop();\n expectKey = false;\n } else if (char === \",\") {\n expectKey = objects[objects.length - 1] instanceof Set;\n }\n }\n return false;\n}\n\nfunction documentProblems(document: unknown): string[] {\n if (\n typeof document !== \"object\" ||\n document === null ||\n Array.isArray(document)\n ) {\n return [\"the document must be one object\"];\n }\n const record = document as Record<string, unknown>;\n const unknown = Object.keys(record)\n .filter((key) => key !== \"uses\")\n .map((key) => `unknown key ${JSON.stringify(key)}, the only key is \"uses\"`);\n if (!(\"uses\" in record)) {\n return [...unknown, `the document needs a \"uses\" key`];\n }\n const uses = record[\"uses\"];\n if (!Array.isArray(uses)) {\n return [...unknown, `\"uses\" must be a list of strings`];\n }\n const entries: unknown[] = uses;\n const tooMany =\n entries.length > vocabulary.maxEntries\n ? [`more than ${vocabulary.maxEntries} entries`]\n : [];\n const repeated = entries\n .filter((entry): entry is string => typeof entry === \"string\")\n .filter((entry, index, all) => all.indexOf(entry) !== index);\n return [\n ...unknown,\n ...tooMany,\n ...entries.flatMap(entryProblems),\n ...[...new Set(repeated)].map(\n (entry) => `${JSON.stringify(entry)} is listed twice`,\n ),\n ];\n}\n\n/** Parses a `config/access.json` text, or says what the file API would refuse. */\nexport function parseAccessDeclaration(text: string): AccessParse {\n if (Buffer.byteLength(text, \"utf8\") > vocabulary.maxBytes) {\n return {\n declaration: null,\n problems: [`the document is over ${vocabulary.maxBytes} bytes`],\n };\n }\n let document: unknown;\n try {\n document = JSON.parse(text);\n } catch {\n return { declaration: null, problems: [\"the document is not valid JSON\"] };\n }\n if (repeatsAKey(text)) {\n return {\n declaration: null,\n problems: [\n \"the document repeats a key, and a repeated key has no single meaning\",\n ],\n };\n }\n const problems = documentProblems(document);\n if (problems.length > 0) return { declaration: null, problems };\n\n const entries = (document as { uses: string[] }).uses;\n const fields = entries\n .filter((entry) => entry.startsWith(FIELD_PREFIX))\n .map((entry) => entry.slice(FIELD_PREFIX.length));\n const scopes = entries.filter((entry) => !entry.startsWith(FIELD_PREFIX));\n return {\n declaration: { fields: [...fields].sort(), scopes: [...scopes].sort() },\n problems: [],\n };\n}\n","import { parseAccessDeclaration } from \"./access-declaration.js\";\nimport {\n PAGES_CONFIG_PATH,\n sha256,\n type PageRow,\n type SiteBinding,\n} from \"./binding.js\";\nimport {\n ACCESS_PATH,\n classifyPath,\n MENUS_PATH,\n SETTINGS_PATH,\n type LocalTree,\n} from \"./local-tree.js\";\nimport { pageFilePath } from \"./pages-config.js\";\n\nconst PAGE_SLUG_PATTERN = /^(\\/|[a-z0-9]+(-[a-z0-9]+)*)$/;\nconst ROW_KEYS = [\"slug\", \"title\", \"position\", \"published\", \"layout\", \"menu\"];\nconst ROW_FIELDS = [\"title\", \"position\", \"published\", \"layout\"] as const;\ntype RowField = (typeof ROW_FIELDS)[number];\n\n/** The most pages one site holds, the same number the server refuses past. */\nexport const PAGE_LIMIT = 50;\nconst MENU_SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n/** Why a change is listed and not sent. */\nconst NOT_PUSHED_YET = \"the site row is not pushed yet\";\n\n/** The refusal for a file the binding holds no version of, which a write or a delete needs. */\nfunction noVersion(path: string): string {\n return `The binding holds no version of ${path}. Pull the site again first.`;\n}\n\n/**\n * The kinds of file that are written and removed through the file routes. The\n * access declaration is one: it is written at its version, created only when\n * the site has none, and removed to clear its draft.\n */\nfunction isPlainFile(path: string): boolean {\n const kind = classifyPath(path).kind;\n return (\n kind === \"layout\" ||\n kind === \"section\" ||\n kind === \"snippet\" ||\n kind === \"asset\" ||\n kind === \"access\"\n );\n}\n\n/** A company site takes no declaration and the server refuses the path, so a push leaves it alone. */\nfunction skipsAccess(binding: SiteBinding, path: string): boolean {\n return binding.owner_kind === \"company\" && path === ACCESS_PATH;\n}\n\n/** One request of a push, in the order they are sent. */\nexport type PushStep =\n | {\n kind: \"settings\";\n path: typeof SETTINGS_PATH;\n content: string;\n /** The version the binding recorded. */\n lockVersion: number;\n }\n | {\n /** A layout, section, snippet or asset: created, or replaced at a version. */\n kind: \"file-write\";\n path: string;\n content: string;\n /** The version the binding recorded, or, for a path it never saw, that none may exist. */\n precondition: { version: string } | { mustNotExist: true };\n }\n | {\n kind: \"page-create\";\n slug: string;\n row: PageRow;\n /** The text of the page's file, which a new page needs. */\n content: string;\n }\n | {\n kind: \"page-update\";\n slug: string;\n id: string;\n lockVersion: number;\n /** Row fields that changed, by their `config/pages.json` names. */\n fields: Partial<Omit<PageRow, \"menu\">>;\n content: string | undefined;\n }\n | {\n kind: \"menus\";\n path: typeof MENUS_PATH;\n content: string;\n /** The digest of the menus document the binding recorded. */\n version: string;\n }\n | { kind: \"page-menu\"; slug: string; menu: string | null }\n | { kind: \"page-delete\"; slug: string; id: string; lockVersion: number }\n | { kind: \"file-delete\"; path: string; version: string };\n\n/** A change the folder holds that this push does not send. */\nexport interface NotSent {\n subject: string;\n why: string;\n}\n\nexport interface PushPlan {\n steps: PushStep[];\n notSent: NotSent[];\n /** Files that cannot be removed, which the push leaves as the server has them. */\n notes: string[];\n /** What takes effect without a publish, and so before one. */\n immediate: string[];\n /** Why the folder cannot be pushed. Nothing is sent while any remain. */\n refusals: string[];\n /**\n * Whether every row in `config/pages.json` is now what the server holds, so\n * the file counts as pushed.\n */\n pagesConfigSettled: boolean;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The slugs of the menus a `config/menus.json` text defines, or null when it has no such list. */\nfunction menuSlugs(text: string): Set<string> | null {\n try {\n const document: unknown = JSON.parse(text);\n const menus = isRecord(document) ? document[\"menus\"] : undefined;\n if (!Array.isArray(menus)) return null;\n return new Set(\n menus.flatMap((menu) =>\n isRecord(menu) && typeof menu[\"slug\"] === \"string\"\n ? [menu[\"slug\"]]\n : [],\n ),\n );\n } catch {\n return null;\n }\n}\n\nfunction isText(value: unknown): boolean {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nconst HEX = \"[0-9A-Fa-f]\";\nconst PCT = `%${HEX}{2}`;\nconst H16 = `${HEX}{1,4}`;\nconst DEC_OCTET = \"(?:[1-9]\\\\d|1\\\\d{2}|2[0-4]\\\\d|25[0-5]|\\\\d)\";\nconst IPV4 = `${DEC_OCTET}(?:\\\\.${DEC_OCTET}){3}`;\nconst LS32 = `(?:${H16}:${H16}|${IPV4})`;\nconst IPV6 = [\n `(?:${H16}:){6}${LS32}`,\n `::(?:${H16}:){5}${LS32}`,\n `(?:${H16})?::(?:${H16}:){4}${LS32}`,\n `(?:(?:${H16}:)?${H16})?::(?:${H16}:){3}${LS32}`,\n `(?:(?:${H16}:){0,2}${H16})?::(?:${H16}:){2}${LS32}`,\n `(?:(?:${H16}:){0,3}${H16})?::${H16}:${LS32}`,\n `(?:(?:${H16}:){0,4}${H16})?::${LS32}`,\n `(?:(?:${H16}:){0,5}${H16})?::${H16}`,\n `(?:(?:${H16}:){0,6}${H16})?::`,\n].join(\"|\");\nconst IP_LITERAL = `\\\\[(?:${IPV6}|v${HEX}+\\\\.[!$&-.0-9:;=A-Z_a-z~]+)\\\\]`;\nconst REG_NAME = `(?:${PCT}|[!$&-.0-9;=A-Z_a-z~])+`;\nconst SEGMENT = `(?:${PCT}|[!$&-.0-9:;=@A-Z_a-z~/])`;\n\n/**\n * The URL grammar of Ruby's `URI.parse` (RFC 3986), which the server runs a\n * menu link through, narrowed to an http or https address with a host. Ruby\n * takes any ASCII in a query, so the query is the one part left that wide, and\n * its one capture group. `queryEscapesInvalid` covers what Ruby then refuses.\n */\nconst HTTP_URL = new RegExp(\n `^[Hh][Tt][Tt][Pp][Ss]?://(?:(?:${PCT}|[!$&-.0-9:;=A-Z_a-z~])*@)?` +\n `(?:${IP_LITERAL}|${IPV4}|${REG_NAME})(?::\\\\d*)?` +\n `(?:/${SEGMENT}*)?` +\n \"(?:\\\\?([\\\\x00-\\\\x22\\\\x24-\\\\x7f]*))?\" +\n `(?:#(?:${PCT}|[!$&-.0-9:;=@A-Z_a-z~/?])*)?$`,\n);\n\n/**\n * Whether the server accepts the value as a menu link. The value is judged as\n * written: no trimming, and no escaping of spaces or non-ASCII, which `new URL`\n * would do and the server's parser does not.\n */\nfunction isHttpUrl(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const match = HTTP_URL.exec(value);\n return match !== null && !queryEscapesInvalid(match[1]);\n}\n\n/**\n * Whether a query holds a `%` followed by two characters that are not hex\n * digits, which Ruby refuses as an invalid percent escape. It drops tabs and\n * line breaks from the query first, so those cannot hide one.\n */\nfunction queryEscapesInvalid(query: string | undefined): boolean {\n return /%[^0-9A-Fa-f][^0-9A-Fa-f]/.test(\n (query ?? \"\").replace(/[\\t\\r\\n]/g, \"\"),\n );\n}\n\n/** The problems of a node that must be an object with only these keys and all the required ones. */\nfunction objectProblems(\n node: unknown,\n path: string,\n allowed: string[],\n required: string[],\n): string[] {\n if (!isRecord(node)) return [`${path} must be an object.`];\n const problems: string[] = [];\n const unknown = Object.keys(node).filter((key) => !allowed.includes(key));\n const missing = required.filter((key) => !(key in node));\n if (unknown.length > 0) {\n problems.push(`${path} has unknown keys: ${unknown.join(\", \")}.`);\n }\n if (missing.length > 0) {\n problems.push(`${path} needs ${missing.join(\", \")}.`);\n }\n return problems;\n}\n\nfunction menuItemsProblems(items: unknown, path: string): string[] {\n if (!Array.isArray(items)) return [`${path} must be an array.`];\n return items.flatMap((item: unknown, index) => {\n const where = `${path}[${index}]`;\n const shape = objectProblems(\n item,\n where,\n [\"title\", \"page\", \"url\", \"items\"],\n [\"title\"],\n );\n if (shape.length > 0 || !isRecord(item)) return shape;\n const problems: string[] = [];\n if (!isText(item[\"title\"])) {\n problems.push(`${where}.title must be a non-empty string.`);\n }\n if (\"page\" in item && \"url\" in item) {\n problems.push(`${where} cannot have both page and url.`);\n } else if (\"page\" in item && typeof item[\"page\"] !== \"string\") {\n problems.push(`${where}.page must be a page slug.`);\n } else if (\"url\" in item && !isHttpUrl(item[\"url\"])) {\n problems.push(`${where}.url must be an http or https address.`);\n }\n if (\"items\" in item) {\n problems.push(...menuItemsProblems(item[\"items\"], `${where}.items`));\n }\n return problems;\n });\n}\n\n/**\n * The problems of a parsed `config/menus.json` that the server would refuse\n * for its shape alone. Whether a linked page exists is left to the server,\n * which holds the pages.\n */\nexport function menusProblems(document: unknown): string[] {\n const top = objectProblems(document, MENUS_PATH, [\"menus\"], [\"menus\"]);\n if (top.length > 0 || !isRecord(document)) return top;\n const menus = document[\"menus\"];\n if (!Array.isArray(menus))\n return [`${MENUS_PATH}: \"menus\" must be an array.`];\n\n const problems: string[] = [];\n menus.forEach((menu: unknown, index) => {\n const where = `${MENUS_PATH}, menus[${index}]`;\n const shape = objectProblems(\n menu,\n where,\n [\"slug\", \"title\", \"items\"],\n [\"slug\", \"title\"],\n );\n problems.push(...shape);\n if (shape.length > 0 || !isRecord(menu)) return;\n const slug = menu[\"slug\"];\n if (typeof slug !== \"string\" || !MENU_SLUG_PATTERN.test(slug)) {\n problems.push(`${where}.slug must be lowercase words joined by hyphens.`);\n }\n if (!isText(menu[\"title\"])) {\n problems.push(`${where}.title must be a non-empty string.`);\n }\n if (\"items\" in menu) {\n problems.push(...menuItemsProblems(menu[\"items\"], `${where}.items`));\n }\n });\n\n const counts = new Map<string, number>();\n for (const menu of menus) {\n if (isRecord(menu) && typeof menu[\"slug\"] === \"string\") {\n counts.set(menu[\"slug\"], (counts.get(menu[\"slug\"]) ?? 0) + 1);\n }\n }\n for (const [slug, count] of counts) {\n if (count > 1) {\n problems.push(\n `${MENUS_PATH}: the menu slug \"${slug}\" is used more than once.`,\n );\n }\n }\n return problems;\n}\n\nexport interface ParsedPages {\n layout: string | null;\n rows: Array<PageRow & { slug: string }>;\n}\n\n/** The rows of `config/pages.json`, or the reasons it is not one. */\nexport function parsePagesConfig(text: string): ParsedPages | string[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return [`${PAGES_CONFIG_PATH} is not valid JSON.`];\n }\n if (!isRecord(parsed) || !Array.isArray(parsed[\"pages\"])) {\n return [`${PAGES_CONFIG_PATH} must hold an object with a \"pages\" list.`];\n }\n const layout = parsed[\"layout\"] ?? null;\n if (layout !== null && typeof layout !== \"string\") {\n return [`${PAGES_CONFIG_PATH}: \"layout\" must be a layout name or null.`];\n }\n\n const problems: string[] = [];\n const rows: ParsedPages[\"rows\"] = [];\n const seen = new Set<string>();\n parsed[\"pages\"].forEach((raw: unknown, index) => {\n const where = `${PAGES_CONFIG_PATH}, page ${index + 1}`;\n if (!isRecord(raw)) {\n problems.push(`${where} is not an object.`);\n return;\n }\n for (const key of Object.keys(raw)) {\n if (!ROW_KEYS.includes(key)) {\n problems.push(\n `${where} has an unknown key \"${key}\". A page row holds ${ROW_KEYS.join(\", \")}.`,\n );\n }\n }\n const { slug, title, position, published } = raw;\n const rowLayout = raw[\"layout\"] ?? null;\n const menu = raw[\"menu\"] ?? null;\n if (typeof slug !== \"string\" || !PAGE_SLUG_PATTERN.test(slug)) {\n problems.push(\n `${where}: \"slug\" must be \"/\" or lowercase letters and digits separated by single hyphens.`,\n );\n return;\n }\n if (seen.has(slug))\n problems.push(`${where}: the slug \"${slug}\" is used twice.`);\n seen.add(slug);\n const named = `${PAGES_CONFIG_PATH}, page \"${slug}\"`;\n if (\n typeof title !== \"string\" ||\n title.trim() === \"\" ||\n title.length > 255\n ) {\n problems.push(`${named}: \"title\" must be 1 to 255 characters.`);\n }\n if (!Number.isInteger(position)) {\n problems.push(`${named}: \"position\" must be an integer.`);\n }\n if (typeof published !== \"boolean\") {\n problems.push(`${named}: \"published\" must be true or false.`);\n }\n if (rowLayout !== null && typeof rowLayout !== \"string\") {\n problems.push(`${named}: \"layout\" must be a layout name or null.`);\n }\n if (menu !== null && typeof menu !== \"string\") {\n problems.push(`${named}: \"menu\" must be a menu slug or null.`);\n }\n rows.push({\n slug,\n title: title as string,\n position: position as number,\n published: published as boolean,\n layout: rowLayout as string | null,\n menu: menu as string | null,\n });\n });\n return problems.length > 0 ? problems : { layout, rows };\n}\n\n/** Whether two JSON texts hold the same value, whatever their formatting and key order. */\nexport function sameJson(a: string, b: string): boolean {\n const canonical = (value: unknown): unknown =>\n Array.isArray(value)\n ? value.map(canonical)\n : isRecord(value)\n ? Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [key, canonical(value[key])]),\n )\n : value;\n try {\n return (\n JSON.stringify(canonical(JSON.parse(a))) ===\n JSON.stringify(canonical(JSON.parse(b)))\n );\n } catch {\n return false;\n }\n}\n\n/** `true` when the folder's text differs from what the binding says the pull wrote. */\nfunction changed(text: string, recorded: string | undefined): boolean {\n return recorded === undefined || sha256(text) !== recorded;\n}\n\n/**\n * Compares the folder with the binding and orders what a push sends. It reads\n * nothing but its arguments.\n *\n * The order is the server's: settings first, then layouts, sections, snippets\n * and assets, then new pages, then changed pages, then the menus, then each\n * page's menu assignment, then the removed pages and files. A page's file is\n * written with its row, so it never precedes its page. A menu link needs its\n * page, and a page's assignment needs its menu, so the menus come between.\n * Removals come last, so a write that fails never leaves a menu pointing at a\n * page that is gone.\n *\n * The plan puts the settings ahead of every page request, which is not where\n * the file list has them: every page change moves the settings version, so a\n * settings write that followed one would answer 409 to the push's own work.\n *\n * Every write carries the version the binding recorded for its file or page,\n * and a file the binding never saw carries a must-not-exist condition. A file\n * the binding holds no version of is refused, as a write without one would\n * replace whatever the server has.\n */\nexport function planPush(binding: SiteBinding, tree: LocalTree): PushPlan {\n const plan: PushPlan = {\n steps: [],\n notSent: [],\n notes: [],\n immediate: [],\n refusals: [...tree.refusals],\n pagesConfigSettled: true,\n };\n const settingsStep: PushStep[] = [];\n const fileSteps: PushStep[] = [];\n const createSteps: PushStep[] = [];\n const updateSteps: PushStep[] = [];\n const menuStep: PushStep[] = [];\n const assignSteps: PushStep[] = [];\n const pageDeleteSteps: PushStep[] = [];\n const fileDeleteSteps: PushStep[] = [];\n const rowsSent = new Set<string>();\n const assetLines: string[] = [];\n\n // config/settings.json carries the version the binding recorded.\n const settings = tree.files.get(SETTINGS_PATH);\n const settingsBound = binding.files[SETTINGS_PATH];\n if (settings === undefined) {\n if (settingsBound) {\n plan.notes.push(\n `${SETTINGS_PATH} is missing here. Settings cannot be removed, so the server keeps its copy.`,\n );\n }\n } else if (changed(settings, settingsBound?.sha256)) {\n let valid = false;\n try {\n valid = isRecord(JSON.parse(settings));\n } catch {\n valid = false;\n }\n const lockVersion = binding.settings.lock_version;\n if (!valid) {\n plan.refusals.push(`${SETTINGS_PATH} must hold a JSON object.`);\n } else if (lockVersion === null || lockVersion === undefined) {\n plan.refusals.push(\n `The binding holds no version of ${SETTINGS_PATH}. Pull the site again first.`,\n );\n } else {\n settingsStep.push({\n kind: \"settings\",\n path: SETTINGS_PATH,\n content: settings,\n lockVersion,\n });\n }\n }\n\n // The menus replace every menu of the site, so they go only when they changed.\n const menus = tree.files.get(MENUS_PATH);\n const menusBound = binding.files[MENUS_PATH];\n let localMenus: Set<string> | null = null;\n if (menus === undefined) {\n if (menusBound) {\n plan.notes.push(\n `${MENUS_PATH} is missing here. Menus cannot be removed; write an empty menus document to clear them.`,\n );\n }\n } else {\n localMenus = menuSlugs(menus);\n if (changed(menus, menusBound?.sha256)) {\n let document: unknown;\n let parsedOk = true;\n try {\n document = JSON.parse(menus);\n } catch {\n parsedOk = false;\n }\n if (!parsedOk) {\n plan.refusals.push(`${MENUS_PATH} is not valid JSON.`);\n } else {\n // The menus are written after the settings and pages, so a document\n // the server would refuse for its shape must stop the push here.\n const problems = menusProblems(document);\n if (problems.length > 0) {\n plan.refusals.push(...problems);\n } else if (menusBound?.version === undefined) {\n plan.refusals.push(noVersion(MENUS_PATH));\n } else {\n menuStep.push({\n kind: \"menus\",\n path: MENUS_PATH,\n content: menus,\n version: menusBound.version,\n });\n plan.immediate.push(\n `${MENUS_PATH} (replaces every menu of the site)`,\n );\n }\n }\n }\n }\n\n // Page rows and page files.\n const pagesText = tree.files.get(PAGES_CONFIG_PATH);\n let parsed: ParsedPages | null = null;\n if (pagesText === undefined) {\n if (Object.keys(binding.pages).length > 0) {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH} is missing. Pull the site to restore it.`,\n );\n } else {\n parsed = { layout: binding.layout ?? null, rows: [] };\n }\n } else {\n const result = parsePagesConfig(pagesText);\n if (Array.isArray(result)) plan.refusals.push(...result);\n else parsed = result;\n }\n\n if (parsed) {\n const rowSlugs = new Set(parsed.rows.map((row) => row.slug));\n\n if (\n binding.layout !== undefined &&\n parsed.layout !== (binding.layout ?? null)\n ) {\n plan.notSent.push({\n subject: `${PAGES_CONFIG_PATH}: the site's default layout`,\n why: NOT_PUSHED_YET,\n });\n plan.pagesConfigSettled = false;\n }\n\n for (const row of parsed.rows) {\n const path = pageFilePath(row.slug);\n const content = tree.files.get(path);\n const bound = binding.pages[row.slug];\n const boundFile = binding.files[path];\n\n if (row.slug === \"index\") {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH}: \"index\" is reserved for the home page, whose slug is \"/\".`,\n );\n continue;\n }\n if (row.menu !== null && localMenus && !localMenus.has(row.menu)) {\n plan.refusals.push(\n `Page \"${row.slug}\" uses the menu \"${row.menu}\", which ${MENUS_PATH} does not define.`,\n );\n }\n\n if (!bound) {\n if (content === undefined) {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH} adds the page \"${row.slug}\", and ${path} is missing. A new page needs its file.`,\n );\n continue;\n }\n const { menu, ...rest } = row;\n createSteps.push({\n kind: \"page-create\",\n slug: row.slug,\n row: { ...rest, menu: null },\n content,\n });\n if (menu !== null) {\n assignSteps.push({ kind: \"page-menu\", slug: row.slug, menu });\n }\n rowsSent.add(row.slug);\n continue;\n }\n\n // A page that exists: its row fields, then its file.\n const fields: Partial<Omit<PageRow, \"menu\">> = {};\n for (const field of ROW_FIELDS) {\n if (bound.row === undefined || bound.row[field] !== row[field]) {\n (fields as Record<RowField, unknown>)[field] = row[field];\n }\n }\n\n let pageContent: string | undefined;\n if (content === undefined) {\n if (boundFile) {\n plan.refusals.push(\n `${path} is missing, and the page \"${row.slug}\" is still in ${PAGES_CONFIG_PATH}. A page goes with its file: restore the file, or remove the page's row to delete the page.`,\n );\n }\n } else if (!boundFile) {\n plan.refusals.push(\n `${path} is a file of the page \"${row.slug}\", which uses a template the site does not own. Its content cannot be pushed.`,\n );\n } else if (changed(content, boundFile.sha256)) {\n pageContent = content;\n }\n\n if (Object.keys(fields).length > 0 || pageContent !== undefined) {\n updateSteps.push({\n kind: \"page-update\",\n slug: row.slug,\n id: bound.id,\n lockVersion: bound.lock_version,\n fields,\n content: pageContent,\n });\n if (Object.keys(fields).length > 0) rowsSent.add(row.slug);\n }\n if (bound.row === undefined || bound.row.menu !== row.menu) {\n assignSteps.push({ kind: \"page-menu\", slug: row.slug, menu: row.menu });\n rowsSent.add(row.slug);\n }\n }\n\n // A page whose row is gone is deleted, at the version the binding holds.\n for (const slug of Object.keys(binding.pages).sort()) {\n if (rowSlugs.has(slug)) continue;\n const path = pageFilePath(slug);\n if (tree.files.has(path)) {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH} no longer has the page \"${slug}\", and ${path} is still here. Remove the file too to delete the page, or restore the row.`,\n );\n continue;\n }\n const bound = binding.pages[slug]!;\n pageDeleteSteps.push({\n kind: \"page-delete\",\n slug,\n id: bound.id,\n lockVersion: bound.lock_version,\n });\n }\n\n // A page file that no row of config/pages.json names has no page to take it.\n for (const path of tree.files.keys()) {\n const verdict = classifyPath(path);\n if (verdict.kind !== \"page\" || rowSlugs.has(verdict.slug)) continue;\n if (binding.pages[verdict.slug]) continue;\n plan.refusals.push(\n `${path} has no row in ${PAGES_CONFIG_PATH}. Add a row for the page \"${verdict.slug}\", or remove the file.`,\n );\n }\n }\n\n // Templates and assets are written at the version the binding recorded, and\n // a path the binding never saw is created only if the path is still free.\n for (const [path, text] of [...tree.files].sort(([a], [b]) =>\n a < b ? -1 : 1,\n )) {\n if (!isPlainFile(path)) continue;\n if (skipsAccess(binding, path)) {\n plan.notes.push(\n `${path} is not pushed: a company site takes no access declaration.`,\n );\n continue;\n }\n const bound = binding.files[path];\n if (!changed(text, bound?.sha256)) continue;\n // The server refuses an invalid declaration after the settings and files\n // before it were written, so it is judged here, before any request.\n if (path === ACCESS_PATH) {\n const { problems } = parseAccessDeclaration(text);\n if (problems.length > 0) {\n plan.refusals.push(...problems.map((p) => `${ACCESS_PATH}: ${p}.`));\n continue;\n }\n }\n const isAsset = classifyPath(path).kind === \"asset\";\n if (bound === undefined) {\n fileSteps.push({\n kind: \"file-write\",\n path,\n content: text,\n precondition: { mustNotExist: true },\n });\n if (isAsset) assetLines.push(`${path} (new)`);\n } else if (bound.version === undefined) {\n plan.refusals.push(noVersion(path));\n } else {\n fileSteps.push({\n kind: \"file-write\",\n path,\n content: text,\n precondition: { version: bound.version },\n });\n if (isAsset) assetLines.push(`${path} (changed)`);\n }\n }\n for (const path of Object.keys(binding.files).sort()) {\n if (!isPlainFile(path) || tree.files.has(path)) continue;\n if (skipsAccess(binding, path)) continue;\n const bound = binding.files[path]!;\n if (bound.version === undefined) {\n plan.refusals.push(noVersion(path));\n continue;\n }\n fileDeleteSteps.push({ kind: \"file-delete\", path, version: bound.version });\n // A removal is live at once for every file but the declaration: deleting a\n // layout, section or snippet removes its published copy too, not only the\n // draft, while removing the declaration clears its draft until a publish.\n if (path !== ACCESS_PATH) assetLines.push(`${path} (removed)`);\n }\n\n plan.steps = [\n ...settingsStep,\n ...fileSteps,\n ...createSteps,\n ...updateSteps,\n ...menuStep,\n ...assignSteps,\n ...pageDeleteSteps,\n ...fileDeleteSteps,\n ];\n plan.immediate = [\n ...assetLines,\n ...plan.immediate,\n ...[...rowsSent].map((slug) => `page row \"${slug}\"`),\n ...pageDeleteSteps.flatMap((step) =>\n step.kind === \"page-delete\" ? [`page row \"${step.slug}\" (removed)`] : [],\n ),\n ];\n return plan;\n}\n\n/**\n * The reason a plan's new pages do not fit in the site, or null. Removals are\n * sent after the new pages are created, so a page the folder dropped or\n * renamed still counts against the limit while its replacement is created.\n *\n * `remotePageCount` is how many pages the site holds now.\n */\nexport function pageCapacityRefusal(\n plan: PushPlan,\n remotePageCount: number,\n): string | null {\n const added = plan.steps.filter((step) => step.kind === \"page-create\").length;\n const room = Math.max(0, PAGE_LIMIT - remotePageCount);\n if (added <= room) return null;\n return `Adding ${added} page${added === 1 ? \"\" : \"s\"} would take the site past its limit of ${PAGE_LIMIT} pages: it holds ${remotePageCount} now and has room for ${room}. Pages removed or renamed here are deleted only after the new pages are created, so they still count. Push the removals on their own first, then add the new pages.`;\n}\n\n/** The label for a step in a plan and in the summary of what a push wrote. */\nexport function describeStep(step: PushStep): string {\n switch (step.kind) {\n case \"settings\":\n return `PUT ${step.path}`;\n case \"file-write\":\n return `PUT ${step.path} (${\"version\" in step.precondition ? \"replace\" : \"create\"})`;\n case \"page-create\":\n return `POST page \"${step.slug}\" (${pageFilePath(step.slug)})`;\n case \"page-update\": {\n const parts = [\n ...Object.keys(step.fields),\n ...(step.content === undefined ? [] : [\"content\"]),\n ];\n return `PATCH page \"${step.slug}\" (${parts.join(\", \")})`;\n }\n case \"menus\":\n return `PUT ${step.path}`;\n case \"page-menu\":\n return `PATCH page \"${step.slug}\" (menu: ${step.menu ?? \"none\"})`;\n case \"page-delete\":\n return `DELETE page \"${step.slug}\" (${pageFilePath(step.slug)})`;\n case \"file-delete\":\n return `DELETE ${step.path}`;\n }\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n declareInputs,\n withInternalReadsAsync,\n type CommandInputs,\n} from \"@fluid-app/fluid-cli\";\nimport { createClients, loadLogin } from \"../api/credential.js\";\nimport {\n MemberSiteCliError,\n describeError,\n type ErrorContext,\n} from \"../api/errors.js\";\nimport type { Site, SiteApi } from \"../api/types.js\";\nimport {\n BINDING_FILE,\n serializeJson,\n sha256,\n type SiteBinding,\n} from \"../tree/binding.js\";\nimport { readLocal, writeLocal } from \"../tree/folder.js\";\nimport { classifyPath, SETTINGS_PATH } from \"../tree/local-tree.js\";\nimport { sameJson } from \"../tree/push-plan.js\";\nimport { openBoundSite, type BoundSiteDeps } from \"./bound-site.js\";\nimport { runCommand } from \"./run.js\";\n\nexport interface PublishOptions {\n as?: string;\n}\n\nexport type PublishDeps = BoundSiteDeps;\n\nconst defaultDeps: PublishDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\n/** What a publish did. */\nexport interface PublishResult {\n site: Site;\n dir: string;\n}\n\n/**\n * How far one publish moves the site's `lock_version`: once for the status and\n * `content_version` write, and once more when the settings draft was not yet\n * the published copy. The response does not say which, so the settings are\n * proven by their content, and this bounds the move.\n */\nconst PUBLISH_SITE_MOVES = [1, 2];\n\n/** Kinds of file with a draft and a published copy, so a publish moves their version. */\nconst PUBLISHED_KINDS = [\"layout\", \"section\", \"snippet\", \"page\"];\n\n/**\n * The text of a file in the folder, when it is what the binding says was last\n * pushed or pulled. A file edited here since is not that, and the binding\n * does not keep that content, so nothing can be proven for it.\n */\nasync function baselineText(\n dir: string,\n binding: SiteBinding,\n path: string,\n): Promise<string | null> {\n const text = await withInternalReadsAsync(() => readLocal(dir, path));\n return text !== null && binding.files[path]?.sha256 === sha256(text)\n ? text\n : null;\n}\n\n/**\n * The version a template or page file has once it is published from the draft\n * `content`: the published copy is the draft, and the server's version is the\n * digest of the draft, a NUL byte and the published copy.\n */\nfunction publishedVersion(content: string): string {\n return sha256(`${content}\\0${content}`);\n}\n\n/**\n * Takes the versions a publish moved into the binding, for what is proven to\n * be what the publish published, and returns what is not so the caller can\n * report it. Anything else keeps its old version, so the next push finds the\n * change another writer made between the pull and the publish.\n *\n * - A template or page file: the server's new version must equal the one\n * computed from the content the folder holds. It is a digest of the draft\n * and the published copy, so a match proves both are that content.\n * - A page: its `lock_version` moves once per publish, so only a move by\n * exactly one is the publish's own.\n * - The settings: the site's `lock_version` as the publish response gave it,\n * with the settings the publish published, which must be the ones the folder\n * holds. A move beyond what a publish makes is another writer's.\n * - Assets and menus have no draft, so a publish writes neither and their\n * versions stay as they are.\n *\n * A file the folder holds edited, since the pull or push, is left alone and\n * not reported: it is the local edit that differs, not the server.\n */\nasync function adoptPublishedVersions(\n api: SiteApi,\n binding: SiteBinding,\n dir: string,\n site: Site,\n): Promise<string[]> {\n const entries = await api.listFiles(binding.site_id);\n const pages = await api.listPages(binding.site_id);\n const changed: string[] = [];\n\n for (const entry of entries) {\n const bound = binding.files[entry.path];\n if (!bound || bound.version === undefined) continue;\n if (!PUBLISHED_KINDS.includes(classifyPath(entry.path).kind)) continue;\n if (entry.version === bound.version) continue;\n const text = await baselineText(dir, binding, entry.path);\n if (text === null) continue;\n if (entry.version === publishedVersion(text)) {\n bound.version = entry.version;\n } else {\n changed.push(entry.path);\n }\n }\n\n for (const [slug, bound] of Object.entries(binding.pages)) {\n const fresh = pages.find((page) => page.id === bound.id);\n if (!fresh || fresh.lock_version === bound.lock_version) continue;\n if (fresh.slug === slug && fresh.lock_version === bound.lock_version + 1) {\n bound.lock_version = fresh.lock_version;\n } else {\n changed.push(`the page \"${slug}\"`);\n }\n }\n\n const settings = binding.files[SETTINGS_PATH];\n if (\n settings &&\n typeof settings.lock_version === \"number\" &&\n typeof site.lock_version === \"number\"\n ) {\n const moved = site.lock_version - settings.lock_version;\n const text =\n moved === 0 ? null : await baselineText(dir, binding, SETTINGS_PATH);\n if (text !== null) {\n if (\n PUBLISH_SITE_MOVES.includes(moved) &&\n sameJson(JSON.stringify(site.settings), text)\n ) {\n settings.lock_version = site.lock_version;\n settings.version = String(site.lock_version);\n binding.settings.lock_version = site.lock_version;\n } else {\n changed.push(SETTINGS_PATH);\n }\n }\n }\n return changed;\n}\n\n/**\n * Publishes the folder's site with one request, prints the status and the new\n * `content_version`, and records the versions the publish moved that are\n * proven to be the publish's own, so the next push does not conflict with\n * them and still finds a change another writer made. The binding is saved. A\n * publish that fails is reported with `notPublished` after the API's message,\n * and is not retried.\n */\nexport async function publishBoundSite(\n api: SiteApi,\n binding: SiteBinding,\n dir: string,\n context: ErrorContext,\n log: (line: string) => void,\n notPublished: string,\n): Promise<Site> {\n let site: Site;\n try {\n context.subject = \"The site\";\n context.conflict = \"publish\";\n site = await api.publishSite(binding.site_id);\n } catch (error) {\n throw new MemberSiteCliError(\n `${describeError(error, context)}\\n${notPublished}`,\n );\n }\n log(`Published ${binding.owner_kind} site ${chalk.cyan(binding.handle)}.`);\n log(` Status: ${site.status}`);\n log(` content_version: ${site.content_version}`);\n\n // The site is live whatever happens next, so a failed read is a warning.\n try {\n context.subject = \"The site's files and pages\";\n delete context.conflict;\n const changed = await adoptPublishedVersions(api, binding, dir, site);\n await writeLocal(dir, BINDING_FILE, serializeJson(binding));\n if (changed.length > 0) {\n log(\n chalk.yellow(\n ` Changed on the server, so their versions were not recorded: ${changed.join(\", \")}. Pull before the next push.`,\n ),\n );\n }\n } catch (error) {\n log(\n chalk.yellow(\n ` The versions the publish moved were not recorded: ${describeError(error, context).split(\"\\n\")[0]} Pull before the next push.`,\n ),\n );\n }\n return site;\n}\n\n/**\n * Publishes a pulled folder's site: what the server holds as drafts becomes\n * the live site. It sends no file, so local edits that were not pushed are\n * not in it.\n */\nexport async function runPublish(\n dirArg: string | undefined,\n options: PublishOptions,\n deps: PublishDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<PublishResult> {\n const { dir, binding, api } = await openBoundSite(\n dirArg,\n options.as,\n deps,\n context,\n );\n const site = await publishBoundSite(\n api,\n binding,\n dir,\n context,\n deps.log,\n \"The site was not published.\",\n );\n return { site, dir };\n}\n\nexport function createPublishCommand(\n inputs: CommandInputs,\n deps: PublishDeps = defaultDeps,\n): Command {\n const command = new Command(\"publish\")\n .description(\n \"Publish the site: its saved drafts become the live site. Prints the status and the new content_version\",\n )\n .argument(\"[dir]\", \"The pulled folder (default: the current folder)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .action((dir: string | undefined, options: PublishOptions) => {\n const context: ErrorContext = {};\n return runCommand(\n () =>\n runPublish(dir, options, { ...deps, inputs }, context).then(\n () => undefined,\n ),\n context,\n );\n });\n return declareInputs(command, (_opts, args) => [\n { kind: \"config\", path: resolve(args[0] ?? \".\", BINDING_FILE) },\n ]);\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { declareInputs, type CommandInputs } from \"@fluid-app/fluid-cli\";\nimport { ApiError } from \"@fluid-app/api-client-core\";\nimport { createClients, loadLogin } from \"../api/credential.js\";\nimport {\n MemberSiteCliError,\n describeError,\n type ErrorContext,\n} from \"../api/errors.js\";\nimport type { Site, SiteApi, SitePage } from \"../api/types.js\";\nimport {\n BINDING_FILE,\n PAGES_CONFIG_PATH,\n pageRow,\n serializeJson,\n sha256,\n type SiteBinding,\n} from \"../tree/binding.js\";\nimport { writeLocal } from \"../tree/folder.js\";\nimport {\n inputTreeSource,\n readLocalTree,\n SETTINGS_PATH,\n type LocalTree,\n} from \"../tree/local-tree.js\";\nimport { pageFilePath } from \"../tree/pages-config.js\";\nimport {\n describeStep,\n pageCapacityRefusal,\n planPush,\n sameJson,\n type PushPlan,\n type PushStep,\n} from \"../tree/push-plan.js\";\nimport { openBoundSite, type BoundSiteDeps } from \"./bound-site.js\";\nimport { publishBoundSite } from \"./publish.js\";\nimport { runCommand } from \"./run.js\";\n\nexport interface PushOptions {\n as?: string;\n dryRun?: boolean;\n publish?: boolean;\n}\n\nexport type PushDeps = BoundSiteDeps;\n\n/** Names a push never reads, besides the dot names its declared tree skips. */\nexport const PUSH_TREE_IGNORE = [\"node_modules\"] as const;\n\nconst defaultDeps: PushDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\n/** What a push did. */\nexport interface PushResult {\n plan: PushPlan;\n dir: string;\n /** The requests that succeeded, in order. Empty for a dry run. */\n sent: string[];\n /** The site as the publish answered it, when `--publish` ran one. */\n published?: Site;\n}\n\n/** The lines `push` prints for a plan, before it sends anything. */\nexport function formatPlan(\n binding: SiteBinding,\n plan: PushPlan,\n dryRun: boolean,\n publish = false,\n): string[] {\n const lines = [\n `${dryRun ? \"Dry run. \" : \"\"}Push to ${binding.owner_kind} site ${chalk.cyan(binding.handle)}`,\n ];\n if (plan.steps.length === 0) {\n lines.push(\" Nothing to send.\");\n } else {\n lines.push(\n dryRun ? \" Would send, in this order:\" : \" Sending, in this order:\",\n ...plan.steps.map((step, i) => ` ${i + 1}. ${describeStep(step)}`),\n );\n }\n if (plan.immediate.length > 0) {\n lines.push(\n \" These take effect as soon as they are written, without a publish:\",\n ...plan.immediate.map((line) => ` ${line}`),\n );\n }\n for (const note of plan.notes) lines.push(chalk.dim(` ${note}`));\n if (plan.notSent.length > 0) {\n lines.push(\n \" Not sent:\",\n ...plan.notSent.map((item) => ` ${item.subject}: ${item.why}`),\n );\n }\n if (dryRun && plan.steps.some((step) => step.kind === \"page-create\")) {\n lines.push(\n \" The site's page limit was not checked, because a dry run sends no request.\",\n );\n }\n if (publish) {\n lines.push(\n dryRun\n ? \" A publish would follow, after the last write.\"\n : \" A publish follows the last write.\",\n );\n }\n if (dryRun) lines.push(\" Nothing was sent.\");\n return lines;\n}\n\n/** The thing a 409 on this step names. */\nfunction subjectOf(step: PushStep): string {\n switch (step.kind) {\n case \"settings\":\n case \"menus\":\n case \"file-write\":\n case \"file-delete\":\n return step.path;\n case \"page-delete\":\n return `The page \"${step.slug}\"`;\n case \"page-create\":\n return `The page \"${step.slug}\"`;\n case \"page-update\":\n return step.content === undefined\n ? `The page \"${step.slug}\" (its row in ${PAGES_CONFIG_PATH})`\n : pageFilePath(step.slug);\n case \"page-menu\":\n return `The page \"${step.slug}\" (its menu in ${PAGES_CONFIG_PATH})`;\n }\n}\n\n/** What a 409 on this step means: a file created meanwhile, or a removal refused. */\nfunction conflictOf(step: PushStep): ErrorContext[\"conflict\"] {\n if (step.kind === \"file-write\" && \"mustNotExist\" in step.precondition) {\n return \"exists\";\n }\n return step.kind === \"page-delete\" || step.kind === \"file-delete\"\n ? \"delete\"\n : undefined;\n}\n\nfunction sameRowExceptMenu(\n a: NonNullable<SiteBinding[\"pages\"][string][\"row\"]>,\n b: NonNullable<SiteBinding[\"pages\"][string][\"row\"]>,\n): boolean {\n return (\n a.title === b.title &&\n a.position === b.position &&\n a.published === b.published &&\n a.layout === b.layout\n );\n}\n\n/**\n * Sends a plan's steps, one request each. The binding takes each response's\n * versions as it arrives and is saved after every success, so a push that\n * stops can be run again after a pull.\n */\nasync function execute(\n api: SiteApi,\n binding: SiteBinding,\n plan: PushPlan,\n tree: LocalTree,\n dir: string,\n context: ErrorContext,\n sent: string[],\n log: (line: string) => void,\n): Promise<void> {\n const siteId = binding.site_id;\n let siteMoved = false;\n\n const save = async () => {\n const text = serializeJson(binding);\n await writeLocal(dir, BINDING_FILE, text);\n };\n\n for (const step of plan.steps) {\n context.subject = subjectOf(step);\n context.conflict = conflictOf(step);\n switch (step.kind) {\n case \"settings\": {\n const file = await api.writeFile(siteId, step.path, {\n content: step.content,\n lock_version: step.lockVersion,\n });\n const lockVersion = file.lock_version ?? step.lockVersion;\n const prior = binding.files[step.path];\n binding.settings.lock_version = lockVersion;\n binding.files[step.path] = {\n lock_version: lockVersion,\n updated_at: prior?.updated_at ?? null,\n version: file.version ?? String(lockVersion),\n sha256: sha256(step.content),\n };\n break;\n }\n case \"file-write\": {\n const file = await api.writeFile(siteId, step.path, {\n content: step.content,\n ...(\"version\" in step.precondition\n ? { version: step.precondition.version }\n : { must_not_exist: true }),\n });\n const prior = binding.files[step.path];\n binding.files[step.path] = {\n lock_version: null,\n updated_at: prior?.updated_at ?? null,\n ...(typeof file.version === \"string\"\n ? { version: file.version }\n : {}),\n sha256: sha256(step.content),\n };\n break;\n }\n case \"page-create\": {\n const page = await api.createPage(siteId, {\n slug: step.slug,\n title: step.row.title,\n position: step.row.position,\n published: step.row.published,\n layout_name: step.row.layout,\n content: step.content,\n });\n siteMoved = true;\n binding.pages[step.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n binding.files[pageFilePath(step.slug)] = {\n lock_version: null,\n updated_at: page.updated_at,\n sha256: sha256(step.content),\n };\n break;\n }\n case \"page-update\": {\n const page = await api.updatePage(siteId, step.id, {\n ...(\"title\" in step.fields ? { title: step.fields.title } : {}),\n ...(\"position\" in step.fields\n ? { position: step.fields.position }\n : {}),\n ...(\"published\" in step.fields\n ? { published: step.fields.published }\n : {}),\n ...(\"layout\" in step.fields\n ? { layout_name: step.fields.layout }\n : {}),\n ...(step.content === undefined ? {} : { content: step.content }),\n lock_version: step.lockVersion,\n });\n siteMoved = true;\n binding.pages[step.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n if (step.content !== undefined) {\n const path = pageFilePath(step.slug);\n // The page's response has no file version, so none is kept.\n binding.files[path] = {\n lock_version: null,\n updated_at: page.updated_at,\n sha256: sha256(step.content),\n };\n }\n break;\n }\n case \"menus\": {\n const file = await api.writeFile(siteId, step.path, {\n content: step.content,\n version: step.version,\n });\n siteMoved = true;\n binding.files[step.path] = {\n lock_version: null,\n updated_at: null,\n ...(typeof file.version === \"string\"\n ? { version: file.version }\n : {}),\n sha256: sha256(step.content),\n };\n break;\n }\n case \"page-menu\": {\n const bound = binding.pages[step.slug];\n if (!bound) {\n throw new MemberSiteCliError(\n `The page \"${step.slug}\" is not in the binding.`,\n );\n }\n const page = await api.updatePage(siteId, bound.id, {\n menu: step.menu,\n lock_version: bound.lock_version,\n });\n siteMoved = true;\n binding.pages[step.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n break;\n }\n case \"page-delete\": {\n // The version the binding holds now: a menus write moved it for a\n // page it let go of, and the push took the new one.\n const bound = binding.pages[step.slug];\n await api.deletePage(siteId, step.id, {\n lockVersion: bound?.lock_version ?? step.lockVersion,\n });\n siteMoved = true;\n delete binding.pages[step.slug];\n delete binding.files[pageFilePath(step.slug)];\n break;\n }\n case \"file-delete\": {\n await api.deleteFile(siteId, step.path, { version: step.version });\n delete binding.files[step.path];\n break;\n }\n }\n await save();\n sent.push(describeStep(step));\n\n if (step.kind === \"menus\") {\n // The menus write moved the site's version, and the version of every\n // page assigned to a menu it dropped, so the page versions are read\n // again before an assignment carries one.\n context.subject = \"The site's pages\";\n adoptPageVersions(binding, await api.listPages(siteId));\n await save();\n }\n }\n\n if (siteMoved) {\n // Every page and menus write moved the settings version. It is read\n // again, and kept only when the settings are still the ones this folder\n // holds: a copy another writer changed meanwhile must still conflict.\n context.subject = SETTINGS_PATH;\n const local = tree.files.get(SETTINGS_PATH);\n const file = await api.readFile(siteId, SETTINGS_PATH);\n if (local !== undefined && file.lock_version !== null) {\n if (sameJson(file.content, local)) {\n const lockVersion = file.lock_version;\n const prior = binding.files[SETTINGS_PATH];\n binding.settings.lock_version = lockVersion;\n binding.files[SETTINGS_PATH] = {\n lock_version: lockVersion,\n updated_at: prior?.updated_at ?? null,\n version: file.version ?? String(lockVersion),\n sha256: prior?.sha256 ?? sha256(local),\n };\n } else {\n log(\n chalk.yellow(\n ` ${SETTINGS_PATH} changed on the server during the push, so its version was not recorded. Pull before the next push.`,\n ),\n );\n }\n }\n }\n\n // The file counts as pushed once every row in it is what the server holds.\n const pagesText = tree.files.get(PAGES_CONFIG_PATH);\n if (plan.pagesConfigSettled && pagesText !== undefined) {\n binding.pages_config_sha256 = sha256(pagesText);\n }\n await save();\n}\n\n/**\n * Takes the versions of a fresh page list into the binding, for each page\n * whose server row is the one the binding holds, apart from a menu the write\n * dropped. A page someone else changed keeps its old version and conflicts.\n */\nfunction adoptPageVersions(binding: SiteBinding, fresh: SitePage[]): void {\n for (const page of fresh) {\n const entry = Object.entries(binding.pages).find(\n ([slug, bound]) => bound.id === page.id && slug === page.slug,\n );\n const bound = entry?.[1];\n if (!bound?.row) continue;\n const row = pageRow(page);\n if (\n sameRowExceptMenu(bound.row, row) &&\n (row.menu === bound.row.menu || row.menu === null)\n ) {\n bound.lock_version = page.lock_version;\n bound.row = row;\n }\n }\n}\n\n/**\n * Pushes the folder's changes to its site: settings, templates and assets,\n * page rows and page files, menus, and the removal of pages and files. Each\n * write and delete carries the version the binding recorded. Everything that\n * can be refused is refused before the first request.\n */\nexport async function runPush(\n dirArg: string | undefined,\n options: PushOptions,\n deps: PushDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<PushResult> {\n const { dir, binding, api } = await openBoundSite(\n dirArg,\n options.as,\n deps,\n context,\n );\n\n const tree = await readLocalTree(\n dir,\n binding,\n deps.inputs &&\n inputTreeSource(\n dir,\n deps.inputs.tree(dir, {\n ignore: PUSH_TREE_IGNORE,\n skipDotNames: true,\n }),\n PUSH_TREE_IGNORE,\n ),\n );\n const plan = planPush(binding, tree);\n const dryRun = options.dryRun === true;\n const createsPages = plan.steps.some((step) => step.kind === \"page-create\");\n if (plan.refusals.length === 0 && createsPages && !dryRun) {\n // A new page is refused past the site's page limit, and the push does not\n // remove pages, so the count is read before the first write. A dry run\n // sends no request, so it skips the count and says so.\n context.subject = \"The site's pages\";\n const refusal = pageCapacityRefusal(\n plan,\n (await api.listPages(binding.site_id)).length,\n );\n if (refusal) plan.refusals.push(refusal);\n delete context.subject;\n }\n if (plan.refusals.length > 0) {\n throw new MemberSiteCliError(\n `Nothing was sent. These cannot be pushed:\\n${plan.refusals.map((r) => ` ${r}`).join(\"\\n\")}`,\n );\n }\n\n const publish = options.publish === true;\n for (const line of formatPlan(binding, plan, dryRun, publish)) deps.log(line);\n const sent: string[] = [];\n if (dryRun) return { plan, dir, sent };\n\n try {\n await execute(api, binding, plan, tree, dir, context, sent, deps.log);\n } catch (error) {\n const lines = [describeError(error, context)];\n lines.push(\n sent.length > 0\n ? `Written before the stop:\\n${sent.map((s) => ` ${s}`).join(\"\\n\")}`\n : \"Nothing was written.\",\n );\n if (error instanceof ApiError && error.status === 409) {\n lines.push(\"The rest of the push was not sent.\");\n }\n throw new MemberSiteCliError(lines.join(\"\\n\"));\n }\n\n if (sent.length > 0) {\n deps.log(\n `Pushed ${sent.length} change(s) to ${binding.owner_kind} site ${chalk.cyan(binding.handle)}.`,\n );\n }\n\n if (!publish) return { plan, dir, sent };\n // Every write succeeded, or this line would not be reached.\n const published = await publishBoundSite(\n api,\n binding,\n dir,\n context,\n deps.log,\n \"The pushed changes are saved as drafts, and the site was not published.\",\n );\n return { plan, dir, sent, published };\n}\n\nexport function createPushCommand(\n inputs: CommandInputs,\n deps: PushDeps = defaultDeps,\n): Command {\n const command = new Command(\"push\")\n .description(\n \"Push local changes to the site: settings, templates, assets, menus, page rows and page content, and the removal of pages and files. A file or page changed on the server since the pull stops the push\",\n )\n .argument(\"[dir]\", \"The pulled folder (default: the current folder)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .option(\"--dry-run\", \"Print what would be sent, and send nothing\")\n .option(\n \"--publish\",\n \"Publish the site once, after every write succeeded. Never after a failed write\",\n )\n .action((dir: string | undefined, options: PushOptions) => {\n const context: ErrorContext = {};\n return runCommand(\n () =>\n runPush(dir, options, { ...deps, inputs }, context).then(\n () => undefined,\n ),\n context,\n );\n });\n return declareInputs(command, (_opts, args) => {\n const dir = resolve(args[0] ?? \".\");\n return [\n {\n kind: \"tree\",\n path: dir,\n ignore: PUSH_TREE_IGNORE,\n skipDotNames: true,\n },\n { kind: \"config\", path: resolve(dir, BINDING_FILE) },\n ];\n });\n}\n","import type { Server } from \"node:http\";\nimport http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { createClients, loadLogin, parseLoginKind } from \"../api/credential.js\";\nimport {\n MemberSiteCliError,\n describeError,\n type ErrorContext,\n} from \"../api/errors.js\";\nimport { getApiBase, isProductionApiBase } from \"../api/http.js\";\nimport { isLocalPreviewRequest } from \"../dev/local-guard.js\";\nimport { checkPortAvailable } from \"../dev/port-preflight.js\";\nimport { PreviewProxy, type PreviewTarget } from \"../dev/preview-proxy.js\";\nimport { watchFolder } from \"../dev/watcher.js\";\nimport { openBoundSite, type BoundSiteDeps } from \"./bound-site.js\";\nimport { runPush } from \"./push.js\";\nimport { runCommand } from \"./run.js\";\n\n/** Where the preview listens, and the same on every machine. */\nexport const DEV_HOST = \"127.0.0.1\";\nexport const DEFAULT_DEV_PORT = 9500;\n\n/** How long edits are gathered before they are saved. */\nconst DEBOUNCE_MS = 300;\n\n/** What the command prints when the server has no preview route. */\nexport const DRAFT_PREVIEW_UNAVAILABLE =\n \"Draft preview is not available on this server yet, so `fluid member-site dev` did not start. \" +\n \"It shows a draft through the server's draft preview route and never the published page in its place. \" +\n \"Nothing was saved. Use `fluid member-site push` to save drafts, and update the server or the CLI to preview them.\";\n\n/** What the command says when the API is production and `--allow-production` is not given. */\nexport function productionRefusal(apiBase: string, handle: string): string {\n return (\n `This login is for ${apiBase}, which is production or not known to be anything else, and \\`fluid member-site dev\\` saves ` +\n `${handle} to it at start and again on every change you make. Assets, removals, menus and ` +\n \"page rows are live as soon as they are written. Nothing was saved. \" +\n \"Log in to a stage or local API to develop against it, or pass --allow-production \" +\n \"to save to this one.\"\n );\n}\n\nexport interface DevOptions {\n as?: string;\n port?: string;\n /** Saves to the production API, which dev does not do unless it is told to. */\n allowProduction?: boolean;\n}\n\n/** Stops what was started. */\ntype Stop = () => Promise<void> | void;\n\nexport interface DevDeps extends BoundSiteDeps {\n /** Calls `onChange` when the folder changes; returns the stop function. */\n watch: (\n dir: string,\n onChange: () => void,\n onError: (error: unknown) => void,\n ) => Stop | Promise<Stop>;\n /** How long edits are gathered before they are saved. */\n debounceMs: number;\n logError: (line: string) => void;\n}\n\nconst defaultDeps: DevDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n watch: watchFolder,\n debounceMs: DEBOUNCE_MS,\n logError: (line) => console.error(line),\n};\n\n/** A running preview. */\nexport interface DevSession {\n /** Where the preview is served, such as `http://127.0.0.1:9500`. */\n url: string;\n /** Resolves when no save is waiting or running. */\n idle: () => Promise<void>;\n /** Stops the watcher and the server. */\n stop: () => Promise<void>;\n}\n\nfunction parsePort(value: string | undefined): number {\n const port = Number(value ?? DEFAULT_DEV_PORT);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new MemberSiteCliError(\n `Invalid port: '${value}'. Must be an integer between 0 and 65535.`,\n );\n }\n return port;\n}\n\nfunction listen(server: Server, port: number): Promise<number> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, DEV_HOST, () => {\n server.off(\"error\", reject);\n resolve((server.address() as AddressInfo).port);\n });\n });\n}\n\n/**\n * Starts the preview of a pulled folder: saves its changes as drafts, keeps\n * saving as it changes, and serves the draft on a loopback port. Nothing\n * starts, and nothing is saved, when the server has no draft preview route.\n * It never publishes.\n */\nexport async function runDev(\n dirArg: string | undefined,\n options: DevOptions,\n deps: DevDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<DevSession> {\n const port = parsePort(options.port);\n const { dir, binding, api } = await openBoundSite(\n dirArg,\n options.as,\n deps,\n context,\n );\n\n // A dev session writes the site, at start and on every save. Production is\n // never written without being asked for, as `fluid theme dev` never writes\n // a live theme unless `--theme` names it. Nothing has been requested yet.\n const apiBase = getApiBase();\n if (isProductionApiBase(apiBase) && !options.allowProduction) {\n throw new MemberSiteCliError(productionRefusal(apiBase, binding.handle));\n }\n\n // Reads only: a missing site or a login that is refused stops here, before\n // anything is written.\n context.subject = \"The site\";\n await api.getSite(binding.site_id);\n delete context.subject;\n\n const target: PreviewTarget = {\n siteId: binding.site_id,\n handle: binding.handle,\n ownerKind: binding.owner_kind,\n };\n const proxy = new PreviewProxy({\n target,\n apiBase,\n getToken: async () =>\n (await deps.loadLogin(parseLoginKind(options.as))).token,\n });\n if ((await proxy.probe()) === \"unavailable\") {\n throw new MemberSiteCliError(DRAFT_PREVIEW_UNAVAILABLE);\n }\n await checkPortAvailable(DEV_HOST, port);\n\n // The folder is watched before it is first read, so an edit made after that\n // is either in the first save or is reported by the watcher and saved after\n // it. Until the preview is up, a change only asks for another first save.\n // After that it is saved after a pause: a change that arrives while a save\n // runs makes it run again, and never starts a second one beside it.\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running: Promise<void> | undefined;\n let again = false;\n let serving = false;\n let changedAtStart = false;\n\n const save = async (): Promise<void> => {\n const pushContext: ErrorContext = {};\n try {\n const result = await runPush(dir, { as: options.as }, deps, pushContext);\n if (result.sent.length > 0) {\n deps.log(\n chalk.green(\" Saved as drafts. Reload the preview to see them.\"),\n );\n }\n } catch (error) {\n deps.logError(chalk.red(describeError(error, pushContext)));\n }\n };\n const drain = async (): Promise<void> => {\n do {\n again = false;\n await save();\n } while (again);\n running = undefined;\n };\n const trigger = (): void => {\n timer = undefined;\n if (running) again = true;\n else running = drain();\n };\n const onChange = (): void => {\n if (!serving) {\n changedAtStart = true;\n return;\n }\n clearTimeout(timer);\n timer = setTimeout(trigger, deps.debounceMs);\n };\n\n let stopWatching: Stop;\n try {\n stopWatching = await deps.watch(dir, onChange, (error) =>\n deps.logError(chalk.red(`Watcher error: ${String(error)}`)),\n );\n } catch (error) {\n // Without a watcher an edit would never be saved, so nothing is saved or\n // served.\n throw new MemberSiteCliError(\n `Could not watch ${dir} for changes (${String(error)}). Nothing was saved and no preview was started.`,\n );\n }\n\n const server = http.createServer((req, res) => {\n if (!isLocalPreviewRequest(req, DEV_HOST, proxy.previewToken)) {\n res.writeHead(403, {\n \"content-type\": \"text/plain; charset=utf-8\",\n \"cache-control\": \"private, no-store\",\n \"referrer-policy\": \"no-referrer\",\n });\n res.end(\"Local preview requests only.\");\n return;\n }\n proxy.handle(req, res).catch(() => {\n if (!res.headersSent) res.writeHead(502);\n res.end();\n });\n });\n let url: string;\n try {\n // The draft a browser shows is what the server holds, so what is in the\n // folder is saved first. A save that is refused or conflicts stops the\n // command: it would show a draft that is not the folder's. A change made\n // while it ran is saved before the preview is served.\n deps.log(\n `Saving ${chalk.cyan(binding.handle)} to the server as drafts. Nothing is published.`,\n );\n do {\n changedAtStart = false;\n await runPush(dir, { as: options.as }, deps, context);\n } while (changedAtStart);\n url = `http://${DEV_HOST}:${await listen(server, port)}`;\n } catch (error) {\n await stopWatching();\n throw error;\n }\n serving = true;\n // A change that came while the server started is saved as any other.\n if (changedAtStart) onChange();\n\n deps.log(`Preview of ${chalk.cyan(binding.handle)}: ${chalk.cyan(url)}`);\n deps.log(\n \"Edits are saved as drafts as you make them. Run `fluid member-site publish` to make them live.\",\n );\n\n return {\n url,\n idle: async () => {\n while (timer || running) {\n if (timer) {\n clearTimeout(timer);\n trigger();\n }\n await running;\n }\n },\n stop: async () => {\n clearTimeout(timer);\n timer = undefined;\n await stopWatching();\n server.closeAllConnections();\n await new Promise<void>((resolve) => server.close(() => resolve()));\n await running;\n },\n };\n}\n\nexport function createDevCommand(): Command {\n return new Command(\"dev\")\n .description(\n \"Preview a site's draft while you edit: saves each change as a draft, never publishes, and serves the draft rendered by the server on a local port. Refuses the production API unless --allow-production is given\",\n )\n .argument(\"[dir]\", \"The pulled folder (default: the current folder)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .option(\n \"--port <port>\",\n \"Local port to serve the preview on\",\n String(DEFAULT_DEV_PORT),\n )\n .option(\n \"--allow-production\",\n \"Save to the production API, which dev refuses to do otherwise\",\n )\n .action((dir: string | undefined, options: DevOptions) => {\n const context: ErrorContext = {};\n return runCommand(async () => {\n const session = await runDev(dir, options, defaultDeps, context);\n const stop = () => {\n void session.stop().finally(() => process.exit(0));\n };\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n }, context);\n });\n}\n","/**\n * Characters a terminal acts on rather than prints: C0 and C1 controls (escape\n * sequences start with one), DEL, the line and paragraph separators, and the\n * bidirectional overrides that reorder what follows them.\n */\nconst TERMINAL_ACTIVE = new RegExp(\n \"[\\\\u0000-\\\\u001f\\\\u007f-\\\\u009f\\\\u2028\\\\u2029\\\\u202a-\\\\u202e\\\\u2066-\\\\u2069]\",\n \"gu\",\n);\n\n/**\n * Text from the file system, such as a file name, made safe to print: each\n * character a terminal would act on is shown as a `\\u{...}` escape, and the rest\n * is left as it is, so an ordinary path reads the same.\n */\nexport function inert(text: string): string {\n return text.replace(\n TERMINAL_ACTIVE,\n (char) => `\\\\u{${char.codePointAt(0)!.toString(16)}}`,\n );\n}\n","","import settingTypesJson from \"./setting-types.json\" with { type: \"json\" };\n\nexport type SettingType =\n | \"text\"\n | \"plaintext\"\n | \"rich_text\"\n | \"richtext\"\n | \"textarea\"\n | \"html\"\n | \"html_textarea\"\n | \"url\"\n | \"range\"\n | \"number\"\n | \"select\"\n | \"radio\"\n | \"checkbox\"\n | \"color\"\n | \"color_background\"\n | \"font\"\n | \"font_picker\"\n | \"image\"\n | \"image_picker\"\n | \"video_picker\"\n | \"media_picker\"\n | \"text_alignment\"\n | \"media_fit\"\n | \"corner_radius\"\n | \"padding\"\n | \"border\"\n | \"gradient_overlay\"\n | \"header\"\n | \"product\"\n | \"products\"\n | \"collection\"\n | \"collections\"\n | \"category\"\n | \"categories\"\n | \"blog\"\n | \"posts\"\n | \"post\"\n | \"enrollment\"\n | \"enrollments\"\n | \"enrollment_pack\"\n | \"forms\"\n | \"media\"\n | \"variant\"\n | \"link_list\"\n | \"product_list\"\n | \"products_list\"\n | \"collection_list\"\n | \"collections_list\"\n | \"category_list\"\n | \"categories_list\"\n | \"posts_list\"\n | \"enrollment_list\"\n | \"enrollments_list\"\n | \"blog_list\"\n | \"blogs_list\"\n | \"post_list\"\n | \"enrollment_packs_list\";\n\n// Runtime list loaded from the canonical JSON — used for validation.\nexport const VALID_SETTING_TYPES: readonly string[] = Object.values(\n settingTypesJson.types as Record<string, string[]>,\n).flat();\n\n// Compile-time drift guard: if a type exists in the SettingType union but\n// not in setting-types.json, this object literal will error on the missing key.\n// When adding types to setting-types.json, also add them to SettingType above.\nconst _settingTypeCheck: Record<SettingType, true> = {\n text: true,\n plaintext: true,\n rich_text: true,\n richtext: true,\n textarea: true,\n html: true,\n html_textarea: true,\n url: true,\n range: true,\n number: true,\n select: true,\n radio: true,\n checkbox: true,\n color: true,\n color_background: true,\n font: true,\n font_picker: true,\n image: true,\n image_picker: true,\n video_picker: true,\n media_picker: true,\n text_alignment: true,\n media_fit: true,\n corner_radius: true,\n padding: true,\n border: true,\n gradient_overlay: true,\n header: true,\n product: true,\n products: true,\n collection: true,\n collections: true,\n category: true,\n categories: true,\n blog: true,\n posts: true,\n post: true,\n enrollment: true,\n enrollments: true,\n enrollment_pack: true,\n forms: true,\n media: true,\n variant: true,\n link_list: true,\n product_list: true,\n products_list: true,\n collection_list: true,\n collections_list: true,\n category_list: true,\n categories_list: true,\n posts_list: true,\n enrollment_list: true,\n enrollments_list: true,\n blog_list: true,\n blogs_list: true,\n post_list: true,\n enrollment_packs_list: true,\n} satisfies Record<SettingType, true>;\nvoid _settingTypeCheck;\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\nexport interface SchemaSetting {\n type: SettingType;\n id: string;\n default?: string | number | boolean | null;\n label?: string;\n options?: SelectOption[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n content?: string;\n visible_if?: Record<string, unknown>;\n}\n\nexport interface SchemaBlock {\n type: string;\n name?: string;\n limit?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[];\n}\n\nexport interface SchemaPreset {\n name?: string;\n category?: string;\n settings?: Record<string, unknown>;\n blocks?: Array<{ type: string; settings?: Record<string, unknown> }>;\n}\n\nexport interface SectionSchema {\n name?: string;\n tag?: string;\n class?: string;\n enabled_on?: { templates?: string[] };\n disabled_on?: { templates?: string[] };\n max_blocks?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[] | Record<string, unknown>;\n presets?: SchemaPreset[];\n}\n\nexport type BlocksSchemaType = \"array\" | \"object\" | \"unknown\";\n\n/**\n * Structured locator describing the schema element a diagnostic refers to.\n *\n * The rule logic (this package) is intentionally position-agnostic so it can\n * be shared by the CLI (`fluid theme push`) and the CodeMirror-based theme /\n * visual editor alike. Position-aware consumers use `target` to map a\n * diagnostic back to a source range without re-deriving the validation rules.\n */\nexport type SettingDiagnosticTarget = {\n kind: \"setting\";\n /** Index of the setting within its `settings` array. */\n index: number;\n /** The setting's `id`, when present — used to locate the offending entry. */\n settingId?: string;\n /** The setting's `type`, when present — used to locate the offending entry. */\n settingType?: string;\n /** Which field the diagnostic concerns. */\n field: \"id\" | \"type\";\n};\n\nexport type BlockDiagnosticTarget = {\n kind: \"block\";\n /** Index of the block within its `blocks` array. */\n index: number;\n /** The block's `type`, when present — used to locate the offending entry. */\n blockType?: string;\n /** Which field the diagnostic concerns. */\n field: \"type\" | \"name\" | \"settings\";\n};\n\nexport type SectionDiagnosticTarget = {\n kind: \"section\";\n /** The referenced section `type` that has no matching section file. */\n sectionType: string;\n /** The `{% section %}` tag's instance id, when the tag included one. */\n tagId?: string;\n};\n\nexport type TemplateDiagnosticTarget = {\n kind: \"template\";\n /** The routed page type (`post`, `product`, …) the theme has no template for. */\n templateType: string;\n};\n\nexport type MemberManifestDiagnosticTarget = {\n kind: \"member_manifest\";\n /** JSON path of the offending entry in `members_sites.json`, as the server\n * reports it (`$.sites[0].pages[1].slug`). */\n pointer: string;\n};\n\nexport type DiagnosticTarget =\n | SettingDiagnosticTarget\n | BlockDiagnosticTarget\n | SectionDiagnosticTarget\n | TemplateDiagnosticTarget\n | MemberManifestDiagnosticTarget;\n\nexport interface Diagnostic {\n severity: \"error\" | \"warning\";\n message: string;\n /**\n * Optional structured locator so position-aware consumers (e.g. the\n * CodeMirror-based theme editor) can map a diagnostic back to a source\n * range. The CLI ignores this and only renders `message`.\n */\n target?: DiagnosticTarget;\n}\n","import { VALID_SETTING_TYPES } from \"./types\";\nimport type { Diagnostic } from \"./types\";\n\n/**\n * Message shown when a setting declares a `type` that is not one of the\n * canonical `VALID_SETTING_TYPES`. Centralized here so the CLI and the editor\n * render identical text. Kept to a single line — the list of valid types is a\n * static set exposed once via the `VALID_SETTING_TYPES` export, so repeating it\n * in every diagnostic only bloats structured output.\n */\nexport function invalidSettingTypeMessage(type: string): string {\n return `Invalid settings type: '${type}'`;\n}\n\nexport function validateSettings(settings: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const ids = new Set<string>();\n\n for (let index = 0; index < settings.length; index++) {\n const raw = settings[index];\n const setting: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const id = typeof setting.id === \"string\" ? setting.id : undefined;\n const type = typeof setting.type === \"string\" ? setting.type : undefined;\n\n if (id !== undefined && id.trim() === \"\") {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in settings: id cannot be empty\",\n target: { kind: \"setting\", index, settingType: type, field: \"id\" },\n });\n } else if (id && ids.has(id)) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in settings: duplicate id '${id}' found`,\n target: { kind: \"setting\", index, settingId: id, field: \"id\" },\n });\n } else if (id) {\n ids.add(id);\n }\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in setting '${id ?? index}': missing required field 'type'`,\n target: { kind: \"setting\", index, settingId: id, field: \"type\" },\n });\n } else if (!VALID_SETTING_TYPES.includes(type)) {\n diagnostics.push({\n severity: \"error\",\n message: invalidSettingTypeMessage(type),\n target: { kind: \"setting\", index, settingType: type, field: \"type\" },\n });\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\n\nexport function validateBlocks(blocks: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const types = new Set<string>();\n\n for (let index = 0; index < blocks.length; index++) {\n const raw = blocks[index];\n const block: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const type = typeof block.type === \"string\" ? block.type : undefined;\n const name = typeof block.name === \"string\" ? block.name : undefined;\n const settings = block.settings;\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in blocks at index ${index}: missing required field 'type'`,\n target: { kind: \"block\", index, field: \"type\" },\n });\n } else if (types.has(type)) {\n diagnostics.push({\n severity: \"warning\",\n message: `Warning in blocks: duplicate type '${type}' found`,\n target: { kind: \"block\", index, blockType: type, field: \"type\" },\n });\n } else {\n types.add(type);\n }\n\n // Named block references (type only, no name or settings) point to\n // standalone block templates — skip the name requirement for those.\n const isNamedBlockRef = !name && !settings;\n if (!name && type !== \"@app\" && type !== \"@theme\" && !isNamedBlockRef) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': missing required field 'name'`,\n target: { kind: \"block\", index, blockType: type, field: \"name\" },\n });\n }\n\n if (settings) {\n if (!Array.isArray(settings)) {\n // e.g. the author wrote `\"settings\": {}` instead of `\"settings\": []`.\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': 'settings' must be an array ([])`,\n target: { kind: \"block\", index, blockType: type, field: \"settings\" },\n });\n } else {\n diagnostics.push(...validateSettings(settings));\n }\n }\n\n // Recurse into nested blocks (max 2 levels enforced by the engine,\n // but we validate whatever is declared)\n if (Array.isArray(block.blocks)) {\n diagnostics.push(...validateBlocks(block.blocks as unknown[]));\n }\n }\n\n return diagnostics;\n}\n","import type { BlocksSchemaType, Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\nimport { validateBlocks } from \"./validate-blocks\";\n\n// Strip Liquid comment blocks so they don't interfere with schema extraction.\nfunction stripLiquidComments(text: string): string {\n return text.replace(\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g,\n \"\",\n );\n}\n\n// Detect duplicate \"blocks\" keys in the same JSON object.\n// Standard JSON.parse silently drops duplicates, so we scan tokens manually.\nfunction findDuplicateBlocksKeys(jsonText: string): number {\n let count = 0;\n const stack: Array<{\n type: \"object\" | \"array\";\n keys: Set<string>;\n expectingKey: boolean;\n }> = [];\n let pendingKey: string | null = null;\n let i = 0;\n\n while (i < jsonText.length) {\n const ch = jsonText.charCodeAt(i);\n\n // Whitespace\n if (ch === 0x20 || ch === 0x0a || ch === 0x0d || ch === 0x09) {\n i++;\n continue;\n }\n\n if (ch === 0x7b) {\n // {\n stack.push({ type: \"object\", keys: new Set(), expectingKey: true });\n pendingKey = null;\n i++;\n } else if (ch === 0x7d) {\n // }\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x5b) {\n // [\n pendingKey = null;\n stack.push({ type: \"array\", keys: new Set(), expectingKey: false });\n i++;\n } else if (ch === 0x5d) {\n // ]\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x3a) {\n // :\n i++;\n } else if (ch === 0x2c) {\n // ,\n const top = stack[stack.length - 1];\n if (top?.type === \"object\") {\n top.expectingKey = true;\n }\n pendingKey = null;\n i++;\n } else if (ch === 0x22) {\n // \"\n let j = i + 1;\n while (j < jsonText.length) {\n if (\n jsonText.charCodeAt(j) === 0x22 &&\n jsonText.charCodeAt(j - 1) !== 0x5c\n ) {\n break;\n }\n j++;\n }\n const str = jsonText.slice(i + 1, j);\n i = j + 1;\n\n const top = stack[stack.length - 1];\n if (top?.type === \"object\" && top.expectingKey) {\n if (str === \"blocks\" && top.keys.has(str)) {\n count++;\n }\n top.keys.add(str);\n top.expectingKey = false;\n pendingKey = str;\n } else {\n pendingKey = null;\n }\n } else {\n pendingKey = null;\n i++;\n }\n }\n\n return count;\n}\n\nexport interface ValidateSchemaOptions {\n blocksSchemaType?: BlocksSchemaType;\n}\n\n// Validate the full Liquid file content containing a {% schema %} block.\n// Returns an array of diagnostics (empty = valid).\nexport function validateSchemaText(\n text: string,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n const stripped = stripLiquidComments(text);\n const match = stripped.match(\n /\\{%-?\\s*schema\\s*-?%\\}([\\s\\S]*?)\\{%-?\\s*endschema\\s*-?%\\}/,\n );\n if (!match) return diagnostics;\n\n const jsonText = match[1] ?? \"\";\n\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(jsonText) as Record<string, unknown>;\n } catch (e) {\n diagnostics.push({\n severity: \"error\",\n message: `Invalid JSON:\\n ${(e as Error).message}`,\n });\n return diagnostics;\n }\n\n const dupes = findDuplicateBlocksKeys(jsonText);\n for (let d = 0; d < dupes; d++) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: duplicate 'blocks' key in the same object\",\n });\n }\n\n // Settings\n if (\n schema !== null &&\n typeof schema === \"object\" &&\n Array.isArray(schema.settings)\n ) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n // Blocks\n if (schema !== null && typeof schema === \"object\" && \"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n // \"unknown\" — validate if array, accept if object\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n\n// Validate a parsed schema object directly (when you already have the JSON).\nexport function validateSchema(\n schema: Record<string, unknown>,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n if (Array.isArray(schema.settings)) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n if (\"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\n\n// Liquid comment blocks — stripped so commented-out section tags are not\n// treated as real references.\nconst LIQUID_COMMENT_REGEX =\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g;\n\n// The `{% schema %} … {% endschema %}` block — removed so section types\n// declared inside the schema JSON are not mistaken for `{% section %}` usages.\nconst SCHEMA_BLOCK_REGEX =\n /\\{%-?\\s*schema\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endschema\\s*-?%\\}/;\n\n// Matches a `{% section %}` tag. Supports both the id-bearing form\n// (`{% section 'hero', id: 'abc' %}`) emitted by the visual editor and the\n// bare form (`{% section 'hero' %}`) hand-authored themes use, plus\n// whitespace-control tags (`{%- … -%}`) and single or double quotes.\n// `id` is optional — capture group 2 is undefined for bare tags.\nconst SECTION_TAG_PATTERN =\n \"\\\\{%-?\\\\s*section\\\\s+['\\\"]([^'\\\"]+)['\\\"](?:\\\\s*,\\\\s*id:\\\\s*['\\\"]([^'\\\"]+)['\\\"])?\\\\s*-?%\\\\}\";\n\n// Reserved layout-region section types. `{% section 'navbar' %}` and friends\n// resolve to the theme's navbar/footer/library_navbar template slots rather than\n// a `sections/<name>` definition, and render empty when absent — so they are\n// never \"missing\". Mirrors `LiquidTags::Section::SECTION_TEMPLATES` server-side.\nconst RESERVED_SECTION_TYPES = new Set([\"navbar\", \"library_navbar\", \"footer\"]);\n\n// `fluid://extensions/{id}/{type}/{name}` references resolve against an app\n// extension installed on the company at render time — they cannot be validated\n// against local files, so they are never flagged. Mirrors `Themes::ExtensionUri`.\nconst EXTENSION_URI_PATTERN = /^fluid:\\/\\/extensions\\/[^/]+\\/[^/]+\\/[^/]+$/;\n\n// `fluid://native/widgets/{key}` (or `{key}/sections/{name}` for a bundle's\n// inner section) references a Fluid-owned native widget that the server\n// resolves at render time — it has no local file. Mirrors\n// `Themes::NativeWidgets::Catalog` (`URI_PREFIX`, `resolve`) and the name rules\n// of `Themes::NativeWidgets::Bundle.valid_name?`. Capture group 1 is the key.\nconst NATIVE_WIDGET_URI_PATTERN =\n /^fluid:\\/\\/native\\/widgets\\/([a-zA-Z0-9_-]+)(?:\\/sections\\/[a-zA-Z0-9_-]+)?$/;\n\n// Native widget keys. Copy of `Themes::NativeWidgets::Catalog::KEYS`\n// (apps/rails/app/models/themes/native_widgets/catalog.rb), in the same order;\n// `test_theme_cli_lint_knows_every_native_widget_key` in the Rails catalog test\n// fails when the two drift apart.\nexport const NATIVE_WIDGET_KEYS: readonly string[] = [\n \"member\",\n \"next\",\n \"points\",\n \"order\",\n \"again\",\n \"orders\",\n \"onway\",\n \"subs\",\n \"carenext\",\n \"journey\",\n \"careteam\",\n \"labs\",\n \"meds\",\n \"wallet\",\n \"status\",\n \"benefits\",\n \"links\",\n \"givget\",\n \"refs\",\n \"goals\",\n \"resume\",\n \"course\",\n \"courses\",\n \"certs\",\n \"signals\",\n \"funnel\",\n \"pulse\",\n \"live\",\n \"nextsend\",\n \"today\",\n \"acct\",\n \"sign\",\n \"owed\",\n \"chsales\",\n \"chgoal\",\n \"chbest\",\n \"chmix\",\n \"plink\",\n \"pcar\",\n \"nextstep\",\n \"brief\",\n \"asked\",\n \"working\",\n \"streak\",\n \"earnings\",\n \"sponsor\",\n \"contacts\",\n \"tasks\",\n \"messages\",\n \"groups\",\n \"repcard\",\n \"snapshot\",\n \"social\",\n \"team\",\n \"egoal\",\n \"ntitle\",\n \"circle\",\n \"teamfeed\",\n \"site\",\n \"link\",\n \"video\",\n \"photos\",\n \"product\",\n \"countdown\",\n \"qualify\",\n \"board\",\n \"pass\",\n];\n\nconst NATIVE_WIDGET_KEY_SET = new Set(NATIVE_WIDGET_KEYS);\n\nfunction nativeWidgetKeyOf(type: string): string | undefined {\n return NATIVE_WIDGET_URI_PATTERN.exec(type)?.[1];\n}\n\nfunction isNativeWidgetUri(type: string): boolean {\n const key = nativeWidgetKeyOf(type);\n return key !== undefined && NATIVE_WIDGET_KEY_SET.has(key);\n}\n\n/** Whether a `{% section %}` type resolves to something other than an on-disk `sections/<name>` definition (and so cannot be flagged as missing). */\nexport function isNonLocalSectionType(type: string): boolean {\n return (\n RESERVED_SECTION_TYPES.has(type) ||\n EXTENSION_URI_PATTERN.test(type) ||\n isNativeWidgetUri(type)\n );\n}\n\nexport interface SectionReference {\n /** The referenced section type/name (group 1). */\n type: string;\n /** The section instance id, when the tag declares one. */\n id?: string;\n /** The full matched tag text. */\n fullTag: string;\n /** 0-based position of the tag within the template body. */\n order: number;\n}\n\n/** A template, identified by `path`, with its raw liquid `content`. */\nexport interface TemplateInput {\n path: string;\n content: string;\n}\n\n// Liquid outside the comment and schema blocks — the rendered template body\n// where `{% section %}` references actually live.\nfunction templateBody(liquid: string): string {\n return liquid\n .replace(LIQUID_COMMENT_REGEX, \"\")\n .replace(SCHEMA_BLOCK_REGEX, \"\");\n}\n\n/**\n * Extract every `{% section %}` reference from a liquid template, ignoring\n * tags inside comments or the `{% schema %}` block. Shared by the editor's\n * section-usage detection and the CLI linter so both parse references\n * identically.\n */\nexport function extractSectionReferences(liquid: string): SectionReference[] {\n const body = templateBody(liquid);\n const pattern = new RegExp(SECTION_TAG_PATTERN, \"g\");\n const references: SectionReference[] = [];\n let order = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(body)) !== null) {\n const type = match[1];\n if (!type) continue;\n references.push({ type, id: match[2], fullTag: match[0], order: order++ });\n }\n return references;\n}\n\n/**\n * Paths of the templates that reference `sectionName`. Used by the editor to\n * warn before deleting a section that is still in use.\n */\nexport function findTemplatesReferencingSection(\n templates: TemplateInput[],\n sectionName: string,\n): string[] {\n const matches: string[] = [];\n for (const template of templates) {\n const references = extractSectionReferences(template.content);\n if (references.some((reference) => reference.type === sectionName)) {\n matches.push(template.path);\n }\n }\n return matches;\n}\n\nexport interface MissingSectionRef {\n templatePath: string;\n sectionType: string;\n diagnostic: Diagnostic;\n}\n\n/**\n * Find `{% section %}` references that point to a section that does not exist\n * in `existingSectionNames` — the static equivalent of \"an in-use section was\n * deleted\". Reserved layout-region types (navbar/footer/library_navbar) and\n * `fluid://` extension and known native widget URIs are never flagged (see\n * `isNonLocalSectionType`). Emits one `error` diagnostic per missing section\n * type per template. A well-formed native widget URI with an unknown key gets a\n * `warning` instead: the key may belong to a widget newer than this copy of\n * `NATIVE_WIDGET_KEYS`, so it must not fail a lint run.\n */\nexport function findMissingSectionReferences(\n templates: TemplateInput[],\n existingSectionNames: Set<string>,\n): MissingSectionRef[] {\n const missing: MissingSectionRef[] = [];\n for (const template of templates) {\n const reported = new Set<string>();\n for (const reference of extractSectionReferences(template.content)) {\n if (existingSectionNames.has(reference.type)) continue;\n if (isNonLocalSectionType(reference.type)) continue;\n if (reported.has(reference.type)) continue;\n reported.add(reference.type);\n const unknownKey = nativeWidgetKeyOf(reference.type);\n missing.push({\n templatePath: template.path,\n sectionType: reference.type,\n diagnostic: {\n severity: unknownKey === undefined ? \"error\" : \"warning\",\n message:\n unknownKey === undefined\n ? `references missing section '${reference.type}'`\n : `references unknown native widget key '${unknownKey}' (it may be newer than this CLI)`,\n target: {\n kind: \"section\",\n sectionType: reference.type,\n tagId: reference.id,\n },\n },\n });\n }\n }\n return missing;\n}\n","export interface LiquidBlockTagDiagnostic {\n severity: \"error\";\n message: string;\n}\n\nconst BLOCK_TAGS = new Map([\n [\"capture\", \"endcapture\"],\n [\"case\", \"endcase\"],\n [\"comment\", \"endcomment\"],\n [\"for\", \"endfor\"],\n [\"form\", \"endform\"],\n [\"if\", \"endif\"],\n [\"ifchanged\", \"endifchanged\"],\n [\"javascript\", \"endjavascript\"],\n [\"paginate\", \"endpaginate\"],\n [\"raw\", \"endraw\"],\n [\"schema\", \"endschema\"],\n [\"style\", \"endstyle\"],\n [\"stylesheet\", \"endstylesheet\"],\n [\"tablerow\", \"endtablerow\"],\n [\"unless\", \"endunless\"],\n]);\n\nconst CLOSING_TAGS = new Set(BLOCK_TAGS.values());\nconst OPAQUE_BLOCK_TAGS = new Set([\n \"comment\",\n \"javascript\",\n \"raw\",\n \"schema\",\n \"style\",\n \"stylesheet\",\n]);\n\ninterface OpenBlock {\n name: string;\n expectedClose: string;\n line: number;\n}\n\n/**\n * Find structurally unbalanced Liquid block tags such as an `{% if %}` with\n * no `{% endif %}`. This intentionally recognizes only established paired\n * tags; custom and inline tags are ignored rather than guessed at.\n *\n * Content inside raw/comment/schema/style/javascript blocks is opaque to\n * Liquid and therefore skipped until that block's matching close tag. This\n * prevents CSS, JSON, and examples containing Liquid-looking text from\n * producing false errors.\n */\nexport function findLiquidBlockTagDiagnostics(\n content: string,\n): LiquidBlockTagDiagnostic[] {\n const stack: OpenBlock[] = [];\n const diagnostics: LiquidBlockTagDiagnostic[] = [];\n let line = 1;\n let previousTagIndex = 0;\n\n const processTag = (name: string, tagLine: number): void => {\n const open = stack.at(-1);\n\n if (open && OPAQUE_BLOCK_TAGS.has(open.name)) {\n if (name === open.expectedClose) stack.pop();\n return;\n }\n\n const expectedClose = BLOCK_TAGS.get(name);\n if (expectedClose) {\n stack.push({ name, expectedClose, line: tagLine });\n return;\n }\n\n if (!CLOSING_TAGS.has(name)) return;\n\n if (!open) {\n diagnostics.push({\n severity: \"error\",\n message: `Unexpected Liquid tag '{% ${name} %}' on line ${tagLine}; there is no open block to close.`,\n });\n return;\n }\n\n if (name !== open.expectedClose) {\n diagnostics.push({\n severity: \"error\",\n message: `Mismatched Liquid tag '{% ${name} %}' on line ${tagLine}; '{% ${open.name} %}' from line ${open.line} must close with '{% ${open.expectedClose} %}'.`,\n });\n return;\n }\n\n stack.pop();\n };\n\n for (const match of content.matchAll(\n /\\{%-?\\s*([a-zA-Z_][\\w-]*)\\b(?:(?!\\{%)[\\s\\S])*?-?%\\}/g,\n )) {\n const name = match[1]?.toLowerCase();\n if (!name) continue;\n\n const index = match.index ?? 0;\n // Count each character at most once across the scan. Re-slicing from the\n // start for every tag turns large generated templates into O(n²) work.\n for (let cursor = previousTagIndex; cursor < index; cursor++) {\n if (content.charCodeAt(cursor) === 10) line += 1;\n }\n previousTagIndex = index;\n\n const open = stack.at(-1);\n if (name === \"liquid\" && !(open && OPAQUE_BLOCK_TAGS.has(open.name))) {\n // `{% liquid %}` places one delimiter-free statement on each line. Feed\n // those statements through the same stack so this supported syntax\n // cannot bypass lint, push, or watch-mode validation.\n const statements = match[0]\n .replace(/^\\{%-?\\s*liquid\\b/i, \"\")\n .replace(/-?%\\}$/, \"\")\n .split(\"\\n\");\n for (const [offset, statement] of statements.entries()) {\n const statementName = /^\\s*([a-zA-Z_][\\w-]*)\\b/.exec(statement)?.[1];\n if (!statementName) continue;\n processTag(statementName.toLowerCase(), line + offset);\n }\n } else {\n processTag(name, line);\n }\n }\n\n for (const open of stack.reverse()) {\n diagnostics.push({\n severity: \"error\",\n message: `Unclosed Liquid tag '{% ${open.name} %}' on line ${open.line}; expected '{% ${open.expectedClose} %}'.`,\n });\n }\n\n return diagnostics;\n}\n","","import pathScopes from \"./path-scopes.json\";\n\n/**\n * Reads the `fluid.fetch(` calls in a site's text and says which scope each\n * needs. It is a lint and not a parser: it reads a call whose path is a string\n * literal, or a template literal that interpolates only whole path segments,\n * and whose method is a string literal in an object-literal second argument.\n * Any other call is reported as one it cannot read, never skipped.\n *\n * `path-scopes.json` is a copy of the SDK's (member-storefront-sdk/src/site),\n * which Rails generates from the routes and `MemberSites::BridgeScopes`, and a\n * test holds the two equal.\n */\n\n/** One entry of `path-scopes.json`: `:param` is one segment and a trailing `*glob` is the rest. */\ninterface PathScope {\n pattern: string;\n method: string;\n /** The scope the route needs, or null for a route a site can never reach. */\n scope: string | null;\n reason?: string;\n}\n\nconst ENTRIES = (pathScopes as { entries: PathScope[] }).entries;\n\n/** A segment written as `${...}`: it matches only a `:param` or a glob. */\nexport type Segment = string | null;\n\n/** One `fluid.fetch(` call. A field is null when the lint cannot read it. */\nexport interface FetchCall {\n /** The 1-based line of the call. */\n line: number;\n /** The path as written, without its query string. */\n written: string | null;\n /** The path as `fluid.fetch` sends it, one entry per segment. */\n segments: Segment[] | null;\n /** The method, upper case. */\n method: string | null;\n}\n\n/** What a call resolves to once it is matched against `path-scopes.json`. */\nexport type CallScope =\n | { kind: \"scope\"; scope: string }\n | { kind: \"never\"; reason: string | undefined }\n | { kind: \"unknown\" };\n\nconst MEMBER_API_PREFIX = \"/api/member/v2026-10/\";\nconst STORE_PREFIX = \"/api/member-storefront/\";\nconst HOLE = \"\\u0000\";\n\n/** Index after the whitespace and comments from `i`. */\nfunction skipBlank(text: string, i: number): number {\n for (;;) {\n while (i < text.length && /\\s/.test(text[i] as string)) i += 1;\n if (text.startsWith(\"//\", i)) {\n const end = text.indexOf(\"\\n\", i);\n i = end < 0 ? text.length : end;\n } else if (text.startsWith(\"/*\", i)) {\n const end = text.indexOf(\"*/\", i + 2);\n i = end < 0 ? text.length : end + 2;\n } else {\n return i;\n }\n }\n}\n\n/** Index after the `}` that closes the `{` at `open`, or -1. */\nfunction skipBraces(text: string, open: number): number {\n let depth = 0;\n for (let i = open; i < text.length; i++) {\n const char = text[i] as string;\n if (char === '\"' || char === \"'\" || char === \"`\") {\n i = skipQuoted(text, i) - 1;\n if (i < 0) return -1;\n } else if (text.startsWith(\"//\", i) || text.startsWith(\"/*\", i)) {\n i = skipBlank(text, i) - 1;\n } else if (char === \"{\") {\n depth += 1;\n } else if (char === \"}\") {\n depth -= 1;\n if (depth === 0) return i + 1;\n }\n }\n return -1;\n}\n\n/** Index after the string or template literal that starts at `start`, or -1. */\nfunction skipQuoted(text: string, start: number): number {\n const quote = text[start] as string;\n for (let i = start + 1; i < text.length; i++) {\n const char = text[i] as string;\n if (char === \"\\\\\") {\n i += 1;\n } else if (char === quote) {\n return i + 1;\n } else if (quote === \"`\" && char === \"$\" && text[i + 1] === \"{\") {\n i = skipBraces(text, i + 1) - 1;\n if (i < 0) return -1;\n } else if (char === \"\\n\" && quote !== \"`\") {\n return -1;\n }\n }\n return -1;\n}\n\n/**\n * Reads the string or template literal at `start`. A `${...}` becomes HOLE in\n * `value`; `raw` is the text between the quotes. Null for a literal with an\n * escape other than a quote, a slash, a dollar sign or a backslash, because\n * its value is not what the text shows.\n */\nfunction readLiteral(\n text: string,\n start: number,\n): { value: string; raw: string; end: number } | null {\n const quote = text[start] as string;\n let value = \"\";\n for (let i = start + 1; i < text.length; i++) {\n const char = text[i] as string;\n if (char === quote) {\n return { value, raw: text.slice(start + 1, i), end: i + 1 };\n }\n if (char === \"\\\\\") {\n const next = text[i + 1];\n if (next === undefined || !`\\\\'\"\\`/$`.includes(next)) return null;\n value += next;\n i += 1;\n } else if (quote === \"`\" && char === \"$\" && text[i + 1] === \"{\") {\n const end = skipBraces(text, i + 1);\n if (end < 0) return null;\n value += HOLE;\n i = end - 1;\n } else if (char === \"\\n\" && quote !== \"`\") {\n return null;\n } else {\n value += char;\n }\n }\n return null;\n}\n\n/**\n * The method of an object literal that starts at `open`: undefined when it\n * has no `method` key (a GET), null when the lint cannot tell, as for a\n * spread, a computed key or a value that is not a string literal.\n */\nfunction readMethod(text: string, open: number): string | null | undefined {\n let method: string | undefined;\n let depth = 0;\n let atStart = true;\n for (let i = open + 1; i < text.length; i++) {\n if (depth === 0 && atStart) {\n i = skipBlank(text, i);\n atStart = false;\n if (text.startsWith(\"...\", i) || text[i] === \"[\") return null;\n const key = /(?:method|\"method\"|'method')(?![\\w$])\\s*/y;\n key.lastIndex = i;\n if (key.test(text)) {\n const valueAt = skipBlank(text, key.lastIndex + 1);\n const value =\n text[key.lastIndex] === \":\" && /[\"']/.test(text[valueAt] as string)\n ? readLiteral(text, valueAt)\n : null;\n const after = value === null ? -1 : skipBlank(text, value.end);\n if (value === null || (text[after] !== \",\" && text[after] !== \"}\")) {\n return null;\n }\n method = value.value.toUpperCase();\n i = value.end - 1;\n continue;\n }\n }\n const char = text[i] as string;\n if (char === '\"' || char === \"'\" || char === \"`\") {\n i = skipQuoted(text, i) - 1;\n if (i < 0) return null;\n } else if (text.startsWith(\"//\", i) || text.startsWith(\"/*\", i)) {\n i = skipBlank(text, i) - 1;\n } else if (char === \"{\" || char === \"[\" || char === \"(\") {\n depth += 1;\n } else if (char === \"}\" || char === \"]\" || char === \")\") {\n if (depth === 0) return method;\n depth -= 1;\n } else if (char === \",\" && depth === 0) {\n atStart = true;\n }\n }\n return null;\n}\n\n/**\n * The path `fluid.fetch` sends for a literal path, one entry per segment, or\n * null when it is not one the lint can read: a path outside `/api/`, one whose\n * interpolation is not a whole segment or sits in the part that picks the\n * mount, or one with a dot segment or an empty one. It moves a path as the\n * SDK does (`memberApiPathname`): a Member API path stays, and any other\n * `/api/` path moves under `/api/member-storefront/`.\n */\nfunction sentSegments(value: string): Segment[] | null {\n const query = value.search(/[?#]/);\n let path = query < 0 ? value : value.slice(0, query);\n if (!path.startsWith(\"/api/\")) return null;\n // What follows `/api/` decides the mount, and a version decides it for `member`.\n const parts = path.split(\"/\");\n if (parts[2]?.includes(HOLE)) return null;\n if (parts[2] === \"member\" && parts[3]?.includes(HOLE)) return null;\n if (!path.startsWith(STORE_PREFIX) && !path.startsWith(MEMBER_API_PREFIX)) {\n path = path.replace(/^\\/api\\//, STORE_PREFIX);\n }\n const segments = path.split(\"/\").slice(1);\n if (segments[segments.length - 1] === \"\") segments.pop();\n const read: Segment[] = [];\n for (const segment of segments) {\n if (segment === HOLE) {\n read.push(null);\n } else if (\n segment.includes(HOLE) ||\n segment === \"\" ||\n segment === \".\" ||\n segment === \"..\"\n ) {\n return null;\n } else {\n read.push(segment);\n }\n }\n return read;\n}\n\n/** The text of a path without its query string, as written. */\nfunction writtenPath(raw: string): string {\n const query = raw.search(/[?#]/);\n return query < 0 ? raw : raw.slice(0, query);\n}\n\n/** The `fluid.fetch(` and `window.fluid.fetch(` calls in a text, in source order. */\nexport function fluidFetchCalls(text: string): FetchCall[] {\n const calls: FetchCall[] = [];\n const start = /(?<![\\w$.])(?:window\\s*\\.\\s*)?fluid\\s*\\.\\s*fetch\\s*\\(/g;\n for (const match of text.matchAll(start)) {\n const line = text.slice(0, match.index).split(\"\\n\").length;\n const unreadable: FetchCall = {\n line,\n written: null,\n segments: null,\n method: null,\n };\n const argument = skipBlank(text, match.index + match[0].length);\n if (!/[\"'`]/.test(text[argument] ?? \"\")) {\n calls.push(unreadable);\n continue;\n }\n const literal = readLiteral(text, argument);\n if (literal === null) {\n calls.push(unreadable);\n continue;\n }\n // The path is the whole first argument, as in `\"/api/x\"`, not `\"/api/\" + x`.\n const next = skipBlank(text, literal.end);\n if (text[next] !== \",\" && text[next] !== \")\") {\n calls.push(unreadable);\n continue;\n }\n\n let method: string | null = \"GET\";\n if (text[next] === \",\") {\n const init = skipBlank(text, next + 1);\n if (text[init] === \"{\") {\n const read = readMethod(text, init);\n method = read === undefined ? \"GET\" : read;\n } else if (text[init] !== \")\") {\n method = null;\n }\n }\n calls.push({\n line,\n written: writtenPath(literal.raw),\n segments: sentSegments(literal.value),\n method,\n });\n }\n return calls;\n}\n\n/** How loosely an entry's pattern matches: fewer globs, then fewer params, is more exact. */\nfunction looseness(pattern: string): number {\n const parts = pattern.split(\"/\");\n return (\n parts.filter((part) => part.startsWith(\"*\")).length * 1000 +\n parts.filter((part) => part.startsWith(\":\")).length\n );\n}\n\nfunction matches(pattern: string, segments: Segment[]): boolean {\n const parts = pattern.split(\"/\").slice(1);\n for (const [index, part] of parts.entries()) {\n if (part.startsWith(\"*\")) return segments.length > index;\n const segment = segments[index];\n if (segment === undefined) return false;\n if (part.startsWith(\":\")) continue;\n // A segment written as `${...}` fits only a `:param` or a glob.\n if (segment !== part) return false;\n }\n return segments.length === parts.length;\n}\n\n/** The scope the entry for a path and method names, or what the lint does not know. */\nexport function scopeOfCall(segments: Segment[], method: string): CallScope {\n const entry = ENTRIES.filter(\n (candidate) =>\n candidate.method === method && matches(candidate.pattern, segments),\n ).sort((a, b) => looseness(a.pattern) - looseness(b.pattern))[0];\n if (entry === undefined) return { kind: \"unknown\" };\n return entry.scope === null\n ? { kind: \"never\", reason: entry.reason }\n : { kind: \"scope\", scope: entry.scope };\n}\n","import { stat } from \"node:fs/promises\";\nimport {\n findLiquidBlockTagDiagnostics,\n validateSchemaText,\n type BlocksSchemaType,\n type Diagnostic,\n} from \"@fluid-app/theme-schema\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport {\n FIELDS,\n parseAccessDeclaration,\n type AccessDeclaration,\n} from \"../tree/access-declaration.js\";\nimport { PAGES_CONFIG_PATH } from \"../tree/binding.js\";\nimport {\n ACCESS_PATH,\n classifyPath,\n MENUS_PATH,\n readLocalTree,\n} from \"../tree/local-tree.js\";\nimport { fluidFetchCalls, scopeOfCall } from \"./fluid-fetch.js\";\nimport {\n isRecord,\n menusProblems,\n PAGE_LIMIT,\n parsePagesConfig,\n} from \"../tree/push-plan.js\";\n\n/** One thing wrong in the folder, and the file it is in. */\nexport interface LintFinding {\n path: string;\n severity: Diagnostic[\"severity\"];\n message: string;\n}\n\n/**\n * Whether the theme linter reads a template's blocks as objects or as an\n * array. It decides by folder (`ThemeFile#isTemplate`): a layout and a section\n * have array blocks, and any other folder holds page templates with object\n * blocks.\n */\nfunction blocksSchemaType(path: string): BlocksSchemaType {\n const folder = path.split(\"/\")[0];\n return folder === \"layouts\" || folder === \"sections\" ? \"array\" : \"object\";\n}\n\n/** The `page` of every link in a menus document, at any depth. */\nfunction linkedPages(items: unknown): string[] {\n if (!Array.isArray(items)) return [];\n return items.flatMap((item: unknown) => {\n if (!isRecord(item)) return [];\n const page = typeof item[\"page\"] === \"string\" ? [item[\"page\"]] : [];\n return [...page, ...linkedPages(item[\"items\"])];\n });\n}\n\n/** The menu links, by page slug, in a menus text that is valid JSON of the menus shape. */\nfunction menuLinks(document: unknown): string[] {\n if (!isRecord(document) || !Array.isArray(document[\"menus\"])) return [];\n return document[\"menus\"].flatMap((menu: unknown) =>\n isRecord(menu) ? linkedPages(menu[\"items\"]) : [],\n );\n}\n\n/** The member fields a Liquid text reads: `member.email` and `member[\"email\"]`. */\nfunction memberFieldsRead(text: string): string[] {\n const found = new Set<string>();\n for (const match of text.matchAll(\n /(?<![.\\w])member(?:\\.([a-z_]+)|\\[\\s*['\"]([a-z_]+)['\"]\\s*\\])/g,\n )) {\n const field = match[1] ?? match[2];\n if (field !== undefined && FIELDS.includes(field)) found.add(field);\n }\n return [...found].sort();\n}\n\ntype Add = (\n path: string,\n message: string,\n severity?: LintFinding[\"severity\"],\n) => void;\n\n/**\n * Warns for each member field a Liquid file reads. A site's member data\n * reaches its script through the SDK, never its Liquid, so a read in Liquid is\n * not something a declaration can cover.\n */\nfunction liquidMemberWarnings(path: string, content: string, add: Add): void {\n for (const field of memberFieldsRead(content)) {\n add(\n path,\n `${path} reads member.${field}, and member data is leaving member-site Liquid. Read it in a script with fluid.fetch(\"/api/member/v2026-10/me\"), which needs \"profile: read\" in ${ACCESS_PATH}.`,\n \"warning\",\n );\n }\n}\n\n/**\n * Warns for each `fluid.fetch(` call a file makes whose scope the declaration\n * lacks, and for each call the lint cannot check. A warning and not an error:\n * the call may be conditional, and the file API accepts the site either way.\n * `session: read` is covered by any declared scope and is never warned.\n */\nfunction fetchWarnings(\n path: string,\n content: string,\n declaration: AccessDeclaration,\n add: Add,\n): void {\n const seen = new Set<string>();\n const warn = (message: string) => {\n if (seen.has(message)) return;\n seen.add(message);\n add(path, message, \"warning\");\n };\n const refused = `It is refused at run time if its scope is not declared in ${ACCESS_PATH}.`;\n for (const call of fluidFetchCalls(content)) {\n if (call.segments === null || call.method === null) {\n warn(\n `${path} line ${call.line} makes a fluid.fetch call whose path or method the lint cannot read, so it cannot check it. ${refused}`,\n );\n continue;\n }\n const made = `${path} calls fluid.fetch(\"${call.written}\") with ${call.method}`;\n const outcome = scopeOfCall(call.segments, call.method);\n if (outcome.kind === \"unknown\") {\n warn(\n `${made}, which matches no route the lint knows, so it cannot check it. ${refused}`,\n );\n } else if (outcome.kind === \"never\") {\n warn(\n `${made}, which a site can never reach${outcome.reason === undefined ? \"\" : `: ${outcome.reason}`}.`,\n );\n } else if (\n outcome.scope !== \"session: read\" &&\n !declaration.scopes.includes(outcome.scope)\n ) {\n warn(\n `${made}, which needs \"${outcome.scope}\", and ${ACCESS_PATH} does not declare it.`,\n );\n }\n }\n}\n\nfunction parseJson(text: string): { value: unknown } | null {\n try {\n return { value: JSON.parse(text) };\n } catch {\n return null;\n }\n}\n\n/**\n * Checks a folder with no network call: the file rules a push enforces,\n * Liquid in layouts, sections, snippets and pages, `config/pages.json` and\n * `config/menus.json`. A folder needs no binding, so every file is judged,\n * including a file a pull wrote.\n */\nexport async function lintFolder(root: string): Promise<{\n findings: LintFinding[];\n filesChecked: number;\n}> {\n let info;\n try {\n info = await stat(root);\n } catch {\n info = null;\n }\n if (!info?.isDirectory()) {\n throw new MemberSiteCliError(`${root} is not a folder.`);\n }\n\n const findings: LintFinding[] = [];\n const add: Add = (path, message, severity = \"error\") =>\n findings.push({ path, severity, message });\n\n const tree = await readLocalTree(root, null);\n for (const { path, reason } of tree.refusedFiles) add(path, reason);\n\n // Liquid, as `fluid theme lint` checks it: the {% schema %} block and the\n // pairing of block tags.\n let filesChecked = 0;\n for (const [path, content] of tree.files) {\n const kind = classifyPath(path).kind;\n if (\n kind !== \"layout\" &&\n kind !== \"section\" &&\n kind !== \"snippet\" &&\n kind !== \"page\"\n ) {\n continue;\n }\n filesChecked += 1;\n const diagnostics: Diagnostic[] = [\n ...validateSchemaText(content, {\n blocksSchemaType: blocksSchemaType(path),\n }),\n ...findLiquidBlockTagDiagnostics(content),\n ];\n for (const d of diagnostics) add(path, d.message, d.severity);\n liquidMemberWarnings(path, content, add);\n }\n\n // The pages the site would have: every row of config/pages.json.\n const pagesText = tree.files.get(PAGES_CONFIG_PATH);\n let pageSlugs: Set<string> | null = new Set();\n if (pagesText !== undefined) {\n filesChecked += 1;\n const parsed = parsePagesConfig(pagesText);\n if (Array.isArray(parsed)) {\n for (const problem of parsed) add(PAGES_CONFIG_PATH, problem);\n pageSlugs = null;\n } else {\n pageSlugs = new Set(parsed.rows.map((row) => row.slug));\n if (parsed.rows.length > PAGE_LIMIT) {\n add(\n PAGES_CONFIG_PATH,\n `${PAGES_CONFIG_PATH} lists ${parsed.rows.length} pages, and a site holds at most ${PAGE_LIMIT}.`,\n );\n }\n }\n }\n\n const menusText = tree.files.get(MENUS_PATH);\n if (menusText !== undefined) {\n filesChecked += 1;\n const document = parseJson(menusText);\n if (document === null) {\n add(MENUS_PATH, `${MENUS_PATH} is not valid JSON.`);\n } else {\n for (const problem of menusProblems(document.value)) {\n add(MENUS_PATH, problem);\n }\n // A link to a page the site does not hold is refused by the server.\n // When config/pages.json is invalid its own errors come first.\n if (pageSlugs !== null) {\n for (const slug of new Set(menuLinks(document.value))) {\n if (!pageSlugs.has(slug)) {\n add(\n MENUS_PATH,\n `${MENUS_PATH} links to the page \"${slug}\", which ${PAGES_CONFIG_PATH} does not list.`,\n );\n }\n }\n }\n }\n }\n\n // The access declaration: its shape is an error, as the file API refuses it.\n // Once it is valid, a `fluid.fetch` call it lacks is a warning. A folder with no\n // declaration is not checked for calls, because a site with none is not\n // limited.\n const accessText = tree.files.get(ACCESS_PATH);\n if (accessText !== undefined) {\n filesChecked += 1;\n const parsedAccess = parseAccessDeclaration(accessText);\n if (parsedAccess.declaration === null) {\n for (const problem of parsedAccess.problems) {\n add(ACCESS_PATH, `${ACCESS_PATH}: ${problem}.`);\n }\n } else {\n for (const [path, content] of tree.files) {\n const kind = classifyPath(path).kind;\n const runsScripts =\n kind === \"layout\" ||\n kind === \"section\" ||\n kind === \"snippet\" ||\n kind === \"page\" ||\n (kind === \"asset\" && path.endsWith(\".js\"));\n if (runsScripts) {\n fetchWarnings(path, content, parsedAccess.declaration, add);\n }\n }\n }\n }\n\n // The sort is stable, so a file's findings stay in the order they were found.\n findings.sort((a, b) => a.path.localeCompare(b.path));\n return { findings, filesChecked };\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { inert } from \"../display.js\";\nimport { lintFolder, type LintFinding } from \"../lint/lint-folder.js\";\nimport { runCommand } from \"./run.js\";\n\nexport interface LintResult {\n findings: LintFinding[];\n filesChecked: number;\n /** Whether any finding is an error, which fails the command. */\n failed: boolean;\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\n/**\n * The lines `lint` prints. A folder with no findings prints none. A finding\n * is shown as `fluid theme lint` shows one: the file, then each message's\n * first line, because a few messages carry a longer body. Paths and messages\n * can carry a file name, so both are printed with terminal controls made inert.\n */\nexport function formatFindings(result: LintResult): string[] {\n const { findings, filesChecked } = result;\n if (findings.length === 0) return [];\n\n const lines: string[] = [];\n let current: string | null = null;\n for (const finding of findings) {\n if (finding.path !== current) {\n lines.push(chalk.bold(inert(finding.path)));\n current = finding.path;\n }\n const label =\n finding.severity === \"error\"\n ? chalk.red(\"error\".padEnd(7))\n : chalk.yellow(\"warning\".padEnd(7));\n lines.push(` ${label} ${inert(finding.message.split(\"\\n\")[0]!)}`);\n }\n\n const errors = findings.filter((f) => f.severity === \"error\").length;\n const warnings = findings.length - errors;\n const suffix = `(${plural(filesChecked, \"file\")} checked)`;\n lines.push(\n errors > 0\n ? `\\n${chalk.red(`✖ ${plural(errors, \"error\")}, ${plural(warnings, \"warning\")}`)} ${suffix}`\n : `\\n${chalk.yellow(`⚠ ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n return lines;\n}\n\n/** Lints a folder, prints what it finds, and returns it. It makes no request. */\nexport async function runLint(\n dirArg: string | undefined,\n log: (line: string) => void = (line) => console.log(line),\n): Promise<LintResult> {\n const dir = resolve(dirArg ?? \".\");\n const { findings, filesChecked } = await lintFolder(dir);\n const result: LintResult = {\n findings,\n filesChecked,\n failed: findings.some((f) => f.severity === \"error\"),\n };\n for (const line of formatFindings(result)) log(line);\n return result;\n}\n\nexport function createLintCommand(): Command {\n return new Command(\"lint\")\n .description(\n \"Check a local site folder without a request: Liquid, file names and paths, assets, pages and menus\",\n )\n .argument(\"[dir]\", \"The site folder (default: the current folder)\")\n .action((dir: string | undefined) =>\n runCommand(async () => {\n const result = await runLint(dir);\n if (result.failed) process.exitCode = 1;\n }),\n );\n}\n","import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n createClients,\n listForLogin,\n loadLogin,\n parseLoginKind,\n type Clients,\n type Login,\n type SiteListing,\n} from \"../api/credential.js\";\nimport type { Site } from \"../api/types.js\";\nimport { runCommand } from \"./run.js\";\n\nfunction ownerLabel(site: Site): string {\n if (site.owner_kind === \"company\") return \"company\";\n const name = site.owner_member?.display_name;\n return name ? `member: ${name}` : \"member\";\n}\n\n/** The lines `list` prints for a listing. */\nexport function formatListing(listing: SiteListing): string[] {\n const { sites, as } = listing;\n if (sites.length === 0) {\n return [\n as === \"admin\"\n ? \"No member sites in this company yet.\"\n : \"You have no sites yet. Create one with: fluid member-site create <handle>\",\n ];\n }\n\n const lines = sites.map((site) => {\n // An admin reads a member's site and cannot change it.\n const readOnly = as === \"admin\" && site.owner_kind === \"member\";\n return [\n chalk.cyan(site.handle.padEnd(24)),\n site.status.padEnd(10),\n ownerLabel(site).padEnd(24),\n chalk.dim(site.id),\n readOnly ? chalk.dim(\"(read-only)\") : \"\",\n ]\n .join(\" \")\n .trimEnd();\n });\n if (as === \"admin\" && sites.some((s) => s.owner_kind === \"member\")) {\n lines.push(\n \"\",\n chalk.dim(\n \"Member sites are read-only here: their owners build them with their own login.\",\n ),\n );\n }\n return lines;\n}\n\nexport interface ListDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n}\n\nconst defaultDeps: ListDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\nexport async function runList(\n options: { as?: string },\n deps: ListDeps = defaultDeps,\n): Promise<void> {\n const login = await deps.loadLogin(parseLoginKind(options.as));\n const listing = await listForLogin(login, deps.createClients(login.token));\n for (const line of formatListing(listing)) deps.log(line);\n}\n\nexport function createListCommand(): Command {\n return new Command(\"list\")\n .description(\n \"List your sites (member login) or every site in the company (admin login)\",\n )\n .option(\n \"--as <login>\",\n \"Which kind of login the token is: member or admin (default: try the company API, then the member API)\",\n )\n .action((options: { as?: string }) => runCommand(() => runList(options)));\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n clientForOwner,\n createClients,\n loadLogin,\n parseLoginKind,\n resolveSite,\n type Clients,\n type Login,\n} from \"../api/credential.js\";\nimport { MemberSiteCliError, type ErrorContext } from \"../api/errors.js\";\nimport { getApiBase } from \"../api/http.js\";\nimport type { Site, SiteApi, SiteFileEntry } from \"../api/types.js\";\nimport {\n BINDING_FILE,\n PAGES_CONFIG_PATH,\n buildBinding,\n serializeJson,\n sha256,\n type PulledFile,\n type SiteBinding,\n} from \"../tree/binding.js\";\nimport {\n inspectFolder,\n checkedPath,\n listedPath,\n readBinding,\n readLocal,\n removeLocal,\n writeLocal,\n} from \"../tree/folder.js\";\nimport { ACCESS_PATH } from \"../tree/local-tree.js\";\nimport { buildPagesConfig, pageFilePath } from \"../tree/pages-config.js\";\nimport { runCommand } from \"./run.js\";\n\n/** The content types a site holds as text, the ones the file API reads. */\nconst TEXT_ASSET_TYPES = new Set([\n \"text/css\",\n \"text/javascript\",\n \"application/json\",\n \"text/plain\",\n \"text/html\",\n]);\n\n/** Files read at once. The API has no bulk read, so a pull is one request per file. */\nconst READ_CONCURRENCY = 5;\n\nexport interface PullOptions {\n as?: string;\n force?: boolean;\n}\n\nexport interface PullDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n}\n\nconst defaultDeps: PullDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\n/** What a pull did. */\nexport interface PullResult {\n site: Site;\n dir: string;\n written: string[];\n unchanged: string[];\n /** Local files a pull removed because the server no longer has them. */\n removed: string[];\n skippedBinary: string[];\n /** Pages whose content is not a file of this site, so only their row is pulled. */\n pagesWithoutFile: string[];\n}\n\n/** A listed file that is not text. The server lists none today. */\nfunction isBinary(entry: SiteFileEntry): boolean {\n return entry.kind === \"asset\" && !TEXT_ASSET_TYPES.has(entry.content_type);\n}\n\nasync function mapLimit<T, R>(\n items: T[],\n limit: number,\n work: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = Array.from({ length: items.length });\n let next = 0;\n const lane = async () => {\n while (next < items.length) {\n const index = next++;\n results[index] = await work(items[index] as T);\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, lane),\n );\n return results;\n}\n\n/**\n * Refuses a folder that holds something else: files with no binding, or a\n * binding for another site. Nothing of it is read or removed.\n */\nfunction assertFolderIsThisSite(\n dir: string,\n site: Site,\n state: { nonEmpty: boolean; binding: SiteBinding | null },\n): void {\n if (!state.nonEmpty) return;\n if (state.binding?.site_id === site.id) return;\n const holds = state.binding\n ? `is bound to another site (${state.binding.handle})`\n : `is not empty and is not a pulled site`;\n throw new MemberSiteCliError(\n `${dir} ${holds}. Pull into an empty folder, or pass --force to write into it.`,\n );\n}\n\n/**\n * The paths a pull would overwrite that were edited since the last pull, or\n * that the last pull did not write.\n */\nasync function editedPaths(\n dir: string,\n binding: SiteBinding | null,\n outputs: Array<{ path: string; content: string }>,\n): Promise<string[]> {\n const edited: string[] = [];\n for (const { path, content } of outputs) {\n const local = await readLocal(dir, path);\n if (local === null || local === content) continue;\n const pulled =\n path === PAGES_CONFIG_PATH\n ? binding?.pages_config_sha256\n : binding?.files[path]?.sha256;\n if (pulled !== sha256(local)) edited.push(path);\n }\n return edited;\n}\n\n/** The lines `pull` prints for a result. */\nexport function formatPull(result: PullResult): string[] {\n const {\n site,\n dir,\n written,\n unchanged,\n removed,\n skippedBinary,\n pagesWithoutFile,\n } = result;\n const lines = [\n `Pulled ${site.owner_kind} site ${chalk.cyan(site.handle)} into ${dir}`,\n ` ${written.length} written, ${unchanged.length} unchanged`,\n ];\n if (removed.length > 0) {\n lines.push(\n chalk.dim(\n ` ${removed.length} removed, no longer on the site: ${removed.join(\", \")}`,\n ),\n );\n }\n if (pagesWithoutFile.length > 0) {\n lines.push(\n chalk.dim(\n ` ${pagesWithoutFile.length} page(s) use a template the site does not own, so only their rows are in ${PAGES_CONFIG_PATH}: ${pagesWithoutFile.join(\", \")}`,\n ),\n );\n }\n if (skippedBinary.length > 0) {\n lines.push(\n ` Skipped ${skippedBinary.length} binary asset(s): ${skippedBinary.join(\", \")}`,\n chalk.dim(\n \" A site holds text assets only. Use images and fonts from the media library by URL.\",\n ),\n );\n }\n return lines;\n}\n\n/**\n * Pulls a site into a folder: every text file at its API path, the page rows\n * in `config/pages.json`, and a binding file with the versions the API gave.\n * `context` gets the site's owner kind once it is known, so a failure is\n * reported with the login the site needs.\n */\nexport async function runPull(\n ref: string,\n dirArg: string | undefined,\n options: PullOptions,\n deps: PullDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<PullResult> {\n const login = await deps.loadLogin(parseLoginKind(options.as));\n const clients = deps.createClients(login.token);\n\n // A folder already bound to this site names its owner kind, which a login\n // of the other kind cannot pull, so that stops before any request.\n const folderGuess = dirArg ?? ref;\n const known = await readBinding(resolve(folderGuess));\n if (known && (known.site_id === ref || known.handle === ref)) {\n context.ownerKind = known.owner_kind;\n clientForOwner(known.owner_kind, login, clients);\n }\n\n const { site, api } = await resolveSite(ref, login, clients);\n context.ownerKind = site.owner_kind;\n // An admin reads a member's site and cannot pull it: its owner builds it.\n if (api.ownerKind !== site.owner_kind) {\n clientForOwner(\n site.owner_kind,\n { token: login.token, kind: \"admin\" },\n clients,\n );\n }\n\n const dir = resolve(dirArg ?? site.handle);\n const state = await inspectFolder(dir);\n if (!options.force) assertFolderIsThisSite(dir, site, state);\n\n const result = await fetchAndWrite(api, site, dir, state.binding, options);\n for (const line of formatPull(result)) deps.log(line);\n return result;\n}\n\nasync function fetchAndWrite(\n api: SiteApi,\n site: Site,\n dir: string,\n priorBinding: SiteBinding | null,\n options: PullOptions,\n): Promise<PullResult> {\n const entries = await api.listFiles(site.id);\n const skippedBinary = entries.filter(isBinary).map((e) => e.path);\n // `config/pages.json` is written from the pages list whether or not the\n // server also serves a file of that name.\n const textEntries = entries.filter(\n (e) => !isBinary(e) && e.path !== PAGES_CONFIG_PATH,\n );\n // Every path is checked before the first request for content.\n for (const entry of textEntries) listedPath(dir, entry.path);\n\n // All content is read before anything is written, so a failed read leaves\n // the folder as it was.\n const files: PulledFile[] = await mapLimit(\n textEntries,\n READ_CONCURRENCY,\n async (entry) => ({ entry, file: await api.readFile(site.id, entry.path) }),\n );\n const pages = await api.listPages(site.id);\n\n const pagesConfig = buildPagesConfig(site, pages);\n const outputs = [\n ...files.map(({ entry, file }) => ({\n path: entry.path,\n content: file.content,\n })),\n { path: PAGES_CONFIG_PATH, content: pagesConfig },\n ];\n\n // No path may lead through a link, forced or not, and none is read, hashed\n // or written before every one is checked.\n for (const { path } of outputs) await checkedPath(dir, path);\n await checkedPath(dir, BINDING_FILE);\n\n const binding = priorBinding?.site_id === site.id ? priorBinding : null;\n if (!options.force) {\n const edited = await editedPaths(dir, binding, outputs);\n if (edited.length > 0) {\n throw new MemberSiteCliError(\n `These files were edited since the last pull, and a pull would overwrite them:\\n${edited\n .map((p) => ` ${p}`)\n .join(\n \"\\n\",\n )}\\nPush or save your edits, or pass --force to overwrite them.`,\n );\n }\n }\n\n const written: string[] = [];\n const unchanged: string[] = [];\n for (const { path, content } of outputs) {\n if ((await readLocal(dir, path)) === content) {\n unchanged.push(path);\n } else {\n await writeLocal(dir, path, content);\n written.push(path);\n }\n }\n\n // The site's declaration was removed by another writer. The local copy is\n // removed with it when it is the one the last pull or push recorded, so the\n // next push does not take it for a new declaration and create it again. A\n // copy edited since is the builder's own work and stays, unless the pull\n // is forced.\n const removed: string[] = [];\n const boundAccess = binding?.files[ACCESS_PATH];\n if (boundAccess && !outputs.some(({ path }) => path === ACCESS_PATH)) {\n const local = await readLocal(dir, ACCESS_PATH);\n if (\n local !== null &&\n (options.force || sha256(local) === boundAccess.sha256)\n ) {\n await removeLocal(dir, ACCESS_PATH);\n removed.push(ACCESS_PATH);\n }\n }\n\n const bindingText = serializeJson(\n buildBinding({ site, apiBase: getApiBase(), files, pages, pagesConfig }),\n );\n if ((await readLocal(dir, BINDING_FILE)) !== bindingText) {\n await writeLocal(dir, BINDING_FILE, bindingText);\n }\n\n const listed = new Set(entries.map((e) => e.path));\n return {\n site,\n dir,\n written,\n unchanged,\n removed,\n skippedBinary,\n pagesWithoutFile: pages\n .filter((page) => !listed.has(pageFilePath(page.slug)))\n .map((page) => page.slug),\n };\n}\n\nexport function createPullCommand(): Command {\n return new Command(\"pull\")\n .description(\n \"Pull a site into a local folder: its files at their API paths, its pages in config/pages.json\",\n )\n .argument(\"<site>\", \"The site's handle or id\")\n .argument(\"[dir]\", \"The folder to pull into (default: the site's handle)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .option(\n \"--force\",\n \"Write into a folder that is not bound to this site, and overwrite local edits\",\n )\n .action((ref: string, dir: string | undefined, options: PullOptions) => {\n const context: ErrorContext = {};\n return runCommand(\n () =>\n runPull(ref, dir, options, defaultDeps, context).then(\n () => undefined,\n ),\n context,\n );\n });\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createCreateCommand } from \"./create.js\";\nimport { createDevCommand } from \"./dev.js\";\nimport { createLintCommand } from \"./lint.js\";\nimport { createListCommand } from \"./list.js\";\nimport { createPublishCommand } from \"./publish.js\";\nimport { createPullCommand } from \"./pull.js\";\nimport { createPushCommand } from \"./push.js\";\n\nexport function registerMemberSiteCommand(ctx: PluginContext): void {\n const cmd = new Command(\"member-site\").description(\n \"Member sites — list, create, pull, push, publish, lint and preview sites with the member or company site API\",\n );\n\n cmd.addCommand(createListCommand());\n cmd.addCommand(createCreateCommand());\n cmd.addCommand(createPullCommand());\n cmd.addCommand(createPushCommand(ctx.inputs));\n cmd.addCommand(createPublishCommand(ctx.inputs));\n cmd.addCommand(createLintCommand());\n cmd.addCommand(createDevCommand());\n\n ctx.program.addCommand(cmd);\n}\n","import type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerMemberSiteCommand } from \"./commands/member-site.js\";\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-member-site\",\n version: \"0.1.0\",\n register(ctx: PluginContext) {\n registerMemberSiteCommand(ctx);\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["field","delay","refused","defaultDeps","isRecord","PAGE_SLUG_PATTERN","vocabulary.fields","vocabulary.families","vocabulary.modes","vocabulary.maxEntries","vocabulary.maxBytes","defaultDeps","defaultDeps","defaultDeps","settingTypesJson.types","(pathScopes as { entries: PathScope[] }).entries","defaultDeps"],"sources":["../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../src/api/site-api.ts","../src/api/errors.ts","../src/api/http.ts","../src/api/credential.ts","../src/commands/run.ts","../src/commands/create.ts","../src/dev/local-guard.ts","../src/dev/port-preflight.ts","../src/dev/preview-proxy.ts","../src/dev/watcher.ts","../src/tree/binding.ts","../src/tree/folder.ts","../src/commands/bound-site.ts","../src/tree/local-tree.ts","../src/tree/pages-config.ts","../src/tree/access-vocabulary.json","../src/tree/access-declaration.ts","../src/tree/push-plan.ts","../src/commands/publish.ts","../src/commands/push.ts","../src/commands/dev.ts","../src/display.ts","../../../platform/theme-schema/src/setting-types.json","../../../platform/theme-schema/src/types.ts","../../../platform/theme-schema/src/validate-settings.ts","../../../platform/theme-schema/src/validate-blocks.ts","../../../platform/theme-schema/src/validate.ts","../../../platform/theme-schema/src/sections.ts","../../../platform/theme-schema/src/liquid-blocks.ts","../src/lint/path-scopes.json","../src/lint/fluid-fetch.ts","../src/lint/lint-folder.ts","../src/commands/lint.ts","../src/commands/list.ts","../src/commands/pull.ts","../src/commands/member-site.ts","../src/index.ts"],"sourcesContent":["/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, else the field errors in its `error.details`, with its `error.message`\n * as `error.message`, when the body has that, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * Rails sends two error envelopes. `ControllerAction#error_response` puts the\n * bag under `errors`; `Api::Response.error` puts it under `error.details`,\n * beside `error.message`. Both unwrap to the bag, `errors` first.\n *\n * `!= null` rather than a truthiness check so an explicitly empty `errors`\n * (`\"\"`, `0`, `false`) is preserved instead of silently falling back to the\n * whole envelope.\n * `details` is taken whatever its value, `null` included, so data from an\n * `Api::Response` error is never the envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n if (body.errors != null) {\n return body.errors as ApiErrorData;\n }\n\n const error = toApiErrorBody(body.error);\n if (error && error.details !== undefined) {\n return withErrorMessage(toFieldErrorDetails(error.details), error.message);\n }\n\n return body as ApiErrorData;\n}\n\n/**\n * `Api::Response` errors carry diagnostics such as `{ type: \"not_provisioned\" }`\n * or `{ violations: [{ type, description }] }` in `details` as readily as\n * field errors, and sometimes both. A field error is a list of message\n * strings, at any depth, so only those entries make up the bag; an `errors`\n * wrapper inside `details` is unwrapped as the envelope's own is. Details with\n * no field error unwrap to an empty bag. The diagnostics stay on\n * `ApiError.body`.\n */\nfunction toFieldErrorDetails(details: unknown): ApiErrorData {\n if (details === null) {\n return null;\n }\n\n const record = toApiErrorBody(details);\n const bag =\n record && record.errors != null ? toApiErrorBody(record.errors) : record;\n\n return ((bag && pickFieldErrors(bag)) ?? {}) as ApiErrorData;\n}\n\n/**\n * Telling a field error from a diagnostic list such as\n * `{ missing_keys: [\"coupons.create\"] }` by its content cannot be exact, so\n * the bag also carries `error.message` as `{ error: { message } }`, after the\n * field errors. `parseApiErrors` and the inlined `fluidToast` copies render\n * that nested form first and without a field prefix, so the server's message\n * is never hidden by whatever reaches the bag.\n */\nfunction withErrorMessage(\n fieldErrors: ApiErrorData,\n message: unknown,\n): ApiErrorData {\n if (typeof message !== \"string\" || message.trim().length === 0) {\n return fieldErrors;\n }\n\n const bag = toApiErrorBody(fieldErrors) ?? {};\n if (\"error\" in bag) {\n return fieldErrors;\n }\n\n return { ...bag, error: { message } };\n}\n\nfunction pickFieldErrors(\n record: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const picked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(record)) {\n const nested = toApiErrorBody(value);\n const fieldErrors = isMessageList(value)\n ? value\n : nested && pickFieldErrors(nested);\n\n if (fieldErrors) {\n picked[key] = fieldErrors;\n }\n }\n\n return Object.keys(picked).length > 0 ? picked : undefined;\n}\n\nfunction isMessageList(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((message) => typeof message === \"string\")\n );\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n\n /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n priority?: RequestInit[\"priority\"];\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\n );\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]. The OpenAPI-declared\n // param name is sometimes already bracketed (e.g. \"product_ids[]\",\n // \"filters[integration_class][]\") to match Rails' own convention;\n // appending another \"[]\" in that case would double-bracket the wire\n // format (key[][]=1), which Rails does not parse as the array.\n const arrayKey = key.endsWith(\"[]\") ? key : `${key}[]`;\n value.forEach((item) => queryString.append(arrayKey, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n const headerRequestId = getRequestIdFromHeaders(response.headers);\n\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n priority,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import type { HttpClient } from \"./http.js\";\nimport type {\n CreatePageInput,\n CreateSiteInput,\n ListSitesOptions,\n OwnerKind,\n Site,\n SiteApi,\n SiteFile,\n SiteFileEntry,\n SitePage,\n UpdatePageInput,\n WriteFileInput,\n} from \"./types.js\";\n\n/** What a response or a request body carries. */\ntype Resource = \"site\" | \"sites\" | \"file\" | \"files\" | \"page\" | \"pages\";\n\n/**\n * How one API wraps its payloads. The member API answers `{ data }` and takes\n * flat bodies. The company API names the payload after the resource\n * (`member_site`, `member_site_files`) and nests a body under the same name.\n */\ninterface Envelope {\n payload(response: unknown, resource: Resource): unknown;\n body(resource: \"site\" | \"file\" | \"page\", fields: object): object;\n}\n\ninterface Dialect {\n readonly ownerKind: OwnerKind;\n /** Path of the site collection, with no trailing slash. */\n readonly basePath: string;\n readonly envelope: Envelope;\n}\n\nfunction field(response: unknown, key: string): unknown {\n return response && typeof response === \"object\"\n ? (response as Record<string, unknown>)[key]\n : undefined;\n}\n\nconst MEMBER_DIALECT: Dialect = {\n ownerKind: \"member\",\n basePath: \"/api/member/v2026-10/sites\",\n envelope: {\n payload: (response) => field(response, \"data\"),\n body: (_resource, fields) => fields,\n },\n};\n\nconst COMPANY_KEYS: Record<Resource, string> = {\n site: \"member_site\",\n sites: \"member_sites\",\n file: \"member_site_file\",\n files: \"member_site_files\",\n page: \"member_site_page\",\n pages: \"member_site_pages\",\n};\n\nconst COMPANY_DIALECT: Dialect = {\n ownerKind: \"company\",\n basePath: \"/api/company/v2026-10/member-sites\",\n envelope: {\n payload: (response, resource) => field(response, COMPANY_KEYS[resource]),\n body: (resource, fields) => ({ [COMPANY_KEYS[resource]]: fields }),\n },\n};\n\n/** The cursor a list response names for its next page, if any. */\nfunction nextCursor(response: unknown): string | null {\n const meta = field(response, \"meta\");\n const pagination = field(meta, \"pagination\");\n const cursor = field(pagination, \"next_cursor\");\n return typeof cursor === \"string\" && cursor.length > 0 ? cursor : null;\n}\n\n/**\n * A file's path is part of the URL. Each segment is escaped and the slashes\n * between them are kept, as the routes take `files/*path`.\n */\nfunction filePath(path: string): string {\n return path.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\n\nfunction createSiteApi(http: HttpClient, dialect: Dialect): SiteApi {\n const { basePath, envelope } = dialect;\n const siteUrl = (siteId: string) =>\n `${basePath}/${encodeURIComponent(siteId)}`;\n\n /** Follows `meta.pagination.next_cursor` to the last page. */\n async function listAll<T>(\n url: string,\n resource: \"sites\" | \"files\",\n params: Record<string, unknown> = {},\n ): Promise<T[]> {\n const rows: T[] = [];\n const seen = new Set<string>();\n let cursor: string | null = null;\n do {\n const response: unknown = await http.get(url, {\n ...params,\n ...(cursor ? { page: { cursor } } : {}),\n });\n rows.push(...((envelope.payload(response, resource) as T[]) ?? []));\n const next = nextCursor(response);\n // A server that repeats a cursor would otherwise loop forever.\n cursor = next && !seen.has(next) ? next : null;\n if (cursor) seen.add(cursor);\n } while (cursor);\n return rows;\n }\n\n return {\n ownerKind: dialect.ownerKind,\n\n listSites: (options: ListSitesOptions = {}) =>\n // Only the company list has filters; the member list has none.\n listAll<Site>(\n basePath,\n \"sites\",\n options.ownerKind || options.status\n ? {\n filter: { owner_kind: options.ownerKind, status: options.status },\n }\n : {},\n ),\n\n getSite: async (siteId) =>\n envelope.payload(await http.get(siteUrl(siteId)), \"site\") as Site,\n\n createSite: async (input: CreateSiteInput) =>\n envelope.payload(\n await http.post(basePath, envelope.body(\"site\", input)),\n \"site\",\n ) as Site,\n\n publishSite: async (siteId) =>\n envelope.payload(\n await http.post(`${siteUrl(siteId)}/publish`),\n \"site\",\n ) as Site,\n\n listFiles: (siteId) =>\n listAll<SiteFileEntry>(`${siteUrl(siteId)}/files`, \"files\"),\n\n readFile: async (siteId, path) =>\n envelope.payload(\n await http.get(`${siteUrl(siteId)}/files/${filePath(path)}`),\n \"file\",\n ) as SiteFile,\n\n writeFile: async (siteId, path, input: WriteFileInput) =>\n envelope.payload(\n await http.put(\n `${siteUrl(siteId)}/files/${filePath(path)}`,\n envelope.body(\"file\", input),\n ),\n \"file\",\n ) as SiteFile,\n\n deleteFile: async (siteId, path, options = {}) => {\n await http.delete(\n `${siteUrl(siteId)}/files/${filePath(path)}`,\n options.version === undefined\n ? undefined\n : { params: { version: options.version } },\n );\n },\n\n listPages: async (siteId) =>\n (envelope.payload(\n await http.get(`${siteUrl(siteId)}/pages`),\n \"pages\",\n ) as SitePage[]) ?? [],\n\n createPage: async (siteId, input: CreatePageInput) =>\n envelope.payload(\n await http.post(\n `${siteUrl(siteId)}/pages`,\n envelope.body(\"page\", input),\n ),\n \"page\",\n ) as SitePage,\n\n updatePage: async (siteId, pageId, input: UpdatePageInput) =>\n envelope.payload(\n await http.patch(\n `${siteUrl(siteId)}/pages/${encodeURIComponent(pageId)}`,\n envelope.body(\"page\", input),\n ),\n \"page\",\n ) as SitePage,\n\n deletePage: async (siteId, pageId, options = {}) => {\n await http.delete(\n `${siteUrl(siteId)}/pages/${encodeURIComponent(pageId)}`,\n options.lockVersion === undefined\n ? undefined\n : { params: { lock_version: options.lockVersion } },\n );\n },\n };\n}\n\n/**\n * The path of the collection of sites an owner kind builds, with no trailing\n * slash: the member API for a member's site and the company API for a company\n * site.\n */\nexport function siteCollectionPath(ownerKind: OwnerKind): string {\n return (ownerKind === \"member\" ? MEMBER_DIALECT : COMPANY_DIALECT).basePath;\n}\n\n/** The client for a member's own sites: `/api/member/v2026-10/sites`. */\nexport function createMemberSiteApi(http: HttpClient): SiteApi {\n return createSiteApi(http, MEMBER_DIALECT);\n}\n\n/** The client for company sites and oversight: `/api/company/v2026-10/member-sites`. */\nexport function createCompanySiteApi(http: HttpClient): SiteApi {\n return createSiteApi(http, COMPANY_DIALECT);\n}\n","import { ApiError } from \"@fluid-app/api-client-core\";\nimport type { OwnerKind } from \"./types.js\";\n\n/** A failure with a message that is ready to show. No stack trace follows it. */\nexport class MemberSiteCliError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MemberSiteCliError\";\n }\n}\n\nexport interface ErrorContext {\n /** The owner kind of the site the request was for, when it was for one. */\n ownerKind?: OwnerKind;\n /** The file or page a request wrote, named in a conflict. */\n subject?: string;\n /**\n * What a 409 on the request means when it is not a stale write: the path\n * was to be created and already holds a file, a removal was refused, or the\n * site was changed while it was being published.\n */\n conflict?: \"exists\" | \"delete\" | \"publish\";\n}\n\n/**\n * Which login a site needs. The credential follows the owner: a member's site\n * is built with that member's own login, a company site with an admin's.\n */\nfunction loginNeeded(ownerKind: OwnerKind): string {\n return ownerKind === \"member\"\n ? \"A member site needs the member's own login, and a read-only login or an admin's cannot change it.\"\n : \"A company site needs a signed-in admin who may change themes. A token with no user can only read.\";\n}\n\nfunction field(value: unknown, key: string): unknown {\n return value && typeof value === \"object\"\n ? (value as Record<string, unknown>)[key]\n : undefined;\n}\n\n/** The request id the API puts in `meta`, or the one the client found. */\nfunction requestIdOf(error: ApiError): string | undefined {\n const fromMeta = field(field(error.body, \"meta\"), \"request_uuid\");\n if (typeof fromMeta === \"string\" && fromMeta) return fromMeta;\n return error.requestId;\n}\n\n/** `error.details` as `field: message` lines, for a 422. */\nfunction detailLines(error: ApiError): string[] {\n const details = field(field(error.body, \"error\"), \"details\");\n if (!details || typeof details !== \"object\") return [];\n return Object.entries(details).flatMap(([name, value]) => {\n const messages = (Array.isArray(value) ? value : [value]).filter(\n (m): m is string => typeof m === \"string\" && m.length > 0,\n );\n return messages.map((message) => ` ${name}: ${message}`);\n });\n}\n\n/**\n * The message to show for an API failure. A 404 is the same answer for a\n * disabled feature, a missing site and another owner's site, so it names the\n * first and last and never says which.\n */\nexport function describeApiError(\n error: ApiError,\n context: ErrorContext = {},\n): string {\n const lines: string[] = [];\n const hint = context.ownerKind ? loginNeeded(context.ownerKind) : null;\n\n switch (error.status) {\n case 404:\n lines.push(\n \"Member sites are not enabled for this company or the site is not yours.\",\n );\n break;\n case 401:\n lines.push(\n \"The login was not accepted. Run `fluid login` again, or check FLUID_TOKEN.\",\n );\n if (hint) lines.push(hint);\n break;\n case 403:\n lines.push(`The login may not do this: ${error.message}`);\n if (hint) lines.push(hint);\n break;\n case 409: {\n const subject = context.subject ?? \"The site\";\n lines.push(\n context.conflict === \"exists\"\n ? `${subject} exists on the server: another writer created it since the pull, so it was not replaced. Pull the latest copy and try again.`\n : context.conflict === \"delete\"\n ? `${subject} changed on the server since it was read, so it was not deleted. Pull the latest copy and try again.`\n : context.conflict === \"publish\"\n ? `${subject} was changed on the server while it was being published, so it was not published (a conflict). Run the publish again.`\n : `${subject} changed on the server since it was read. Pull the latest copy and try again.`,\n );\n break;\n }\n case 422:\n lines.push(`The API refused the request: ${error.message}`);\n lines.push(...detailLines(error));\n break;\n default:\n lines.push(error.message);\n }\n\n const requestId = requestIdOf(error);\n if (requestId) lines.push(`Request id: ${requestId}`);\n return lines.join(\"\\n\");\n}\n\n/** The message for any failure a command can meet. */\nexport function describeError(\n error: unknown,\n context: ErrorContext = {},\n): string {\n if (error instanceof ApiError) return describeApiError(error, context);\n if (error instanceof Error) return error.message;\n return String(error);\n}\n","import {\n ApiError,\n createFetchClient,\n type FetchClient,\n type RequestOptions,\n} from \"@fluid-app/api-client-core\";\nimport {\n findProjectConfig,\n getAuthToken,\n getRoleAuthTokenAsync,\n isRoleNotGrantedError,\n readConfig,\n} from \"@fluid-app/fluid-cli\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { MemberSiteCliError } from \"./errors.js\";\n\nexport type HttpClient = FetchClient;\n\n/**\n * The API base the stored login was made for. It follows the same choice of\n * credential `getAuthToken` makes: a token in `FLUID_TOKEN` or\n * `FLUID_API_TOKEN` records no base, then the project's `.fluidrc` profile,\n * then the active profile.\n */\nfunction loginApiBase(): string | null {\n if (process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"]) return null;\n const config = readConfig();\n const named = findProjectConfig(process.cwd())?.profile;\n const profile =\n (named ? config.profiles[named] : undefined) ??\n (config.activeProfile ? config.profiles[config.activeProfile] : undefined);\n return profile?.baseUrl ?? null;\n}\n\n/**\n * Base URL for all API calls: `FLUID_API_BASE`, else the API the stored login\n * was made for, so a staging login talks to staging. With neither there is\n * no base, and no request is made: there is no default, so a command never\n * reaches an API the login was not made for.\n *\n * Throws a MemberSiteCliError when there is no base.\n */\nexport function getApiBase(): string {\n const base = process.env[\"FLUID_API_BASE\"] || loginApiBase();\n if (!base) {\n throw new MemberSiteCliError(\n \"No API base for this login: run `fluid login` or set FLUID_API_BASE.\",\n );\n }\n return base;\n}\n\n/**\n * Whether an API base is, or may be, production. Only a base that is known\n * not to be is not: a local one (`localhost`, a loopback address, that is any of 127.0.0.0/8 or\n * `::1`, and `*.localhost`),\n * a stage one (`*.stage.fluid.app`, as the identity service names them) and a\n * name under the reserved `.test` TLD. Any other base, a custom or unknown\n * host included, is taken for production, so that a command that must not\n * write production data by default errs on the side of refusing.\n */\nexport function isProductionApiBase(apiBase: string): boolean {\n let hostname: string;\n try {\n hostname = new URL(apiBase).hostname.toLowerCase();\n } catch {\n return true;\n }\n const local =\n hostname === \"localhost\" ||\n /^127(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}$/.test(hostname) ||\n hostname === \"[::1]\" ||\n hostname.endsWith(\".localhost\");\n const stage = hostname.endsWith(\".stage.fluid.app\");\n const reserved = hostname.endsWith(\".test\");\n return !(local || stage || reserved);\n}\n\n/**\n * The stored login's token, in the order the core CLI resolves it:\n * FLUID_TOKEN or FLUID_API_TOKEN, then the project's `.fluidrc` profile, then\n * the active profile. This plugin keeps no credential of its own.\n */\nexport function resolveAuthToken(): string | null {\n return getAuthToken() ?? null;\n}\n\n/**\n * The stored login's token narrowed to a rep, which is what the member API\n * accepts from a person who is also an admin: the identity service issues it\n * only for a person who holds rep standing in the company. A login that\n * cannot be narrowed, a literal token (`FLUID_TOKEN`) or a legacy profile,\n * gives the token `resolveAuthToken` gives.\n *\n * Throws a MemberSiteCliError, before any member API request, when the person\n * is not a rep in the company.\n */\nexport async function resolveRepToken(): Promise<string | null> {\n try {\n return (await getRoleAuthTokenAsync([\"rep\"])) ?? null;\n } catch (error) {\n if (!isRoleNotGrantedError(error)) throw error;\n throw new MemberSiteCliError(\n `You are not a rep at ${error.companyName ?? \"this company\"}, so you cannot build a member site there. ` +\n \"Use --as admin for company sites.\",\n );\n }\n}\n\n/**\n * A client for one login. Reads that fail with 502, 503 or 504 are retried\n * twice, as the theme CLI retries them. A write is never retried, because a\n * failed response may follow a successful change, and a 404 never is,\n * because it answers the same for a disabled feature and a missing site.\n */\nexport function createHttpClient(token: string): HttpClient {\n const client = createFetchClient({\n baseUrl: getApiBase(),\n getAuthToken: () => token,\n });\n const request = async <TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> => {\n for (let attempt = 0; ; attempt++) {\n options.signal?.throwIfAborted();\n try {\n return await client.request<TResponse>(endpoint, options);\n } catch (error) {\n if (\n (options.method ?? \"GET\") !== \"GET\" ||\n !(error instanceof ApiError) ||\n ![502, 503, 504].includes(error.status) ||\n attempt >= 2 ||\n options.signal?.aborted\n ) {\n throw error;\n }\n await delay(500 * 2 ** attempt, undefined, { signal: options.signal });\n }\n }\n };\n return {\n ...client,\n request,\n get: (endpoint, params, options) =>\n request(endpoint, { ...options, method: \"GET\", params }),\n };\n}\n","import { ApiError } from \"@fluid-app/api-client-core\";\nimport { createCompanySiteApi, createMemberSiteApi } from \"./site-api.js\";\nimport { createHttpClient, resolveAuthToken, resolveRepToken } from \"./http.js\";\nimport { MemberSiteCliError } from \"./errors.js\";\nimport type { OwnerKind, Site, SiteApi } from \"./types.js\";\n\n/**\n * Which kind of login holds a token. The credential follows the owner: a\n * member's site is read and written with the member's own login through the\n * member API, and a company site with an admin's login through the company\n * API.\n */\nexport type LoginKind = \"member\" | \"admin\";\n\nexport interface Login {\n token: string;\n /**\n * `null` when the kind is not known before a request. The core CLI stores\n * a token and no record of whose it is, so the kind is known only when the\n * caller states it (`--as`).\n */\n kind: LoginKind | null;\n}\n\n/** The two clients one token can drive. */\nexport interface Clients {\n member: SiteApi;\n company: SiteApi;\n}\n\nexport function parseLoginKind(value: string | undefined): LoginKind | null {\n if (value === undefined) return null;\n if (value === \"member\" || value === \"admin\") return value;\n throw new MemberSiteCliError(\n `--as takes \"member\" or \"admin\", not \"${value}\".`,\n );\n}\n\n/** The login a site of this owner kind needs. */\nexport function loginFor(ownerKind: OwnerKind): LoginKind {\n return ownerKind === \"member\" ? \"member\" : \"admin\";\n}\n\nfunction describeLogin(kind: LoginKind): string {\n return kind === \"member\" ? \"a member login\" : \"an admin login\";\n}\n\n/** Where a login's token comes from. Replaced in tests. */\nexport interface TokenSources {\n /** The stored login's token, as the core CLI resolves it. */\n readonly token: () => string | null;\n /** The same login narrowed to a rep, for the member API. */\n readonly repToken: () => Promise<string | null>;\n}\n\nconst defaultSources: TokenSources = {\n token: resolveAuthToken,\n repToken: resolveRepToken,\n};\n\n/**\n * The login a command acts with, or an error that says to sign in. With\n * `--as member` the token is the stored login's rep session, so an admin who\n * is also a rep builds their own sites with the login they already have;\n * otherwise it is the stored login's token as it is.\n */\nexport async function loadLogin(\n kind: LoginKind | null,\n sources: TokenSources = defaultSources,\n): Promise<Login> {\n const token = kind === \"member\" ? await sources.repToken() : sources.token();\n if (!token) {\n throw new MemberSiteCliError(\n \"Not logged in. Run `fluid login` first (or set FLUID_TOKEN).\",\n );\n }\n return { token, kind };\n}\n\nexport function createClients(token: string): Clients {\n const http = createHttpClient(token);\n return {\n member: createMemberSiteApi(http),\n company: createCompanySiteApi(http),\n };\n}\n\n/**\n * The client that builds a site of this owner kind. When the login's kind is\n * known and is the other one, this stops before any request and names the\n * login the site needs. When it is not known, the server decides, and a\n * refusal is reported with the login the site needs.\n */\nexport function clientForOwner(\n ownerKind: OwnerKind,\n login: Login,\n clients: Clients,\n): SiteApi {\n const needed = loginFor(ownerKind);\n if (login.kind !== null && login.kind !== needed) {\n throw new MemberSiteCliError(\n `A ${ownerKind} site needs ${describeLogin(needed)}, and this is ${describeLogin(login.kind)}. ` +\n (needed === \"admin\"\n ? \"Sign in as an admin with `fluid login`.\"\n : \"Use the member's own token (set FLUID_TOKEN).\"),\n );\n }\n return needed === \"member\" ? clients.member : clients.company;\n}\n\n/** What `list` shows, and which login it listed with. */\nexport interface SiteListing {\n as: LoginKind;\n sites: Site[];\n}\n\nfunction refused(error: unknown): boolean {\n return (\n error instanceof ApiError && (error.status === 401 || error.status === 403)\n );\n}\n\n/**\n * The sites this login can see. An admin lists every site in the company, a\n * member's own sites among them read-only. A member lists their own. A login\n * of unknown kind tries the company API first and falls back to the member\n * API when the company API refuses it, so an admin who is also a member lists\n * the company's sites.\n */\nexport async function listForLogin(\n login: Login,\n clients: Clients,\n): Promise<SiteListing> {\n if (login.kind === \"member\") {\n return { as: \"member\", sites: await clients.member.listSites() };\n }\n if (login.kind === \"admin\") {\n return { as: \"admin\", sites: await clients.company.listSites() };\n }\n try {\n return { as: \"admin\", sites: await clients.company.listSites() };\n } catch (error) {\n if (!refused(error)) throw error;\n }\n try {\n return { as: \"member\", sites: await clients.member.listSites() };\n } catch (error) {\n if (!refused(error)) throw error;\n throw new MemberSiteCliError(\n \"This login can list neither the company's sites nor a member's own. \" +\n \"Sign in as an admin with `fluid login`, or use a member's own token (set FLUID_TOKEN).\",\n );\n }\n}\n\nconst SITE_ID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** A site found by `resolveSite`, and the client that found it. */\nexport interface ResolvedSite {\n site: Site;\n api: SiteApi;\n}\n\n/** The clients a login of this kind may look a site up with, in order. */\nfunction lookupClients(login: Login, clients: Clients): SiteApi[] {\n if (login.kind === \"member\") return [clients.member];\n if (login.kind === \"admin\") return [clients.company];\n return [clients.company, clients.member];\n}\n\nasync function findSite(api: SiteApi, ref: string): Promise<Site | null> {\n if (SITE_ID_PATTERN.test(ref)) return api.getSite(ref);\n const matches = (await api.listSites()).filter((s) => s.handle === ref);\n if (matches.length > 1) {\n throw new MemberSiteCliError(\n `More than one site has the handle \"${ref}\". Use the site's id: ${matches.map((s) => s.id).join(\", \")}.`,\n );\n }\n return matches[0] ?? null;\n}\n\n/**\n * The site a handle or id names, with the client that found it. A login of\n * unknown kind tries the company API first and the member API when the\n * company API refuses it or does not know the site. When every client refuses\n * or answers 404, the last of those is thrown, so a 404 keeps its \"not enabled\n * or not yours\" wording. When any client answered and has no such site, that\n * is the answer instead.\n */\nexport async function resolveSite(\n ref: string,\n login: Login,\n clients: Clients,\n): Promise<ResolvedSite> {\n let failure: ApiError | null = null;\n let answered = false;\n for (const api of lookupClients(login, clients)) {\n try {\n const site = await findSite(api, ref);\n if (site) return { site, api };\n // A client that answered and has no such site is the last word.\n answered = true;\n } catch (error) {\n if (\n !(error instanceof ApiError) ||\n ![401, 403, 404].includes(error.status)\n ) {\n throw error;\n }\n failure = error;\n }\n }\n if (failure && !answered) throw failure;\n throw new MemberSiteCliError(\n `No site \"${ref}\" among the sites this login can see. List them with: fluid member-site list`,\n );\n}\n","import chalk from \"chalk\";\nimport { exitOnInputsError } from \"@fluid-app/fluid-cli\";\nimport { describeError, type ErrorContext } from \"../api/errors.js\";\n\n/**\n * Runs a command's work and reports a failure as a message, with no stack\n * trace and a non-zero exit code.\n */\nexport async function runCommand(\n work: () => Promise<void>,\n context: ErrorContext = {},\n): Promise<void> {\n try {\n await work();\n } catch (error) {\n exitOnInputsError(error);\n console.error(chalk.red(describeError(error, context)));\n process.exitCode = 1;\n }\n}\n","import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n clientForOwner,\n createClients,\n loadLogin,\n parseLoginKind,\n type Clients,\n type Login,\n} from \"../api/credential.js\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport type { OwnerKind, Site } from \"../api/types.js\";\nimport { runCommand } from \"./run.js\";\n\nconst HANDLE_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst HANDLE_MAX_LENGTH = 40;\n\n/** The site's name when `--name` is not given: its handle, in words. */\nexport function nameFromHandle(handle: string): string {\n return handle\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/** Refuses a handle the API would refuse, before any request. */\nexport function assertValidHandle(handle: string): void {\n if (handle.length > HANDLE_MAX_LENGTH || !HANDLE_PATTERN.test(handle)) {\n throw new MemberSiteCliError(\n `\"${handle}\" is not a valid handle. Use lowercase letters, digits and single hyphens, at most ${HANDLE_MAX_LENGTH} characters.`,\n );\n }\n}\n\nexport interface CreateOptions {\n company?: boolean;\n name?: string;\n as?: string;\n}\n\nexport interface CreateDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n}\n\nconst defaultDeps: CreateDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\nexport function ownerKindOf(options: CreateOptions): OwnerKind {\n return options.company ? \"company\" : \"member\";\n}\n\nexport async function runCreate(\n handle: string,\n options: CreateOptions,\n deps: CreateDeps = defaultDeps,\n): Promise<Site> {\n assertValidHandle(handle);\n const name = options.name?.trim() || nameFromHandle(handle);\n const ownerKind = ownerKindOf(options);\n\n const login = await deps.loadLogin(parseLoginKind(options.as));\n const api = clientForOwner(ownerKind, login, deps.createClients(login.token));\n const site = await api.createSite({ name, handle });\n\n deps.log(\n `Created ${site.owner_kind} site ${chalk.cyan(site.handle)} (${site.id}) as a ${site.status}.`,\n );\n return site;\n}\n\nexport function createCreateCommand(): Command {\n return new Command(\"create\")\n .description(\n \"Create a site: yours with a member login, or --company with an admin login\",\n )\n .argument(\n \"<handle>\",\n \"The site's handle: lowercase letters, digits and hyphens\",\n )\n .option(\"--company\", \"Create a company-owned site (needs an admin login)\")\n .option(\"--name <name>\", \"The site's name (default: the handle in words)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .action((handle: string, options: CreateOptions) =>\n runCommand(() => runCreate(handle, options).then(() => undefined), {\n ownerKind: ownerKindOf(options),\n }),\n );\n}\n","import { randomBytes, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\n\n/**\n * The assets a sandboxed preview page loads itself with CORS: a stylesheet\n * and a script (module scripts are fetched with CORS). A font is not one,\n * because the preview serves no font file. A `fetch` has the destination\n * `empty`, and is refused.\n */\nconst ASSET_DESTINATIONS = new Set([\"script\", \"style\"]);\n\n/** The local path a preview serves a site's text assets from. */\nexport const ASSET_ROUTE_PREFIX = \"/files/assets/\";\n\n/** A session's token is this many random bytes, written as hex. */\nconst PREVIEW_TOKEN_BYTES = 16;\n\n/**\n * A secret of this preview session, which the preview puts in the asset paths\n * of the pages it serves (`/files/assets/<token>/<file>`). It is never the\n * CLI's credential and it authorizes nothing at the API: it only tells the\n * preview's own page from any other page, which cannot know it.\n */\nexport function createPreviewToken(): string {\n return randomBytes(PREVIEW_TOKEN_BYTES).toString(\"hex\");\n}\n\n/** Whether `candidate` is the session's token, compared in constant time. */\nexport function isPreviewToken(\n candidate: string | undefined,\n previewToken: string,\n): boolean {\n if (candidate === undefined) return false;\n const given = Buffer.from(candidate);\n const expected = Buffer.from(previewToken);\n return given.length === expected.length && timingSafeEqual(given, expected);\n}\n\nfunction header(req: IncomingMessage, name: string): string | undefined {\n const value = req.headers[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\n/**\n * Whether a path is `/files/assets/<token>/<file>` with this session's token,\n * the path the preview gave its own page.\n */\nexport function hasPreviewTokenPath(\n path: string,\n previewToken: string,\n): boolean {\n if (!path.startsWith(ASSET_ROUTE_PREFIX)) return false;\n return isPreviewToken(\n path.slice(ASSET_ROUTE_PREFIX.length).split(\"/\")[0],\n previewToken,\n );\n}\n\n/**\n * Whether a request comes from the preview's own origin or from the browser's\n * address bar: `Sec-Fetch-Site` says `same-origin` or `none`. A request\n * without the header is not one, because a browser that does not send it\n * cannot be told from a page of another site, and the answer is a draft's\n * asset, which a page of another site could read by loading it as a\n * stylesheet or script without CORS. A sandboxed preview page has an opaque\n * origin, so it is never one: it loads its assets by the path with the token.\n */\nexport function isFirstPartyRequest(req: IncomingMessage): boolean {\n const site = header(req, \"sec-fetch-site\");\n return site === \"same-origin\" || site === \"none\";\n}\n\n/**\n * Whether a request is a CORS request for an asset from the preview's own\n * page: a document with an opaque origin (the sandbox policy it is served\n * with gives it an `Origin: null`) that loads an asset from the path the\n * preview gave it, `/files/assets/<token>/<file>`.\n *\n * `Origin: null` is also what any other sandboxed page sends, so the origin\n * proves nothing, and the token is what tells the preview's page from another\n * one: only a request for a path with this session's token is one. Only\n * `GET` or `HEAD` of such a path, asked for as a script or a stylesheet, is.\n * A `fetch` has the destination `empty`.\n */\nexport function isSandboxedAssetRequest(\n req: IncomingMessage,\n previewToken: string,\n): boolean {\n if (header(req, \"origin\") !== \"null\") return false;\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return false;\n const dest = header(req, \"sec-fetch-dest\");\n if (dest === undefined || !ASSET_DESTINATIONS.has(dest)) return false;\n const path = (req.url ?? \"\").split(/[?#]/)[0] ?? \"\";\n return hasPreviewTokenPath(path, previewToken);\n}\n\n/**\n * Deny DNS rebinding and cross-origin access before using Node's CLI\n * credential. It is the guard of `fluid theme dev` (`isLocalPreviewRequest`)\n * with one addition, because a preview is served with the sandbox policy of a\n * member site frame (no `allow-same-origin`).\n *\n * That policy gives the page an opaque origin, and a request from an opaque\n * origin is `Sec-Fetch-Site: cross-site` whatever its target. So the page's own\n * stylesheet, script and link clicks look cross-site to the server. They are\n * let through by what they are:\n *\n * - the Host is one the server listens on (a rebound DNS name is not);\n * - an `Origin`, when sent, is the server's own, or `null` for an asset that\n * the page loads by the path with the session's token\n * (`isSandboxedAssetRequest`), never another site and never another\n * sandboxed page;\n * - `Sec-Fetch-Site: cross-site` is refused for a request with CORS (it could\n * be read) and let through for a navigation or a request without CORS,\n * whose answer the requester cannot read. The page's `frame-ancestors`\n * policy keeps another site from framing a draft.\n */\nexport function isLocalPreviewRequest(\n req: IncomingMessage,\n host: string,\n previewToken: string,\n): boolean {\n const port = req.socket.localPort;\n const hosts = new Set([host, \"localhost\", \"127.0.0.1\", \"[::1]\"]);\n if ((host === \"0.0.0.0\" || host === \"::\") && req.socket.localAddress) {\n const address = req.socket.localAddress.replace(/^::ffff:/, \"\");\n hosts.add(address.includes(\":\") ? `[${address}]` : address);\n }\n let url: URL;\n try {\n url = new URL(`http://${req.headers.host}`);\n } catch {\n return false;\n }\n if (\n !hosts.has(url.hostname) ||\n Number(url.port || 80) !== port ||\n url.username ||\n url.password ||\n url.pathname !== \"/\"\n ) {\n return false;\n }\n\n const origin = header(req, \"origin\");\n if (origin !== undefined && origin !== url.origin) {\n if (!isSandboxedAssetRequest(req, previewToken)) return false;\n }\n\n if (header(req, \"sec-fetch-site\") !== \"cross-site\") return true;\n if (isSandboxedAssetRequest(req, previewToken)) return true;\n // Without the header a request is taken to have CORS, and so is refused.\n const mode = header(req, \"sec-fetch-mode\");\n return origin === undefined && (mode === \"navigate\" || mode === \"no-cors\");\n}\n","import net from \"node:net\";\n\n/** The port a dev server was asked for is already taken. */\nexport class PortInUseError extends Error {\n constructor(\n public readonly host: string,\n public readonly port: number,\n ) {\n super(\n `Port ${port} on ${host} is already in use. Stop the other server or pass --port <number>.`,\n );\n this.name = \"PortInUseError\";\n }\n}\n\n/**\n * Binds `host:port` and releases it at once. Resolves when the port is free\n * and rejects with `PortInUseError` on `EADDRINUSE` or `EACCES`, so a command\n * fails before it saves anything instead of with a stack trace at `listen`.\n */\nexport function checkPortAvailable(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n reject(\n err.code === \"EADDRINUSE\" || err.code === \"EACCES\"\n ? new PortInUseError(host, port)\n : err,\n );\n });\n server.once(\"listening\", () => {\n server.close(() => resolve());\n });\n server.listen(port, host);\n });\n}\n","import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport { siteCollectionPath } from \"../api/site-api.js\";\nimport type { OwnerKind } from \"../api/types.js\";\nimport {\n ASSET_ROUTE_PREFIX,\n createPreviewToken,\n hasPreviewTokenPath,\n isFirstPartyRequest,\n isPreviewToken,\n isSandboxedAssetRequest,\n} from \"./local-guard.js\";\n\n/** The site a preview shows, from the folder's binding. */\nexport interface PreviewTarget {\n siteId: string;\n handle: string;\n ownerKind: OwnerKind;\n}\n\n/** What one local path asks of the preview route. */\nexport type PreviewRoute =\n | { kind: \"page\"; slug: string | null }\n | { kind: \"file\"; path: string };\n\n/**\n * The policy every frame response carries: the sandbox gives a document an\n * opaque origin, and never the same origin. A preview keeps it. It is used\n * for an answer of the proxy's own, and the server's own policy replaces it\n * when the server sent one.\n */\nexport const PREVIEW_SANDBOX_POLICY =\n \"frame-ancestors 'self'; sandbox allow-scripts allow-forms\";\n\nconst SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nconst ASSET_NAME = /^[a-z0-9_-]+\\.(css|js|json|txt|html)$/;\n\nconst UPSTREAM_TIMEOUT_MS = 30_000;\n\nfunction decode(segment: string): string | null {\n try {\n return decodeURIComponent(segment);\n } catch {\n return null;\n }\n}\n\n/**\n * The preview route a local path asks for, or null when it names none.\n *\n * - `/` is the home page, `/<slug>` another page, and\n * `/files/assets/<token>/<file>` a text asset, the path a preview rewrites\n * its links to. The token is the session's (`previewToken`), and a path\n * with another names nothing. `/files/assets/<file>` is the same asset,\n * for a request that is not read with CORS.\n * - A page or asset linked at its published address is taken too:\n * `/accounts/_frame/<site id>/assets/<file>` (a link in a stylesheet or\n * script, which the rewrite of a page does not reach) and\n * `/accounts/<handle>(/<slug>)`.\n */\nexport function routeOf(\n pathname: string,\n target: PreviewTarget,\n previewToken?: string,\n): PreviewRoute | null {\n const segments = pathname.split(\"/\").slice(1);\n if (segments.at(-1) === \"\" && segments.length > 1) segments.pop();\n const parts = segments.map(decode);\n if (parts.some((part) => part === null)) return null;\n const [first, second, third, fourth] = parts as string[];\n\n const page = (slug: string | undefined): PreviewRoute | null => {\n if (slug === undefined || slug === \"\") return { kind: \"page\", slug: null };\n return SLUG.test(slug) ? { kind: \"page\", slug } : null;\n };\n const asset = (name: string | undefined): PreviewRoute | null =>\n name !== undefined && ASSET_NAME.test(name)\n ? { kind: \"file\", path: `assets/${name}` }\n : null;\n\n if (parts.length === 1) return page(first);\n if (first === \"files\" && second === \"assets\" && parts.length === 3) {\n return asset(third);\n }\n if (first === \"files\" && second === \"assets\" && parts.length === 4) {\n return previewToken !== undefined && isPreviewToken(third, previewToken)\n ? asset(fourth)\n : null;\n }\n if (first === \"accounts\") {\n if (second === \"_frame\") {\n return third === target.siteId &&\n fourth === \"assets\" &&\n parts.length === 5\n ? asset(parts[4] ?? undefined)\n : null;\n }\n if (second === target.handle && parts.length <= 3) return page(third);\n }\n return null;\n}\n\n/**\n * Points the links of a rendered page at the preview. A page links a site's\n * assets at the published frame (`/accounts/_frame/<site id>/assets/<file>`),\n * which serves a published site only, and its pages at the site's shell\n * address (`/accounts/<handle>(/<slug>)`). Neither opens a draft, so the first\n * becomes `/files/assets/<file>`, read through the preview route, and the\n * second `/` or `/<slug>`, which the preview serves. The asset path carries the\n * session's token, which lets the page read its assets with CORS and no other\n * page.\n */\nexport function rewritePreviewHtml(\n html: string,\n target: PreviewTarget,\n previewToken: string,\n): string {\n const handle = target.handle.replace(/[^a-z0-9-]/g, \"\");\n const shell = new RegExp(\n `(?<![A-Za-z0-9_.:/-])/accounts/${handle}(?![A-Za-z0-9_-])`,\n \"g\",\n );\n return html\n .split(`/accounts/_frame/${target.siteId}/assets/`)\n .join(`${ASSET_ROUTE_PREFIX}${previewToken}/`)\n .replace(shell, (match, offset: number, whole: string) =>\n whole[offset + match.length] === \"/\" ? \"\" : \"/\",\n );\n}\n\n/**\n * Points the links of a stylesheet or script at the preview's asset path, as\n * `rewritePreviewHtml` does for a page: an asset reached by its published\n * address from a sandboxed page is a request from another origin, which only\n * the path with the session's token serves.\n */\nexport function rewritePreviewAsset(\n source: string,\n target: PreviewTarget,\n previewToken: string,\n): string {\n return source\n .split(`/accounts/_frame/${target.siteId}/assets/`)\n .join(`${ASSET_ROUTE_PREFIX}${previewToken}/`);\n}\n\nconst FETCH_DIRECTIVES = new Set([\n \"script-src\",\n \"style-src\",\n \"img-src\",\n \"font-src\",\n \"media-src\",\n]);\n\n/**\n * Adds the preview's own origin to the sources of code, styles, images, fonts\n * and media in the server's policy. The server renders a draft for no origin\n * in particular and names only Fluid's site SDK, so a page shown from here\n * would otherwise have its assets, which this proxy serves, blocked. Every\n * other directive, the closed network included, is kept as the server sent it.\n */\nexport function allowPreviewOrigin(policy: string, origin: string): string {\n return policy\n .split(\";\")\n .map((directive) => {\n const name = directive.trim().split(/\\s+/)[0] ?? \"\";\n return FETCH_DIRECTIVES.has(name)\n ? `${directive.trimEnd()} ${origin}`\n : directive;\n })\n .join(\";\");\n}\n\n/** What the server's answer to a probe says about the preview route. */\nexport type ProbeResult = \"available\" | \"unavailable\";\n\nexport interface PreviewProxyOptions {\n target: PreviewTarget;\n /** The API base the login is for, with or without a trailing slash. */\n apiBase: string;\n /** The CLI's bearer, read when a request is made and sent to the API only. */\n getToken: () => string | Promise<string>;\n}\n\n/**\n * Serves a draft preview by asking the preview route of the member or company\n * API with the CLI's bearer, from this process only. The browser sends no\n * credential and is sent none: no token is in a URL, a header or a page that\n * the proxy answers, and nothing the browser sent goes to the API.\n */\nexport class PreviewProxy {\n private readonly previewUrl: string;\n /** This session's secret, which only the pages the proxy serves hold. */\n readonly previewToken = createPreviewToken();\n\n constructor(private readonly options: PreviewProxyOptions) {\n const { target, apiBase } = options;\n this.previewUrl = `${apiBase.replace(/\\/+$/, \"\")}${siteCollectionPath(\n target.ownerKind,\n )}/${encodeURIComponent(target.siteId)}/preview`;\n }\n\n private async fetchUpstream(route: PreviewRoute): Promise<Response> {\n const path =\n route.kind === \"page\"\n ? `/pages${route.slug === null ? \"\" : `/${encodeURIComponent(route.slug)}`}`\n : `/files/${route.path.split(\"/\").map(encodeURIComponent).join(\"/\")}`;\n return fetch(`${this.previewUrl}${path}`, {\n method: \"GET\",\n headers: {\n authorization: `Bearer ${await this.options.getToken()}`,\n accept: route.kind === \"page\" ? \"text/html\" : \"*/*\",\n \"accept-encoding\": \"identity\",\n \"user-agent\": \"Fluid CLI\",\n },\n // A redirect would carry the bearer to wherever it points.\n redirect: \"manual\",\n signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),\n });\n }\n\n /**\n * Whether the server has the preview route. A route that is there answers\n * every request, a refusal included, with the frame's sandbox policy and\n * `X-Robots-Tag: noindex`, and one that is not answers without them, so the\n * two are told apart by those headers and not by a 404, which a missing page\n * also is. Throws on a login the server did not accept.\n */\n async probe(): Promise<ProbeResult> {\n const response = await this.fetchUpstream({ kind: \"page\", slug: null });\n await response.body?.cancel();\n if (response.status === 401 || response.status === 403) {\n throw new PreviewAuthError(response.status);\n }\n const present =\n /(^|[\\s;])sandbox(\\s|;|$)/.test(\n response.headers.get(\"content-security-policy\") ?? \"\",\n ) && response.headers.get(\"x-robots-tag\")?.includes(\"noindex\") === true;\n return present ? \"available\" : \"unavailable\";\n }\n\n /**\n * Answers a request for a preview path: the page or asset the route names,\n * or a plain-text refusal. Every answer is `private, no-store`, sends no\n * referrer, and keeps the sandbox policy.\n */\n async handle(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const { target } = this.options;\n const send = (\n status: number,\n type: string,\n body: string | Buffer,\n policy = PREVIEW_SANDBOX_POLICY,\n extra: Record<string, string> = {},\n ) => {\n res.writeHead(status, {\n \"content-type\": type,\n \"content-security-policy\": policy,\n \"cache-control\": \"private, no-store\",\n \"referrer-policy\": \"no-referrer\",\n \"x-content-type-options\": \"nosniff\",\n \"x-robots-tag\": \"noindex\",\n \"content-length\": String(Buffer.byteLength(body)),\n ...extra,\n });\n res.end(req.method === \"HEAD\" ? undefined : body);\n };\n const text = (\n status: number,\n message: string,\n extra: Record<string, string> = {},\n ) => send(status, \"text/plain; charset=utf-8\", message, undefined, extra);\n\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n return text(405, \"The draft preview is read-only.\", {\n allow: \"GET, HEAD\",\n });\n }\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n const route = routeOf(url.pathname, target, this.previewToken);\n if (!route) return text(404, \"No such page or file in this preview.\");\n // An asset path without the session's token is open to a page of another\n // site, which loads it as a stylesheet or script with no CORS and reads\n // what it applies. It is served only to the preview's own origin or to\n // the address bar, and the CLI's bearer is not used for any other.\n if (\n route.kind === \"file\" &&\n !hasPreviewTokenPath(url.pathname, this.previewToken) &&\n !isFirstPartyRequest(req)\n ) {\n return text(403, \"An asset is served only by the path of its preview.\");\n }\n\n let upstream: Response;\n try {\n upstream = await this.fetchUpstream(route);\n } catch {\n return text(\n 502,\n \"The draft preview could not reach the Fluid API. Check your network and try again.\",\n );\n }\n if (upstream.status !== 200) {\n await upstream.body?.cancel();\n // The server's own bodies are never echoed.\n if (upstream.status === 404) {\n return text(\n 404,\n route.kind === \"page\"\n ? \"This site has no draft of that page.\"\n : \"This site has no such text asset.\",\n );\n }\n if (upstream.status === 401 || upstream.status === 403) {\n return text(\n upstream.status,\n \"The draft preview was not authorized. Check your CLI login: a member's own for a member's site, an admin's for a company site.\",\n );\n }\n return text(502, \"The draft preview could not be rendered.\");\n }\n\n const type =\n upstream.headers.get(\"content-type\") ?? \"text/plain; charset=utf-8\";\n const upstreamPolicy = upstream.headers.get(\"content-security-policy\");\n const policy = upstreamPolicy\n ? allowPreviewOrigin(upstreamPolicy, url.origin)\n : PREVIEW_SANDBOX_POLICY;\n const raw = Buffer.from(await upstream.arrayBuffer());\n const rewrite = type.startsWith(\"text/html\")\n ? rewritePreviewHtml\n : /^(text\\/css|(text|application)\\/(x-)?javascript)\\b/.test(type)\n ? rewritePreviewAsset\n : undefined;\n const body = rewrite\n ? Buffer.from(rewrite(raw.toString(\"utf8\"), target, this.previewToken))\n : raw;\n // The page that loads an asset from its opaque origin reads the answer\n // with CORS, so it is let read it, by the path that carries the session's\n // token. No other origin is, and no other sandboxed page.\n send(\n 200,\n type,\n body,\n policy,\n isSandboxedAssetRequest(req, this.previewToken)\n ? { \"access-control-allow-origin\": \"null\", vary: \"Origin\" }\n : {},\n );\n }\n}\n\n/** The server did not accept the login that asked for a preview. */\nexport class PreviewAuthError extends MemberSiteCliError {\n constructor(public readonly status: number) {\n super(\n `The login was not accepted for the draft preview (${status}). Run \\`fluid login\\` again, or check FLUID_TOKEN.`,\n );\n this.name = \"PreviewAuthError\";\n }\n}\n","import path from \"node:path\";\n\nimport chokidar from \"chokidar\";\n\n/**\n * Calls `onChange` when a file in a site folder is added, changed or removed.\n * The binding file and other dotfiles are ignored, so the push that rewrites\n * the binding after each write does not trigger another one. The event says\n * nothing about what changed: a push compares the folder with the binding.\n * Resolves once the folder is being watched, with a function that stops\n * watching: a file that changes after that is reported, so a caller that reads\n * the folder next misses no edit. Rejects, with the watcher closed, when it\n * fails before it is ready, because a folder that is not watched would not be\n * saved, and an error after that is reported to `onError`.\n */\nexport async function watchFolder(\n dir: string,\n onChange: () => void,\n onError: (error: unknown) => void = () => undefined,\n): Promise<() => Promise<void>> {\n const watcher = chokidar.watch(dir, {\n ignoreInitial: true,\n // Folder names are matched whole and inside the site folder only, so an\n // asset named `node_modules.css`, or a site folder under a dot directory,\n // is still watched.\n ignored: (filePath: string) => {\n const parts = path.relative(dir, filePath).split(/[\\\\/]/);\n if (parts.includes(\"node_modules\")) return true;\n return (parts.at(-1) ?? \"\").startsWith(\".\");\n },\n persistent: true,\n awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 10 },\n });\n watcher.on(\"add\", onChange);\n watcher.on(\"change\", onChange);\n watcher.on(\"unlink\", onChange);\n let ready = false;\n watcher.on(\"error\", (error) => {\n if (ready) onError(error);\n });\n try {\n await new Promise<void>((resolve, reject) => {\n watcher.once(\"ready\", resolve);\n // A watcher that fails to start never says it is ready.\n watcher.once(\"error\", reject);\n });\n } catch (error) {\n await watcher.close().catch(() => undefined);\n throw error;\n }\n ready = true;\n return () => watcher.close();\n}\n","import { createHash } from \"node:crypto\";\nimport type {\n OwnerKind,\n Site,\n SiteFile,\n SiteFileEntry,\n SitePage,\n} from \"../api/types.js\";\n\n/**\n * The binding file ties a pulled folder to its site. It is local state, never\n * a site file, and no API path starts with a dot.\n */\nexport const BINDING_FILE = \".member-site.json\";\n\n/** The page metadata file. It is not a file of the site's file list. */\nexport const PAGES_CONFIG_PATH = \"config/pages.json\";\n\n/** What the API reported for one file at the pull, and what the pull wrote. */\nexport interface BoundFile {\n /** The row version, which only `config/settings.json` has today. */\n lock_version: number | null;\n updated_at: string | null;\n /**\n * The version the file list reports, to send back on a write or a delete.\n * A binding from a pull before the API reported one has none.\n */\n version?: string;\n /** The SHA-256 of the content written, to tell a local edit from the pulled copy. */\n sha256: string;\n}\n\n/** What a page row holds besides its content, as `config/pages.json` names it. */\nexport interface PageRow {\n title: string;\n position: number;\n published: boolean;\n layout: string | null;\n menu: string | null;\n}\n\nexport interface BoundPage {\n id: string;\n lock_version: number;\n /**\n * The row as the server last had it, so a push can tell which fields a\n * local edit changed. A binding from an earlier pull has none.\n */\n row?: PageRow;\n}\n\nexport interface SiteBinding {\n site_id: string;\n handle: string;\n owner_kind: OwnerKind;\n api_base: string;\n settings: { lock_version: number | null };\n /** The site's default layout, as `config/pages.json` names it. */\n layout?: string | null;\n /** Keyed by slug, the home page as \"/\". */\n pages: Record<string, BoundPage>;\n files: Record<string, BoundFile>;\n /** The SHA-256 of the `config/pages.json` written. */\n pages_config_sha256: string;\n}\n\n/** A page's row as `config/pages.json` holds it. */\nexport function pageRow(page: SitePage): PageRow {\n return {\n title: page.title,\n position: page.position,\n published: page.published,\n layout: page.layout_name ?? null,\n menu: page.menu ?? null,\n };\n}\n\nexport function sha256(content: string): string {\n return createHash(\"sha256\").update(content, \"utf8\").digest(\"hex\");\n}\n\n/** One JSON document as it is written to disk: stable, indented, one newline at the end. */\nexport function serializeJson(value: unknown): string {\n return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\n/** A file's entry in the list and the content read for it. */\nexport interface PulledFile {\n entry: SiteFileEntry;\n file: SiteFile;\n}\n\nexport interface BindingInput {\n site: Site;\n apiBase: string;\n files: PulledFile[];\n pages: SitePage[];\n pagesConfig: string;\n}\n\n/**\n * The binding for a pull. Each version is what the API gave, null kept as\n * null. A file's own read is newer than the list, so its `lock_version` and\n * `version` win.\n */\nexport function buildBinding(input: BindingInput): SiteBinding {\n const files: Record<string, BoundFile> = {};\n for (const { entry, file } of [...input.files].sort((a, b) =>\n a.entry.path < b.entry.path ? -1 : 1,\n )) {\n const version = file.version ?? entry.version;\n files[entry.path] = {\n lock_version: file.lock_version ?? entry.lock_version ?? null,\n updated_at: entry.updated_at ?? null,\n ...(typeof version === \"string\" ? { version } : {}),\n sha256: sha256(file.content),\n };\n }\n\n const pages: Record<string, BoundPage> = {};\n for (const page of [...input.pages].sort((a, b) =>\n a.slug < b.slug ? -1 : 1,\n )) {\n pages[page.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n }\n\n return {\n site_id: input.site.id,\n handle: input.site.handle,\n owner_kind: input.site.owner_kind,\n api_base: input.apiBase,\n settings: {\n lock_version: files[\"config/settings.json\"]?.lock_version ?? null,\n },\n layout: input.site.layout_name ?? null,\n pages,\n files,\n pages_config_sha256: sha256(input.pagesConfig),\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The binding in a file's text, or null when it is not one. */\nexport function parseBinding(text: string): SiteBinding | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return null;\n }\n if (!isRecord(parsed)) return null;\n const { site_id, handle, owner_kind, files, pages } = parsed;\n if (\n typeof site_id !== \"string\" ||\n typeof handle !== \"string\" ||\n (owner_kind !== \"member\" && owner_kind !== \"company\") ||\n !isRecord(files) ||\n !isRecord(pages)\n ) {\n return null;\n }\n return parsed as unknown as SiteBinding;\n}\n","import { randomBytes } from \"node:crypto\";\nimport {\n lstat,\n mkdir,\n readdir,\n readFile,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\";\nimport {\n withInternalReadsAsync,\n type CommandInputs,\n} from \"@fluid-app/fluid-cli\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport { BINDING_FILE, parseBinding, type SiteBinding } from \"./binding.js\";\n\n/**\n * The absolute file for an API path, which must stay inside the folder. The\n * paths come from the server, so a path that climbs out of the folder is\n * refused rather than written.\n */\nexport function localPath(root: string, apiPath: string): string {\n const segments = apiPath.split(\"/\");\n const unsafe =\n apiPath.length === 0 ||\n apiPath.includes(\"\\\\\") ||\n apiPath.includes(\"\\0\") ||\n segments.some((s) => s === \"\" || s === \".\" || s === \"..\");\n const target = resolve(root, apiPath);\n if (unsafe || !target.startsWith(root + sep)) throw unwritable(apiPath);\n return target;\n}\n\n/**\n * The file for a path the server listed. The binding file is the CLI's own,\n * so a listed file of that name is refused as well.\n */\nexport function listedPath(root: string, apiPath: string): string {\n if (apiPath === BINDING_FILE) throw unwritable(apiPath);\n return localPath(root, apiPath);\n}\n\nfunction unwritable(apiPath: string): MemberSiteCliError {\n return new MemberSiteCliError(\n `The server listed \"${apiPath}\", which cannot be written inside the site's folder.`,\n );\n}\n\nexport interface FolderState {\n /** The folder holds at least one entry. */\n nonEmpty: boolean;\n /** The binding file, when the folder has a readable one. */\n binding: SiteBinding | null;\n}\n\n/** What is in the folder now. A missing folder is empty. */\nexport async function inspectFolder(root: string): Promise<FolderState> {\n let names: string[];\n try {\n names = await readdir(root);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { nonEmpty: false, binding: null };\n }\n if ((error as NodeJS.ErrnoException).code === \"ENOTDIR\") {\n throw new MemberSiteCliError(`${root} is a file, not a folder.`);\n }\n throw error;\n }\n return { nonEmpty: names.length > 0, binding: await readBinding(root) };\n}\n\n/** The binding in a folder, or null when there is none or it is unreadable. */\nexport async function readBinding(\n root: string,\n inputs?: CommandInputs,\n): Promise<SiteBinding | null> {\n const text = inputs\n ? await readLocalVia(inputs, root, BINDING_FILE)\n : await readLocal(root, BINDING_FILE);\n return text === null ? null : parseBinding(text);\n}\n\n/**\n * readLocal with the bytes read through the declared-inputs reader. The\n * symbolic-link and regular-file checks stay, as a fail-safe on the folder\n * itself.\n */\nasync function readLocalVia(\n inputs: CommandInputs,\n root: string,\n apiPath: string,\n): Promise<string | null> {\n const file = await withInternalReadsAsync(async () => {\n const target = await checkedPath(root, apiPath);\n try {\n return (await lstat(target)).isFile() ? target : null;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n });\n return file === null ? null : inputs.readFile(file, \"utf8\");\n}\n\n/**\n * The file for an API path, after a check that no part of its path under the\n * folder is a symbolic link. A write follows a link, so a link in a folder\n * could make a pull read or replace a file outside it. The folder itself may\n * be a link, as it is the one the person chose.\n */\nexport async function checkedPath(\n root: string,\n apiPath: string,\n): Promise<string> {\n const target = localPath(root, apiPath);\n let current = root;\n for (const part of relative(root, target).split(sep)) {\n current = join(current, part);\n let info;\n try {\n info = await lstat(current);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === \"ENOENT\") break;\n if (code === \"ENOTDIR\") throw unwritable(apiPath);\n throw error;\n }\n if (info.isSymbolicLink()) {\n throw new MemberSiteCliError(\n `${apiPath} is, or is inside, a symbolic link, and a pull does not write through one. Remove the link, or pull into another folder.`,\n );\n }\n }\n return target;\n}\n\n/** A file's text, or null when it does not exist or is not a regular file. */\nexport async function readLocal(\n root: string,\n apiPath: string,\n): Promise<string | null> {\n const file = await checkedPath(root, apiPath);\n try {\n if (!(await lstat(file)).isFile()) return null;\n return await readFile(file, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n}\n\n/**\n * Removes a file of the folder. The path is checked first, so a link in it is\n * refused, and a file that is already gone is not an error.\n */\nexport async function removeLocal(\n root: string,\n apiPath: string,\n): Promise<void> {\n const file = await withInternalReadsAsync(() => checkedPath(root, apiPath));\n await rm(file, { force: true });\n}\n\n/**\n * Writes a file through a new file in the same folder that replaces it, so\n * the write never opens an existing path and a link planted after the check\n * is replaced, not followed.\n */\nexport async function writeLocal(\n root: string,\n apiPath: string,\n content: string,\n): Promise<void> {\n const file = await withInternalReadsAsync(() => checkedPath(root, apiPath));\n await mkdir(dirname(file), { recursive: true });\n // A folder made just now is checked again, as a link could stand in its place.\n await withInternalReadsAsync(() => checkedPath(root, apiPath));\n const temp = join(\n dirname(file),\n `.${basename(file)}.${randomBytes(6).toString(\"hex\")}.tmp`,\n );\n try {\n await writeFile(temp, content, { encoding: \"utf8\", flag: \"wx\" });\n await rename(temp, file);\n } catch (error) {\n await rm(temp, { force: true });\n throw error;\n }\n}\n","import { resolve } from \"node:path\";\nimport type { CommandInputs } from \"@fluid-app/fluid-cli\";\nimport {\n clientForOwner,\n parseLoginKind,\n type Clients,\n type Login,\n} from \"../api/credential.js\";\nimport { MemberSiteCliError, type ErrorContext } from \"../api/errors.js\";\nimport { getApiBase } from \"../api/http.js\";\nimport type { SiteApi } from \"../api/types.js\";\nimport { BINDING_FILE, type SiteBinding } from \"../tree/binding.js\";\nimport { readBinding } from \"../tree/folder.js\";\n\n/** What a command that acts on a pulled folder needs from outside. */\nexport interface BoundSiteDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n /** The declared-inputs reader, when the command declared its inputs. */\n inputs?: CommandInputs;\n}\n\n/** A pulled folder, its binding, and the client that builds its site. */\nexport interface BoundSite {\n dir: string;\n /** A copy: a command changes it and saves it. */\n binding: SiteBinding;\n api: SiteApi;\n}\n\n/**\n * Opens a pulled folder for a command that sends requests. The binding names\n * the owner kind, which needs one kind of login, so another is refused here,\n * and so is a folder pulled from another API. No request is made.\n */\nexport async function openBoundSite(\n dirArg: string | undefined,\n as: string | undefined,\n deps: BoundSiteDeps,\n context: ErrorContext,\n): Promise<BoundSite> {\n const dir = resolve(dirArg ?? \".\");\n const stored = await readBinding(dir, deps.inputs);\n if (!stored) {\n throw new MemberSiteCliError(\n `${dir} is not a pulled site: it has no readable ${BINDING_FILE}. Pull the site first: fluid member-site pull <site> [dir]`,\n );\n }\n const binding = structuredClone(stored);\n context.ownerKind = binding.owner_kind;\n\n const login = await deps.loadLogin(parseLoginKind(as));\n const api = clientForOwner(\n binding.owner_kind,\n login,\n deps.createClients(login.token),\n );\n if (binding.api_base !== getApiBase()) {\n throw new MemberSiteCliError(\n `${dir} was pulled from ${binding.api_base}, and this login is for ${getApiBase()}. Use the login it was pulled with, or pull the site again.`,\n );\n }\n return { dir, binding, api };\n}\n","import { lstat, readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { withInternalReadsAsync, type InputTree } from \"@fluid-app/fluid-cli\";\nimport { sha256, type SiteBinding } from \"./binding.js\";\n\n/** Folders whose files are Liquid templates. */\nconst TEMPLATE_FOLDERS = {\n layouts: \"layout\",\n sections: \"section\",\n snippets: \"snippet\",\n} as const;\n\n/** The extensions of a text asset, the ones the file API accepts. */\nconst TEXT_ASSET_PATTERN = /^[a-z0-9_-]+\\.(css|js|json|txt|html)$/;\nconst NAME_PATTERN = /^[a-z0-9_-]+$/;\nconst PAGE_SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\nexport const SETTINGS_PATH = \"config/settings.json\";\nexport const MENUS_PATH = \"config/menus.json\";\nexport const ACCESS_PATH = \"config/access.json\";\n\n/** What a path in the folder is, or why it cannot be one of the site's. */\nexport type PathClass =\n | { kind: \"layout\" | \"section\" | \"snippet\" | \"asset\" }\n | { kind: \"page\"; slug: string }\n | { kind: \"settings\" | \"menus\" | \"pages-config\" | \"access\" }\n | { kind: \"refused\"; reason: string };\n\nconst NOT_TEXT_ASSET = (path: string) =>\n `${path} is not a text asset. A site holds css, js, json, txt and html files; use images and fonts from the media library by URL.`;\n\nfunction refused(reason: string): PathClass {\n return { kind: \"refused\", reason };\n}\n\n/**\n * Classifies a path under the folder by the file API's rules: the folders and\n * config files a site has, names of lowercase letters, digits, hyphens and\n * underscores, and no subfolders.\n */\nexport function classifyPath(path: string): PathClass {\n const segments = path.split(\"/\");\n const [folder, name] = segments;\n if (segments.length === 1 || folder === undefined || name === undefined) {\n return refused(\n `${path} is outside the folders a site holds: layouts, sections, snippets, pages, assets and config.`,\n );\n }\n if (folder === \"config\") {\n if (segments.length > 2) {\n return refused(`${path} is in a subfolder of config.`);\n }\n if (name === \"settings.json\") return { kind: \"settings\" };\n if (name === \"menus.json\") return { kind: \"menus\" };\n if (name === \"pages.json\") return { kind: \"pages-config\" };\n if (name === \"access.json\") return { kind: \"access\" };\n return refused(\n `${path} is not a site file. config/ holds settings.json, menus.json, pages.json and access.json.`,\n );\n }\n if (folder === \"assets\") {\n if (segments.length > 2) return refused(`${path} is in a subfolder.`);\n if (TEXT_ASSET_PATTERN.test(name)) return { kind: \"asset\" };\n return refused(\n /\\.(css|js|json|txt|html)$/.test(name)\n ? `${path}: names use lowercase letters, digits, hyphens and underscores.`\n : NOT_TEXT_ASSET(path),\n );\n }\n const templateKind = (TEMPLATE_FOLDERS as Record<string, string>)[folder];\n if (templateKind === undefined && folder !== \"pages\") {\n return refused(\n `${path} is outside the folders a site holds: layouts, sections, snippets, pages, assets and config.`,\n );\n }\n if (segments.length > 2) return refused(`${path} is in a subfolder.`);\n if (!name.endsWith(\".liquid\")) return refused(`${path} must end in .liquid.`);\n const stem = name.slice(0, -\".liquid\".length);\n if (folder === \"pages\") {\n if (stem === \"index\") return { kind: \"page\", slug: \"/\" };\n if (PAGE_SLUG_PATTERN.test(stem)) return { kind: \"page\", slug: stem };\n return refused(\n `${path}: a page's name uses lowercase letters and digits separated by single hyphens.`,\n );\n }\n if (!NAME_PATTERN.test(stem)) {\n return refused(\n `${path}: names use lowercase letters, digits, hyphens and underscores.`,\n );\n }\n return { kind: templateKind as \"layout\" | \"section\" | \"snippet\" };\n}\n\n/** The text of a file, or null when the bytes are not valid UTF-8 text. */\nfunction decodeText(bytes: Buffer): string | null {\n if (bytes.includes(0)) return null;\n const text = bytes.toString(\"utf8\");\n return Buffer.from(text, \"utf8\").equals(bytes) ? text : null;\n}\n\n/** The local folder as a push sees it. */\nexport interface LocalTree {\n /** The text of each file of the site, keyed by its API path. */\n files: Map<string, string>;\n /** Why a file in the folder cannot be pushed. Nothing is sent while any remain. */\n refusals: string[];\n /** The same reasons with the file each is about. */\n refusedFiles: Array<{ path: string; reason: string }>;\n}\n\ntype Refuse = (path: string, reason: string) => void;\n\n/**\n * The paths under a folder as API paths. Names that start with a dot are\n * skipped, and so are the names in `skip`. A symbolic link is refused, not\n * followed.\n */\nasync function walk(\n root: string,\n prefix: string,\n refuse: Refuse,\n skip: readonly string[] = [],\n): Promise<string[]> {\n const found: string[] = [];\n const entries = await readdir(join(root, prefix), { withFileTypes: true });\n for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {\n if (entry.name.startsWith(\".\") || skip.includes(entry.name)) continue;\n const path = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isSymbolicLink()) {\n refuse(\n path,\n `${path} is a symbolic link, and a push does not follow one.`,\n );\n } else if (entry.isDirectory()) {\n found.push(...(await walk(root, path, refuse, skip)));\n } else if (entry.isFile()) {\n found.push(path);\n }\n }\n return found;\n}\n\n/** Where `readLocalTree` gets the folder's paths and bytes. */\nexport interface TreeSource {\n /** The API paths of the folder's files, refusing what a push must not follow. */\n list(refuse: Refuse): Promise<string[]>;\n /** The bytes of one listed file, or null when it is not a regular file. */\n read(path: string): Promise<Buffer | null>;\n}\n\nfunction diskSource(root: string): TreeSource {\n return {\n list: (refuse) => walk(root, \"\", refuse),\n async read(path) {\n const file = join(root, path);\n return (await lstat(file)).isFile() ? readFile(file) : null;\n },\n };\n}\n\nfunction byPath(a: string, b: string): number {\n const x = a.split(\"/\");\n const y = b.split(\"/\");\n for (let i = 0; i < Math.min(x.length, y.length); i++) {\n if (x[i] !== y[i]) return (x[i] as string) < (y[i] as string) ? -1 : 1;\n }\n return x.length - y.length;\n}\n\n/**\n * A source that lists and reads through a declared input tree. The tree\n * skips symbolic links, and a push deletes remote files missing locally, so a\n * link is still refused here by a walk of the folder itself.\n *\n * root - the folder the tree was opened on\n * tree - the input tree from ctx.inputs\n * skip - the tree's ignored names, which the walk skips too\n *\n * Returns the source.\n */\nexport function inputTreeSource(\n root: string,\n tree: InputTree,\n skip: readonly string[],\n): TreeSource {\n return {\n async list(refuse) {\n await withInternalReadsAsync(() => walk(root, \"\", refuse, skip));\n return tree.list().sort(byPath);\n },\n read: async (path) => tree.readFile(path),\n };\n}\n\n/**\n * Reads the folder's files and refuses what the file API would refuse: a path\n * outside the allowed set, a name outside the allowed characters, an asset\n * that is not text or is empty. A file the binding says the pull wrote and\n * that has not changed is not judged: it came from the server. With no\n * binding every file is judged, as `lint` does.\n */\nexport async function readLocalTree(\n root: string,\n binding: SiteBinding | null,\n source: TreeSource = diskSource(root),\n): Promise<LocalTree> {\n const files = new Map<string, string>();\n const refusals: string[] = [];\n const refusedFiles: LocalTree[\"refusedFiles\"] = [];\n const refuse: Refuse = (path, reason) => {\n refusals.push(reason);\n refusedFiles.push({ path, reason });\n };\n const paths = await source.list(refuse);\n\n for (const path of paths) {\n const verdict = classifyPath(path);\n const pulled = binding?.files[path];\n if (verdict.kind === \"refused\" && !pulled) {\n refuse(path, verdict.reason);\n continue;\n }\n\n const bytes = await source.read(path);\n if (bytes === null) continue;\n const text = decodeText(bytes);\n if (text !== null && pulled?.sha256 === sha256(text)) {\n files.set(path, text);\n continue;\n }\n\n if (verdict.kind === \"refused\") {\n refuse(path, verdict.reason);\n } else if (text === null) {\n refuse(\n path,\n verdict.kind === \"asset\"\n ? NOT_TEXT_ASSET(path)\n : `${path} is not valid UTF-8 text.`,\n );\n } else if (verdict.kind === \"asset\" && text.length === 0) {\n refuse(path, `${path} is empty, and an asset cannot be empty.`);\n } else {\n files.set(path, text);\n }\n }\n return { files, refusals, refusedFiles };\n}\n","import type { Site, SitePage } from \"../api/types.js\";\nimport { pageRow, serializeJson } from \"./binding.js\";\n\n/** The home page's slug. Its template, and so its file, is `index`. */\nconst HOME_SLUG = \"/\";\n\n/** The path of the file that holds a page's content. */\nexport function pageFilePath(slug: string): string {\n return `pages/${slug === HOME_SLUG ? \"index\" : slug}.liquid`;\n}\n\n/**\n * `config/pages.json`: the site's default layout and what each page row holds\n * besides its content. Pages are in position order, then slug, so a second\n * pull of an unchanged site writes the same bytes.\n */\nexport function buildPagesConfig(site: Site, pages: SitePage[]): string {\n const ordered = [...pages].sort(\n (a, b) => a.position - b.position || (a.slug < b.slug ? -1 : 1),\n );\n return serializeJson({\n layout: site.layout_name ?? null,\n pages: ordered.map((page) => ({ slug: page.slug, ...pageRow(page) })),\n });\n}\n","","import vocabulary from \"./access-vocabulary.json\";\n\n/**\n * Reads `config/access.json`, a site's declaration of the viewer data it uses\n * and the actions it takes as the viewer. It mirrors\n * `MemberSites::AccessDeclaration` (apps/rails/app/models/member_sites/\n * access_declaration.rb) rule for rule, so `lint` reports what the file API\n * would refuse. `access-vocabulary.json` holds the lists, and a Rails test\n * holds them equal to the model's.\n *\n * The document is one object with one key, `uses`, a list of unique strings,\n * and no key is repeated at any depth. Each string is a member field\n * (`member.email`) or a scope (`orders: read`).\n */\n\n/** The fields and the families a declaration may name. */\nexport const FIELDS: readonly string[] = vocabulary.fields;\nexport const FAMILIES: readonly string[] = vocabulary.families;\nconst MODES: readonly string[] = vocabulary.modes;\n\n/** What a valid declaration holds, sorted and without duplicates. */\nexport interface AccessDeclaration {\n /** The member fields without the `member.` prefix. */\n fields: string[];\n /** The scopes, such as `orders: read`. */\n scopes: string[];\n}\n\nexport type AccessParse =\n | { declaration: AccessDeclaration; problems: [] }\n | { declaration: null; problems: string[] };\n\nconst FIELD_PREFIX = \"member.\";\n\nfunction entryProblems(entry: unknown): string[] {\n if (typeof entry !== \"string\") {\n return [`${JSON.stringify(entry)} is not a string`];\n }\n if (entry.startsWith(FIELD_PREFIX)) {\n return FIELDS.includes(entry.slice(FIELD_PREFIX.length))\n ? []\n : [`${JSON.stringify(entry)} is not a member field`];\n }\n const separator = entry.indexOf(\": \");\n if (separator < 0) {\n return [\n `${JSON.stringify(entry)} is neither \"member.<field>\" nor \"<family>: read\" or \"<family>: write\"`,\n ];\n }\n const family = entry.slice(0, separator);\n const mode = entry.slice(separator + 2);\n if (family === \"session\") {\n return [\n `\"session: read\" is granted with any other scope and is not declared`,\n ];\n }\n return [\n ...(FAMILIES.includes(family)\n ? []\n : [`${JSON.stringify(entry)} names an unknown family`]),\n ...(MODES.includes(mode)\n ? []\n : [`${JSON.stringify(entry)} has a mode other than read or write`]),\n ];\n}\n\n/**\n * Whether a text of valid JSON repeats a key in any object, which the file API\n * refuses because the value of a key would depend on which copy a parser\n * keeps. It walks the text, so a repeat is seen that `JSON.parse` hides.\n */\nfunction repeatsAKey(text: string): boolean {\n const objects: Array<Set<string> | null> = [];\n let expectKey = false;\n for (let i = 0; i < text.length; i++) {\n const char = text[i] as string;\n if (char === '\"') {\n let end = i + 1;\n while (text[end] !== '\"') end += text[end] === \"\\\\\" ? 2 : 1;\n const scope = objects[objects.length - 1];\n if (expectKey && scope) {\n const key = JSON.parse(text.slice(i, end + 1)) as string;\n if (scope.has(key)) return true;\n scope.add(key);\n expectKey = false;\n }\n i = end;\n } else if (char === \"{\") {\n objects.push(new Set());\n expectKey = true;\n } else if (char === \"[\") {\n objects.push(null);\n expectKey = false;\n } else if (char === \"}\" || char === \"]\") {\n objects.pop();\n expectKey = false;\n } else if (char === \",\") {\n expectKey = objects[objects.length - 1] instanceof Set;\n }\n }\n return false;\n}\n\nfunction documentProblems(document: unknown): string[] {\n if (\n typeof document !== \"object\" ||\n document === null ||\n Array.isArray(document)\n ) {\n return [\"the document must be one object\"];\n }\n const record = document as Record<string, unknown>;\n const unknown = Object.keys(record)\n .filter((key) => key !== \"uses\")\n .map((key) => `unknown key ${JSON.stringify(key)}, the only key is \"uses\"`);\n if (!(\"uses\" in record)) {\n return [...unknown, `the document needs a \"uses\" key`];\n }\n const uses = record[\"uses\"];\n if (!Array.isArray(uses)) {\n return [...unknown, `\"uses\" must be a list of strings`];\n }\n const entries: unknown[] = uses;\n const tooMany =\n entries.length > vocabulary.maxEntries\n ? [`more than ${vocabulary.maxEntries} entries`]\n : [];\n const repeated = entries\n .filter((entry): entry is string => typeof entry === \"string\")\n .filter((entry, index, all) => all.indexOf(entry) !== index);\n return [\n ...unknown,\n ...tooMany,\n ...entries.flatMap(entryProblems),\n ...[...new Set(repeated)].map(\n (entry) => `${JSON.stringify(entry)} is listed twice`,\n ),\n ];\n}\n\n/** Parses a `config/access.json` text, or says what the file API would refuse. */\nexport function parseAccessDeclaration(text: string): AccessParse {\n if (Buffer.byteLength(text, \"utf8\") > vocabulary.maxBytes) {\n return {\n declaration: null,\n problems: [`the document is over ${vocabulary.maxBytes} bytes`],\n };\n }\n let document: unknown;\n try {\n document = JSON.parse(text);\n } catch {\n return { declaration: null, problems: [\"the document is not valid JSON\"] };\n }\n if (repeatsAKey(text)) {\n return {\n declaration: null,\n problems: [\n \"the document repeats a key, and a repeated key has no single meaning\",\n ],\n };\n }\n const problems = documentProblems(document);\n if (problems.length > 0) return { declaration: null, problems };\n\n const entries = (document as { uses: string[] }).uses;\n const fields = entries\n .filter((entry) => entry.startsWith(FIELD_PREFIX))\n .map((entry) => entry.slice(FIELD_PREFIX.length));\n const scopes = entries.filter((entry) => !entry.startsWith(FIELD_PREFIX));\n return {\n declaration: { fields: [...fields].sort(), scopes: [...scopes].sort() },\n problems: [],\n };\n}\n","import { parseAccessDeclaration } from \"./access-declaration.js\";\nimport {\n PAGES_CONFIG_PATH,\n sha256,\n type PageRow,\n type SiteBinding,\n} from \"./binding.js\";\nimport {\n ACCESS_PATH,\n classifyPath,\n MENUS_PATH,\n SETTINGS_PATH,\n type LocalTree,\n} from \"./local-tree.js\";\nimport { pageFilePath } from \"./pages-config.js\";\n\nconst PAGE_SLUG_PATTERN = /^(\\/|[a-z0-9]+(-[a-z0-9]+)*)$/;\nconst ROW_KEYS = [\"slug\", \"title\", \"position\", \"published\", \"layout\", \"menu\"];\nconst ROW_FIELDS = [\"title\", \"position\", \"published\", \"layout\"] as const;\ntype RowField = (typeof ROW_FIELDS)[number];\n\n/** The most pages one site holds, the same number the server refuses past. */\nexport const PAGE_LIMIT = 50;\nconst MENU_SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\n/** Why a change is listed and not sent. */\nconst NOT_PUSHED_YET = \"the site row is not pushed yet\";\n\n/** The refusal for a file the binding holds no version of, which a write or a delete needs. */\nfunction noVersion(path: string): string {\n return `The binding holds no version of ${path}. Pull the site again first.`;\n}\n\n/**\n * The kinds of file that are written and removed through the file routes. The\n * access declaration is one: it is written at its version, created only when\n * the site has none, and removed to clear its draft.\n */\nfunction isPlainFile(path: string): boolean {\n const kind = classifyPath(path).kind;\n return (\n kind === \"layout\" ||\n kind === \"section\" ||\n kind === \"snippet\" ||\n kind === \"asset\" ||\n kind === \"access\"\n );\n}\n\n/** One request of a push, in the order they are sent. */\nexport type PushStep =\n | {\n kind: \"settings\";\n path: typeof SETTINGS_PATH;\n content: string;\n /** The version the binding recorded. */\n lockVersion: number;\n }\n | {\n /** A layout, section, snippet or asset: created, or replaced at a version. */\n kind: \"file-write\";\n path: string;\n content: string;\n /** The version the binding recorded, or, for a path it never saw, that none may exist. */\n precondition: { version: string } | { mustNotExist: true };\n }\n | {\n kind: \"page-create\";\n slug: string;\n row: PageRow;\n /** The text of the page's file, which a new page needs. */\n content: string;\n }\n | {\n kind: \"page-update\";\n slug: string;\n id: string;\n lockVersion: number;\n /** Row fields that changed, by their `config/pages.json` names. */\n fields: Partial<Omit<PageRow, \"menu\">>;\n content: string | undefined;\n }\n | {\n kind: \"menus\";\n path: typeof MENUS_PATH;\n content: string;\n /** The digest of the menus document the binding recorded. */\n version: string;\n }\n | { kind: \"page-menu\"; slug: string; menu: string | null }\n | { kind: \"page-delete\"; slug: string; id: string; lockVersion: number }\n | { kind: \"file-delete\"; path: string; version: string };\n\n/** A change the folder holds that this push does not send. */\nexport interface NotSent {\n subject: string;\n why: string;\n}\n\nexport interface PushPlan {\n steps: PushStep[];\n notSent: NotSent[];\n /** Files that cannot be removed, which the push leaves as the server has them. */\n notes: string[];\n /** What takes effect without a publish, and so before one. */\n immediate: string[];\n /** Why the folder cannot be pushed. Nothing is sent while any remain. */\n refusals: string[];\n /**\n * Whether every row in `config/pages.json` is now what the server holds, so\n * the file counts as pushed.\n */\n pagesConfigSettled: boolean;\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** The slugs of the menus a `config/menus.json` text defines, or null when it has no such list. */\nfunction menuSlugs(text: string): Set<string> | null {\n try {\n const document: unknown = JSON.parse(text);\n const menus = isRecord(document) ? document[\"menus\"] : undefined;\n if (!Array.isArray(menus)) return null;\n return new Set(\n menus.flatMap((menu) =>\n isRecord(menu) && typeof menu[\"slug\"] === \"string\"\n ? [menu[\"slug\"]]\n : [],\n ),\n );\n } catch {\n return null;\n }\n}\n\nfunction isText(value: unknown): boolean {\n return typeof value === \"string\" && value.trim() !== \"\";\n}\n\nconst HEX = \"[0-9A-Fa-f]\";\nconst PCT = `%${HEX}{2}`;\nconst H16 = `${HEX}{1,4}`;\nconst DEC_OCTET = \"(?:[1-9]\\\\d|1\\\\d{2}|2[0-4]\\\\d|25[0-5]|\\\\d)\";\nconst IPV4 = `${DEC_OCTET}(?:\\\\.${DEC_OCTET}){3}`;\nconst LS32 = `(?:${H16}:${H16}|${IPV4})`;\nconst IPV6 = [\n `(?:${H16}:){6}${LS32}`,\n `::(?:${H16}:){5}${LS32}`,\n `(?:${H16})?::(?:${H16}:){4}${LS32}`,\n `(?:(?:${H16}:)?${H16})?::(?:${H16}:){3}${LS32}`,\n `(?:(?:${H16}:){0,2}${H16})?::(?:${H16}:){2}${LS32}`,\n `(?:(?:${H16}:){0,3}${H16})?::${H16}:${LS32}`,\n `(?:(?:${H16}:){0,4}${H16})?::${LS32}`,\n `(?:(?:${H16}:){0,5}${H16})?::${H16}`,\n `(?:(?:${H16}:){0,6}${H16})?::`,\n].join(\"|\");\nconst IP_LITERAL = `\\\\[(?:${IPV6}|v${HEX}+\\\\.[!$&-.0-9:;=A-Z_a-z~]+)\\\\]`;\nconst REG_NAME = `(?:${PCT}|[!$&-.0-9;=A-Z_a-z~])+`;\nconst SEGMENT = `(?:${PCT}|[!$&-.0-9:;=@A-Z_a-z~/])`;\n\n/**\n * The URL grammar of Ruby's `URI.parse` (RFC 3986), which the server runs a\n * menu link through, narrowed to an http or https address with a host. Ruby\n * takes any ASCII in a query, so the query is the one part left that wide, and\n * its one capture group. `queryEscapesInvalid` covers what Ruby then refuses.\n */\nconst HTTP_URL = new RegExp(\n `^[Hh][Tt][Tt][Pp][Ss]?://(?:(?:${PCT}|[!$&-.0-9:;=A-Z_a-z~])*@)?` +\n `(?:${IP_LITERAL}|${IPV4}|${REG_NAME})(?::\\\\d*)?` +\n `(?:/${SEGMENT}*)?` +\n \"(?:\\\\?([\\\\x00-\\\\x22\\\\x24-\\\\x7f]*))?\" +\n `(?:#(?:${PCT}|[!$&-.0-9:;=@A-Z_a-z~/?])*)?$`,\n);\n\n/**\n * Whether the server accepts the value as a menu link. The value is judged as\n * written: no trimming, and no escaping of spaces or non-ASCII, which `new URL`\n * would do and the server's parser does not.\n */\nfunction isHttpUrl(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n const match = HTTP_URL.exec(value);\n return match !== null && !queryEscapesInvalid(match[1]);\n}\n\n/**\n * Whether a query holds a `%` followed by two characters that are not hex\n * digits, which Ruby refuses as an invalid percent escape. It drops tabs and\n * line breaks from the query first, so those cannot hide one.\n */\nfunction queryEscapesInvalid(query: string | undefined): boolean {\n return /%[^0-9A-Fa-f][^0-9A-Fa-f]/.test(\n (query ?? \"\").replace(/[\\t\\r\\n]/g, \"\"),\n );\n}\n\n/** The problems of a node that must be an object with only these keys and all the required ones. */\nfunction objectProblems(\n node: unknown,\n path: string,\n allowed: string[],\n required: string[],\n): string[] {\n if (!isRecord(node)) return [`${path} must be an object.`];\n const problems: string[] = [];\n const unknown = Object.keys(node).filter((key) => !allowed.includes(key));\n const missing = required.filter((key) => !(key in node));\n if (unknown.length > 0) {\n problems.push(`${path} has unknown keys: ${unknown.join(\", \")}.`);\n }\n if (missing.length > 0) {\n problems.push(`${path} needs ${missing.join(\", \")}.`);\n }\n return problems;\n}\n\nfunction menuItemsProblems(items: unknown, path: string): string[] {\n if (!Array.isArray(items)) return [`${path} must be an array.`];\n return items.flatMap((item: unknown, index) => {\n const where = `${path}[${index}]`;\n const shape = objectProblems(\n item,\n where,\n [\"title\", \"page\", \"url\", \"items\"],\n [\"title\"],\n );\n if (shape.length > 0 || !isRecord(item)) return shape;\n const problems: string[] = [];\n if (!isText(item[\"title\"])) {\n problems.push(`${where}.title must be a non-empty string.`);\n }\n if (\"page\" in item && \"url\" in item) {\n problems.push(`${where} cannot have both page and url.`);\n } else if (\"page\" in item && typeof item[\"page\"] !== \"string\") {\n problems.push(`${where}.page must be a page slug.`);\n } else if (\"url\" in item && !isHttpUrl(item[\"url\"])) {\n problems.push(`${where}.url must be an http or https address.`);\n }\n if (\"items\" in item) {\n problems.push(...menuItemsProblems(item[\"items\"], `${where}.items`));\n }\n return problems;\n });\n}\n\n/**\n * The problems of a parsed `config/menus.json` that the server would refuse\n * for its shape alone. Whether a linked page exists is left to the server,\n * which holds the pages.\n */\nexport function menusProblems(document: unknown): string[] {\n const top = objectProblems(document, MENUS_PATH, [\"menus\"], [\"menus\"]);\n if (top.length > 0 || !isRecord(document)) return top;\n const menus = document[\"menus\"];\n if (!Array.isArray(menus))\n return [`${MENUS_PATH}: \"menus\" must be an array.`];\n\n const problems: string[] = [];\n menus.forEach((menu: unknown, index) => {\n const where = `${MENUS_PATH}, menus[${index}]`;\n const shape = objectProblems(\n menu,\n where,\n [\"slug\", \"title\", \"items\"],\n [\"slug\", \"title\"],\n );\n problems.push(...shape);\n if (shape.length > 0 || !isRecord(menu)) return;\n const slug = menu[\"slug\"];\n if (typeof slug !== \"string\" || !MENU_SLUG_PATTERN.test(slug)) {\n problems.push(`${where}.slug must be lowercase words joined by hyphens.`);\n }\n if (!isText(menu[\"title\"])) {\n problems.push(`${where}.title must be a non-empty string.`);\n }\n if (\"items\" in menu) {\n problems.push(...menuItemsProblems(menu[\"items\"], `${where}.items`));\n }\n });\n\n const counts = new Map<string, number>();\n for (const menu of menus) {\n if (isRecord(menu) && typeof menu[\"slug\"] === \"string\") {\n counts.set(menu[\"slug\"], (counts.get(menu[\"slug\"]) ?? 0) + 1);\n }\n }\n for (const [slug, count] of counts) {\n if (count > 1) {\n problems.push(\n `${MENUS_PATH}: the menu slug \"${slug}\" is used more than once.`,\n );\n }\n }\n return problems;\n}\n\nexport interface ParsedPages {\n layout: string | null;\n rows: Array<PageRow & { slug: string }>;\n}\n\n/** The rows of `config/pages.json`, or the reasons it is not one. */\nexport function parsePagesConfig(text: string): ParsedPages | string[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return [`${PAGES_CONFIG_PATH} is not valid JSON.`];\n }\n if (!isRecord(parsed) || !Array.isArray(parsed[\"pages\"])) {\n return [`${PAGES_CONFIG_PATH} must hold an object with a \"pages\" list.`];\n }\n const layout = parsed[\"layout\"] ?? null;\n if (layout !== null && typeof layout !== \"string\") {\n return [`${PAGES_CONFIG_PATH}: \"layout\" must be a layout name or null.`];\n }\n\n const problems: string[] = [];\n const rows: ParsedPages[\"rows\"] = [];\n const seen = new Set<string>();\n parsed[\"pages\"].forEach((raw: unknown, index) => {\n const where = `${PAGES_CONFIG_PATH}, page ${index + 1}`;\n if (!isRecord(raw)) {\n problems.push(`${where} is not an object.`);\n return;\n }\n for (const key of Object.keys(raw)) {\n if (!ROW_KEYS.includes(key)) {\n problems.push(\n `${where} has an unknown key \"${key}\". A page row holds ${ROW_KEYS.join(\", \")}.`,\n );\n }\n }\n const { slug, title, position, published } = raw;\n const rowLayout = raw[\"layout\"] ?? null;\n const menu = raw[\"menu\"] ?? null;\n if (typeof slug !== \"string\" || !PAGE_SLUG_PATTERN.test(slug)) {\n problems.push(\n `${where}: \"slug\" must be \"/\" or lowercase letters and digits separated by single hyphens.`,\n );\n return;\n }\n if (seen.has(slug))\n problems.push(`${where}: the slug \"${slug}\" is used twice.`);\n seen.add(slug);\n const named = `${PAGES_CONFIG_PATH}, page \"${slug}\"`;\n if (\n typeof title !== \"string\" ||\n title.trim() === \"\" ||\n title.length > 255\n ) {\n problems.push(`${named}: \"title\" must be 1 to 255 characters.`);\n }\n if (!Number.isInteger(position)) {\n problems.push(`${named}: \"position\" must be an integer.`);\n }\n if (typeof published !== \"boolean\") {\n problems.push(`${named}: \"published\" must be true or false.`);\n }\n if (rowLayout !== null && typeof rowLayout !== \"string\") {\n problems.push(`${named}: \"layout\" must be a layout name or null.`);\n }\n if (menu !== null && typeof menu !== \"string\") {\n problems.push(`${named}: \"menu\" must be a menu slug or null.`);\n }\n rows.push({\n slug,\n title: title as string,\n position: position as number,\n published: published as boolean,\n layout: rowLayout as string | null,\n menu: menu as string | null,\n });\n });\n return problems.length > 0 ? problems : { layout, rows };\n}\n\n/** Whether two JSON texts hold the same value, whatever their formatting and key order. */\nexport function sameJson(a: string, b: string): boolean {\n const canonical = (value: unknown): unknown =>\n Array.isArray(value)\n ? value.map(canonical)\n : isRecord(value)\n ? Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [key, canonical(value[key])]),\n )\n : value;\n try {\n return (\n JSON.stringify(canonical(JSON.parse(a))) ===\n JSON.stringify(canonical(JSON.parse(b)))\n );\n } catch {\n return false;\n }\n}\n\n/** `true` when the folder's text differs from what the binding says the pull wrote. */\nfunction changed(text: string, recorded: string | undefined): boolean {\n return recorded === undefined || sha256(text) !== recorded;\n}\n\n/**\n * Compares the folder with the binding and orders what a push sends. It reads\n * nothing but its arguments.\n *\n * The order is the server's: settings first, then layouts, sections, snippets\n * and assets, then new pages, then changed pages, then the menus, then each\n * page's menu assignment, then the removed pages and files. A page's file is\n * written with its row, so it never precedes its page. A menu link needs its\n * page, and a page's assignment needs its menu, so the menus come between.\n * Removals come last, so a write that fails never leaves a menu pointing at a\n * page that is gone.\n *\n * The plan puts the settings ahead of every page request, which is not where\n * the file list has them: every page change moves the settings version, so a\n * settings write that followed one would answer 409 to the push's own work.\n *\n * Every write carries the version the binding recorded for its file or page,\n * and a file the binding never saw carries a must-not-exist condition. A file\n * the binding holds no version of is refused, as a write without one would\n * replace whatever the server has.\n */\nexport function planPush(binding: SiteBinding, tree: LocalTree): PushPlan {\n const plan: PushPlan = {\n steps: [],\n notSent: [],\n notes: [],\n immediate: [],\n refusals: [...tree.refusals],\n pagesConfigSettled: true,\n };\n const settingsStep: PushStep[] = [];\n const fileSteps: PushStep[] = [];\n const createSteps: PushStep[] = [];\n const updateSteps: PushStep[] = [];\n const menuStep: PushStep[] = [];\n const assignSteps: PushStep[] = [];\n const pageDeleteSteps: PushStep[] = [];\n const fileDeleteSteps: PushStep[] = [];\n const rowsSent = new Set<string>();\n const assetLines: string[] = [];\n\n // config/settings.json carries the version the binding recorded.\n const settings = tree.files.get(SETTINGS_PATH);\n const settingsBound = binding.files[SETTINGS_PATH];\n if (settings === undefined) {\n if (settingsBound) {\n plan.notes.push(\n `${SETTINGS_PATH} is missing here. Settings cannot be removed, so the server keeps its copy.`,\n );\n }\n } else if (changed(settings, settingsBound?.sha256)) {\n let valid = false;\n try {\n valid = isRecord(JSON.parse(settings));\n } catch {\n valid = false;\n }\n const lockVersion = binding.settings.lock_version;\n if (!valid) {\n plan.refusals.push(`${SETTINGS_PATH} must hold a JSON object.`);\n } else if (lockVersion === null || lockVersion === undefined) {\n plan.refusals.push(\n `The binding holds no version of ${SETTINGS_PATH}. Pull the site again first.`,\n );\n } else {\n settingsStep.push({\n kind: \"settings\",\n path: SETTINGS_PATH,\n content: settings,\n lockVersion,\n });\n }\n }\n\n // The menus replace every menu of the site, so they go only when they changed.\n const menus = tree.files.get(MENUS_PATH);\n const menusBound = binding.files[MENUS_PATH];\n let localMenus: Set<string> | null = null;\n if (menus === undefined) {\n if (menusBound) {\n plan.notes.push(\n `${MENUS_PATH} is missing here. Menus cannot be removed; write an empty menus document to clear them.`,\n );\n }\n } else {\n localMenus = menuSlugs(menus);\n if (changed(menus, menusBound?.sha256)) {\n let document: unknown;\n let parsedOk = true;\n try {\n document = JSON.parse(menus);\n } catch {\n parsedOk = false;\n }\n if (!parsedOk) {\n plan.refusals.push(`${MENUS_PATH} is not valid JSON.`);\n } else {\n // The menus are written after the settings and pages, so a document\n // the server would refuse for its shape must stop the push here.\n const problems = menusProblems(document);\n if (problems.length > 0) {\n plan.refusals.push(...problems);\n } else if (menusBound?.version === undefined) {\n plan.refusals.push(noVersion(MENUS_PATH));\n } else {\n menuStep.push({\n kind: \"menus\",\n path: MENUS_PATH,\n content: menus,\n version: menusBound.version,\n });\n plan.immediate.push(\n `${MENUS_PATH} (replaces every menu of the site)`,\n );\n }\n }\n }\n }\n\n // Page rows and page files.\n const pagesText = tree.files.get(PAGES_CONFIG_PATH);\n let parsed: ParsedPages | null = null;\n if (pagesText === undefined) {\n if (Object.keys(binding.pages).length > 0) {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH} is missing. Pull the site to restore it.`,\n );\n } else {\n parsed = { layout: binding.layout ?? null, rows: [] };\n }\n } else {\n const result = parsePagesConfig(pagesText);\n if (Array.isArray(result)) plan.refusals.push(...result);\n else parsed = result;\n }\n\n if (parsed) {\n const rowSlugs = new Set(parsed.rows.map((row) => row.slug));\n\n if (\n binding.layout !== undefined &&\n parsed.layout !== (binding.layout ?? null)\n ) {\n plan.notSent.push({\n subject: `${PAGES_CONFIG_PATH}: the site's default layout`,\n why: NOT_PUSHED_YET,\n });\n plan.pagesConfigSettled = false;\n }\n\n for (const row of parsed.rows) {\n const path = pageFilePath(row.slug);\n const content = tree.files.get(path);\n const bound = binding.pages[row.slug];\n const boundFile = binding.files[path];\n\n if (row.slug === \"index\") {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH}: \"index\" is reserved for the home page, whose slug is \"/\".`,\n );\n continue;\n }\n if (row.menu !== null && localMenus && !localMenus.has(row.menu)) {\n plan.refusals.push(\n `Page \"${row.slug}\" uses the menu \"${row.menu}\", which ${MENUS_PATH} does not define.`,\n );\n }\n\n if (!bound) {\n if (content === undefined) {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH} adds the page \"${row.slug}\", and ${path} is missing. A new page needs its file.`,\n );\n continue;\n }\n const { menu, ...rest } = row;\n createSteps.push({\n kind: \"page-create\",\n slug: row.slug,\n row: { ...rest, menu: null },\n content,\n });\n if (menu !== null) {\n assignSteps.push({ kind: \"page-menu\", slug: row.slug, menu });\n }\n rowsSent.add(row.slug);\n continue;\n }\n\n // A page that exists: its row fields, then its file.\n const fields: Partial<Omit<PageRow, \"menu\">> = {};\n for (const field of ROW_FIELDS) {\n if (bound.row === undefined || bound.row[field] !== row[field]) {\n (fields as Record<RowField, unknown>)[field] = row[field];\n }\n }\n\n let pageContent: string | undefined;\n if (content === undefined) {\n if (boundFile) {\n plan.refusals.push(\n `${path} is missing, and the page \"${row.slug}\" is still in ${PAGES_CONFIG_PATH}. A page goes with its file: restore the file, or remove the page's row to delete the page.`,\n );\n }\n } else if (!boundFile) {\n plan.refusals.push(\n `${path} is a file of the page \"${row.slug}\", which uses a template the site does not own. Its content cannot be pushed.`,\n );\n } else if (changed(content, boundFile.sha256)) {\n pageContent = content;\n }\n\n if (Object.keys(fields).length > 0 || pageContent !== undefined) {\n updateSteps.push({\n kind: \"page-update\",\n slug: row.slug,\n id: bound.id,\n lockVersion: bound.lock_version,\n fields,\n content: pageContent,\n });\n if (Object.keys(fields).length > 0) rowsSent.add(row.slug);\n }\n if (bound.row === undefined || bound.row.menu !== row.menu) {\n assignSteps.push({ kind: \"page-menu\", slug: row.slug, menu: row.menu });\n rowsSent.add(row.slug);\n }\n }\n\n // A page whose row is gone is deleted, at the version the binding holds.\n for (const slug of Object.keys(binding.pages).sort()) {\n if (rowSlugs.has(slug)) continue;\n const path = pageFilePath(slug);\n if (tree.files.has(path)) {\n plan.refusals.push(\n `${PAGES_CONFIG_PATH} no longer has the page \"${slug}\", and ${path} is still here. Remove the file too to delete the page, or restore the row.`,\n );\n continue;\n }\n const bound = binding.pages[slug]!;\n pageDeleteSteps.push({\n kind: \"page-delete\",\n slug,\n id: bound.id,\n lockVersion: bound.lock_version,\n });\n }\n\n // A page file that no row of config/pages.json names has no page to take it.\n for (const path of tree.files.keys()) {\n const verdict = classifyPath(path);\n if (verdict.kind !== \"page\" || rowSlugs.has(verdict.slug)) continue;\n if (binding.pages[verdict.slug]) continue;\n plan.refusals.push(\n `${path} has no row in ${PAGES_CONFIG_PATH}. Add a row for the page \"${verdict.slug}\", or remove the file.`,\n );\n }\n }\n\n // Templates and assets are written at the version the binding recorded, and\n // a path the binding never saw is created only if the path is still free.\n for (const [path, text] of [...tree.files].sort(([a], [b]) =>\n a < b ? -1 : 1,\n )) {\n if (!isPlainFile(path)) continue;\n const bound = binding.files[path];\n if (!changed(text, bound?.sha256)) continue;\n // The server refuses an invalid declaration after the settings and files\n // before it were written, so it is judged here, before any request.\n if (path === ACCESS_PATH) {\n const { problems } = parseAccessDeclaration(text);\n if (problems.length > 0) {\n plan.refusals.push(...problems.map((p) => `${ACCESS_PATH}: ${p}.`));\n continue;\n }\n }\n const isAsset = classifyPath(path).kind === \"asset\";\n if (bound === undefined) {\n fileSteps.push({\n kind: \"file-write\",\n path,\n content: text,\n precondition: { mustNotExist: true },\n });\n if (isAsset) assetLines.push(`${path} (new)`);\n } else if (bound.version === undefined) {\n plan.refusals.push(noVersion(path));\n } else {\n fileSteps.push({\n kind: \"file-write\",\n path,\n content: text,\n precondition: { version: bound.version },\n });\n if (isAsset) assetLines.push(`${path} (changed)`);\n }\n }\n for (const path of Object.keys(binding.files).sort()) {\n if (!isPlainFile(path) || tree.files.has(path)) continue;\n const bound = binding.files[path]!;\n if (bound.version === undefined) {\n plan.refusals.push(noVersion(path));\n continue;\n }\n fileDeleteSteps.push({ kind: \"file-delete\", path, version: bound.version });\n // A removal is live at once for every file but the declaration: deleting a\n // layout, section or snippet removes its published copy too, not only the\n // draft, while removing the declaration clears its draft until a publish.\n if (path !== ACCESS_PATH) assetLines.push(`${path} (removed)`);\n }\n\n plan.steps = [\n ...settingsStep,\n ...fileSteps,\n ...createSteps,\n ...updateSteps,\n ...menuStep,\n ...assignSteps,\n ...pageDeleteSteps,\n ...fileDeleteSteps,\n ];\n plan.immediate = [\n ...assetLines,\n ...plan.immediate,\n ...[...rowsSent].map((slug) => `page row \"${slug}\"`),\n ...pageDeleteSteps.flatMap((step) =>\n step.kind === \"page-delete\" ? [`page row \"${step.slug}\" (removed)`] : [],\n ),\n ];\n return plan;\n}\n\n/**\n * The reason a plan's new pages do not fit in the site, or null. Removals are\n * sent after the new pages are created, so a page the folder dropped or\n * renamed still counts against the limit while its replacement is created.\n *\n * `remotePageCount` is how many pages the site holds now.\n */\nexport function pageCapacityRefusal(\n plan: PushPlan,\n remotePageCount: number,\n): string | null {\n const added = plan.steps.filter((step) => step.kind === \"page-create\").length;\n const room = Math.max(0, PAGE_LIMIT - remotePageCount);\n if (added <= room) return null;\n return `Adding ${added} page${added === 1 ? \"\" : \"s\"} would take the site past its limit of ${PAGE_LIMIT} pages: it holds ${remotePageCount} now and has room for ${room}. Pages removed or renamed here are deleted only after the new pages are created, so they still count. Push the removals on their own first, then add the new pages.`;\n}\n\n/** The label for a step in a plan and in the summary of what a push wrote. */\nexport function describeStep(step: PushStep): string {\n switch (step.kind) {\n case \"settings\":\n return `PUT ${step.path}`;\n case \"file-write\":\n return `PUT ${step.path} (${\"version\" in step.precondition ? \"replace\" : \"create\"})`;\n case \"page-create\":\n return `POST page \"${step.slug}\" (${pageFilePath(step.slug)})`;\n case \"page-update\": {\n const parts = [\n ...Object.keys(step.fields),\n ...(step.content === undefined ? [] : [\"content\"]),\n ];\n return `PATCH page \"${step.slug}\" (${parts.join(\", \")})`;\n }\n case \"menus\":\n return `PUT ${step.path}`;\n case \"page-menu\":\n return `PATCH page \"${step.slug}\" (menu: ${step.menu ?? \"none\"})`;\n case \"page-delete\":\n return `DELETE page \"${step.slug}\" (${pageFilePath(step.slug)})`;\n case \"file-delete\":\n return `DELETE ${step.path}`;\n }\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n declareInputs,\n withInternalReadsAsync,\n type CommandInputs,\n} from \"@fluid-app/fluid-cli\";\nimport { createClients, loadLogin } from \"../api/credential.js\";\nimport {\n MemberSiteCliError,\n describeError,\n type ErrorContext,\n} from \"../api/errors.js\";\nimport type { Site, SiteApi } from \"../api/types.js\";\nimport {\n BINDING_FILE,\n serializeJson,\n sha256,\n type SiteBinding,\n} from \"../tree/binding.js\";\nimport { readLocal, writeLocal } from \"../tree/folder.js\";\nimport { classifyPath, SETTINGS_PATH } from \"../tree/local-tree.js\";\nimport { sameJson } from \"../tree/push-plan.js\";\nimport { openBoundSite, type BoundSiteDeps } from \"./bound-site.js\";\nimport { runCommand } from \"./run.js\";\n\nexport interface PublishOptions {\n as?: string;\n}\n\nexport type PublishDeps = BoundSiteDeps;\n\nconst defaultDeps: PublishDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\n/** What a publish did. */\nexport interface PublishResult {\n site: Site;\n dir: string;\n}\n\n/**\n * How far one publish moves the site's `lock_version`: once for the status and\n * `content_version` write, and once more when the settings draft was not yet\n * the published copy. The response does not say which, so the settings are\n * proven by their content, and this bounds the move.\n */\nconst PUBLISH_SITE_MOVES = [1, 2];\n\n/** Kinds of file with a draft and a published copy, so a publish moves their version. */\nconst PUBLISHED_KINDS = [\"layout\", \"section\", \"snippet\", \"page\"];\n\n/**\n * The text of a file in the folder, when it is what the binding says was last\n * pushed or pulled. A file edited here since is not that, and the binding\n * does not keep that content, so nothing can be proven for it.\n */\nasync function baselineText(\n dir: string,\n binding: SiteBinding,\n path: string,\n): Promise<string | null> {\n const text = await withInternalReadsAsync(() => readLocal(dir, path));\n return text !== null && binding.files[path]?.sha256 === sha256(text)\n ? text\n : null;\n}\n\n/**\n * The version a template or page file has once it is published from the draft\n * `content`: the published copy is the draft, and the server's version is the\n * digest of the draft, a NUL byte and the published copy.\n */\nfunction publishedVersion(content: string): string {\n return sha256(`${content}\\0${content}`);\n}\n\n/**\n * Takes the versions a publish moved into the binding, for what is proven to\n * be what the publish published, and returns what is not so the caller can\n * report it. Anything else keeps its old version, so the next push finds the\n * change another writer made between the pull and the publish.\n *\n * - A template or page file: the server's new version must equal the one\n * computed from the content the folder holds. It is a digest of the draft\n * and the published copy, so a match proves both are that content.\n * - A page: its `lock_version` moves once per publish, so only a move by\n * exactly one is the publish's own.\n * - The settings: the site's `lock_version` as the publish response gave it,\n * with the settings the publish published, which must be the ones the folder\n * holds. A move beyond what a publish makes is another writer's.\n * - Assets and menus have no draft, so a publish writes neither and their\n * versions stay as they are.\n *\n * A file the folder holds edited, since the pull or push, is left alone and\n * not reported: it is the local edit that differs, not the server.\n */\nasync function adoptPublishedVersions(\n api: SiteApi,\n binding: SiteBinding,\n dir: string,\n site: Site,\n): Promise<string[]> {\n const entries = await api.listFiles(binding.site_id);\n const pages = await api.listPages(binding.site_id);\n const changed: string[] = [];\n\n for (const entry of entries) {\n const bound = binding.files[entry.path];\n if (!bound || bound.version === undefined) continue;\n if (!PUBLISHED_KINDS.includes(classifyPath(entry.path).kind)) continue;\n if (entry.version === bound.version) continue;\n const text = await baselineText(dir, binding, entry.path);\n if (text === null) continue;\n if (entry.version === publishedVersion(text)) {\n bound.version = entry.version;\n } else {\n changed.push(entry.path);\n }\n }\n\n for (const [slug, bound] of Object.entries(binding.pages)) {\n const fresh = pages.find((page) => page.id === bound.id);\n if (!fresh || fresh.lock_version === bound.lock_version) continue;\n if (fresh.slug === slug && fresh.lock_version === bound.lock_version + 1) {\n bound.lock_version = fresh.lock_version;\n } else {\n changed.push(`the page \"${slug}\"`);\n }\n }\n\n const settings = binding.files[SETTINGS_PATH];\n if (\n settings &&\n typeof settings.lock_version === \"number\" &&\n typeof site.lock_version === \"number\"\n ) {\n const moved = site.lock_version - settings.lock_version;\n const text =\n moved === 0 ? null : await baselineText(dir, binding, SETTINGS_PATH);\n if (text !== null) {\n if (\n PUBLISH_SITE_MOVES.includes(moved) &&\n sameJson(JSON.stringify(site.settings), text)\n ) {\n settings.lock_version = site.lock_version;\n settings.version = String(site.lock_version);\n binding.settings.lock_version = site.lock_version;\n } else {\n changed.push(SETTINGS_PATH);\n }\n }\n }\n return changed;\n}\n\n/**\n * Publishes the folder's site with one request, prints the status and the new\n * `content_version`, and records the versions the publish moved that are\n * proven to be the publish's own, so the next push does not conflict with\n * them and still finds a change another writer made. The binding is saved. A\n * publish that fails is reported with `notPublished` after the API's message,\n * and is not retried.\n */\nexport async function publishBoundSite(\n api: SiteApi,\n binding: SiteBinding,\n dir: string,\n context: ErrorContext,\n log: (line: string) => void,\n notPublished: string,\n): Promise<Site> {\n let site: Site;\n try {\n context.subject = \"The site\";\n context.conflict = \"publish\";\n site = await api.publishSite(binding.site_id);\n } catch (error) {\n throw new MemberSiteCliError(\n `${describeError(error, context)}\\n${notPublished}`,\n );\n }\n log(`Published ${binding.owner_kind} site ${chalk.cyan(binding.handle)}.`);\n log(` Status: ${site.status}`);\n log(` content_version: ${site.content_version}`);\n\n // The site is live whatever happens next, so a failed read is a warning.\n try {\n context.subject = \"The site's files and pages\";\n delete context.conflict;\n const changed = await adoptPublishedVersions(api, binding, dir, site);\n await writeLocal(dir, BINDING_FILE, serializeJson(binding));\n if (changed.length > 0) {\n log(\n chalk.yellow(\n ` Changed on the server, so their versions were not recorded: ${changed.join(\", \")}. Pull before the next push.`,\n ),\n );\n }\n } catch (error) {\n log(\n chalk.yellow(\n ` The versions the publish moved were not recorded: ${describeError(error, context).split(\"\\n\")[0]} Pull before the next push.`,\n ),\n );\n }\n return site;\n}\n\n/**\n * Publishes a pulled folder's site: what the server holds as drafts becomes\n * the live site. It sends no file, so local edits that were not pushed are\n * not in it.\n */\nexport async function runPublish(\n dirArg: string | undefined,\n options: PublishOptions,\n deps: PublishDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<PublishResult> {\n const { dir, binding, api } = await openBoundSite(\n dirArg,\n options.as,\n deps,\n context,\n );\n const site = await publishBoundSite(\n api,\n binding,\n dir,\n context,\n deps.log,\n \"The site was not published.\",\n );\n return { site, dir };\n}\n\nexport function createPublishCommand(\n inputs: CommandInputs,\n deps: PublishDeps = defaultDeps,\n): Command {\n const command = new Command(\"publish\")\n .description(\n \"Publish the site: its saved drafts become the live site. Prints the status and the new content_version\",\n )\n .argument(\"[dir]\", \"The pulled folder (default: the current folder)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .action((dir: string | undefined, options: PublishOptions) => {\n const context: ErrorContext = {};\n return runCommand(\n () =>\n runPublish(dir, options, { ...deps, inputs }, context).then(\n () => undefined,\n ),\n context,\n );\n });\n return declareInputs(command, (_opts, args) => [\n { kind: \"config\", path: resolve(args[0] ?? \".\", BINDING_FILE) },\n ]);\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { declareInputs, type CommandInputs } from \"@fluid-app/fluid-cli\";\nimport { ApiError } from \"@fluid-app/api-client-core\";\nimport { createClients, loadLogin } from \"../api/credential.js\";\nimport {\n MemberSiteCliError,\n describeError,\n type ErrorContext,\n} from \"../api/errors.js\";\nimport type { Site, SiteApi, SitePage } from \"../api/types.js\";\nimport {\n BINDING_FILE,\n PAGES_CONFIG_PATH,\n pageRow,\n serializeJson,\n sha256,\n type SiteBinding,\n} from \"../tree/binding.js\";\nimport { writeLocal } from \"../tree/folder.js\";\nimport {\n inputTreeSource,\n readLocalTree,\n SETTINGS_PATH,\n type LocalTree,\n} from \"../tree/local-tree.js\";\nimport { pageFilePath } from \"../tree/pages-config.js\";\nimport {\n describeStep,\n pageCapacityRefusal,\n planPush,\n sameJson,\n type PushPlan,\n type PushStep,\n} from \"../tree/push-plan.js\";\nimport { openBoundSite, type BoundSiteDeps } from \"./bound-site.js\";\nimport { publishBoundSite } from \"./publish.js\";\nimport { runCommand } from \"./run.js\";\n\nexport interface PushOptions {\n as?: string;\n dryRun?: boolean;\n publish?: boolean;\n}\n\nexport type PushDeps = BoundSiteDeps;\n\n/** Names a push never reads, besides the dot names its declared tree skips. */\nexport const PUSH_TREE_IGNORE = [\"node_modules\"] as const;\n\nconst defaultDeps: PushDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\n/** What a push did. */\nexport interface PushResult {\n plan: PushPlan;\n dir: string;\n /** The requests that succeeded, in order. Empty for a dry run. */\n sent: string[];\n /** The site as the publish answered it, when `--publish` ran one. */\n published?: Site;\n}\n\n/** The lines `push` prints for a plan, before it sends anything. */\nexport function formatPlan(\n binding: SiteBinding,\n plan: PushPlan,\n dryRun: boolean,\n publish = false,\n): string[] {\n const lines = [\n `${dryRun ? \"Dry run. \" : \"\"}Push to ${binding.owner_kind} site ${chalk.cyan(binding.handle)}`,\n ];\n if (plan.steps.length === 0) {\n lines.push(\" Nothing to send.\");\n } else {\n lines.push(\n dryRun ? \" Would send, in this order:\" : \" Sending, in this order:\",\n ...plan.steps.map((step, i) => ` ${i + 1}. ${describeStep(step)}`),\n );\n }\n if (plan.immediate.length > 0) {\n lines.push(\n \" These take effect as soon as they are written, without a publish:\",\n ...plan.immediate.map((line) => ` ${line}`),\n );\n }\n for (const note of plan.notes) lines.push(chalk.dim(` ${note}`));\n if (plan.notSent.length > 0) {\n lines.push(\n \" Not sent:\",\n ...plan.notSent.map((item) => ` ${item.subject}: ${item.why}`),\n );\n }\n if (dryRun && plan.steps.some((step) => step.kind === \"page-create\")) {\n lines.push(\n \" The site's page limit was not checked, because a dry run sends no request.\",\n );\n }\n if (publish) {\n lines.push(\n dryRun\n ? \" A publish would follow, after the last write.\"\n : \" A publish follows the last write.\",\n );\n }\n if (dryRun) lines.push(\" Nothing was sent.\");\n return lines;\n}\n\n/** The thing a 409 on this step names. */\nfunction subjectOf(step: PushStep): string {\n switch (step.kind) {\n case \"settings\":\n case \"menus\":\n case \"file-write\":\n case \"file-delete\":\n return step.path;\n case \"page-delete\":\n return `The page \"${step.slug}\"`;\n case \"page-create\":\n return `The page \"${step.slug}\"`;\n case \"page-update\":\n return step.content === undefined\n ? `The page \"${step.slug}\" (its row in ${PAGES_CONFIG_PATH})`\n : pageFilePath(step.slug);\n case \"page-menu\":\n return `The page \"${step.slug}\" (its menu in ${PAGES_CONFIG_PATH})`;\n }\n}\n\n/** What a 409 on this step means: a file created meanwhile, or a removal refused. */\nfunction conflictOf(step: PushStep): ErrorContext[\"conflict\"] {\n if (step.kind === \"file-write\" && \"mustNotExist\" in step.precondition) {\n return \"exists\";\n }\n return step.kind === \"page-delete\" || step.kind === \"file-delete\"\n ? \"delete\"\n : undefined;\n}\n\nfunction sameRowExceptMenu(\n a: NonNullable<SiteBinding[\"pages\"][string][\"row\"]>,\n b: NonNullable<SiteBinding[\"pages\"][string][\"row\"]>,\n): boolean {\n return (\n a.title === b.title &&\n a.position === b.position &&\n a.published === b.published &&\n a.layout === b.layout\n );\n}\n\n/**\n * Sends a plan's steps, one request each. The binding takes each response's\n * versions as it arrives and is saved after every success, so a push that\n * stops can be run again after a pull.\n */\nasync function execute(\n api: SiteApi,\n binding: SiteBinding,\n plan: PushPlan,\n tree: LocalTree,\n dir: string,\n context: ErrorContext,\n sent: string[],\n log: (line: string) => void,\n): Promise<void> {\n const siteId = binding.site_id;\n let siteMoved = false;\n\n const save = async () => {\n const text = serializeJson(binding);\n await writeLocal(dir, BINDING_FILE, text);\n };\n\n for (const step of plan.steps) {\n context.subject = subjectOf(step);\n context.conflict = conflictOf(step);\n switch (step.kind) {\n case \"settings\": {\n const file = await api.writeFile(siteId, step.path, {\n content: step.content,\n lock_version: step.lockVersion,\n });\n const lockVersion = file.lock_version ?? step.lockVersion;\n const prior = binding.files[step.path];\n binding.settings.lock_version = lockVersion;\n binding.files[step.path] = {\n lock_version: lockVersion,\n updated_at: prior?.updated_at ?? null,\n version: file.version ?? String(lockVersion),\n sha256: sha256(step.content),\n };\n break;\n }\n case \"file-write\": {\n const file = await api.writeFile(siteId, step.path, {\n content: step.content,\n ...(\"version\" in step.precondition\n ? { version: step.precondition.version }\n : { must_not_exist: true }),\n });\n const prior = binding.files[step.path];\n binding.files[step.path] = {\n lock_version: null,\n updated_at: prior?.updated_at ?? null,\n ...(typeof file.version === \"string\"\n ? { version: file.version }\n : {}),\n sha256: sha256(step.content),\n };\n break;\n }\n case \"page-create\": {\n const page = await api.createPage(siteId, {\n slug: step.slug,\n title: step.row.title,\n position: step.row.position,\n published: step.row.published,\n layout_name: step.row.layout,\n content: step.content,\n });\n siteMoved = true;\n binding.pages[step.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n binding.files[pageFilePath(step.slug)] = {\n lock_version: null,\n updated_at: page.updated_at,\n sha256: sha256(step.content),\n };\n break;\n }\n case \"page-update\": {\n const page = await api.updatePage(siteId, step.id, {\n ...(\"title\" in step.fields ? { title: step.fields.title } : {}),\n ...(\"position\" in step.fields\n ? { position: step.fields.position }\n : {}),\n ...(\"published\" in step.fields\n ? { published: step.fields.published }\n : {}),\n ...(\"layout\" in step.fields\n ? { layout_name: step.fields.layout }\n : {}),\n ...(step.content === undefined ? {} : { content: step.content }),\n lock_version: step.lockVersion,\n });\n siteMoved = true;\n binding.pages[step.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n if (step.content !== undefined) {\n const path = pageFilePath(step.slug);\n // The page's response has no file version, so none is kept.\n binding.files[path] = {\n lock_version: null,\n updated_at: page.updated_at,\n sha256: sha256(step.content),\n };\n }\n break;\n }\n case \"menus\": {\n const file = await api.writeFile(siteId, step.path, {\n content: step.content,\n version: step.version,\n });\n siteMoved = true;\n binding.files[step.path] = {\n lock_version: null,\n updated_at: null,\n ...(typeof file.version === \"string\"\n ? { version: file.version }\n : {}),\n sha256: sha256(step.content),\n };\n break;\n }\n case \"page-menu\": {\n const bound = binding.pages[step.slug];\n if (!bound) {\n throw new MemberSiteCliError(\n `The page \"${step.slug}\" is not in the binding.`,\n );\n }\n const page = await api.updatePage(siteId, bound.id, {\n menu: step.menu,\n lock_version: bound.lock_version,\n });\n siteMoved = true;\n binding.pages[step.slug] = {\n id: page.id,\n lock_version: page.lock_version,\n row: pageRow(page),\n };\n break;\n }\n case \"page-delete\": {\n // The version the binding holds now: a menus write moved it for a\n // page it let go of, and the push took the new one.\n const bound = binding.pages[step.slug];\n await api.deletePage(siteId, step.id, {\n lockVersion: bound?.lock_version ?? step.lockVersion,\n });\n siteMoved = true;\n delete binding.pages[step.slug];\n delete binding.files[pageFilePath(step.slug)];\n break;\n }\n case \"file-delete\": {\n await api.deleteFile(siteId, step.path, { version: step.version });\n delete binding.files[step.path];\n break;\n }\n }\n await save();\n sent.push(describeStep(step));\n\n if (step.kind === \"menus\") {\n // The menus write moved the site's version, and the version of every\n // page assigned to a menu it dropped, so the page versions are read\n // again before an assignment carries one.\n context.subject = \"The site's pages\";\n adoptPageVersions(binding, await api.listPages(siteId));\n await save();\n }\n }\n\n if (siteMoved) {\n // Every page and menus write moved the settings version. It is read\n // again, and kept only when the settings are still the ones this folder\n // holds: a copy another writer changed meanwhile must still conflict.\n context.subject = SETTINGS_PATH;\n const local = tree.files.get(SETTINGS_PATH);\n const file = await api.readFile(siteId, SETTINGS_PATH);\n if (local !== undefined && file.lock_version !== null) {\n if (sameJson(file.content, local)) {\n const lockVersion = file.lock_version;\n const prior = binding.files[SETTINGS_PATH];\n binding.settings.lock_version = lockVersion;\n binding.files[SETTINGS_PATH] = {\n lock_version: lockVersion,\n updated_at: prior?.updated_at ?? null,\n version: file.version ?? String(lockVersion),\n sha256: prior?.sha256 ?? sha256(local),\n };\n } else {\n log(\n chalk.yellow(\n ` ${SETTINGS_PATH} changed on the server during the push, so its version was not recorded. Pull before the next push.`,\n ),\n );\n }\n }\n }\n\n // The file counts as pushed once every row in it is what the server holds.\n const pagesText = tree.files.get(PAGES_CONFIG_PATH);\n if (plan.pagesConfigSettled && pagesText !== undefined) {\n binding.pages_config_sha256 = sha256(pagesText);\n }\n await save();\n}\n\n/**\n * Takes the versions of a fresh page list into the binding, for each page\n * whose server row is the one the binding holds, apart from a menu the write\n * dropped. A page someone else changed keeps its old version and conflicts.\n */\nfunction adoptPageVersions(binding: SiteBinding, fresh: SitePage[]): void {\n for (const page of fresh) {\n const entry = Object.entries(binding.pages).find(\n ([slug, bound]) => bound.id === page.id && slug === page.slug,\n );\n const bound = entry?.[1];\n if (!bound?.row) continue;\n const row = pageRow(page);\n if (\n sameRowExceptMenu(bound.row, row) &&\n (row.menu === bound.row.menu || row.menu === null)\n ) {\n bound.lock_version = page.lock_version;\n bound.row = row;\n }\n }\n}\n\n/**\n * Pushes the folder's changes to its site: settings, templates and assets,\n * page rows and page files, menus, and the removal of pages and files. Each\n * write and delete carries the version the binding recorded. Everything that\n * can be refused is refused before the first request.\n */\nexport async function runPush(\n dirArg: string | undefined,\n options: PushOptions,\n deps: PushDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<PushResult> {\n const { dir, binding, api } = await openBoundSite(\n dirArg,\n options.as,\n deps,\n context,\n );\n\n const tree = await readLocalTree(\n dir,\n binding,\n deps.inputs &&\n inputTreeSource(\n dir,\n deps.inputs.tree(dir, {\n ignore: PUSH_TREE_IGNORE,\n skipDotNames: true,\n }),\n PUSH_TREE_IGNORE,\n ),\n );\n const plan = planPush(binding, tree);\n const dryRun = options.dryRun === true;\n const createsPages = plan.steps.some((step) => step.kind === \"page-create\");\n if (plan.refusals.length === 0 && createsPages && !dryRun) {\n // A new page is refused past the site's page limit, and the push does not\n // remove pages, so the count is read before the first write. A dry run\n // sends no request, so it skips the count and says so.\n context.subject = \"The site's pages\";\n const refusal = pageCapacityRefusal(\n plan,\n (await api.listPages(binding.site_id)).length,\n );\n if (refusal) plan.refusals.push(refusal);\n delete context.subject;\n }\n if (plan.refusals.length > 0) {\n throw new MemberSiteCliError(\n `Nothing was sent. These cannot be pushed:\\n${plan.refusals.map((r) => ` ${r}`).join(\"\\n\")}`,\n );\n }\n\n const publish = options.publish === true;\n for (const line of formatPlan(binding, plan, dryRun, publish)) deps.log(line);\n const sent: string[] = [];\n if (dryRun) return { plan, dir, sent };\n\n try {\n await execute(api, binding, plan, tree, dir, context, sent, deps.log);\n } catch (error) {\n const lines = [describeError(error, context)];\n lines.push(\n sent.length > 0\n ? `Written before the stop:\\n${sent.map((s) => ` ${s}`).join(\"\\n\")}`\n : \"Nothing was written.\",\n );\n if (error instanceof ApiError && error.status === 409) {\n lines.push(\"The rest of the push was not sent.\");\n }\n throw new MemberSiteCliError(lines.join(\"\\n\"));\n }\n\n if (sent.length > 0) {\n deps.log(\n `Pushed ${sent.length} change(s) to ${binding.owner_kind} site ${chalk.cyan(binding.handle)}.`,\n );\n }\n\n if (!publish) return { plan, dir, sent };\n // Every write succeeded, or this line would not be reached.\n const published = await publishBoundSite(\n api,\n binding,\n dir,\n context,\n deps.log,\n \"The pushed changes are saved as drafts, and the site was not published.\",\n );\n return { plan, dir, sent, published };\n}\n\nexport function createPushCommand(\n inputs: CommandInputs,\n deps: PushDeps = defaultDeps,\n): Command {\n const command = new Command(\"push\")\n .description(\n \"Push local changes to the site: settings, templates, assets, menus, page rows and page content, and the removal of pages and files. A file or page changed on the server since the pull stops the push\",\n )\n .argument(\"[dir]\", \"The pulled folder (default: the current folder)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .option(\"--dry-run\", \"Print what would be sent, and send nothing\")\n .option(\n \"--publish\",\n \"Publish the site once, after every write succeeded. Never after a failed write\",\n )\n .action((dir: string | undefined, options: PushOptions) => {\n const context: ErrorContext = {};\n return runCommand(\n () =>\n runPush(dir, options, { ...deps, inputs }, context).then(\n () => undefined,\n ),\n context,\n );\n });\n return declareInputs(command, (_opts, args) => {\n const dir = resolve(args[0] ?? \".\");\n return [\n {\n kind: \"tree\",\n path: dir,\n ignore: PUSH_TREE_IGNORE,\n skipDotNames: true,\n },\n { kind: \"config\", path: resolve(dir, BINDING_FILE) },\n ];\n });\n}\n","import type { Server } from \"node:http\";\nimport http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { createClients, loadLogin, parseLoginKind } from \"../api/credential.js\";\nimport {\n MemberSiteCliError,\n describeError,\n type ErrorContext,\n} from \"../api/errors.js\";\nimport { getApiBase, isProductionApiBase } from \"../api/http.js\";\nimport { isLocalPreviewRequest } from \"../dev/local-guard.js\";\nimport { checkPortAvailable } from \"../dev/port-preflight.js\";\nimport { PreviewProxy, type PreviewTarget } from \"../dev/preview-proxy.js\";\nimport { watchFolder } from \"../dev/watcher.js\";\nimport { openBoundSite, type BoundSiteDeps } from \"./bound-site.js\";\nimport { runPush } from \"./push.js\";\nimport { runCommand } from \"./run.js\";\n\n/** Where the preview listens, and the same on every machine. */\nexport const DEV_HOST = \"127.0.0.1\";\nexport const DEFAULT_DEV_PORT = 9500;\n\n/** How long edits are gathered before they are saved. */\nconst DEBOUNCE_MS = 300;\n\n/** What the command prints when the server has no preview route. */\nexport const DRAFT_PREVIEW_UNAVAILABLE =\n \"Draft preview is not available on this server yet, so `fluid member-site dev` did not start. \" +\n \"It shows a draft through the server's draft preview route and never the published page in its place. \" +\n \"Nothing was saved. Use `fluid member-site push` to save drafts, and update the server or the CLI to preview them.\";\n\n/** What the command says when the API is production and `--allow-production` is not given. */\nexport function productionRefusal(apiBase: string, handle: string): string {\n return (\n `This login is for ${apiBase}, which is production or not known to be anything else, and \\`fluid member-site dev\\` saves ` +\n `${handle} to it at start and again on every change you make. Assets, removals, menus and ` +\n \"page rows are live as soon as they are written. Nothing was saved. \" +\n \"Log in to a stage or local API to develop against it, or pass --allow-production \" +\n \"to save to this one.\"\n );\n}\n\nexport interface DevOptions {\n as?: string;\n port?: string;\n /** Saves to the production API, which dev does not do unless it is told to. */\n allowProduction?: boolean;\n}\n\n/** Stops what was started. */\ntype Stop = () => Promise<void> | void;\n\nexport interface DevDeps extends BoundSiteDeps {\n /** Calls `onChange` when the folder changes; returns the stop function. */\n watch: (\n dir: string,\n onChange: () => void,\n onError: (error: unknown) => void,\n ) => Stop | Promise<Stop>;\n /** How long edits are gathered before they are saved. */\n debounceMs: number;\n logError: (line: string) => void;\n}\n\nconst defaultDeps: DevDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n watch: watchFolder,\n debounceMs: DEBOUNCE_MS,\n logError: (line) => console.error(line),\n};\n\n/** A running preview. */\nexport interface DevSession {\n /** Where the preview is served, such as `http://127.0.0.1:9500`. */\n url: string;\n /** Resolves when no save is waiting or running. */\n idle: () => Promise<void>;\n /** Stops the watcher and the server. */\n stop: () => Promise<void>;\n}\n\nfunction parsePort(value: string | undefined): number {\n const port = Number(value ?? DEFAULT_DEV_PORT);\n if (!Number.isInteger(port) || port < 0 || port > 65535) {\n throw new MemberSiteCliError(\n `Invalid port: '${value}'. Must be an integer between 0 and 65535.`,\n );\n }\n return port;\n}\n\nfunction listen(server: Server, port: number): Promise<number> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, DEV_HOST, () => {\n server.off(\"error\", reject);\n resolve((server.address() as AddressInfo).port);\n });\n });\n}\n\n/**\n * Starts the preview of a pulled folder: saves its changes as drafts, keeps\n * saving as it changes, and serves the draft on a loopback port. Nothing\n * starts, and nothing is saved, when the server has no draft preview route.\n * It never publishes.\n */\nexport async function runDev(\n dirArg: string | undefined,\n options: DevOptions,\n deps: DevDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<DevSession> {\n const port = parsePort(options.port);\n const { dir, binding, api } = await openBoundSite(\n dirArg,\n options.as,\n deps,\n context,\n );\n\n // A dev session writes the site, at start and on every save. Production is\n // never written without being asked for, as `fluid theme dev` never writes\n // a live theme unless `--theme` names it. Nothing has been requested yet.\n const apiBase = getApiBase();\n if (isProductionApiBase(apiBase) && !options.allowProduction) {\n throw new MemberSiteCliError(productionRefusal(apiBase, binding.handle));\n }\n\n // Reads only: a missing site or a login that is refused stops here, before\n // anything is written.\n context.subject = \"The site\";\n await api.getSite(binding.site_id);\n delete context.subject;\n\n const target: PreviewTarget = {\n siteId: binding.site_id,\n handle: binding.handle,\n ownerKind: binding.owner_kind,\n };\n const proxy = new PreviewProxy({\n target,\n apiBase,\n getToken: async () =>\n (await deps.loadLogin(parseLoginKind(options.as))).token,\n });\n if ((await proxy.probe()) === \"unavailable\") {\n throw new MemberSiteCliError(DRAFT_PREVIEW_UNAVAILABLE);\n }\n await checkPortAvailable(DEV_HOST, port);\n\n // The folder is watched before it is first read, so an edit made after that\n // is either in the first save or is reported by the watcher and saved after\n // it. Until the preview is up, a change only asks for another first save.\n // After that it is saved after a pause: a change that arrives while a save\n // runs makes it run again, and never starts a second one beside it.\n let timer: ReturnType<typeof setTimeout> | undefined;\n let running: Promise<void> | undefined;\n let again = false;\n let serving = false;\n let changedAtStart = false;\n\n const save = async (): Promise<void> => {\n const pushContext: ErrorContext = {};\n try {\n const result = await runPush(dir, { as: options.as }, deps, pushContext);\n if (result.sent.length > 0) {\n deps.log(\n chalk.green(\" Saved as drafts. Reload the preview to see them.\"),\n );\n }\n } catch (error) {\n deps.logError(chalk.red(describeError(error, pushContext)));\n }\n };\n const drain = async (): Promise<void> => {\n do {\n again = false;\n await save();\n } while (again);\n running = undefined;\n };\n const trigger = (): void => {\n timer = undefined;\n if (running) again = true;\n else running = drain();\n };\n const onChange = (): void => {\n if (!serving) {\n changedAtStart = true;\n return;\n }\n clearTimeout(timer);\n timer = setTimeout(trigger, deps.debounceMs);\n };\n\n let stopWatching: Stop;\n try {\n stopWatching = await deps.watch(dir, onChange, (error) =>\n deps.logError(chalk.red(`Watcher error: ${String(error)}`)),\n );\n } catch (error) {\n // Without a watcher an edit would never be saved, so nothing is saved or\n // served.\n throw new MemberSiteCliError(\n `Could not watch ${dir} for changes (${String(error)}). Nothing was saved and no preview was started.`,\n );\n }\n\n const server = http.createServer((req, res) => {\n if (!isLocalPreviewRequest(req, DEV_HOST, proxy.previewToken)) {\n res.writeHead(403, {\n \"content-type\": \"text/plain; charset=utf-8\",\n \"cache-control\": \"private, no-store\",\n \"referrer-policy\": \"no-referrer\",\n });\n res.end(\"Local preview requests only.\");\n return;\n }\n proxy.handle(req, res).catch(() => {\n if (!res.headersSent) res.writeHead(502);\n res.end();\n });\n });\n let url: string;\n try {\n // The draft a browser shows is what the server holds, so what is in the\n // folder is saved first. A save that is refused or conflicts stops the\n // command: it would show a draft that is not the folder's. A change made\n // while it ran is saved before the preview is served.\n deps.log(\n `Saving ${chalk.cyan(binding.handle)} to the server as drafts. Nothing is published.`,\n );\n do {\n changedAtStart = false;\n await runPush(dir, { as: options.as }, deps, context);\n } while (changedAtStart);\n url = `http://${DEV_HOST}:${await listen(server, port)}`;\n } catch (error) {\n await stopWatching();\n throw error;\n }\n serving = true;\n // A change that came while the server started is saved as any other.\n if (changedAtStart) onChange();\n\n deps.log(`Preview of ${chalk.cyan(binding.handle)}: ${chalk.cyan(url)}`);\n deps.log(\n \"Edits are saved as drafts as you make them. Run `fluid member-site publish` to make them live.\",\n );\n\n return {\n url,\n idle: async () => {\n while (timer || running) {\n if (timer) {\n clearTimeout(timer);\n trigger();\n }\n await running;\n }\n },\n stop: async () => {\n clearTimeout(timer);\n timer = undefined;\n await stopWatching();\n server.closeAllConnections();\n await new Promise<void>((resolve) => server.close(() => resolve()));\n await running;\n },\n };\n}\n\nexport function createDevCommand(): Command {\n return new Command(\"dev\")\n .description(\n \"Preview a site's draft while you edit: saves each change as a draft, never publishes, and serves the draft rendered by the server on a local port. Refuses the production API unless --allow-production is given\",\n )\n .argument(\"[dir]\", \"The pulled folder (default: the current folder)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .option(\n \"--port <port>\",\n \"Local port to serve the preview on\",\n String(DEFAULT_DEV_PORT),\n )\n .option(\n \"--allow-production\",\n \"Save to the production API, which dev refuses to do otherwise\",\n )\n .action((dir: string | undefined, options: DevOptions) => {\n const context: ErrorContext = {};\n return runCommand(async () => {\n const session = await runDev(dir, options, defaultDeps, context);\n const stop = () => {\n void session.stop().finally(() => process.exit(0));\n };\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n }, context);\n });\n}\n","/**\n * Characters a terminal acts on rather than prints: C0 and C1 controls (escape\n * sequences start with one), DEL, the line and paragraph separators, and the\n * bidirectional overrides that reorder what follows them.\n */\nconst TERMINAL_ACTIVE = new RegExp(\n \"[\\\\u0000-\\\\u001f\\\\u007f-\\\\u009f\\\\u2028\\\\u2029\\\\u202a-\\\\u202e\\\\u2066-\\\\u2069]\",\n \"gu\",\n);\n\n/**\n * Text from the file system, such as a file name, made safe to print: each\n * character a terminal would act on is shown as a `\\u{...}` escape, and the rest\n * is left as it is, so an ordinary path reads the same.\n */\nexport function inert(text: string): string {\n return text.replace(\n TERMINAL_ACTIVE,\n (char) => `\\\\u{${char.codePointAt(0)!.toString(16)}}`,\n );\n}\n","","import settingTypesJson from \"./setting-types.json\" with { type: \"json\" };\n\nexport type SettingType =\n | \"text\"\n | \"plaintext\"\n | \"rich_text\"\n | \"richtext\"\n | \"textarea\"\n | \"html\"\n | \"html_textarea\"\n | \"url\"\n | \"range\"\n | \"number\"\n | \"select\"\n | \"radio\"\n | \"checkbox\"\n | \"color\"\n | \"color_background\"\n | \"font\"\n | \"font_picker\"\n | \"image\"\n | \"image_picker\"\n | \"video_picker\"\n | \"media_picker\"\n | \"text_alignment\"\n | \"media_fit\"\n | \"corner_radius\"\n | \"padding\"\n | \"border\"\n | \"gradient_overlay\"\n | \"header\"\n | \"product\"\n | \"products\"\n | \"collection\"\n | \"collections\"\n | \"category\"\n | \"categories\"\n | \"blog\"\n | \"posts\"\n | \"post\"\n | \"enrollment\"\n | \"enrollments\"\n | \"enrollment_pack\"\n | \"forms\"\n | \"media\"\n | \"variant\"\n | \"link_list\"\n | \"product_list\"\n | \"products_list\"\n | \"collection_list\"\n | \"collections_list\"\n | \"category_list\"\n | \"categories_list\"\n | \"posts_list\"\n | \"enrollment_list\"\n | \"enrollments_list\"\n | \"blog_list\"\n | \"blogs_list\"\n | \"post_list\"\n | \"enrollment_packs_list\";\n\n// Runtime list loaded from the canonical JSON — used for validation.\nexport const VALID_SETTING_TYPES: readonly string[] = Object.values(\n settingTypesJson.types as Record<string, string[]>,\n).flat();\n\n// Compile-time drift guard: if a type exists in the SettingType union but\n// not in setting-types.json, this object literal will error on the missing key.\n// When adding types to setting-types.json, also add them to SettingType above.\nconst _settingTypeCheck: Record<SettingType, true> = {\n text: true,\n plaintext: true,\n rich_text: true,\n richtext: true,\n textarea: true,\n html: true,\n html_textarea: true,\n url: true,\n range: true,\n number: true,\n select: true,\n radio: true,\n checkbox: true,\n color: true,\n color_background: true,\n font: true,\n font_picker: true,\n image: true,\n image_picker: true,\n video_picker: true,\n media_picker: true,\n text_alignment: true,\n media_fit: true,\n corner_radius: true,\n padding: true,\n border: true,\n gradient_overlay: true,\n header: true,\n product: true,\n products: true,\n collection: true,\n collections: true,\n category: true,\n categories: true,\n blog: true,\n posts: true,\n post: true,\n enrollment: true,\n enrollments: true,\n enrollment_pack: true,\n forms: true,\n media: true,\n variant: true,\n link_list: true,\n product_list: true,\n products_list: true,\n collection_list: true,\n collections_list: true,\n category_list: true,\n categories_list: true,\n posts_list: true,\n enrollment_list: true,\n enrollments_list: true,\n blog_list: true,\n blogs_list: true,\n post_list: true,\n enrollment_packs_list: true,\n} satisfies Record<SettingType, true>;\nvoid _settingTypeCheck;\n\nexport interface SelectOption {\n label: string;\n value: string;\n}\n\nexport interface SchemaSetting {\n type: SettingType;\n id: string;\n default?: string | number | boolean | null;\n label?: string;\n options?: SelectOption[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n content?: string;\n visible_if?: Record<string, unknown>;\n}\n\nexport interface SchemaBlock {\n type: string;\n name?: string;\n limit?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[];\n}\n\nexport interface SchemaPreset {\n name?: string;\n category?: string;\n settings?: Record<string, unknown>;\n blocks?: Array<{ type: string; settings?: Record<string, unknown> }>;\n}\n\nexport interface SectionSchema {\n name?: string;\n tag?: string;\n class?: string;\n enabled_on?: { templates?: string[] };\n disabled_on?: { templates?: string[] };\n max_blocks?: number;\n settings?: SchemaSetting[];\n blocks?: SchemaBlock[] | Record<string, unknown>;\n presets?: SchemaPreset[];\n}\n\nexport type BlocksSchemaType = \"array\" | \"object\" | \"unknown\";\n\n/**\n * Structured locator describing the schema element a diagnostic refers to.\n *\n * The rule logic (this package) is intentionally position-agnostic so it can\n * be shared by the CLI (`fluid theme push`) and the CodeMirror-based theme /\n * visual editor alike. Position-aware consumers use `target` to map a\n * diagnostic back to a source range without re-deriving the validation rules.\n */\nexport type SettingDiagnosticTarget = {\n kind: \"setting\";\n /** Index of the setting within its `settings` array. */\n index: number;\n /** The setting's `id`, when present — used to locate the offending entry. */\n settingId?: string;\n /** The setting's `type`, when present — used to locate the offending entry. */\n settingType?: string;\n /** Which field the diagnostic concerns. */\n field: \"id\" | \"type\";\n};\n\nexport type BlockDiagnosticTarget = {\n kind: \"block\";\n /** Index of the block within its `blocks` array. */\n index: number;\n /** The block's `type`, when present — used to locate the offending entry. */\n blockType?: string;\n /** Which field the diagnostic concerns. */\n field: \"type\" | \"name\" | \"settings\";\n};\n\nexport type SectionDiagnosticTarget = {\n kind: \"section\";\n /** The referenced section `type` that has no matching section file. */\n sectionType: string;\n /** The `{% section %}` tag's instance id, when the tag included one. */\n tagId?: string;\n};\n\nexport type TemplateDiagnosticTarget = {\n kind: \"template\";\n /** The routed page type (`post`, `product`, …) the theme has no template for. */\n templateType: string;\n};\n\nexport type MemberManifestDiagnosticTarget = {\n kind: \"member_manifest\";\n /** JSON path of the offending entry in `members_sites.json`, as the server\n * reports it (`$.sites[0].pages[1].slug`). */\n pointer: string;\n};\n\nexport type DiagnosticTarget =\n | SettingDiagnosticTarget\n | BlockDiagnosticTarget\n | SectionDiagnosticTarget\n | TemplateDiagnosticTarget\n | MemberManifestDiagnosticTarget;\n\nexport interface Diagnostic {\n severity: \"error\" | \"warning\";\n message: string;\n /**\n * Optional structured locator so position-aware consumers (e.g. the\n * CodeMirror-based theme editor) can map a diagnostic back to a source\n * range. The CLI ignores this and only renders `message`.\n */\n target?: DiagnosticTarget;\n}\n","import { VALID_SETTING_TYPES } from \"./types\";\nimport type { Diagnostic } from \"./types\";\n\n/**\n * Message shown when a setting declares a `type` that is not one of the\n * canonical `VALID_SETTING_TYPES`. Centralized here so the CLI and the editor\n * render identical text. Kept to a single line — the list of valid types is a\n * static set exposed once via the `VALID_SETTING_TYPES` export, so repeating it\n * in every diagnostic only bloats structured output.\n */\nexport function invalidSettingTypeMessage(type: string): string {\n return `Invalid settings type: '${type}'`;\n}\n\nexport function validateSettings(settings: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const ids = new Set<string>();\n\n for (let index = 0; index < settings.length; index++) {\n const raw = settings[index];\n const setting: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const id = typeof setting.id === \"string\" ? setting.id : undefined;\n const type = typeof setting.type === \"string\" ? setting.type : undefined;\n\n if (id !== undefined && id.trim() === \"\") {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in settings: id cannot be empty\",\n target: { kind: \"setting\", index, settingType: type, field: \"id\" },\n });\n } else if (id && ids.has(id)) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in settings: duplicate id '${id}' found`,\n target: { kind: \"setting\", index, settingId: id, field: \"id\" },\n });\n } else if (id) {\n ids.add(id);\n }\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in setting '${id ?? index}': missing required field 'type'`,\n target: { kind: \"setting\", index, settingId: id, field: \"type\" },\n });\n } else if (!VALID_SETTING_TYPES.includes(type)) {\n diagnostics.push({\n severity: \"error\",\n message: invalidSettingTypeMessage(type),\n target: { kind: \"setting\", index, settingType: type, field: \"type\" },\n });\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\n\nexport function validateBlocks(blocks: unknown[]): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n const types = new Set<string>();\n\n for (let index = 0; index < blocks.length; index++) {\n const raw = blocks[index];\n const block: Record<string, unknown> =\n raw !== null && typeof raw === \"object\" && !Array.isArray(raw)\n ? (raw as Record<string, unknown>)\n : {};\n\n const type = typeof block.type === \"string\" ? block.type : undefined;\n const name = typeof block.name === \"string\" ? block.name : undefined;\n const settings = block.settings;\n\n if (!type) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in blocks at index ${index}: missing required field 'type'`,\n target: { kind: \"block\", index, field: \"type\" },\n });\n } else if (types.has(type)) {\n diagnostics.push({\n severity: \"warning\",\n message: `Warning in blocks: duplicate type '${type}' found`,\n target: { kind: \"block\", index, blockType: type, field: \"type\" },\n });\n } else {\n types.add(type);\n }\n\n // Named block references (type only, no name or settings) point to\n // standalone block templates — skip the name requirement for those.\n const isNamedBlockRef = !name && !settings;\n if (!name && type !== \"@app\" && type !== \"@theme\" && !isNamedBlockRef) {\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': missing required field 'name'`,\n target: { kind: \"block\", index, blockType: type, field: \"name\" },\n });\n }\n\n if (settings) {\n if (!Array.isArray(settings)) {\n // e.g. the author wrote `\"settings\": {}` instead of `\"settings\": []`.\n diagnostics.push({\n severity: \"error\",\n message: `Error in block '${type ?? index}': 'settings' must be an array ([])`,\n target: { kind: \"block\", index, blockType: type, field: \"settings\" },\n });\n } else {\n diagnostics.push(...validateSettings(settings));\n }\n }\n\n // Recurse into nested blocks (max 2 levels enforced by the engine,\n // but we validate whatever is declared)\n if (Array.isArray(block.blocks)) {\n diagnostics.push(...validateBlocks(block.blocks as unknown[]));\n }\n }\n\n return diagnostics;\n}\n","import type { BlocksSchemaType, Diagnostic } from \"./types\";\nimport { validateSettings } from \"./validate-settings\";\nimport { validateBlocks } from \"./validate-blocks\";\n\n// Strip Liquid comment blocks so they don't interfere with schema extraction.\nfunction stripLiquidComments(text: string): string {\n return text.replace(\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g,\n \"\",\n );\n}\n\n// Detect duplicate \"blocks\" keys in the same JSON object.\n// Standard JSON.parse silently drops duplicates, so we scan tokens manually.\nfunction findDuplicateBlocksKeys(jsonText: string): number {\n let count = 0;\n const stack: Array<{\n type: \"object\" | \"array\";\n keys: Set<string>;\n expectingKey: boolean;\n }> = [];\n let pendingKey: string | null = null;\n let i = 0;\n\n while (i < jsonText.length) {\n const ch = jsonText.charCodeAt(i);\n\n // Whitespace\n if (ch === 0x20 || ch === 0x0a || ch === 0x0d || ch === 0x09) {\n i++;\n continue;\n }\n\n if (ch === 0x7b) {\n // {\n stack.push({ type: \"object\", keys: new Set(), expectingKey: true });\n pendingKey = null;\n i++;\n } else if (ch === 0x7d) {\n // }\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x5b) {\n // [\n pendingKey = null;\n stack.push({ type: \"array\", keys: new Set(), expectingKey: false });\n i++;\n } else if (ch === 0x5d) {\n // ]\n stack.pop();\n pendingKey = null;\n i++;\n } else if (ch === 0x3a) {\n // :\n i++;\n } else if (ch === 0x2c) {\n // ,\n const top = stack[stack.length - 1];\n if (top?.type === \"object\") {\n top.expectingKey = true;\n }\n pendingKey = null;\n i++;\n } else if (ch === 0x22) {\n // \"\n let j = i + 1;\n while (j < jsonText.length) {\n if (\n jsonText.charCodeAt(j) === 0x22 &&\n jsonText.charCodeAt(j - 1) !== 0x5c\n ) {\n break;\n }\n j++;\n }\n const str = jsonText.slice(i + 1, j);\n i = j + 1;\n\n const top = stack[stack.length - 1];\n if (top?.type === \"object\" && top.expectingKey) {\n if (str === \"blocks\" && top.keys.has(str)) {\n count++;\n }\n top.keys.add(str);\n top.expectingKey = false;\n pendingKey = str;\n } else {\n pendingKey = null;\n }\n } else {\n pendingKey = null;\n i++;\n }\n }\n\n return count;\n}\n\nexport interface ValidateSchemaOptions {\n blocksSchemaType?: BlocksSchemaType;\n}\n\n// Validate the full Liquid file content containing a {% schema %} block.\n// Returns an array of diagnostics (empty = valid).\nexport function validateSchemaText(\n text: string,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n const stripped = stripLiquidComments(text);\n const match = stripped.match(\n /\\{%-?\\s*schema\\s*-?%\\}([\\s\\S]*?)\\{%-?\\s*endschema\\s*-?%\\}/,\n );\n if (!match) return diagnostics;\n\n const jsonText = match[1] ?? \"\";\n\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(jsonText) as Record<string, unknown>;\n } catch (e) {\n diagnostics.push({\n severity: \"error\",\n message: `Invalid JSON:\\n ${(e as Error).message}`,\n });\n return diagnostics;\n }\n\n const dupes = findDuplicateBlocksKeys(jsonText);\n for (let d = 0; d < dupes; d++) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: duplicate 'blocks' key in the same object\",\n });\n }\n\n // Settings\n if (\n schema !== null &&\n typeof schema === \"object\" &&\n Array.isArray(schema.settings)\n ) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n // Blocks\n if (schema !== null && typeof schema === \"object\" && \"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n // \"unknown\" — validate if array, accept if object\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n\n// Validate a parsed schema object directly (when you already have the JSON).\nexport function validateSchema(\n schema: Record<string, unknown>,\n options?: ValidateSchemaOptions,\n): Diagnostic[] {\n const blocksSchemaType = options?.blocksSchemaType ?? \"unknown\";\n const diagnostics: Diagnostic[] = [];\n\n if (Array.isArray(schema.settings)) {\n diagnostics.push(...validateSettings(schema.settings));\n }\n\n if (\"blocks\" in schema) {\n const blocks = schema.blocks;\n\n if (blocksSchemaType === \"array\") {\n if (!Array.isArray(blocks)) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an array ([])\",\n });\n } else {\n diagnostics.push(...validateBlocks(blocks));\n }\n } else if (blocksSchemaType === \"object\") {\n if (\n Array.isArray(blocks) ||\n typeof blocks !== \"object\" ||\n blocks === null\n ) {\n diagnostics.push({\n severity: \"error\",\n message: \"Error in blocks: expected an object ({})\",\n });\n }\n } else {\n if (Array.isArray(blocks)) {\n diagnostics.push(...validateBlocks(blocks));\n }\n }\n }\n\n return diagnostics;\n}\n","import type { Diagnostic } from \"./types\";\n\n// Liquid comment blocks — stripped so commented-out section tags are not\n// treated as real references.\nconst LIQUID_COMMENT_REGEX =\n /\\{%-?\\s*comment\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endcomment\\s*-?%\\}/g;\n\n// The `{% schema %} … {% endschema %}` block — removed so section types\n// declared inside the schema JSON are not mistaken for `{% section %}` usages.\nconst SCHEMA_BLOCK_REGEX =\n /\\{%-?\\s*schema\\s*-?%\\}[\\s\\S]*?\\{%-?\\s*endschema\\s*-?%\\}/;\n\n// Matches a `{% section %}` tag. Supports both the id-bearing form\n// (`{% section 'hero', id: 'abc' %}`) emitted by the visual editor and the\n// bare form (`{% section 'hero' %}`) hand-authored themes use, plus\n// whitespace-control tags (`{%- … -%}`) and single or double quotes.\n// `id` is optional — capture group 2 is undefined for bare tags.\nconst SECTION_TAG_PATTERN =\n \"\\\\{%-?\\\\s*section\\\\s+['\\\"]([^'\\\"]+)['\\\"](?:\\\\s*,\\\\s*id:\\\\s*['\\\"]([^'\\\"]+)['\\\"])?\\\\s*-?%\\\\}\";\n\n// Reserved layout-region section types. `{% section 'navbar' %}` and friends\n// resolve to the theme's navbar/footer/library_navbar template slots rather than\n// a `sections/<name>` definition, and render empty when absent — so they are\n// never \"missing\". Mirrors `LiquidTags::Section::SECTION_TEMPLATES` server-side.\nconst RESERVED_SECTION_TYPES = new Set([\"navbar\", \"library_navbar\", \"footer\"]);\n\n// `fluid://extensions/{id}/{type}/{name}` references resolve against an app\n// extension installed on the company at render time — they cannot be validated\n// against local files, so they are never flagged. Mirrors `Themes::ExtensionUri`.\nconst EXTENSION_URI_PATTERN = /^fluid:\\/\\/extensions\\/[^/]+\\/[^/]+\\/[^/]+$/;\n\n// `fluid://native/widgets/{key}` (or `{key}/sections/{name}` for a bundle's\n// inner section) references a Fluid-owned native widget that the server\n// resolves at render time — it has no local file. Mirrors\n// `Themes::NativeWidgets::Catalog` (`URI_PREFIX`, `resolve`) and the name rules\n// of `Themes::NativeWidgets::Bundle.valid_name?`. Capture group 1 is the key.\nconst NATIVE_WIDGET_URI_PATTERN =\n /^fluid:\\/\\/native\\/widgets\\/([a-zA-Z0-9_-]+)(?:\\/sections\\/[a-zA-Z0-9_-]+)?$/;\n\n// Native widget keys. Copy of `Themes::NativeWidgets::Catalog::KEYS`\n// (apps/rails/app/models/themes/native_widgets/catalog.rb), in the same order;\n// `test_theme_cli_lint_knows_every_native_widget_key` in the Rails catalog test\n// fails when the two drift apart.\nexport const NATIVE_WIDGET_KEYS: readonly string[] = [\n \"member\",\n \"next\",\n \"points\",\n \"order\",\n \"again\",\n \"orders\",\n \"onway\",\n \"subs\",\n \"carenext\",\n \"journey\",\n \"careteam\",\n \"labs\",\n \"meds\",\n \"wallet\",\n \"status\",\n \"benefits\",\n \"links\",\n \"givget\",\n \"refs\",\n \"goals\",\n \"resume\",\n \"course\",\n \"courses\",\n \"certs\",\n \"signals\",\n \"funnel\",\n \"pulse\",\n \"live\",\n \"nextsend\",\n \"today\",\n \"acct\",\n \"sign\",\n \"owed\",\n \"chsales\",\n \"chgoal\",\n \"chbest\",\n \"chmix\",\n \"plink\",\n \"pcar\",\n \"nextstep\",\n \"brief\",\n \"asked\",\n \"working\",\n \"streak\",\n \"earnings\",\n \"sponsor\",\n \"contacts\",\n \"tasks\",\n \"messages\",\n \"groups\",\n \"repcard\",\n \"snapshot\",\n \"social\",\n \"team\",\n \"egoal\",\n \"ntitle\",\n \"circle\",\n \"teamfeed\",\n \"site\",\n \"link\",\n \"video\",\n \"photos\",\n \"product\",\n \"countdown\",\n \"qualify\",\n \"board\",\n \"pass\",\n];\n\nconst NATIVE_WIDGET_KEY_SET = new Set(NATIVE_WIDGET_KEYS);\n\nfunction nativeWidgetKeyOf(type: string): string | undefined {\n return NATIVE_WIDGET_URI_PATTERN.exec(type)?.[1];\n}\n\nfunction isNativeWidgetUri(type: string): boolean {\n const key = nativeWidgetKeyOf(type);\n return key !== undefined && NATIVE_WIDGET_KEY_SET.has(key);\n}\n\n/** Whether a `{% section %}` type resolves to something other than an on-disk `sections/<name>` definition (and so cannot be flagged as missing). */\nexport function isNonLocalSectionType(type: string): boolean {\n return (\n RESERVED_SECTION_TYPES.has(type) ||\n EXTENSION_URI_PATTERN.test(type) ||\n isNativeWidgetUri(type)\n );\n}\n\nexport interface SectionReference {\n /** The referenced section type/name (group 1). */\n type: string;\n /** The section instance id, when the tag declares one. */\n id?: string;\n /** The full matched tag text. */\n fullTag: string;\n /** 0-based position of the tag within the template body. */\n order: number;\n}\n\n/** A template, identified by `path`, with its raw liquid `content`. */\nexport interface TemplateInput {\n path: string;\n content: string;\n}\n\n// Liquid outside the comment and schema blocks — the rendered template body\n// where `{% section %}` references actually live.\nfunction templateBody(liquid: string): string {\n return liquid\n .replace(LIQUID_COMMENT_REGEX, \"\")\n .replace(SCHEMA_BLOCK_REGEX, \"\");\n}\n\n/**\n * Extract every `{% section %}` reference from a liquid template, ignoring\n * tags inside comments or the `{% schema %}` block. Shared by the editor's\n * section-usage detection and the CLI linter so both parse references\n * identically.\n */\nexport function extractSectionReferences(liquid: string): SectionReference[] {\n const body = templateBody(liquid);\n const pattern = new RegExp(SECTION_TAG_PATTERN, \"g\");\n const references: SectionReference[] = [];\n let order = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(body)) !== null) {\n const type = match[1];\n if (!type) continue;\n references.push({ type, id: match[2], fullTag: match[0], order: order++ });\n }\n return references;\n}\n\n/**\n * Paths of the templates that reference `sectionName`. Used by the editor to\n * warn before deleting a section that is still in use.\n */\nexport function findTemplatesReferencingSection(\n templates: TemplateInput[],\n sectionName: string,\n): string[] {\n const matches: string[] = [];\n for (const template of templates) {\n const references = extractSectionReferences(template.content);\n if (references.some((reference) => reference.type === sectionName)) {\n matches.push(template.path);\n }\n }\n return matches;\n}\n\nexport interface MissingSectionRef {\n templatePath: string;\n sectionType: string;\n diagnostic: Diagnostic;\n}\n\n/**\n * Find `{% section %}` references that point to a section that does not exist\n * in `existingSectionNames` — the static equivalent of \"an in-use section was\n * deleted\". Reserved layout-region types (navbar/footer/library_navbar) and\n * `fluid://` extension and known native widget URIs are never flagged (see\n * `isNonLocalSectionType`). Emits one `error` diagnostic per missing section\n * type per template. A well-formed native widget URI with an unknown key gets a\n * `warning` instead: the key may belong to a widget newer than this copy of\n * `NATIVE_WIDGET_KEYS`, so it must not fail a lint run.\n */\nexport function findMissingSectionReferences(\n templates: TemplateInput[],\n existingSectionNames: Set<string>,\n): MissingSectionRef[] {\n const missing: MissingSectionRef[] = [];\n for (const template of templates) {\n const reported = new Set<string>();\n for (const reference of extractSectionReferences(template.content)) {\n if (existingSectionNames.has(reference.type)) continue;\n if (isNonLocalSectionType(reference.type)) continue;\n if (reported.has(reference.type)) continue;\n reported.add(reference.type);\n const unknownKey = nativeWidgetKeyOf(reference.type);\n missing.push({\n templatePath: template.path,\n sectionType: reference.type,\n diagnostic: {\n severity: unknownKey === undefined ? \"error\" : \"warning\",\n message:\n unknownKey === undefined\n ? `references missing section '${reference.type}'`\n : `references unknown native widget key '${unknownKey}' (it may be newer than this CLI)`,\n target: {\n kind: \"section\",\n sectionType: reference.type,\n tagId: reference.id,\n },\n },\n });\n }\n }\n return missing;\n}\n","export interface LiquidBlockTagDiagnostic {\n severity: \"error\";\n message: string;\n}\n\nconst BLOCK_TAGS = new Map([\n [\"capture\", \"endcapture\"],\n [\"case\", \"endcase\"],\n [\"comment\", \"endcomment\"],\n [\"for\", \"endfor\"],\n [\"form\", \"endform\"],\n [\"if\", \"endif\"],\n [\"ifchanged\", \"endifchanged\"],\n [\"javascript\", \"endjavascript\"],\n [\"paginate\", \"endpaginate\"],\n [\"raw\", \"endraw\"],\n [\"schema\", \"endschema\"],\n [\"style\", \"endstyle\"],\n [\"stylesheet\", \"endstylesheet\"],\n [\"tablerow\", \"endtablerow\"],\n [\"unless\", \"endunless\"],\n]);\n\nconst CLOSING_TAGS = new Set(BLOCK_TAGS.values());\nconst OPAQUE_BLOCK_TAGS = new Set([\n \"comment\",\n \"javascript\",\n \"raw\",\n \"schema\",\n \"style\",\n \"stylesheet\",\n]);\n\ninterface OpenBlock {\n name: string;\n expectedClose: string;\n line: number;\n}\n\n/**\n * Find structurally unbalanced Liquid block tags such as an `{% if %}` with\n * no `{% endif %}`. This intentionally recognizes only established paired\n * tags; custom and inline tags are ignored rather than guessed at.\n *\n * Content inside raw/comment/schema/style/javascript blocks is opaque to\n * Liquid and therefore skipped until that block's matching close tag. This\n * prevents CSS, JSON, and examples containing Liquid-looking text from\n * producing false errors.\n */\nexport function findLiquidBlockTagDiagnostics(\n content: string,\n): LiquidBlockTagDiagnostic[] {\n const stack: OpenBlock[] = [];\n const diagnostics: LiquidBlockTagDiagnostic[] = [];\n let line = 1;\n let previousTagIndex = 0;\n\n const processTag = (name: string, tagLine: number): void => {\n const open = stack.at(-1);\n\n if (open && OPAQUE_BLOCK_TAGS.has(open.name)) {\n if (name === open.expectedClose) stack.pop();\n return;\n }\n\n const expectedClose = BLOCK_TAGS.get(name);\n if (expectedClose) {\n stack.push({ name, expectedClose, line: tagLine });\n return;\n }\n\n if (!CLOSING_TAGS.has(name)) return;\n\n if (!open) {\n diagnostics.push({\n severity: \"error\",\n message: `Unexpected Liquid tag '{% ${name} %}' on line ${tagLine}; there is no open block to close.`,\n });\n return;\n }\n\n if (name !== open.expectedClose) {\n diagnostics.push({\n severity: \"error\",\n message: `Mismatched Liquid tag '{% ${name} %}' on line ${tagLine}; '{% ${open.name} %}' from line ${open.line} must close with '{% ${open.expectedClose} %}'.`,\n });\n return;\n }\n\n stack.pop();\n };\n\n for (const match of content.matchAll(\n /\\{%-?\\s*([a-zA-Z_][\\w-]*)\\b(?:(?!\\{%)[\\s\\S])*?-?%\\}/g,\n )) {\n const name = match[1]?.toLowerCase();\n if (!name) continue;\n\n const index = match.index ?? 0;\n // Count each character at most once across the scan. Re-slicing from the\n // start for every tag turns large generated templates into O(n²) work.\n for (let cursor = previousTagIndex; cursor < index; cursor++) {\n if (content.charCodeAt(cursor) === 10) line += 1;\n }\n previousTagIndex = index;\n\n const open = stack.at(-1);\n if (name === \"liquid\" && !(open && OPAQUE_BLOCK_TAGS.has(open.name))) {\n // `{% liquid %}` places one delimiter-free statement on each line. Feed\n // those statements through the same stack so this supported syntax\n // cannot bypass lint, push, or watch-mode validation.\n const statements = match[0]\n .replace(/^\\{%-?\\s*liquid\\b/i, \"\")\n .replace(/-?%\\}$/, \"\")\n .split(\"\\n\");\n for (const [offset, statement] of statements.entries()) {\n const statementName = /^\\s*([a-zA-Z_][\\w-]*)\\b/.exec(statement)?.[1];\n if (!statementName) continue;\n processTag(statementName.toLowerCase(), line + offset);\n }\n } else {\n processTag(name, line);\n }\n }\n\n for (const open of stack.reverse()) {\n diagnostics.push({\n severity: \"error\",\n message: `Unclosed Liquid tag '{% ${open.name} %}' on line ${open.line}; expected '{% ${open.expectedClose} %}'.`,\n });\n }\n\n return diagnostics;\n}\n","","import pathScopes from \"./path-scopes.json\";\n\n/**\n * Reads the `fluid.fetch(` calls in a site's text and says which scope each\n * needs. It is a lint and not a parser: it reads a call whose path is a string\n * literal, or a template literal that interpolates only whole path segments,\n * and whose method is a string literal in an object-literal second argument.\n * Any other call is reported as one it cannot read, never skipped.\n *\n * `path-scopes.json` is a copy of the SDK's (member-storefront-sdk/src/site),\n * which Rails generates from the routes and `MemberSites::BridgeScopes`, and a\n * test holds the two equal.\n */\n\n/** One entry of `path-scopes.json`: `:param` is one segment and a trailing `*glob` is the rest. */\ninterface PathScope {\n pattern: string;\n method: string;\n /** The scope the route needs, or null for a route a site can never reach. */\n scope: string | null;\n reason?: string;\n}\n\nconst ENTRIES = (pathScopes as { entries: PathScope[] }).entries;\n\n/** A segment written as `${...}`: it matches only a `:param` or a glob. */\nexport type Segment = string | null;\n\n/** One `fluid.fetch(` call. A field is null when the lint cannot read it. */\nexport interface FetchCall {\n /** The 1-based line of the call. */\n line: number;\n /** The path as written, without its query string. */\n written: string | null;\n /** The path as `fluid.fetch` sends it, one entry per segment. */\n segments: Segment[] | null;\n /** The method, upper case. */\n method: string | null;\n}\n\n/** What a call resolves to once it is matched against `path-scopes.json`. */\nexport type CallScope =\n | { kind: \"scope\"; scope: string }\n | { kind: \"never\"; reason: string | undefined }\n | { kind: \"unknown\" };\n\nconst MEMBER_API_PREFIX = \"/api/member/v2026-10/\";\nconst STORE_PREFIX = \"/api/member-storefront/\";\nconst HOLE = \"\\u0000\";\n\n/** Index after the whitespace and comments from `i`. */\nfunction skipBlank(text: string, i: number): number {\n for (;;) {\n while (i < text.length && /\\s/.test(text[i] as string)) i += 1;\n if (text.startsWith(\"//\", i)) {\n const end = text.indexOf(\"\\n\", i);\n i = end < 0 ? text.length : end;\n } else if (text.startsWith(\"/*\", i)) {\n const end = text.indexOf(\"*/\", i + 2);\n i = end < 0 ? text.length : end + 2;\n } else {\n return i;\n }\n }\n}\n\n/** Index after the `}` that closes the `{` at `open`, or -1. */\nfunction skipBraces(text: string, open: number): number {\n let depth = 0;\n for (let i = open; i < text.length; i++) {\n const char = text[i] as string;\n if (char === '\"' || char === \"'\" || char === \"`\") {\n i = skipQuoted(text, i) - 1;\n if (i < 0) return -1;\n } else if (text.startsWith(\"//\", i) || text.startsWith(\"/*\", i)) {\n i = skipBlank(text, i) - 1;\n } else if (char === \"{\") {\n depth += 1;\n } else if (char === \"}\") {\n depth -= 1;\n if (depth === 0) return i + 1;\n }\n }\n return -1;\n}\n\n/** Index after the string or template literal that starts at `start`, or -1. */\nfunction skipQuoted(text: string, start: number): number {\n const quote = text[start] as string;\n for (let i = start + 1; i < text.length; i++) {\n const char = text[i] as string;\n if (char === \"\\\\\") {\n i += 1;\n } else if (char === quote) {\n return i + 1;\n } else if (quote === \"`\" && char === \"$\" && text[i + 1] === \"{\") {\n i = skipBraces(text, i + 1) - 1;\n if (i < 0) return -1;\n } else if (char === \"\\n\" && quote !== \"`\") {\n return -1;\n }\n }\n return -1;\n}\n\n/**\n * Reads the string or template literal at `start`. A `${...}` becomes HOLE in\n * `value`; `raw` is the text between the quotes. Null for a literal with an\n * escape other than a quote, a slash, a dollar sign or a backslash, because\n * its value is not what the text shows.\n */\nfunction readLiteral(\n text: string,\n start: number,\n): { value: string; raw: string; end: number } | null {\n const quote = text[start] as string;\n let value = \"\";\n for (let i = start + 1; i < text.length; i++) {\n const char = text[i] as string;\n if (char === quote) {\n return { value, raw: text.slice(start + 1, i), end: i + 1 };\n }\n if (char === \"\\\\\") {\n const next = text[i + 1];\n if (next === undefined || !`\\\\'\"\\`/$`.includes(next)) return null;\n value += next;\n i += 1;\n } else if (quote === \"`\" && char === \"$\" && text[i + 1] === \"{\") {\n const end = skipBraces(text, i + 1);\n if (end < 0) return null;\n value += HOLE;\n i = end - 1;\n } else if (char === \"\\n\" && quote !== \"`\") {\n return null;\n } else {\n value += char;\n }\n }\n return null;\n}\n\n/**\n * The method of an object literal that starts at `open`: undefined when it\n * has no `method` key (a GET), null when the lint cannot tell, as for a\n * spread, a computed key or a value that is not a string literal.\n */\nfunction readMethod(text: string, open: number): string | null | undefined {\n let method: string | undefined;\n let depth = 0;\n let atStart = true;\n for (let i = open + 1; i < text.length; i++) {\n if (depth === 0 && atStart) {\n i = skipBlank(text, i);\n atStart = false;\n if (text.startsWith(\"...\", i) || text[i] === \"[\") return null;\n const key = /(?:method|\"method\"|'method')(?![\\w$])\\s*/y;\n key.lastIndex = i;\n if (key.test(text)) {\n const valueAt = skipBlank(text, key.lastIndex + 1);\n const value =\n text[key.lastIndex] === \":\" && /[\"']/.test(text[valueAt] as string)\n ? readLiteral(text, valueAt)\n : null;\n const after = value === null ? -1 : skipBlank(text, value.end);\n if (value === null || (text[after] !== \",\" && text[after] !== \"}\")) {\n return null;\n }\n method = value.value.toUpperCase();\n i = value.end - 1;\n continue;\n }\n }\n const char = text[i] as string;\n if (char === '\"' || char === \"'\" || char === \"`\") {\n i = skipQuoted(text, i) - 1;\n if (i < 0) return null;\n } else if (text.startsWith(\"//\", i) || text.startsWith(\"/*\", i)) {\n i = skipBlank(text, i) - 1;\n } else if (char === \"{\" || char === \"[\" || char === \"(\") {\n depth += 1;\n } else if (char === \"}\" || char === \"]\" || char === \")\") {\n if (depth === 0) return method;\n depth -= 1;\n } else if (char === \",\" && depth === 0) {\n atStart = true;\n }\n }\n return null;\n}\n\n/**\n * The path `fluid.fetch` sends for a literal path, one entry per segment, or\n * null when it is not one the lint can read: a path outside `/api/`, one whose\n * interpolation is not a whole segment or sits in the part that picks the\n * mount, or one with a dot segment or an empty one. It moves a path as the\n * SDK does (`memberApiPathname`): a Member API path stays, and any other\n * `/api/` path moves under `/api/member-storefront/`.\n */\nfunction sentSegments(value: string): Segment[] | null {\n const query = value.search(/[?#]/);\n let path = query < 0 ? value : value.slice(0, query);\n if (!path.startsWith(\"/api/\")) return null;\n // What follows `/api/` decides the mount, and a version decides it for `member`.\n const parts = path.split(\"/\");\n if (parts[2]?.includes(HOLE)) return null;\n if (parts[2] === \"member\" && parts[3]?.includes(HOLE)) return null;\n if (!path.startsWith(STORE_PREFIX) && !path.startsWith(MEMBER_API_PREFIX)) {\n path = path.replace(/^\\/api\\//, STORE_PREFIX);\n }\n const segments = path.split(\"/\").slice(1);\n if (segments[segments.length - 1] === \"\") segments.pop();\n const read: Segment[] = [];\n for (const segment of segments) {\n if (segment === HOLE) {\n read.push(null);\n } else if (\n segment.includes(HOLE) ||\n segment === \"\" ||\n segment === \".\" ||\n segment === \"..\"\n ) {\n return null;\n } else {\n read.push(segment);\n }\n }\n return read;\n}\n\n/** The text of a path without its query string, as written. */\nfunction writtenPath(raw: string): string {\n const query = raw.search(/[?#]/);\n return query < 0 ? raw : raw.slice(0, query);\n}\n\n/** The `fluid.fetch(` and `window.fluid.fetch(` calls in a text, in source order. */\nexport function fluidFetchCalls(text: string): FetchCall[] {\n const calls: FetchCall[] = [];\n const start = /(?<![\\w$.])(?:window\\s*\\.\\s*)?fluid\\s*\\.\\s*fetch\\s*\\(/g;\n for (const match of text.matchAll(start)) {\n const line = text.slice(0, match.index).split(\"\\n\").length;\n const unreadable: FetchCall = {\n line,\n written: null,\n segments: null,\n method: null,\n };\n const argument = skipBlank(text, match.index + match[0].length);\n if (!/[\"'`]/.test(text[argument] ?? \"\")) {\n calls.push(unreadable);\n continue;\n }\n const literal = readLiteral(text, argument);\n if (literal === null) {\n calls.push(unreadable);\n continue;\n }\n // The path is the whole first argument, as in `\"/api/x\"`, not `\"/api/\" + x`.\n const next = skipBlank(text, literal.end);\n if (text[next] !== \",\" && text[next] !== \")\") {\n calls.push(unreadable);\n continue;\n }\n\n let method: string | null = \"GET\";\n if (text[next] === \",\") {\n const init = skipBlank(text, next + 1);\n if (text[init] === \"{\") {\n const read = readMethod(text, init);\n method = read === undefined ? \"GET\" : read;\n } else if (text[init] !== \")\") {\n method = null;\n }\n }\n calls.push({\n line,\n written: writtenPath(literal.raw),\n segments: sentSegments(literal.value),\n method,\n });\n }\n return calls;\n}\n\n/** How loosely an entry's pattern matches: fewer globs, then fewer params, is more exact. */\nfunction looseness(pattern: string): number {\n const parts = pattern.split(\"/\");\n return (\n parts.filter((part) => part.startsWith(\"*\")).length * 1000 +\n parts.filter((part) => part.startsWith(\":\")).length\n );\n}\n\nfunction matches(pattern: string, segments: Segment[]): boolean {\n const parts = pattern.split(\"/\").slice(1);\n for (const [index, part] of parts.entries()) {\n if (part.startsWith(\"*\")) return segments.length > index;\n const segment = segments[index];\n if (segment === undefined) return false;\n if (part.startsWith(\":\")) continue;\n // A segment written as `${...}` fits only a `:param` or a glob.\n if (segment !== part) return false;\n }\n return segments.length === parts.length;\n}\n\n/** The scope the entry for a path and method names, or what the lint does not know. */\nexport function scopeOfCall(segments: Segment[], method: string): CallScope {\n const entry = ENTRIES.filter(\n (candidate) =>\n candidate.method === method && matches(candidate.pattern, segments),\n ).sort((a, b) => looseness(a.pattern) - looseness(b.pattern))[0];\n if (entry === undefined) return { kind: \"unknown\" };\n return entry.scope === null\n ? { kind: \"never\", reason: entry.reason }\n : { kind: \"scope\", scope: entry.scope };\n}\n","import { stat } from \"node:fs/promises\";\nimport {\n findLiquidBlockTagDiagnostics,\n validateSchemaText,\n type BlocksSchemaType,\n type Diagnostic,\n} from \"@fluid-app/theme-schema\";\nimport { MemberSiteCliError } from \"../api/errors.js\";\nimport {\n FIELDS,\n parseAccessDeclaration,\n type AccessDeclaration,\n} from \"../tree/access-declaration.js\";\nimport { PAGES_CONFIG_PATH } from \"../tree/binding.js\";\nimport {\n ACCESS_PATH,\n classifyPath,\n MENUS_PATH,\n readLocalTree,\n} from \"../tree/local-tree.js\";\nimport { fluidFetchCalls, scopeOfCall } from \"./fluid-fetch.js\";\nimport {\n isRecord,\n menusProblems,\n PAGE_LIMIT,\n parsePagesConfig,\n} from \"../tree/push-plan.js\";\n\n/** One thing wrong in the folder, and the file it is in. */\nexport interface LintFinding {\n path: string;\n severity: Diagnostic[\"severity\"];\n message: string;\n}\n\n/**\n * Whether the theme linter reads a template's blocks as objects or as an\n * array. It decides by folder (`ThemeFile#isTemplate`): a layout and a section\n * have array blocks, and any other folder holds page templates with object\n * blocks.\n */\nfunction blocksSchemaType(path: string): BlocksSchemaType {\n const folder = path.split(\"/\")[0];\n return folder === \"layouts\" || folder === \"sections\" ? \"array\" : \"object\";\n}\n\n/** The `page` of every link in a menus document, at any depth. */\nfunction linkedPages(items: unknown): string[] {\n if (!Array.isArray(items)) return [];\n return items.flatMap((item: unknown) => {\n if (!isRecord(item)) return [];\n const page = typeof item[\"page\"] === \"string\" ? [item[\"page\"]] : [];\n return [...page, ...linkedPages(item[\"items\"])];\n });\n}\n\n/** The menu links, by page slug, in a menus text that is valid JSON of the menus shape. */\nfunction menuLinks(document: unknown): string[] {\n if (!isRecord(document) || !Array.isArray(document[\"menus\"])) return [];\n return document[\"menus\"].flatMap((menu: unknown) =>\n isRecord(menu) ? linkedPages(menu[\"items\"]) : [],\n );\n}\n\n/** The member fields a Liquid text reads: `member.email` and `member[\"email\"]`. */\nfunction memberFieldsRead(text: string): string[] {\n const found = new Set<string>();\n for (const match of text.matchAll(\n /(?<![.\\w])member(?:\\.([a-z_]+)|\\[\\s*['\"]([a-z_]+)['\"]\\s*\\])/g,\n )) {\n const field = match[1] ?? match[2];\n if (field !== undefined && FIELDS.includes(field)) found.add(field);\n }\n return [...found].sort();\n}\n\ntype Add = (\n path: string,\n message: string,\n severity?: LintFinding[\"severity\"],\n) => void;\n\n/**\n * Warns for each member field a Liquid file reads. A site's member data\n * reaches its script through the SDK, never its Liquid, so a read in Liquid is\n * not something a declaration can cover.\n */\nfunction liquidMemberWarnings(path: string, content: string, add: Add): void {\n for (const field of memberFieldsRead(content)) {\n add(\n path,\n `${path} reads member.${field}, and member data is leaving member-site Liquid. Read it in a script with fluid.fetch(\"/api/member/v2026-10/me\"), which needs \"profile: read\" in ${ACCESS_PATH}.`,\n \"warning\",\n );\n }\n}\n\n/**\n * Warns for each `fluid.fetch(` call a file makes whose scope the declaration\n * lacks, and for each call the lint cannot check. A warning and not an error:\n * the call may be conditional, and the file API accepts the site either way.\n * `session: read` is covered by any declared scope and is never warned.\n */\nfunction fetchWarnings(\n path: string,\n content: string,\n declaration: AccessDeclaration,\n add: Add,\n): void {\n const seen = new Set<string>();\n const warn = (message: string) => {\n if (seen.has(message)) return;\n seen.add(message);\n add(path, message, \"warning\");\n };\n const refused = `It is refused at run time if its scope is not declared in ${ACCESS_PATH}.`;\n for (const call of fluidFetchCalls(content)) {\n if (call.segments === null || call.method === null) {\n warn(\n `${path} line ${call.line} makes a fluid.fetch call whose path or method the lint cannot read, so it cannot check it. ${refused}`,\n );\n continue;\n }\n const made = `${path} calls fluid.fetch(\"${call.written}\") with ${call.method}`;\n const outcome = scopeOfCall(call.segments, call.method);\n if (outcome.kind === \"unknown\") {\n warn(\n `${made}, which matches no route the lint knows, so it cannot check it. ${refused}`,\n );\n } else if (outcome.kind === \"never\") {\n warn(\n `${made}, which a site can never reach${outcome.reason === undefined ? \"\" : `: ${outcome.reason}`}.`,\n );\n } else if (\n outcome.scope !== \"session: read\" &&\n !declaration.scopes.includes(outcome.scope)\n ) {\n warn(\n `${made}, which needs \"${outcome.scope}\", and ${ACCESS_PATH} does not declare it.`,\n );\n }\n }\n}\n\nfunction parseJson(text: string): { value: unknown } | null {\n try {\n return { value: JSON.parse(text) };\n } catch {\n return null;\n }\n}\n\n/**\n * Checks a folder with no network call: the file rules a push enforces,\n * Liquid in layouts, sections, snippets and pages, `config/pages.json` and\n * `config/menus.json`. A folder needs no binding, so every file is judged,\n * including a file a pull wrote.\n */\nexport async function lintFolder(root: string): Promise<{\n findings: LintFinding[];\n filesChecked: number;\n}> {\n let info;\n try {\n info = await stat(root);\n } catch {\n info = null;\n }\n if (!info?.isDirectory()) {\n throw new MemberSiteCliError(`${root} is not a folder.`);\n }\n\n const findings: LintFinding[] = [];\n const add: Add = (path, message, severity = \"error\") =>\n findings.push({ path, severity, message });\n\n const tree = await readLocalTree(root, null);\n for (const { path, reason } of tree.refusedFiles) add(path, reason);\n\n // Liquid, as `fluid theme lint` checks it: the {% schema %} block and the\n // pairing of block tags.\n let filesChecked = 0;\n for (const [path, content] of tree.files) {\n const kind = classifyPath(path).kind;\n if (\n kind !== \"layout\" &&\n kind !== \"section\" &&\n kind !== \"snippet\" &&\n kind !== \"page\"\n ) {\n continue;\n }\n filesChecked += 1;\n const diagnostics: Diagnostic[] = [\n ...validateSchemaText(content, {\n blocksSchemaType: blocksSchemaType(path),\n }),\n ...findLiquidBlockTagDiagnostics(content),\n ];\n for (const d of diagnostics) add(path, d.message, d.severity);\n liquidMemberWarnings(path, content, add);\n }\n\n // The pages the site would have: every row of config/pages.json.\n const pagesText = tree.files.get(PAGES_CONFIG_PATH);\n let pageSlugs: Set<string> | null = new Set();\n if (pagesText !== undefined) {\n filesChecked += 1;\n const parsed = parsePagesConfig(pagesText);\n if (Array.isArray(parsed)) {\n for (const problem of parsed) add(PAGES_CONFIG_PATH, problem);\n pageSlugs = null;\n } else {\n pageSlugs = new Set(parsed.rows.map((row) => row.slug));\n if (parsed.rows.length > PAGE_LIMIT) {\n add(\n PAGES_CONFIG_PATH,\n `${PAGES_CONFIG_PATH} lists ${parsed.rows.length} pages, and a site holds at most ${PAGE_LIMIT}.`,\n );\n }\n }\n }\n\n const menusText = tree.files.get(MENUS_PATH);\n if (menusText !== undefined) {\n filesChecked += 1;\n const document = parseJson(menusText);\n if (document === null) {\n add(MENUS_PATH, `${MENUS_PATH} is not valid JSON.`);\n } else {\n for (const problem of menusProblems(document.value)) {\n add(MENUS_PATH, problem);\n }\n // A link to a page the site does not hold is refused by the server.\n // When config/pages.json is invalid its own errors come first.\n if (pageSlugs !== null) {\n for (const slug of new Set(menuLinks(document.value))) {\n if (!pageSlugs.has(slug)) {\n add(\n MENUS_PATH,\n `${MENUS_PATH} links to the page \"${slug}\", which ${PAGES_CONFIG_PATH} does not list.`,\n );\n }\n }\n }\n }\n }\n\n // The access declaration: its shape is an error, as the file API refuses it.\n // Once it is valid, a `fluid.fetch` call it lacks is a warning. A folder with no\n // declaration is not checked for calls, because a site with none is not\n // limited.\n const accessText = tree.files.get(ACCESS_PATH);\n if (accessText !== undefined) {\n filesChecked += 1;\n const parsedAccess = parseAccessDeclaration(accessText);\n if (parsedAccess.declaration === null) {\n for (const problem of parsedAccess.problems) {\n add(ACCESS_PATH, `${ACCESS_PATH}: ${problem}.`);\n }\n } else {\n for (const [path, content] of tree.files) {\n const kind = classifyPath(path).kind;\n const runsScripts =\n kind === \"layout\" ||\n kind === \"section\" ||\n kind === \"snippet\" ||\n kind === \"page\" ||\n (kind === \"asset\" && path.endsWith(\".js\"));\n if (runsScripts) {\n fetchWarnings(path, content, parsedAccess.declaration, add);\n }\n }\n }\n }\n\n // The sort is stable, so a file's findings stay in the order they were found.\n findings.sort((a, b) => a.path.localeCompare(b.path));\n return { findings, filesChecked };\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport { inert } from \"../display.js\";\nimport { lintFolder, type LintFinding } from \"../lint/lint-folder.js\";\nimport { runCommand } from \"./run.js\";\n\nexport interface LintResult {\n findings: LintFinding[];\n filesChecked: number;\n /** Whether any finding is an error, which fails the command. */\n failed: boolean;\n}\n\nfunction plural(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? \"\" : \"s\"}`;\n}\n\n/**\n * The lines `lint` prints. A folder with no findings prints none. A finding\n * is shown as `fluid theme lint` shows one: the file, then each message's\n * first line, because a few messages carry a longer body. Paths and messages\n * can carry a file name, so both are printed with terminal controls made inert.\n */\nexport function formatFindings(result: LintResult): string[] {\n const { findings, filesChecked } = result;\n if (findings.length === 0) return [];\n\n const lines: string[] = [];\n let current: string | null = null;\n for (const finding of findings) {\n if (finding.path !== current) {\n lines.push(chalk.bold(inert(finding.path)));\n current = finding.path;\n }\n const label =\n finding.severity === \"error\"\n ? chalk.red(\"error\".padEnd(7))\n : chalk.yellow(\"warning\".padEnd(7));\n lines.push(` ${label} ${inert(finding.message.split(\"\\n\")[0]!)}`);\n }\n\n const errors = findings.filter((f) => f.severity === \"error\").length;\n const warnings = findings.length - errors;\n const suffix = `(${plural(filesChecked, \"file\")} checked)`;\n lines.push(\n errors > 0\n ? `\\n${chalk.red(`✖ ${plural(errors, \"error\")}, ${plural(warnings, \"warning\")}`)} ${suffix}`\n : `\\n${chalk.yellow(`⚠ ${plural(warnings, \"warning\")}`)} ${suffix}`,\n );\n return lines;\n}\n\n/** Lints a folder, prints what it finds, and returns it. It makes no request. */\nexport async function runLint(\n dirArg: string | undefined,\n log: (line: string) => void = (line) => console.log(line),\n): Promise<LintResult> {\n const dir = resolve(dirArg ?? \".\");\n const { findings, filesChecked } = await lintFolder(dir);\n const result: LintResult = {\n findings,\n filesChecked,\n failed: findings.some((f) => f.severity === \"error\"),\n };\n for (const line of formatFindings(result)) log(line);\n return result;\n}\n\nexport function createLintCommand(): Command {\n return new Command(\"lint\")\n .description(\n \"Check a local site folder without a request: Liquid, file names and paths, assets, pages and menus\",\n )\n .argument(\"[dir]\", \"The site folder (default: the current folder)\")\n .action((dir: string | undefined) =>\n runCommand(async () => {\n const result = await runLint(dir);\n if (result.failed) process.exitCode = 1;\n }),\n );\n}\n","import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n createClients,\n listForLogin,\n loadLogin,\n parseLoginKind,\n type Clients,\n type Login,\n type SiteListing,\n} from \"../api/credential.js\";\nimport type { Site } from \"../api/types.js\";\nimport { runCommand } from \"./run.js\";\n\nfunction ownerLabel(site: Site): string {\n if (site.owner_kind === \"company\") return \"company\";\n const name = site.owner_member?.display_name;\n return name ? `member: ${name}` : \"member\";\n}\n\n/** The lines `list` prints for a listing. */\nexport function formatListing(listing: SiteListing): string[] {\n const { sites, as } = listing;\n if (sites.length === 0) {\n return [\n as === \"admin\"\n ? \"No member sites in this company yet.\"\n : \"You have no sites yet. Create one with: fluid member-site create <handle>\",\n ];\n }\n\n const lines = sites.map((site) => {\n // An admin reads a member's site and cannot change it.\n const readOnly = as === \"admin\" && site.owner_kind === \"member\";\n return [\n chalk.cyan(site.handle.padEnd(24)),\n site.status.padEnd(10),\n ownerLabel(site).padEnd(24),\n chalk.dim(site.id),\n readOnly ? chalk.dim(\"(read-only)\") : \"\",\n ]\n .join(\" \")\n .trimEnd();\n });\n if (as === \"admin\" && sites.some((s) => s.owner_kind === \"member\")) {\n lines.push(\n \"\",\n chalk.dim(\n \"Member sites are read-only here: their owners build them with their own login.\",\n ),\n );\n }\n return lines;\n}\n\nexport interface ListDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n}\n\nconst defaultDeps: ListDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\nexport async function runList(\n options: { as?: string },\n deps: ListDeps = defaultDeps,\n): Promise<void> {\n const login = await deps.loadLogin(parseLoginKind(options.as));\n const listing = await listForLogin(login, deps.createClients(login.token));\n for (const line of formatListing(listing)) deps.log(line);\n}\n\nexport function createListCommand(): Command {\n return new Command(\"list\")\n .description(\n \"List your sites (member login) or every site in the company (admin login)\",\n )\n .option(\n \"--as <login>\",\n \"Which kind of login the token is: member or admin (default: try the company API, then the member API)\",\n )\n .action((options: { as?: string }) => runCommand(() => runList(options)));\n}\n","import { resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport {\n clientForOwner,\n createClients,\n loadLogin,\n parseLoginKind,\n resolveSite,\n type Clients,\n type Login,\n} from \"../api/credential.js\";\nimport { MemberSiteCliError, type ErrorContext } from \"../api/errors.js\";\nimport { getApiBase } from \"../api/http.js\";\nimport type { Site, SiteApi, SiteFileEntry } from \"../api/types.js\";\nimport {\n BINDING_FILE,\n PAGES_CONFIG_PATH,\n buildBinding,\n serializeJson,\n sha256,\n type PulledFile,\n type SiteBinding,\n} from \"../tree/binding.js\";\nimport {\n inspectFolder,\n checkedPath,\n listedPath,\n readBinding,\n readLocal,\n removeLocal,\n writeLocal,\n} from \"../tree/folder.js\";\nimport { ACCESS_PATH } from \"../tree/local-tree.js\";\nimport { buildPagesConfig, pageFilePath } from \"../tree/pages-config.js\";\nimport { runCommand } from \"./run.js\";\n\n/** The content types a site holds as text, the ones the file API reads. */\nconst TEXT_ASSET_TYPES = new Set([\n \"text/css\",\n \"text/javascript\",\n \"application/json\",\n \"text/plain\",\n \"text/html\",\n]);\n\n/** Files read at once. The API has no bulk read, so a pull is one request per file. */\nconst READ_CONCURRENCY = 5;\n\nexport interface PullOptions {\n as?: string;\n force?: boolean;\n}\n\nexport interface PullDeps {\n loadLogin: (\n kind: ReturnType<typeof parseLoginKind>,\n ) => Login | Promise<Login>;\n createClients: (token: string) => Clients;\n log: (line: string) => void;\n}\n\nconst defaultDeps: PullDeps = {\n loadLogin: (kind) => loadLogin(kind),\n createClients,\n log: (line) => console.log(line),\n};\n\n/** What a pull did. */\nexport interface PullResult {\n site: Site;\n dir: string;\n written: string[];\n unchanged: string[];\n /** Local files a pull removed because the server no longer has them. */\n removed: string[];\n skippedBinary: string[];\n /** Pages whose content is not a file of this site, so only their row is pulled. */\n pagesWithoutFile: string[];\n}\n\n/** A listed file that is not text. The server lists none today. */\nfunction isBinary(entry: SiteFileEntry): boolean {\n return entry.kind === \"asset\" && !TEXT_ASSET_TYPES.has(entry.content_type);\n}\n\nasync function mapLimit<T, R>(\n items: T[],\n limit: number,\n work: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = Array.from({ length: items.length });\n let next = 0;\n const lane = async () => {\n while (next < items.length) {\n const index = next++;\n results[index] = await work(items[index] as T);\n }\n };\n await Promise.all(\n Array.from({ length: Math.min(limit, items.length) }, lane),\n );\n return results;\n}\n\n/**\n * Refuses a folder that holds something else: files with no binding, or a\n * binding for another site. Nothing of it is read or removed.\n */\nfunction assertFolderIsThisSite(\n dir: string,\n site: Site,\n state: { nonEmpty: boolean; binding: SiteBinding | null },\n): void {\n if (!state.nonEmpty) return;\n if (state.binding?.site_id === site.id) return;\n const holds = state.binding\n ? `is bound to another site (${state.binding.handle})`\n : `is not empty and is not a pulled site`;\n throw new MemberSiteCliError(\n `${dir} ${holds}. Pull into an empty folder, or pass --force to write into it.`,\n );\n}\n\n/**\n * The paths a pull would overwrite that were edited since the last pull, or\n * that the last pull did not write.\n */\nasync function editedPaths(\n dir: string,\n binding: SiteBinding | null,\n outputs: Array<{ path: string; content: string }>,\n): Promise<string[]> {\n const edited: string[] = [];\n for (const { path, content } of outputs) {\n const local = await readLocal(dir, path);\n if (local === null || local === content) continue;\n const pulled =\n path === PAGES_CONFIG_PATH\n ? binding?.pages_config_sha256\n : binding?.files[path]?.sha256;\n if (pulled !== sha256(local)) edited.push(path);\n }\n return edited;\n}\n\n/** The lines `pull` prints for a result. */\nexport function formatPull(result: PullResult): string[] {\n const {\n site,\n dir,\n written,\n unchanged,\n removed,\n skippedBinary,\n pagesWithoutFile,\n } = result;\n const lines = [\n `Pulled ${site.owner_kind} site ${chalk.cyan(site.handle)} into ${dir}`,\n ` ${written.length} written, ${unchanged.length} unchanged`,\n ];\n if (removed.length > 0) {\n lines.push(\n chalk.dim(\n ` ${removed.length} removed, no longer on the site: ${removed.join(\", \")}`,\n ),\n );\n }\n if (pagesWithoutFile.length > 0) {\n lines.push(\n chalk.dim(\n ` ${pagesWithoutFile.length} page(s) use a template the site does not own, so only their rows are in ${PAGES_CONFIG_PATH}: ${pagesWithoutFile.join(\", \")}`,\n ),\n );\n }\n if (skippedBinary.length > 0) {\n lines.push(\n ` Skipped ${skippedBinary.length} binary asset(s): ${skippedBinary.join(\", \")}`,\n chalk.dim(\n \" A site holds text assets only. Use images and fonts from the media library by URL.\",\n ),\n );\n }\n return lines;\n}\n\n/**\n * Pulls a site into a folder: every text file at its API path, the page rows\n * in `config/pages.json`, and a binding file with the versions the API gave.\n * `context` gets the site's owner kind once it is known, so a failure is\n * reported with the login the site needs.\n */\nexport async function runPull(\n ref: string,\n dirArg: string | undefined,\n options: PullOptions,\n deps: PullDeps = defaultDeps,\n context: ErrorContext = {},\n): Promise<PullResult> {\n const login = await deps.loadLogin(parseLoginKind(options.as));\n const clients = deps.createClients(login.token);\n\n // A folder already bound to this site names its owner kind, which a login\n // of the other kind cannot pull, so that stops before any request.\n const folderGuess = dirArg ?? ref;\n const known = await readBinding(resolve(folderGuess));\n if (known && (known.site_id === ref || known.handle === ref)) {\n context.ownerKind = known.owner_kind;\n clientForOwner(known.owner_kind, login, clients);\n }\n\n const { site, api } = await resolveSite(ref, login, clients);\n context.ownerKind = site.owner_kind;\n // An admin reads a member's site and cannot pull it: its owner builds it.\n if (api.ownerKind !== site.owner_kind) {\n clientForOwner(\n site.owner_kind,\n { token: login.token, kind: \"admin\" },\n clients,\n );\n }\n\n const dir = resolve(dirArg ?? site.handle);\n const state = await inspectFolder(dir);\n if (!options.force) assertFolderIsThisSite(dir, site, state);\n\n const result = await fetchAndWrite(api, site, dir, state.binding, options);\n for (const line of formatPull(result)) deps.log(line);\n return result;\n}\n\nasync function fetchAndWrite(\n api: SiteApi,\n site: Site,\n dir: string,\n priorBinding: SiteBinding | null,\n options: PullOptions,\n): Promise<PullResult> {\n const entries = await api.listFiles(site.id);\n const skippedBinary = entries.filter(isBinary).map((e) => e.path);\n // `config/pages.json` is written from the pages list whether or not the\n // server also serves a file of that name.\n const textEntries = entries.filter(\n (e) => !isBinary(e) && e.path !== PAGES_CONFIG_PATH,\n );\n // Every path is checked before the first request for content.\n for (const entry of textEntries) listedPath(dir, entry.path);\n\n // All content is read before anything is written, so a failed read leaves\n // the folder as it was.\n const files: PulledFile[] = await mapLimit(\n textEntries,\n READ_CONCURRENCY,\n async (entry) => ({ entry, file: await api.readFile(site.id, entry.path) }),\n );\n const pages = await api.listPages(site.id);\n\n const pagesConfig = buildPagesConfig(site, pages);\n const outputs = [\n ...files.map(({ entry, file }) => ({\n path: entry.path,\n content: file.content,\n })),\n { path: PAGES_CONFIG_PATH, content: pagesConfig },\n ];\n\n // No path may lead through a link, forced or not, and none is read, hashed\n // or written before every one is checked.\n for (const { path } of outputs) await checkedPath(dir, path);\n await checkedPath(dir, BINDING_FILE);\n\n const binding = priorBinding?.site_id === site.id ? priorBinding : null;\n if (!options.force) {\n const edited = await editedPaths(dir, binding, outputs);\n if (edited.length > 0) {\n throw new MemberSiteCliError(\n `These files were edited since the last pull, and a pull would overwrite them:\\n${edited\n .map((p) => ` ${p}`)\n .join(\n \"\\n\",\n )}\\nPush or save your edits, or pass --force to overwrite them.`,\n );\n }\n }\n\n const written: string[] = [];\n const unchanged: string[] = [];\n for (const { path, content } of outputs) {\n if ((await readLocal(dir, path)) === content) {\n unchanged.push(path);\n } else {\n await writeLocal(dir, path, content);\n written.push(path);\n }\n }\n\n // The site's declaration was removed by another writer. The local copy is\n // removed with it when it is the one the last pull or push recorded, so the\n // next push does not take it for a new declaration and create it again. A\n // copy edited since is the builder's own work and stays, unless the pull\n // is forced.\n const removed: string[] = [];\n const boundAccess = binding?.files[ACCESS_PATH];\n if (boundAccess && !outputs.some(({ path }) => path === ACCESS_PATH)) {\n const local = await readLocal(dir, ACCESS_PATH);\n if (\n local !== null &&\n (options.force || sha256(local) === boundAccess.sha256)\n ) {\n await removeLocal(dir, ACCESS_PATH);\n removed.push(ACCESS_PATH);\n }\n }\n\n const bindingText = serializeJson(\n buildBinding({ site, apiBase: getApiBase(), files, pages, pagesConfig }),\n );\n if ((await readLocal(dir, BINDING_FILE)) !== bindingText) {\n await writeLocal(dir, BINDING_FILE, bindingText);\n }\n\n const listed = new Set(entries.map((e) => e.path));\n return {\n site,\n dir,\n written,\n unchanged,\n removed,\n skippedBinary,\n pagesWithoutFile: pages\n .filter((page) => !listed.has(pageFilePath(page.slug)))\n .map((page) => page.slug),\n };\n}\n\nexport function createPullCommand(): Command {\n return new Command(\"pull\")\n .description(\n \"Pull a site into a local folder: its files at their API paths, its pages in config/pages.json\",\n )\n .argument(\"<site>\", \"The site's handle or id\")\n .argument(\"[dir]\", \"The folder to pull into (default: the site's handle)\")\n .option(\"--as <login>\", \"Which kind of login the token is: member or admin\")\n .option(\n \"--force\",\n \"Write into a folder that is not bound to this site, and overwrite local edits\",\n )\n .action((ref: string, dir: string | undefined, options: PullOptions) => {\n const context: ErrorContext = {};\n return runCommand(\n () =>\n runPull(ref, dir, options, defaultDeps, context).then(\n () => undefined,\n ),\n context,\n );\n });\n}\n","import { Command } from \"commander\";\nimport type { PluginContext } from \"@fluid-app/fluid-cli\";\nimport { createCreateCommand } from \"./create.js\";\nimport { createDevCommand } from \"./dev.js\";\nimport { createLintCommand } from \"./lint.js\";\nimport { createListCommand } from \"./list.js\";\nimport { createPublishCommand } from \"./publish.js\";\nimport { createPullCommand } from \"./pull.js\";\nimport { createPushCommand } from \"./push.js\";\n\nexport function registerMemberSiteCommand(ctx: PluginContext): void {\n const cmd = new Command(\"member-site\").description(\n \"Member sites — list, create, pull, push, publish, lint and preview sites with the member or company site API\",\n );\n\n cmd.addCommand(createListCommand());\n cmd.addCommand(createCreateCommand());\n cmd.addCommand(createPullCommand());\n cmd.addCommand(createPushCommand(ctx.inputs));\n cmd.addCommand(createPublishCommand(ctx.inputs));\n cmd.addCommand(createLintCommand());\n cmd.addCommand(createDevCommand());\n\n ctx.program.addCommand(cmd);\n}\n","import type { FluidPlugin, PluginContext } from \"@fluid-app/fluid-cli\";\nimport { registerMemberSiteCommand } from \"./commands/member-site.js\";\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-member-site\",\n version: \"0.1.0\",\n register(ctx: PluginContext) {\n registerMemberSiteCommand(ctx);\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
|