@guard-rail/core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -7
- package/dist/adapters/supabase/db.js +37 -1
- package/dist/adapters/supabase/db.js.map +1 -1
- package/dist/cli/doctor.js +37 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/adapter-types.d.ts +39 -3
- package/dist/core/seo.d.ts +69 -0
- package/dist/core/seo.js +23 -0
- package/dist/core/seo.js.map +1 -0
- package/dist/index.client.d.ts +5 -3
- package/dist/index.client.js +10 -1
- package/dist/index.client.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/react/Admin.js +14 -1
- package/dist/react/Admin.js.map +1 -1
- package/dist/react/GRail/client.js +59 -9
- package/dist/react/GRail/client.js.map +1 -1
- package/dist/react/Icons.d.ts +5 -1
- package/dist/react/Icons.js +10 -0
- package/dist/react/Icons.js.map +1 -1
- package/dist/react/PageSettings.d.ts +12 -0
- package/dist/react/PageSettings.js +275 -0
- package/dist/react/PageSettings.js.map +1 -0
- package/dist/react/guardrail.css +8 -0
- package/dist/react/revert.d.ts +8 -0
- package/dist/react/revert.js +14 -0
- package/dist/react/revert.js.map +1 -0
- package/dist/server/actions.d.ts +37 -2
- package/dist/server/actions.js +112 -0
- package/dist/server/actions.js.map +1 -1
- package/dist/server/loader.js +3 -1
- package/dist/server/loader.js.map +1 -1
- package/dist/server/seo.d.ts +33 -0
- package/dist/server/seo.js +51 -0
- package/dist/server/seo.js.map +1 -0
- package/package.json +1 -1
- package/supabase/admin/check-grants.sql +12 -3
- package/supabase/migrations/20261103000000_content_history.sql +211 -0
package/dist/cli/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * guardrail-doctor — checks that a project is set up the way this version of\n * Guardrail expects, and (with --fix) repairs what can be repaired locally.\n *\n * It ships inside @guard-rail/core on purpose, so the checks always match the\n * installed version: when a release adds a requirement (a migration, an\n * environment variable, a header), the same release adds the check for it here.\n *\n * Three layers, each opt-in beyond the first:\n * 1. Static + read-only (default): project files, environment, the database's\n * shape and the Supabase project's sign-up settings. Changes nothing.\n * 2. --app-url: asks a running copy of the site for its response headers.\n * 3. --e2e: creates a throwaway user and rows on the real database, exercises\n * sign-in, invite claiming, row-level security, the admin guard, rate\n * limiting and storage the way a signed-in editor would, then deletes it\n * all. Never runs without saying what it will touch.\n *\n * Standalone like the other CLIs: plain Node, local imports end in \".js\", and\n * nothing here imports React or Next.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n readdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport { createInterface } from \"node:readline/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { GR_SCHEMA, GR_MEMBERS_TABLE } from \"../core/defs.js\";\nimport { cspString, guardrailCspDirectives } from \"../core/csp.js\";\n\n/* ------------------------------------------------------------ constants --- */\n\n/**\n * Oldest Next.js release known to carry the fixes for the advisories published\n * so far, by major line. Only the offline fallback: normally the doctor asks the\n * npm registry's advisory service about the installed version, which is always\n * current. Update alongside the dependency bumps; a major not listed gets a\n * \"check for security releases\" note instead of a verdict.\n */\nconst NEXT_MIN_PATCHED: Record<number, [number, number, number]> = {\n 15: [15, 5, 28],\n 16: [16, 4, 1],\n};\n\n/**\n * Kept in step with the block create-guardrail writes into a new project's\n * next.config.ts — the same headers, so a scaffolded and a repaired site match.\n */\nconst SECURITY_HEADERS_BLOCK = ` // Baseline response headers. frame-ancestors keeps another site from framing\n // this one (the editing toolbar included) in a clickjacking overlay. A full\n // Content-Security-Policy is specific to what your pages load, so add yours\n // alongside it.\n async headers() {\n return [\n {\n source: \"/:path*\",\n headers: [\n { key: \"Content-Security-Policy\", value: \"frame-ancestors 'self'\" },\n { key: \"X-Frame-Options\", value: \"SAMEORIGIN\" },\n { key: \"X-Content-Type-Options\", value: \"nosniff\" },\n { key: \"Referrer-Policy\", value: \"strict-origin-when-cross-origin\" },\n { key: \"Permissions-Policy\", value: \"camera=(), microphone=(), geolocation=()\" },\n // Only honored over HTTPS; browsers ignore it on plain HTTP.\n { key: \"Strict-Transport-Security\", value: \"max-age=63072000; includeSubDomains\" },\n ],\n },\n ];\n },\n`;\n\nconst SITE_URL_ENV_NOTE = `\n# The site's public address. Every emailed invite/reset link points here, and in\n# production nothing is sent without it (links are never built from the request).\n# GUARDRAIL_SITE_URL=https://example.com\n`;\n\nconst USAGE = `Usage:\n guardrail-doctor [options]\n\nChecks this project's Guardrail setup. Read-only unless you ask otherwise.\n\nOptions:\n --fix Repair what can be repaired in local files: copy missing\n migrations, add a GUARDRAIL_SITE_URL note to your env\n files, add baseline security headers to next.config.\n Never touches secrets or Supabase dashboard settings.\n --push With --fix: also run \\`supabase db push\\` after copying.\n --app-url=<url> Also check the response headers of a running copy of the\n site (e.g. --app-url=http://localhost:3000).\n --e2e Run the end-to-end suite against the database: creates a\n throwaway user and rows, exercises sign-in, invite claims,\n row-level security, the admin guard, rate limiting and\n storage, then deletes everything it made.\n --yes Don't ask before --e2e touches a non-local database.\n --offline Don't contact the npm registry. By default the installed\n versions of next, @supabase/* and @guard-rail/core are\n checked against its published security advisories (only\n package names and versions are sent, as \\`pnpm audit\\` does).\n --csp Print a starter Content-Security-Policy-Report-Only header\n for this site (built from SUPABASE_URL) and exit. Add it\n report-only first, watch the reports, then enforce.\n --production Treat production-only requirements (like\n GUARDRAIL_SITE_URL) as failures rather than warnings.\n --strict Exit non-zero on warnings too.\n --json Print results as JSON.\n -h, --help This text.\n\nReads SUPABASE_URL, SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY (or\nSERVICE_KEY) from the environment, then .env.local and .env.`;\n\n/* -------------------------------------------------------------- results --- */\n\ntype Status = \"pass\" | \"warn\" | \"fail\" | \"skip\" | \"info\";\n\ninterface Result {\n group: string;\n status: Status;\n title: string;\n detail?: string;\n hint?: string;\n}\n\nconst results: Result[] = [];\nlet currentGroup = \"\";\n\nfunction group(name: string): void {\n currentGroup = name;\n}\n\nfunction record(status: Status, title: string, detail?: string, hint?: string): void {\n results.push({ group: currentGroup, status, title, ...(detail ? { detail } : {}), ...(hint ? { hint } : {}) });\n}\n\nconst pass = (t: string, d?: string) => record(\"pass\", t, d);\nconst warn = (t: string, d?: string, h?: string) => record(\"warn\", t, d, h);\nconst fail = (t: string, d?: string, h?: string) => record(\"fail\", t, d, h);\nconst skip = (t: string, d?: string) => record(\"skip\", t, d);\nconst info = (t: string, d?: string) => record(\"info\", t, d);\n\n/* ------------------------------------------------------------- helpers --- */\n\nfunction parseFlags(argv: string[]) {\n const flags = new Set<string>();\n const values: Record<string, string> = {};\n const unknown: string[] = [];\n for (const arg of argv) {\n const m = arg.match(/^--([^=]+)=([\\s\\S]*)$/);\n if (m) {\n values[m[1]!] = m[2]!;\n } else if (arg.startsWith(\"-\")) {\n flags.add(arg.replace(/^-+/, \"\"));\n } else {\n unknown.push(arg);\n }\n }\n const known = new Set([\"fix\", \"push\", \"e2e\", \"yes\", \"production\", \"strict\", \"json\", \"offline\", \"csp\", \"help\", \"h\"]);\n for (const f of flags) if (!known.has(f)) unknown.push(`--${f}`);\n for (const k of Object.keys(values)) if (k !== \"app-url\") unknown.push(`--${k}`);\n return { flags, values, unknown };\n}\n\nfunction readEnvFiles(cwd: string): Record<string, string> {\n const out: Record<string, string> = {};\n // .env wins last-in, so read it first: .env.local overrides it, as in Next.\n for (const file of [\".env\", \".env.local\"]) {\n const p = path.join(cwd, file);\n if (!existsSync(p)) continue;\n for (const line of readFileSync(p, \"utf8\").split(/\\r?\\n/)) {\n const m = line.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/);\n if (!m) continue;\n const value = m[2]!.trim().replace(/^(['\"])(.*)\\1$/, \"$2\");\n if (value) out[m[1]!] = value;\n }\n }\n return out;\n}\n\nfunction parseVersion(v: string): [number, number, number] | null {\n const m = v.match(/^(\\d+)\\.(\\d+)\\.(\\d+)/);\n return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;\n}\n\nfunction atLeast(v: [number, number, number], min: [number, number, number]): boolean {\n for (let i = 0; i < 3; i++) {\n if (v[i]! !== min[i]!) return v[i]! > min[i]!;\n }\n return true;\n}\n\n/** Decodes a JWT's payload without verifying it — just to read which role a key claims. */\nfunction jwtRole(key: string): string | null {\n const parts = key.split(\".\");\n if (parts.length !== 3) return null;\n try {\n const payload = JSON.parse(Buffer.from(parts[1]!, \"base64url\").toString(\"utf8\"));\n return typeof payload.role === \"string\" ? payload.role : null;\n } catch {\n return null;\n }\n}\n\nfunction isLocalHost(host: string): boolean {\n return host === \"localhost\" || host === \"127.0.0.1\" || host === \"::1\" || host === \"[::1]\" || host.endsWith(\".localhost\");\n}\n\nfunction commandExists(cmd: string): boolean {\n const res = process.platform === \"win32\" ? spawnSync(\"where\", [cmd]) : spawnSync(\"which\", [cmd]);\n return res.status === 0;\n}\n\nfunction findNextConfig(cwd: string): string | null {\n for (const name of [\"next.config.ts\", \"next.config.mjs\", \"next.config.js\", \"next.config.cjs\", \"next.config.mts\"]) {\n const p = path.join(cwd, name);\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction bundledMigrationsDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n return path.join(here, \"..\", \"..\", \"supabase\", \"migrations\");\n}\n\n/* ----------------------------------------------------------------- fixes --- */\n\nconst applied: string[] = [];\n\nfunction fixMigrations(cwd: string): void {\n const src = bundledMigrationsDir();\n if (!existsSync(src)) return;\n const dest = path.join(cwd, \"supabase\", \"migrations\");\n mkdirSync(dest, { recursive: true });\n const have = new Set(readdirSync(dest));\n for (const file of readdirSync(src)) {\n if (have.has(file)) continue;\n copyFileSync(path.join(src, file), path.join(dest, file));\n applied.push(`Copied supabase/migrations/${file}`);\n }\n}\n\nfunction fixEnvNotes(cwd: string): void {\n for (const file of [\".env.example\", \".env.local\"]) {\n const p = path.join(cwd, file);\n if (!existsSync(p)) continue;\n const content = readFileSync(p, \"utf8\");\n if (/GUARDRAIL_SITE_URL/.test(content)) continue;\n writeFileSync(p, content.replace(/\\n*$/, \"\\n\") + SITE_URL_ENV_NOTE);\n applied.push(`Added a GUARDRAIL_SITE_URL note to ${file}`);\n }\n}\n\nconst CACHE_COMPONENTS_LINE = /^[ \\t]*(?:cacheComponents|partialPrefetching)\\s*:\\s*true\\s*,?[ \\t]*\\r?\\n/gm;\n\n/** Removes Cache Components from next.config. Returns true if it changed the file. */\nfunction fixCacheComponents(cwd: string): boolean {\n const configPath = findNextConfig(cwd);\n if (!configPath) return false;\n const content = readFileSync(configPath, \"utf8\");\n const next = content.replace(CACHE_COMPONENTS_LINE, \"\");\n if (next === content) return false;\n writeFileSync(configPath, next);\n applied.push(`Turned off Cache Components in ${path.basename(configPath)}`);\n return true;\n}\n\n/** Inserts the baseline headers into the config object. Returns false if the file isn't a shape it can patch. */\nfunction fixHeaders(cwd: string): \"patched\" | \"present\" | \"manual\" | \"none\" {\n const configPath = findNextConfig(cwd);\n if (!configPath) return \"none\";\n const content = readFileSync(configPath, \"utf8\");\n if (/frame-ancestors|x-frame-options/i.test(content)) return \"present\";\n // An existing headers() can't be merged into safely from here.\n if (/\\bheaders\\s*\\(/.test(content)) return \"manual\";\n\n const openers = [\n /(const\\s+nextConfig\\s*(?::\\s*[A-Za-z<>[\\]| ]+)?=\\s*\\{[ \\t]*\\r?\\n)/,\n /(export\\s+default\\s*\\{[ \\t]*\\r?\\n)/,\n /(module\\.exports\\s*=\\s*\\{[ \\t]*\\r?\\n)/,\n ];\n for (const re of openers) {\n if (!re.test(content)) continue;\n writeFileSync(configPath, content.replace(re, `$1${SECURITY_HEADERS_BLOCK}`));\n applied.push(`Added baseline security headers to ${path.basename(configPath)}`);\n return \"patched\";\n }\n return \"manual\";\n}\n\n/* ------------------------------------------------------------ advisories --- */\n\ninterface Advisory {\n title: string;\n severity: string;\n url: string;\n}\n\n/**\n * Published security advisories for exact package versions, from the npm\n * registry's bulk advisory service (the one `pnpm audit` uses). Null when it\n * can't be reached, so callers can fall back instead of guessing.\n */\nasync function fetchAdvisories(versions: Record<string, string>): Promise<Record<string, Advisory[]> | null> {\n const body: Record<string, string[]> = {};\n for (const [name, v] of Object.entries(versions)) body[name] = [v];\n if (Object.keys(body).length === 0) return {};\n try {\n const res = await fetch(\"https://registry.npmjs.org/-/npm/v1/security/advisories/bulk\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(8000),\n });\n if (!res.ok) return null;\n return (await res.json()) as Record<string, Advisory[]>;\n } catch {\n return null;\n }\n}\n\n/* ---------------------------------------------------------------- checks --- */\n\ninterface Ctx {\n cwd: string;\n env: Record<string, string>;\n production: boolean;\n offline: boolean;\n url?: string;\n anonKey?: string;\n serviceKey?: string;\n}\n\nasync function checkProject(ctx: Ctx): Promise<void> {\n group(\"Project\");\n const pkgPath = path.join(ctx.cwd, \"package.json\");\n if (!existsSync(pkgPath)) {\n fail(\"No package.json here\", \"Run this from the root of your Next.js project.\");\n return;\n }\n\n const ownPkg = JSON.parse(readFileSync(path.join(bundledMigrationsDir(), \"..\", \"..\", \"package.json\"), \"utf8\"));\n pass(`@guard-rail/core ${ownPkg.version}`);\n\n const require = createRequire(pkgPath);\n const installed = (name: string): string | undefined => {\n try {\n return JSON.parse(readFileSync(require.resolve(`${name}/package.json`), \"utf8\")).version;\n } catch {\n return undefined;\n }\n };\n const nextVersion = installed(\"next\");\n if (!nextVersion) warn(\"Next.js isn't installed here\", \"Couldn't resolve `next` from this directory.\");\n\n // Ask the registry about what's actually installed; only fall back to the\n // built-in table for Next.js when that isn't possible.\n const checked: Record<string, string> = {};\n for (const name of [\"next\", \"@supabase/supabase-js\", \"@supabase/ssr\"]) {\n const v = installed(name);\n if (v) checked[name] = v;\n }\n checked[\"@guard-rail/core\"] = ownPkg.version;\n const advisories = ctx.offline ? null : await fetchAdvisories(checked);\n\n if (advisories) {\n let clean = 0;\n for (const [name, version] of Object.entries(checked)) {\n const found = advisories[name] ?? [];\n const serious = found.filter((a) => a.severity === \"critical\" || a.severity === \"high\");\n if (found.length === 0) {\n clean++;\n continue;\n }\n const lines = found.slice(0, 3).map((a) => `${a.severity}: ${a.title} (${a.url})`);\n if (found.length > 3) lines.push(`…and ${found.length - 3} more`);\n (serious.length ? fail : warn)(\n `${name} ${version} has ${found.length} known security advisor${found.length === 1 ? \"y\" : \"ies\"}`,\n lines.join(\"\\n \"),\n `Upgrade: pnpm add ${name}@latest`,\n );\n }\n if (clean) pass(`No known security advisories for ${clean} checked package${clean === 1 ? \"\" : \"s\"}`, Object.entries(checked).filter(([n]) => (advisories[n] ?? []).length === 0).map(([n, v]) => `${n} ${v}`).join(\", \"));\n } else if (nextVersion) {\n // Offline, or the registry couldn't be reached: the built-in table.\n const v = parseVersion(nextVersion);\n const floor = v ? NEXT_MIN_PATCHED[v[0]] : undefined;\n const why = ctx.offline ? \"offline\" : \"the advisory service couldn't be reached\";\n if (!v) {\n info(`Next.js ${nextVersion}`);\n } else if (floor && !atLeast(v, floor)) {\n fail(\n `Next.js ${nextVersion} has known security vulnerabilities`,\n `Releases before ${floor.join(\".\")} carry published advisories (checked against this tool's built-in list — ${why}).`,\n `Upgrade: pnpm add next@latest`,\n );\n } else if (floor) {\n pass(`Next.js ${nextVersion}`, `Checked against this tool's built-in list (${why}); it may not know about newer advisories.`);\n } else {\n warn(\n `Next.js ${nextVersion}`,\n `This tool doesn't know the patched release for the ${v[0]}.x line (${why}).`,\n `Make sure you're on its latest patch, or move to the current major.`,\n );\n }\n }\n\n for (const dep of [\"@supabase/supabase-js\", \"@supabase/ssr\"]) {\n try {\n require.resolve(`${dep}/package.json`);\n pass(`${dep} installed`);\n } catch {\n fail(`${dep} isn't installed`, undefined, `pnpm add ${dep}`);\n }\n }\n}\n\nfunction checkEnvironment(ctx: Ctx): void {\n group(\"Environment\");\n const { env } = ctx;\n\n if (!ctx.url) {\n fail(\"SUPABASE_URL isn't set\", \"Not in the environment, .env.local or .env.\");\n } else {\n try {\n const u = new URL(ctx.url);\n if (u.protocol !== \"https:\" && u.protocol !== \"http:\") throw new Error();\n if (u.protocol === \"http:\" && !isLocalHost(u.hostname)) {\n warn(\"SUPABASE_URL uses plain http\", \"Use the project's https:// address.\");\n } else {\n pass(\"SUPABASE_URL\", u.host);\n }\n } catch {\n fail(\"SUPABASE_URL isn't a valid URL\", \"Expected https://<project>.supabase.co.\");\n }\n }\n\n const anon = ctx.anonKey;\n if (!anon) {\n fail(\"SUPABASE_ANON_KEY isn't set\");\n } else {\n const role = jwtRole(anon);\n if (role === \"service_role\" || anon.startsWith(\"sb_secret_\")) {\n fail(\n \"SUPABASE_ANON_KEY holds the service-role (secret) key\",\n \"The anon key is meant to be public; the service-role key bypasses every access rule.\",\n \"Swap them: put the anon / publishable key here.\",\n );\n } else {\n pass(\"SUPABASE_ANON_KEY\");\n }\n }\n\n const svc = ctx.serviceKey;\n if (!svc) {\n warn(\n \"No service-role key\",\n \"Invites, password resets and database-backed rate limits need SUPABASE_SERVICE_ROLE_KEY (server-only).\",\n );\n } else {\n const role = jwtRole(svc);\n if (role === \"anon\" || svc.startsWith(\"sb_publishable_\")) {\n fail(\"SUPABASE_SERVICE_ROLE_KEY holds the anon (publishable) key\", undefined, \"Use the service-role / secret key here.\");\n } else {\n pass(\"Service-role key present\");\n }\n }\n\n // A secret behind NEXT_PUBLIC_ is compiled into the browser bundle.\n const leaked = Object.keys(env).filter((k) => /^NEXT_PUBLIC_.*(SERVICE|SECRET|RESEND|PRIVATE)/i.test(k));\n if (leaked.length) {\n fail(\n \"Secret behind a NEXT_PUBLIC_ name\",\n `${leaked.join(\", \")} would be published to every visitor's browser.`,\n \"Rename it without the NEXT_PUBLIC_ prefix and read it only in server code.\",\n );\n } else {\n pass(\"No secrets behind NEXT_PUBLIC_ names\");\n }\n\n const siteUrl = env.GUARDRAIL_SITE_URL;\n if (!siteUrl) {\n (ctx.production ? fail : warn)(\n \"GUARDRAIL_SITE_URL isn't set\",\n \"Production sends no invite or password-reset emails without it — and \\\"Forgot password?\\\" fails silently.\",\n \"Set it to your public address, e.g. https://example.com (or pass siteUrl to withGuardrail()).\",\n );\n } else {\n try {\n const u = new URL(siteUrl);\n if (u.protocol !== \"https:\" && u.protocol !== \"http:\") throw new Error();\n if (u.protocol === \"http:\" && !isLocalHost(u.hostname)) {\n warn(\"GUARDRAIL_SITE_URL uses plain http\", \"Emailed links should point at an https address.\");\n } else {\n pass(\"GUARDRAIL_SITE_URL\", u.origin);\n }\n } catch {\n fail(\"GUARDRAIL_SITE_URL isn't a valid http(s) URL\", siteUrl);\n }\n }\n\n const hasKey = Boolean(env.RESEND_API_KEY);\n const hasFrom = Boolean(env.GUARDRAIL_EMAIL_FROM);\n if (hasKey && hasFrom) {\n pass(\"Resend email configured\");\n } else if (hasKey !== hasFrom) {\n fail(\n `${hasKey ? \"GUARDRAIL_EMAIL_FROM\" : \"RESEND_API_KEY\"} is missing`,\n \"Email through Resend needs both RESEND_API_KEY and GUARDRAIL_EMAIL_FROM.\",\n );\n } else {\n info(\"No Resend settings in the environment\", \"Fine if you pass your own onInvite / onPasswordReset, or only use consoleEmails() in development.\");\n }\n\n if (env.GUARDRAIL_SITE_ID) pass(\"GUARDRAIL_SITE_ID\", env.GUARDRAIL_SITE_ID);\n else info(\"GUARDRAIL_SITE_ID isn't set\", 'Sites default to the id \"default\".');\n}\n\nfunction checkFiles(ctx: Ctx): void {\n group(\"Files\");\n\n // Migrations\n const src = bundledMigrationsDir();\n const dest = path.join(ctx.cwd, \"supabase\", \"migrations\");\n if (existsSync(src)) {\n const shipped = readdirSync(src);\n const have = existsSync(dest) ? new Set(readdirSync(dest)) : new Set<string>();\n const missing = shipped.filter((f) => !have.has(f));\n if (missing.length === 0) {\n pass(\"All Guardrail migrations are in supabase/migrations/\");\n } else {\n fail(\n `${missing.length} migration${missing.length === 1 ? \"\" : \"s\"} not copied into supabase/migrations/`,\n missing.join(\", \"),\n \"Run: pnpm exec guardrail-doctor --fix (or guardrail-init-db)\",\n );\n }\n }\n\n // Local Supabase config exposes the schema\n const configToml = path.join(ctx.cwd, \"supabase\", \"config.toml\");\n if (existsSync(configToml)) {\n const toml = readFileSync(configToml, \"utf8\");\n const schemas = toml.match(/\\[api\\][\\s\\S]*?schemas\\s*=\\s*\\[([^\\]]*)\\]/)?.[1] ?? \"\";\n if (schemas.includes('\"guardrail\"')) pass('supabase/config.toml exposes the \"guardrail\" schema');\n else fail('supabase/config.toml doesn\\'t expose the \"guardrail\" schema', undefined, \"Run: pnpm exec guardrail-init-db\");\n }\n\n // Root layout wiring\n const layouts = [\"app/layout.tsx\", \"src/app/layout.tsx\", \"app/layout.jsx\", \"src/app/layout.jsx\", \"app/layout.js\", \"src/app/layout.js\"];\n const layout = layouts.map((l) => path.join(ctx.cwd, l)).find(existsSync);\n const wiredIn = (file: string) => /withGuardrail/.test(readFileSync(file, \"utf8\"));\n if (!layout) {\n skip(\"Root layout not found\", \"Looked for app/layout and src/app/layout.\");\n } else if (wiredIn(layout)) {\n pass(\"Root layout uses withGuardrail()\");\n } else {\n warn(\n \"Root layout doesn't mention withGuardrail()\",\n `${path.relative(ctx.cwd, layout)} — fine if it's wrapped where the layout is exported from another file.`,\n );\n }\n\n // Security headers\n const configPath = findNextConfig(ctx.cwd);\n if (!configPath) {\n skip(\"next.config not found\");\n } else {\n const content = readFileSync(configPath, \"utf8\");\n if (/^[ \\t]*cacheComponents\\s*:\\s*true/m.test(content)) {\n fail(\n \"Cache Components is turned on\",\n \"Guardrail reads the session cookie to decide what to render, which Next's Cache Components mode refuses outside <Suspense> — the site won't pass `next build`.\",\n \"Run: pnpm exec guardrail-doctor --fix (removes `cacheComponents: true` and `partialPrefetching: true`)\",\n );\n }\n if (/frame-ancestors|x-frame-options/i.test(content)) {\n pass(`${path.basename(configPath)} sets anti-framing headers`);\n } else {\n warn(\n `${path.basename(configPath)} sets no security headers`,\n \"Without frame-ancestors, another site can frame yours — including the editing toolbar — under a clickjacking overlay.\",\n \"Run: pnpm exec guardrail-doctor --fix (skip if your host or middleware sets them; verify with --app-url)\",\n );\n }\n }\n\n // Env files must be ignored by git\n const gitignore = path.join(ctx.cwd, \".gitignore\");\n const hasEnvFile = [\".env.local\", \".env\"].some((f) => existsSync(path.join(ctx.cwd, f)));\n if (hasEnvFile) {\n const ignores = existsSync(gitignore) ? readFileSync(gitignore, \"utf8\") : \"\";\n if (/^\\s*\\.env(\\*|\\.local|\\.\\*)?\\s*$/m.test(ignores) || /^\\s*\\.env\\*/m.test(ignores)) {\n pass(\"Env files are gitignored\");\n } else {\n fail(\".env files aren't gitignored\", \"Your Supabase keys could be committed.\", \"Add `.env*` (and `!.env.example`) to .gitignore.\");\n }\n }\n}\n\ntype SupabaseModule = typeof import(\"@supabase/supabase-js\");\ntype SupabaseClient = import(\"@supabase/supabase-js\").SupabaseClient;\n\nasync function loadSupabase(): Promise<SupabaseModule | null> {\n try {\n return (await import(\"@supabase/supabase-js\")) as SupabaseModule;\n } catch {\n return null;\n }\n}\n\nasync function checkDatabase(ctx: Ctx, sb: SupabaseModule | null): Promise<boolean> {\n group(\"Database\");\n if (!ctx.url || !ctx.anonKey) {\n skip(\"Skipped\", \"Needs SUPABASE_URL and SUPABASE_ANON_KEY.\");\n return false;\n }\n if (!sb) {\n skip(\"Skipped\", \"@supabase/supabase-js isn't installed.\");\n return false;\n }\n const base = ctx.url.replace(/\\/$/, \"\");\n\n // Reachability + schema exposure, with the public key only\n try {\n const res = await fetch(`${base}/rest/v1/gr_content?select=path_key&limit=1`, {\n headers: { apikey: ctx.anonKey, Authorization: `Bearer ${ctx.anonKey}`, \"Accept-Profile\": GR_SCHEMA },\n });\n const body = res.ok ? \"\" : await res.text();\n if (res.ok) {\n pass(`\"${GR_SCHEMA}\" schema is exposed and readable`);\n } else if (/PGRST106|Invalid schema/i.test(body)) {\n fail(\n `The \"${GR_SCHEMA}\" schema isn't exposed`,\n \"PostgREST refuses requests for it.\",\n \"Supabase dashboard > Settings > API > Exposed schemas: add guardrail.\",\n );\n return false;\n } else if (/PGRST205|42P01|does not exist|schema cache/i.test(body)) {\n fail(\"Guardrail's tables don't exist\", \"The migration hasn't been applied to this project.\", \"Run: pnpm exec guardrail-init-db --push\");\n return false;\n } else if (res.status === 401 || res.status === 403) {\n fail(\"The anon key was rejected\", `HTTP ${res.status}`, \"Check SUPABASE_ANON_KEY belongs to this project.\");\n return false;\n } else {\n fail(\"Unexpected response reading content\", `HTTP ${res.status}: ${body.slice(0, 160)}`);\n return false;\n }\n } catch (e) {\n fail(\"Couldn't reach Supabase\", e instanceof Error ? e.message : String(e), \"Check SUPABASE_URL, and that the project isn't paused.\");\n return false;\n }\n\n // Sign-up settings: the one thing that decides whether an invite can be claimed by someone else\n try {\n const res = await fetch(`${base}/auth/v1/settings`, { headers: { apikey: ctx.anonKey } });\n if (res.ok) {\n const s = (await res.json()) as {\n disable_signup?: boolean;\n mailer_autoconfirm?: boolean;\n external?: { email?: boolean };\n };\n if (s.external?.email === false) {\n fail(\"Email sign-in is disabled in Supabase\", undefined, \"Authentication > Sign In / Providers: enable Email.\");\n } else if (s.disable_signup) {\n pass(\"Public sign-ups are off\");\n } else if (s.mailer_autoconfirm) {\n warn(\n \"Anyone can sign up, and addresses count as confirmed instantly\",\n \"Someone could register an invited person's address before they do, and claim the invitation.\",\n \"Authentication > Sign In / Providers > Email: turn on Confirm email (or turn off public sign-ups).\",\n );\n } else {\n pass(\"Sign-ups require a confirmed email\");\n }\n } else {\n skip(\"Couldn't read the project's sign-up settings\", `HTTP ${res.status}`);\n }\n } catch {\n skip(\"Couldn't read the project's sign-up settings\");\n }\n\n if (!ctx.serviceKey) {\n skip(\"Service-role checks skipped\", \"No service-role key to inspect the database's functions with.\");\n return true;\n }\n\n const admin = sb.createClient(ctx.url, ctx.serviceKey, { auth: { persistSession: false, autoRefreshToken: false } });\n const gr = () => admin.schema(GR_SCHEMA);\n\n for (const table of [GR_MEMBERS_TABLE, \"gr_content\", \"gr_repeater_items\", \"gr_rate_limits\"]) {\n const { error } = await gr().from(table).select(\"*\", { head: true, count: \"exact\" }).limit(1);\n if (!error) pass(`Table ${table}`);\n else if (table === \"gr_rate_limits\") warn(\"Table gr_rate_limits is missing\", \"Rate limits are counted in memory until the migration is applied.\", \"Run: pnpm exec guardrail-init-db --push\");\n else fail(`Table ${table} isn't readable`, error.message, \"Run: pnpm exec guardrail-init-db --push\");\n }\n\n // Functions: a permission error means it exists but this role can't run it; \"not found\" means it's absent.\n const probes: Array<[string, Record<string, unknown>, string]> = [\n [\"gr_confirmed_email\", {}, \"Confirmed-email identity check\"],\n [\"gr_claim_invite\", { target_site: \"doctor-probe\" }, \"Invite-claim function\"],\n [\"gr_ensure_bucket\", { target_bucket: \"gr-doctor-probe\" }, \"Media bucket function\"],\n ];\n for (const [fn, args, label] of probes) {\n const { error } = await gr().rpc(fn, args);\n if (!error || error.code === \"42501\" || /permission denied/i.test(error.message)) {\n pass(label);\n } else if (error.code === \"PGRST202\" || error.code === \"42883\" || /could not find the function/i.test(error.message)) {\n fail(`${label} is missing`, `Function ${fn}() doesn't exist.`, \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n } else {\n // Exists, but failed for another reason with these dummy arguments.\n pass(label);\n }\n }\n\n const probeKey = `doctor-${randomBytes(3).toString(\"hex\")}`;\n const { data, error } = await gr().rpc(\"gr_rate_hit\", { p_name: \"doctor:probe\", p_key: probeKey, p_limit: 1000, p_window_seconds: 60 });\n if (!error && data) {\n pass(\"Database-backed rate limiting\");\n await gr().from(\"gr_rate_limits\").delete().eq(\"bucket\", `doctor:probe|${probeKey}`);\n } else if (error?.code === \"PGRST202\" || error?.code === \"42883\") {\n warn(\"Rate-limit function is missing\", \"Limits are counted in memory, which doesn't hold across serverless instances.\", \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n } else if (error) {\n warn(\"Rate-limit function failed\", error.message);\n }\n\n info(\"Admin guard (can't delete the last admin)\", \"Verified by --e2e; it's a trigger, which can't be probed read-only.\");\n return true;\n}\n\n/* -------------------------------------------------------------------- csp --- */\n\ntype Csp = Record<string, string[]>;\n\nfunction parseCsp(value: string): Csp {\n const out: Csp = {};\n for (const part of value.split(\";\")) {\n const [name, ...sources] = part.trim().split(/\\s+/);\n if (name) out[name.toLowerCase()] = sources;\n }\n return out;\n}\n\n/** The sources a directive effectively allows: its own list, else default-src's; null if neither (unrestricted). */\nfunction effectiveSources(csp: Csp, name: string): string[] | null {\n return csp[name] ?? csp[\"default-src\"] ?? null;\n}\n\n/** Whether a source list lets the page reach `origin` (a different origin from the site's own). */\nfunction allowsOrigin(sources: string[] | null, origin: string): boolean {\n if (sources === null) return true;\n const target = new URL(origin);\n return sources.some((raw) => {\n const src = raw.toLowerCase();\n if (src === \"*\") return true;\n if (src === `${target.protocol}`) return true; // scheme only, e.g. https:\n if (src === origin.toLowerCase()) return true;\n const wild = src.match(/^(https?:)\\/\\/\\*\\.(.+?)(?::(\\d+))?$/);\n if (wild) {\n const [, scheme, domain, port] = wild;\n return scheme === target.protocol && target.hostname.endsWith(`.${domain}`) && (!port || port === target.port);\n }\n return false;\n });\n}\n\n/** A starter policy for this site: Guardrail's needs plus a conservative baseline for everything else. */\nfunction starterPolicy(supabaseUrl?: string): string {\n const gr = guardrailCspDirectives(supabaseUrl ? { supabaseUrl } : {});\n return cspString({\n \"default-src\": [\"'self'\"],\n \"script-src\": [\"'self'\", \"'nonce-REPLACE_PER_REQUEST'\", \"'strict-dynamic'\"],\n \"style-src\": gr[\"style-src\"]!,\n \"img-src\": [...gr[\"img-src\"]!, \"data:\"],\n \"connect-src\": gr[\"connect-src\"]!,\n \"font-src\": [\"'self'\", \"data:\"],\n \"object-src\": [\"'none'\"],\n \"base-uri\": [\"'self'\"],\n \"form-action\": [\"'self'\"],\n \"frame-ancestors\": gr[\"frame-ancestors\"]!,\n });\n}\n\nfunction printStarterCsp(ctx: Ctx): void {\n console.log(`Starter Content-Security-Policy\n\nContent-Security-Policy-Report-Only: ${starterPolicy(ctx.url)}\n\nHow to use it\n - Send it as Content-Security-Policy-Report-Only first. Browsers report what it\n would block without blocking anything; tune it for the things your site\n loads itself (analytics, fonts, embeds), then switch the header name to\n Content-Security-Policy.\n - Guardrail needs nothing beyond 'self' for styles and scripts. Its stylesheet is\n a real file and it renders no inline scripts, so it needs no 'unsafe-inline' and no\n nonce. The only extras are your Supabase origin in connect-src (image\n uploads) and img-src (the media picker's thumbnails).${ctx.url ? \"\" : \"\\n (Set SUPABASE_URL and run this again to include your Supabase origin.)\"}\n - script-src above uses a per-request nonce, which Next.js sets from a proxy\n (see \"Content Security Policy\" in the Next.js docs). With only a static header\n in next.config you can't use a nonce — use 'self' 'unsafe-inline' for scripts\n instead, which is a weaker policy but still a useful one.\n - guardrailCspDirectives() from @guard-rail/core returns Guardrail's directives\n if you build the header in code.\n`);\n}\n\nasync function checkLiveSite(appUrl: string, ctx: Ctx): Promise<void> {\n group(\"Live site\");\n let res: Response;\n try {\n res = await fetch(appUrl, { redirect: \"manual\" });\n } catch (e) {\n fail(`Couldn't reach ${appUrl}`, e instanceof Error ? e.message : String(e), \"Is the site running?\");\n return;\n }\n if (res.status >= 500) {\n fail(`${appUrl} answered HTTP ${res.status}`);\n return;\n }\n pass(`${appUrl} answered HTTP ${res.status}`);\n\n const h = res.headers;\n const csp = h.get(\"content-security-policy\") ?? \"\";\n if (/frame-ancestors/i.test(csp) || h.get(\"x-frame-options\")) pass(\"Can't be framed by another site\");\n else fail(\"Can be framed by another site\", \"No frame-ancestors or X-Frame-Options header.\", \"Add the baseline headers: pnpm exec guardrail-doctor --fix\");\n\n if (/nosniff/i.test(h.get(\"x-content-type-options\") ?? \"\")) pass(\"X-Content-Type-Options: nosniff\");\n else warn(\"X-Content-Type-Options header missing\");\n\n if (h.get(\"referrer-policy\")) pass(`Referrer-Policy: ${h.get(\"referrer-policy\")}`);\n else warn(\"Referrer-Policy header missing\");\n\n if (new URL(appUrl).protocol === \"https:\") {\n if (h.get(\"strict-transport-security\")) pass(\"Strict-Transport-Security set\");\n else warn(\"Strict-Transport-Security header missing\", \"Browsers won't insist on HTTPS for this site.\");\n } else {\n skip(\"Strict-Transport-Security\", \"Only meaningful over HTTPS.\");\n }\n\n const setCookie = typeof h.getSetCookie === \"function\" ? h.getSetCookie() : [];\n const session = setCookie.filter((c) => /^sb-.*-auth-token/i.test(c));\n if (session.length) fail(\"A session cookie was set for an anonymous request\", session.map((c) => c.split(\"=\")[0]).join(\", \"));\n\n // Guardrail's stylesheet: the chrome is styled by a CSS file the package imports\n // itself. A bundler that doesn't pick up CSS from node_modules would leave it\n // silently unstyled, so confirm the CSS actually reached the page.\n if (res.status === 200) {\n const html = await res.text().catch(() => \"\");\n const MARKER = /--gr-bg\\b|\\.gr-banner\\b/;\n const inline = [...html.matchAll(/<style[^>]*>([\\s\\S]*?)<\\/style>/gi)].map((m) => m[1]).join(\"\\n\");\n let found = MARKER.test(inline);\n const hrefs = [...html.matchAll(/<link\\b[^>]*rel=[\"']stylesheet[\"'][^>]*>/gi)]\n .map((m) => m[0].match(/href=[\"']([^\"']+)[\"']/i)?.[1])\n .filter((x): x is string => Boolean(x))\n .slice(0, 8);\n for (const href of hrefs) {\n if (found) break;\n try {\n const css = await fetch(new URL(href, appUrl), { signal: AbortSignal.timeout(8000) });\n if (css.ok && MARKER.test(await css.text())) found = true;\n } catch {\n // an unreachable stylesheet is the site's own problem; keep looking\n }\n }\n if (found) pass(\"Guardrail's stylesheet is on the page\");\n else if (hrefs.length === 0 && !/<style/i.test(html)) skip(\"Guardrail's stylesheet\", \"The page has no stylesheets to inspect.\");\n else {\n warn(\n \"Guardrail's stylesheet isn't on the page\",\n \"Its editing chrome would render unstyled. The package imports its own CSS, which your bundler may not be picking up.\",\n 'Import it once in your root layout: import \"@guard-rail/core/styles.css\";',\n );\n }\n }\n\n // Content-Security-Policy: is there a real one, and does it let Guardrail work?\n const enforced = h.get(\"content-security-policy\");\n const reportOnly = h.get(\"content-security-policy-report-only\");\n const policy = enforced ?? reportOnly;\n if (!policy) {\n warn(\n \"No Content-Security-Policy\",\n \"Nothing limits where scripts and styles can load from, so an injected script would run.\",\n \"Run: pnpm exec guardrail-doctor --csp (prints a starter report-only policy)\",\n );\n return;\n }\n const parsed = parseCsp(policy);\n const label = enforced ? \"\" : \" (report-only)\";\n if (!parsed[\"default-src\"] && !parsed[\"script-src\"]) {\n info(`Content-Security-Policy only sets ${Object.keys(parsed).join(\", \") || \"nothing\"}`, \"It doesn't restrict scripts. Run: pnpm exec guardrail-doctor --csp for a fuller starter.\");\n return;\n }\n const style = effectiveSources(parsed, \"style-src\");\n if (style && style.map((x) => x.toLowerCase()).includes(\"'unsafe-inline'\")) {\n info(`style-src allows 'unsafe-inline'${label}`, \"Guardrail no longer needs it — its chrome is a stylesheet. Remove it unless something else on the site does.\");\n } else {\n pass(`Policy${label} doesn't need 'unsafe-inline' for Guardrail's styles`);\n }\n if (ctx.url) {\n let supabase: string | undefined;\n try {\n supabase = new URL(ctx.url).origin;\n } catch {\n // reported under Environment\n }\n if (supabase && supabase !== new URL(appUrl).origin) {\n for (const [directive, why] of [\n [\"connect-src\", \"image uploads go straight from the browser to Supabase storage\"],\n [\"img-src\", \"uploaded images and the media picker's thumbnails load from Supabase storage\"],\n ] as const) {\n if (allowsOrigin(effectiveSources(parsed, directive), supabase)) pass(`${directive}${label} allows ${supabase}`);\n else warn(`${directive}${label} blocks ${supabase}`, `Needed because ${why}.`, `Add ${supabase} to ${directive}.`);\n }\n }\n }\n}\n\n/* ------------------------------------------------------------------ e2e --- */\n\n// A 1x1 transparent PNG.\nconst TINY_PNG = Buffer.from(\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==\",\n \"base64\",\n);\n\nasync function confirmE2E(ctx: Ctx, yes: boolean): Promise<boolean> {\n const host = new URL(ctx.url!).host;\n const local = isLocalHost(new URL(ctx.url!).hostname);\n console.log(\n `\\nThe end-to-end suite will create a throwaway user, a few rows and one test image on ${host},\\nexercise them as a signed-in editor would, then delete everything it made.\\n`,\n );\n if (local || yes) return true;\n if (!process.stdin.isTTY) {\n console.log(\"That's not a local database. Re-run with --yes to allow it.\");\n return false;\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = (await rl.question(`${host} is not a local database. Continue? [y/N] `)).trim().toLowerCase();\n rl.close();\n return answer === \"y\" || answer === \"yes\";\n}\n\nasync function runE2E(ctx: Ctx, sb: SupabaseModule): Promise<void> {\n group(\"End-to-end\");\n const url = ctx.url!;\n const admin = sb.createClient(url, ctx.serviceKey!, { auth: { persistSession: false, autoRefreshToken: false } });\n const user = sb.createClient(url, ctx.anonKey!, { auth: { persistSession: false, autoRefreshToken: false } });\n const anon = sb.createClient(url, ctx.anonKey!, { auth: { persistSession: false, autoRefreshToken: false } });\n const gr = (c: SupabaseClient) => c.schema(GR_SCHEMA);\n\n const tag = randomBytes(4).toString(\"hex\");\n const siteId = `doctor-${tag}`;\n const otherSite = `doctor-${tag}-other`;\n const email = `guardrail-doctor-${tag}@example.com`;\n const password = randomBytes(18).toString(\"base64url\");\n const objectPath = `${siteId}/doctor.png`;\n let userId: string | undefined;\n let uploaded = false;\n let storageChecked = false;\n\n // `why` explains a failure and is only shown when the check fails; `note` is\n // shown when it passes.\n const expect = (ok: boolean, title: string, why?: string, hint?: string, note?: string) =>\n ok ? pass(title, note) : fail(title, why, hint);\n\n try {\n // 1. A throwaway account, and a link the way an invite or reset would mint one\n const created = await admin.auth.admin.createUser({ email, password, email_confirm: true });\n if (created.error || !created.data.user) {\n fail(\"Create a test account\", created.error?.message ?? \"No user returned.\", \"Needs a valid service-role key and Email sign-in enabled.\");\n return;\n }\n userId = created.data.user.id;\n pass(\"Create a test account (service-role key works)\");\n\n const link = await admin.auth.admin.generateLink({ type: \"recovery\", email });\n expect(\n !link.error && Boolean(link.data?.properties?.hashed_token),\n \"Mint a password-reset link\",\n link.error?.message,\n \"Invites and resets need this to work; check Email sign-in is enabled.\",\n );\n\n // 2. Invite claim: a pending membership becomes active for the signed-in person\n const seeded = await gr(admin).from(GR_MEMBERS_TABLE).insert({ site_id: siteId, email, role: \"admin\", status: \"pending\" });\n if (seeded.error) {\n fail(\"Record a pending invitation\", seeded.error.message);\n return;\n }\n const signedIn = await user.auth.signInWithPassword({ email, password });\n if (signedIn.error) {\n fail(\"Sign in with a password\", signedIn.error.message);\n return;\n }\n pass(\"Sign in with a password\");\n\n const claim = await gr(user).rpc(\"gr_claim_invite\", { target_site: siteId });\n const after = await gr(user).from(GR_MEMBERS_TABLE).select(\"status,role\").eq(\"site_id\", siteId).eq(\"email\", email).maybeSingle();\n expect(\n !claim.error && after.data?.status === \"active\" && after.data?.role === \"admin\",\n \"Claim an invitation\",\n claim.error?.message ?? (after.data ? undefined : \"The membership row wasn't visible after claiming.\"),\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n );\n\n // 3. Content: an admin can write their own site, anyone can read it, nobody can write another site\n const write = await gr(user).from(\"gr_content\").insert({\n site_id: siteId,\n path_key: \"doctor.check\",\n type: \"text\",\n value: { type: \"text\", text: \"doctor\" },\n });\n expect(!write.error, \"Editor can save content to their site\", write.error?.message);\n\n const read = await gr(anon).from(\"gr_content\").select(\"path_key\").eq(\"site_id\", siteId).eq(\"path_key\", \"doctor.check\");\n expect(!read.error && (read.data?.length ?? 0) === 1, \"Visitors can read saved content\", read.error?.message);\n\n const cross = await gr(user).from(\"gr_content\").insert({\n site_id: otherSite,\n path_key: \"doctor.check\",\n type: \"text\",\n value: { type: \"text\", text: \"nope\" },\n });\n expect(Boolean(cross.error), \"Editor can't write to a site they don't belong to\", cross.error ? undefined : \"The write was allowed — row-level security is not protecting content.\");\n\n const anonWrite = await gr(anon).from(\"gr_content\").insert({\n site_id: siteId,\n path_key: \"doctor.anon\",\n type: \"text\",\n value: { type: \"text\", text: \"nope\" },\n });\n expect(Boolean(anonWrite.error), \"Visitors can't write content\", anonWrite.error ? undefined : \"An unauthenticated write was allowed.\");\n\n // 4. Admin guard: the last admin can't be demoted or removed through the API\n const demote = await gr(user).from(GR_MEMBERS_TABLE).update({ role: \"editor\" }).eq(\"site_id\", siteId).eq(\"email\", email);\n expect(\n Boolean(demote.error),\n \"The last admin can't be demoted\",\n demote.error ? undefined : \"The demotion succeeded — the admin guard trigger isn't installed.\",\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n );\n // Put the admin back first, so this check doesn't depend on the last one\n // having been blocked.\n await gr(admin).from(GR_MEMBERS_TABLE).upsert(\n { site_id: siteId, email, user_id: userId, role: \"admin\", status: \"active\" },\n { onConflict: \"site_id,email\" },\n );\n const remove = await gr(user).from(GR_MEMBERS_TABLE).delete().eq(\"site_id\", siteId).eq(\"email\", email);\n const stillThere = await gr(admin).from(GR_MEMBERS_TABLE).select(\"email\").eq(\"site_id\", siteId).eq(\"email\", email);\n expect(\n Boolean(remove.error) || (stillThere.data?.length ?? 0) === 1,\n \"The last admin can't be removed\",\n remove.error ? undefined : \"The row was deleted — the admin guard trigger isn't installed.\",\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n );\n\n // Whatever the last two checks did, the storage checks below need the admin back.\n await gr(admin).from(GR_MEMBERS_TABLE).upsert(\n { site_id: siteId, email, user_id: userId, role: \"admin\", status: \"active\" },\n { onConflict: \"site_id,email\" },\n );\n\n // 5. Rate limiting counts and then refuses\n const hits: boolean[] = [];\n let rateErr: string | undefined;\n for (let i = 0; i < 3; i++) {\n const r = await gr(admin).rpc(\"gr_rate_hit\", { p_name: \"doctor:e2e\", p_key: siteId, p_limit: 2, p_window_seconds: 60 });\n if (r.error) {\n rateErr = r.error.message;\n break;\n }\n const row = Array.isArray(r.data) ? r.data[0] : r.data;\n hits.push(Boolean(row?.allowed));\n }\n expect(\n !rateErr && hits.join() === \"true,true,false\",\n \"Rate limiter allows up to the limit, then refuses\",\n rateErr ?? (hits.length ? `Got ${hits.join(\", \")} instead of true, true, false` : undefined),\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n `Allowed ${hits.filter(Boolean).length} of ${hits.length} attempts at a limit of 2`,\n );\n\n // 6. Storage, when the site uses it\n const buckets = await admin.storage.listBuckets();\n const mediaBucket = buckets.data?.find((b) => b.id === \"gr-media\");\n if (!mediaBucket) {\n skip(\"Storage\", 'No \"gr-media\" bucket — skipped (this site may not use editable images).');\n } else {\n storageChecked = true;\n const up = await user.storage.from(\"gr-media\").upload(objectPath, TINY_PNG, { contentType: \"image/png\" });\n uploaded = !up.error;\n expect(!up.error, \"Editor can upload an image to their site's folder\", up.error?.message, \"Check the storage policies from the migration are applied.\");\n if (uploaded) {\n const { data } = user.storage.from(\"gr-media\").getPublicUrl(objectPath);\n const res = await fetch(data.publicUrl).catch(() => null);\n expect(Boolean(res?.ok), \"Uploaded images are publicly readable\", res ? `HTTP ${res.status}` : \"Request failed.\", \"next/image also needs the storage host in remotePatterns.\", `HTTP ${res?.status}`);\n }\n const elsewhere = await user.storage.from(\"gr-media\").upload(`${otherSite}/doctor.png`, TINY_PNG, { contentType: \"image/png\" });\n if (!elsewhere.error) await admin.storage.from(\"gr-media\").remove([`${otherSite}/doctor.png`]);\n expect(Boolean(elsewhere.error), \"Editor can't upload into another site's folder\", elsewhere.error ? undefined : \"The upload was allowed.\");\n }\n } catch (e) {\n fail(\"The end-to-end run stopped unexpectedly\", e instanceof Error ? e.message : String(e));\n } finally {\n // Always clean up, and say so if something is left behind.\n const leftovers: string[] = [];\n const attempt = async (what: string, run: () => PromiseLike<{ error: { message: string } | null }>) => {\n try {\n const { error } = await run();\n if (error) leftovers.push(`${what} (${error.message})`);\n } catch (e) {\n leftovers.push(`${what} (${e instanceof Error ? e.message : String(e)})`);\n }\n };\n if (storageChecked && uploaded) await attempt(`storage object ${objectPath}`, () => admin.storage.from(\"gr-media\").remove([objectPath]));\n await attempt(\"content rows\", () => gr(admin).from(\"gr_content\").delete().in(\"site_id\", [siteId, otherSite]));\n await attempt(\"membership rows\", () => gr(admin).from(GR_MEMBERS_TABLE).delete().eq(\"site_id\", siteId));\n await attempt(\"rate-limit row\", () => gr(admin).from(\"gr_rate_limits\").delete().eq(\"bucket\", `doctor:e2e|${siteId}`));\n if (userId) await attempt(`test account ${email}`, () => admin.auth.admin.deleteUser(userId!));\n if (leftovers.length) {\n warn(\"Cleanup left something behind\", leftovers.join(\"; \"), `Everything it made is tagged \"${siteId}\" / ${email}; delete it by hand.`);\n } else {\n pass(\"Cleaned up everything the test created\");\n }\n }\n}\n\n/* --------------------------------------------------------------- output --- */\n\nconst COLOR = process.stdout.isTTY && !process.env.NO_COLOR;\nconst paint = (code: number, s: string) => (COLOR ? `\\u001b[${code}m${s}\\u001b[0m` : s);\nconst ICON: Record<Status, string> = {\n pass: paint(32, \"✓\"),\n warn: paint(33, \"!\"),\n fail: paint(31, \"✗\"),\n skip: paint(90, \"–\"),\n info: paint(36, \"i\"),\n};\n\nfunction printReport(): void {\n let last = \"\";\n for (const r of results) {\n if (r.group !== last) {\n console.log(`\\n${paint(1, r.group)}`);\n last = r.group;\n }\n console.log(` ${ICON[r.status]} ${r.title}`);\n if (r.detail) console.log(` ${paint(90, r.detail)}`);\n if (r.hint && (r.status === \"fail\" || r.status === \"warn\")) console.log(` ${paint(90, \"→ \" + r.hint)}`);\n }\n const count = (s: Status) => results.filter((r) => r.status === s).length;\n const parts = [`${count(\"pass\")} passed`, count(\"warn\") ? paint(33, `${count(\"warn\")} to review`) : null, count(\"fail\") ? paint(31, `${count(\"fail\")} failed`) : null].filter(Boolean);\n console.log(`\\n${parts.join(\", \")}\\n`);\n}\n\n/* ----------------------------------------------------------------- main --- */\n\nasync function main(): Promise<number> {\n const { flags, values, unknown } = parseFlags(process.argv.slice(2));\n if (flags.has(\"help\") || flags.has(\"h\")) {\n console.log(USAGE);\n return 0;\n }\n if (unknown.length) {\n console.error(`Unknown option${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}\\n\\n${USAGE}`);\n return 2;\n }\n\n const cwd = process.cwd();\n const fileEnv = readEnvFiles(cwd);\n const env: Record<string, string> = { ...fileEnv };\n for (const [k, v] of Object.entries(process.env)) if (v) env[k] = v;\n\n const ctx: Ctx = {\n cwd,\n env,\n production: flags.has(\"production\"),\n offline: flags.has(\"offline\"),\n url: env.SUPABASE_URL,\n anonKey: env.SUPABASE_ANON_KEY,\n serviceKey: env.SUPABASE_SERVICE_ROLE_KEY ?? env.SERVICE_KEY,\n };\n\n if (flags.has(\"csp\")) {\n printStarterCsp(ctx);\n return 0;\n }\n\n const json = flags.has(\"json\");\n if (!json) console.log(\"Guardrail doctor\");\n\n if (flags.has(\"fix\")) {\n fixMigrations(cwd);\n fixEnvNotes(cwd);\n fixCacheComponents(cwd);\n const headers = fixHeaders(cwd);\n if (headers === \"manual\") {\n applied.push(\"next.config has a shape I can't patch safely — add the headers from the README's \\\"Before you deploy\\\" yourself\");\n }\n if (flags.has(\"push\")) {\n if (!commandExists(\"supabase\")) {\n applied.push(\"Supabase CLI not found — apply the migrations yourself (`supabase db push`, or paste them into the SQL editor)\");\n } else {\n // A local Docker stack takes pending migrations with `migration up`;\n // `db push` is for a linked hosted project.\n const local = ctx.url ? isLocalHost(new URL(ctx.url).hostname) : false;\n const cmd = local ? [\"migration\", \"up\", \"--local\"] : [\"db\", \"push\"];\n const res = spawnSync(\"supabase\", cmd, { stdio: json ? \"ignore\" : \"inherit\" });\n applied.push(\n res.status === 0\n ? `Applied migrations with \\`supabase ${cmd.slice(0, 2).join(\" \")}\\``\n : local\n ? \"`supabase migration up` failed — is the local stack running? (`supabase start`)\"\n : \"`supabase db push` failed — if the project isn't linked yet, run `supabase link` first\",\n );\n }\n }\n if (!json) {\n console.log(`\\n${paint(1, \"Fixes\")}`);\n if (applied.length === 0) console.log(\" Nothing to fix locally.\");\n for (const a of applied) console.log(` • ${a}`);\n }\n } else if (flags.has(\"push\")) {\n console.error(\"--push only applies together with --fix.\");\n return 2;\n }\n\n await checkProject(ctx);\n checkEnvironment(ctx);\n checkFiles(ctx);\n\n const sb = await loadSupabase();\n const dbReachable = await checkDatabase(ctx, sb);\n\n if (values[\"app-url\"]) await checkLiveSite(values[\"app-url\"], ctx);\n\n if (flags.has(\"e2e\")) {\n group(\"End-to-end\");\n if (!dbReachable || !sb) {\n skip(\"Skipped\", \"The database checks above need to pass first.\");\n } else if (!ctx.serviceKey) {\n skip(\"Skipped\", \"The end-to-end suite needs the service-role key to create and remove its test account.\");\n } else if (await confirmE2E(ctx, flags.has(\"yes\"))) {\n await runE2E(ctx, sb);\n } else {\n skip(\"Skipped\", \"Not confirmed.\");\n }\n }\n\n if (json) {\n console.log(JSON.stringify({ applied, results }, null, 2));\n } else {\n printReport();\n if (results.some((r) => r.status === \"fail\")) {\n console.log(\"Fix the failures above, then run guardrail-doctor again.\\n\");\n }\n }\n\n const failed = results.some((r) => r.status === \"fail\");\n const warned = results.some((r) => r.status === \"warn\");\n return failed || (flags.has(\"strict\") && warned) ? 1 : 0;\n}\n\nmain().then(\n (code) => process.exit(code),\n (err) => {\n console.error(err);\n process.exit(1);\n },\n);\n"],"mappings":";AAqBA,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,WAAW,wBAAwB;AAC5C,SAAS,WAAW,8BAA8B;AAWlD,MAAM,mBAA6D;AAAA,EACjE,IAAI,CAAC,IAAI,GAAG,EAAE;AAAA,EACd,IAAI,CAAC,IAAI,GAAG,CAAC;AACf;AAMA,MAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsB/B,MAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAM1B,MAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8Cd,MAAM,UAAoB,CAAC;AAC3B,IAAI,eAAe;AAEnB,SAAS,MAAM,MAAoB;AACjC,iBAAe;AACjB;AAEA,SAAS,OAAO,QAAgB,OAAe,QAAiB,MAAqB;AACnF,UAAQ,KAAK,EAAE,OAAO,cAAc,QAAQ,OAAO,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,GAAI,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,CAAC;AAC/G;AAEA,MAAM,OAAO,CAAC,GAAW,MAAe,OAAO,QAAQ,GAAG,CAAC;AAC3D,MAAM,OAAO,CAAC,GAAW,GAAY,MAAe,OAAO,QAAQ,GAAG,GAAG,CAAC;AAC1E,MAAM,OAAO,CAAC,GAAW,GAAY,MAAe,OAAO,QAAQ,GAAG,GAAG,CAAC;AAC1E,MAAM,OAAO,CAAC,GAAW,MAAe,OAAO,QAAQ,GAAG,CAAC;AAC3D,MAAM,OAAO,CAAC,GAAW,MAAe,OAAO,QAAQ,GAAG,CAAC;AAI3D,SAAS,WAAW,MAAgB;AAClC,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,SAAiC,CAAC;AACxC,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,MAAM;AACtB,UAAM,IAAI,IAAI,MAAM,uBAAuB;AAC3C,QAAI,GAAG;AACL,aAAO,EAAE,CAAC,CAAE,IAAI,EAAE,CAAC;AAAA,IACrB,WAAW,IAAI,WAAW,GAAG,GAAG;AAC9B,YAAM,IAAI,IAAI,QAAQ,OAAO,EAAE,CAAC;AAAA,IAClC,OAAO;AACL,cAAQ,KAAK,GAAG;AAAA,IAClB;AAAA,EACF;AACA,QAAM,QAAQ,oBAAI,IAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,cAAc,UAAU,QAAQ,WAAW,OAAO,QAAQ,GAAG,CAAC;AAClH,aAAW,KAAK,MAAO,KAAI,CAAC,MAAM,IAAI,CAAC,EAAG,SAAQ,KAAK,KAAK,CAAC,EAAE;AAC/D,aAAW,KAAK,OAAO,KAAK,MAAM,EAAG,KAAI,MAAM,UAAW,SAAQ,KAAK,KAAK,CAAC,EAAE;AAC/E,SAAO,EAAE,OAAO,QAAQ,QAAQ;AAClC;AAEA,SAAS,aAAa,KAAqC;AACzD,QAAM,MAA8B,CAAC;AAErC,aAAW,QAAQ,CAAC,QAAQ,YAAY,GAAG;AACzC,UAAM,IAAI,KAAK,KAAK,KAAK,IAAI;AAC7B,QAAI,CAAC,WAAW,CAAC,EAAG;AACpB,eAAW,QAAQ,aAAa,GAAG,MAAM,EAAE,MAAM,OAAO,GAAG;AACzD,YAAM,IAAI,KAAK,MAAM,wDAAwD;AAC7E,UAAI,CAAC,EAAG;AACR,YAAM,QAAQ,EAAE,CAAC,EAAG,KAAK,EAAE,QAAQ,kBAAkB,IAAI;AACzD,UAAI,MAAO,KAAI,EAAE,CAAC,CAAE,IAAI;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,GAA4C;AAChE,QAAM,IAAI,EAAE,MAAM,sBAAsB;AACxC,SAAO,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI;AAC1D;AAEA,SAAS,QAAQ,GAA6B,KAAwC;AACpF,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,EAAE,CAAC,MAAO,IAAI,CAAC,EAAI,QAAO,EAAE,CAAC,IAAK,IAAI,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;AAGA,SAAS,QAAQ,KAA4B;AAC3C,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI;AACF,UAAM,UAAU,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC,GAAI,WAAW,EAAE,SAAS,MAAM,CAAC;AAC/E,WAAO,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,MAAuB;AAC1C,SAAO,SAAS,eAAe,SAAS,eAAe,SAAS,SAAS,SAAS,WAAW,KAAK,SAAS,YAAY;AACzH;AAEA,SAAS,cAAc,KAAsB;AAC3C,QAAM,MAAM,QAAQ,aAAa,UAAU,UAAU,SAAS,CAAC,GAAG,CAAC,IAAI,UAAU,SAAS,CAAC,GAAG,CAAC;AAC/F,SAAO,IAAI,WAAW;AACxB;AAEA,SAAS,eAAe,KAA4B;AAClD,aAAW,QAAQ,CAAC,kBAAkB,mBAAmB,kBAAkB,mBAAmB,iBAAiB,GAAG;AAChH,UAAM,IAAI,KAAK,KAAK,KAAK,IAAI;AAC7B,QAAI,WAAW,CAAC,EAAG,QAAO;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,SAAS,uBAA+B;AACtC,QAAM,OAAO,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,SAAO,KAAK,KAAK,MAAM,MAAM,MAAM,YAAY,YAAY;AAC7D;AAIA,MAAM,UAAoB,CAAC;AAE3B,SAAS,cAAc,KAAmB;AACxC,QAAM,MAAM,qBAAqB;AACjC,MAAI,CAAC,WAAW,GAAG,EAAG;AACtB,QAAM,OAAO,KAAK,KAAK,KAAK,YAAY,YAAY;AACpD,YAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AACnC,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC;AACtC,aAAW,QAAQ,YAAY,GAAG,GAAG;AACnC,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,iBAAa,KAAK,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,IAAI,CAAC;AACxD,YAAQ,KAAK,8BAA8B,IAAI,EAAE;AAAA,EACnD;AACF;AAEA,SAAS,YAAY,KAAmB;AACtC,aAAW,QAAQ,CAAC,gBAAgB,YAAY,GAAG;AACjD,UAAM,IAAI,KAAK,KAAK,KAAK,IAAI;AAC7B,QAAI,CAAC,WAAW,CAAC,EAAG;AACpB,UAAM,UAAU,aAAa,GAAG,MAAM;AACtC,QAAI,qBAAqB,KAAK,OAAO,EAAG;AACxC,kBAAc,GAAG,QAAQ,QAAQ,QAAQ,IAAI,IAAI,iBAAiB;AAClE,YAAQ,KAAK,sCAAsC,IAAI,EAAE;AAAA,EAC3D;AACF;AAEA,MAAM,wBAAwB;AAG9B,SAAS,mBAAmB,KAAsB;AAChD,QAAM,aAAa,eAAe,GAAG;AACrC,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,UAAU,aAAa,YAAY,MAAM;AAC/C,QAAM,OAAO,QAAQ,QAAQ,uBAAuB,EAAE;AACtD,MAAI,SAAS,QAAS,QAAO;AAC7B,gBAAc,YAAY,IAAI;AAC9B,UAAQ,KAAK,kCAAkC,KAAK,SAAS,UAAU,CAAC,EAAE;AAC1E,SAAO;AACT;AAGA,SAAS,WAAW,KAAwD;AAC1E,QAAM,aAAa,eAAe,GAAG;AACrC,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,UAAU,aAAa,YAAY,MAAM;AAC/C,MAAI,mCAAmC,KAAK,OAAO,EAAG,QAAO;AAE7D,MAAI,iBAAiB,KAAK,OAAO,EAAG,QAAO;AAE3C,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,aAAW,MAAM,SAAS;AACxB,QAAI,CAAC,GAAG,KAAK,OAAO,EAAG;AACvB,kBAAc,YAAY,QAAQ,QAAQ,IAAI,KAAK,sBAAsB,EAAE,CAAC;AAC5E,YAAQ,KAAK,sCAAsC,KAAK,SAAS,UAAU,CAAC,EAAE;AAC9E,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAeA,eAAe,gBAAgB,UAA8E;AAC3G,QAAM,OAAiC,CAAC;AACxC,aAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,QAAQ,EAAG,MAAK,IAAI,IAAI,CAAC,CAAC;AACjE,MAAI,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG,QAAO,CAAC;AAC5C,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,gEAAgE;AAAA,MACtF,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,MACzB,QAAQ,YAAY,QAAQ,GAAI;AAAA,IAClC,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcA,eAAe,aAAa,KAAyB;AACnD,QAAM,SAAS;AACf,QAAM,UAAU,KAAK,KAAK,IAAI,KAAK,cAAc;AACjD,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,SAAK,wBAAwB,iDAAiD;AAC9E;AAAA,EACF;AAEA,QAAM,SAAS,KAAK,MAAM,aAAa,KAAK,KAAK,qBAAqB,GAAG,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAC7G,OAAK,oBAAoB,OAAO,OAAO,EAAE;AAEzC,QAAMA,WAAU,cAAc,OAAO;AACrC,QAAM,YAAY,CAAC,SAAqC;AACtD,QAAI;AACF,aAAO,KAAK,MAAM,aAAaA,SAAQ,QAAQ,GAAG,IAAI,eAAe,GAAG,MAAM,CAAC,EAAE;AAAA,IACnF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,cAAc,UAAU,MAAM;AACpC,MAAI,CAAC,YAAa,MAAK,gCAAgC,8CAA8C;AAIrG,QAAM,UAAkC,CAAC;AACzC,aAAW,QAAQ,CAAC,QAAQ,yBAAyB,eAAe,GAAG;AACrE,UAAM,IAAI,UAAU,IAAI;AACxB,QAAI,EAAG,SAAQ,IAAI,IAAI;AAAA,EACzB;AACA,UAAQ,kBAAkB,IAAI,OAAO;AACrC,QAAM,aAAa,IAAI,UAAU,OAAO,MAAM,gBAAgB,OAAO;AAErE,MAAI,YAAY;AACd,QAAI,QAAQ;AACZ,eAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,OAAO,GAAG;AACrD,YAAM,QAAQ,WAAW,IAAI,KAAK,CAAC;AACnC,YAAM,UAAU,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,cAAc,EAAE,aAAa,MAAM;AACtF,UAAI,MAAM,WAAW,GAAG;AACtB;AACA;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,KAAK,EAAE,KAAK,KAAK,EAAE,GAAG,GAAG;AACjF,UAAI,MAAM,SAAS,EAAG,OAAM,KAAK,aAAQ,MAAM,SAAS,CAAC,OAAO;AAChE,OAAC,QAAQ,SAAS,OAAO;AAAA,QACvB,GAAG,IAAI,IAAI,OAAO,QAAQ,MAAM,MAAM,0BAA0B,MAAM,WAAW,IAAI,MAAM,KAAK;AAAA,QAChG,MAAM,KAAK,UAAU;AAAA,QACrB,qBAAqB,IAAI;AAAA,MAC3B;AAAA,IACF;AACA,QAAI,MAAO,MAAK,oCAAoC,KAAK,mBAAmB,UAAU,IAAI,KAAK,GAAG,IAAI,OAAO,QAAQ,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,GAAG,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EAC3N,WAAW,aAAa;AAEtB,UAAM,IAAI,aAAa,WAAW;AAClC,UAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC,CAAC,IAAI;AAC3C,UAAM,MAAM,IAAI,UAAU,YAAY;AACtC,QAAI,CAAC,GAAG;AACN,WAAK,WAAW,WAAW,EAAE;AAAA,IAC/B,WAAW,SAAS,CAAC,QAAQ,GAAG,KAAK,GAAG;AACtC;AAAA,QACE,WAAW,WAAW;AAAA,QACtB,mBAAmB,MAAM,KAAK,GAAG,CAAC,iFAA4E,GAAG;AAAA,QACjH;AAAA,MACF;AAAA,IACF,WAAW,OAAO;AAChB,WAAK,WAAW,WAAW,IAAI,8CAA8C,GAAG,4CAA4C;AAAA,IAC9H,OAAO;AACL;AAAA,QACE,WAAW,WAAW;AAAA,QACtB,sDAAsD,EAAE,CAAC,CAAC,YAAY,GAAG;AAAA,QACzE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,aAAW,OAAO,CAAC,yBAAyB,eAAe,GAAG;AAC5D,QAAI;AACF,MAAAA,SAAQ,QAAQ,GAAG,GAAG,eAAe;AACrC,WAAK,GAAG,GAAG,YAAY;AAAA,IACzB,QAAQ;AACN,WAAK,GAAG,GAAG,oBAAoB,QAAW,YAAY,GAAG,EAAE;AAAA,IAC7D;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,KAAgB;AACxC,QAAM,aAAa;AACnB,QAAM,EAAE,IAAI,IAAI;AAEhB,MAAI,CAAC,IAAI,KAAK;AACZ,SAAK,0BAA0B,6CAA6C;AAAA,EAC9E,OAAO;AACL,QAAI;AACF,YAAM,IAAI,IAAI,IAAI,IAAI,GAAG;AACzB,UAAI,EAAE,aAAa,YAAY,EAAE,aAAa,QAAS,OAAM,IAAI,MAAM;AACvE,UAAI,EAAE,aAAa,WAAW,CAAC,YAAY,EAAE,QAAQ,GAAG;AACtD,aAAK,gCAAgC,qCAAqC;AAAA,MAC5E,OAAO;AACL,aAAK,gBAAgB,EAAE,IAAI;AAAA,MAC7B;AAAA,IACF,QAAQ;AACN,WAAK,kCAAkC,yCAAyC;AAAA,IAClF;AAAA,EACF;AAEA,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,MAAM;AACT,SAAK,6BAA6B;AAAA,EACpC,OAAO;AACL,UAAM,OAAO,QAAQ,IAAI;AACzB,QAAI,SAAS,kBAAkB,KAAK,WAAW,YAAY,GAAG;AAC5D;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,MAAM,IAAI;AAChB,MAAI,CAAC,KAAK;AACR;AAAA,MACE;AAAA,MACA;AAAA,IACF;AAAA,EACF,OAAO;AACL,UAAM,OAAO,QAAQ,GAAG;AACxB,QAAI,SAAS,UAAU,IAAI,WAAW,iBAAiB,GAAG;AACxD,WAAK,8DAA8D,QAAW,yCAAyC;AAAA,IACzH,OAAO;AACL,WAAK,0BAA0B;AAAA,IACjC;AAAA,EACF;AAGA,QAAM,SAAS,OAAO,KAAK,GAAG,EAAE,OAAO,CAAC,MAAM,kDAAkD,KAAK,CAAC,CAAC;AACvG,MAAI,OAAO,QAAQ;AACjB;AAAA,MACE;AAAA,MACA,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,MACpB;AAAA,IACF;AAAA,EACF,OAAO;AACL,SAAK,sCAAsC;AAAA,EAC7C;AAEA,QAAM,UAAU,IAAI;AACpB,MAAI,CAAC,SAAS;AACZ,KAAC,IAAI,aAAa,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,OAAO;AACL,QAAI;AACF,YAAM,IAAI,IAAI,IAAI,OAAO;AACzB,UAAI,EAAE,aAAa,YAAY,EAAE,aAAa,QAAS,OAAM,IAAI,MAAM;AACvE,UAAI,EAAE,aAAa,WAAW,CAAC,YAAY,EAAE,QAAQ,GAAG;AACtD,aAAK,sCAAsC,iDAAiD;AAAA,MAC9F,OAAO;AACL,aAAK,sBAAsB,EAAE,MAAM;AAAA,MACrC;AAAA,IACF,QAAQ;AACN,WAAK,gDAAgD,OAAO;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,IAAI,cAAc;AACzC,QAAM,UAAU,QAAQ,IAAI,oBAAoB;AAChD,MAAI,UAAU,SAAS;AACrB,SAAK,yBAAyB;AAAA,EAChC,WAAW,WAAW,SAAS;AAC7B;AAAA,MACE,GAAG,SAAS,yBAAyB,gBAAgB;AAAA,MACrD;AAAA,IACF;AAAA,EACF,OAAO;AACL,SAAK,yCAAyC,mGAAmG;AAAA,EACnJ;AAEA,MAAI,IAAI,kBAAmB,MAAK,qBAAqB,IAAI,iBAAiB;AAAA,MACrE,MAAK,+BAA+B,oCAAoC;AAC/E;AAEA,SAAS,WAAW,KAAgB;AAClC,QAAM,OAAO;AAGb,QAAM,MAAM,qBAAqB;AACjC,QAAM,OAAO,KAAK,KAAK,IAAI,KAAK,YAAY,YAAY;AACxD,MAAI,WAAW,GAAG,GAAG;AACnB,UAAM,UAAU,YAAY,GAAG;AAC/B,UAAM,OAAO,WAAW,IAAI,IAAI,IAAI,IAAI,YAAY,IAAI,CAAC,IAAI,oBAAI,IAAY;AAC7E,UAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AAClD,QAAI,QAAQ,WAAW,GAAG;AACxB,WAAK,sDAAsD;AAAA,IAC7D,OAAO;AACL;AAAA,QACE,GAAG,QAAQ,MAAM,aAAa,QAAQ,WAAW,IAAI,KAAK,GAAG;AAAA,QAC7D,QAAQ,KAAK,IAAI;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,KAAK,KAAK,IAAI,KAAK,YAAY,aAAa;AAC/D,MAAI,WAAW,UAAU,GAAG;AAC1B,UAAM,OAAO,aAAa,YAAY,MAAM;AAC5C,UAAM,UAAU,KAAK,MAAM,2CAA2C,IAAI,CAAC,KAAK;AAChF,QAAI,QAAQ,SAAS,aAAa,EAAG,MAAK,qDAAqD;AAAA,QAC1F,MAAK,8DAA+D,QAAW,kCAAkC;AAAA,EACxH;AAGA,QAAM,UAAU,CAAC,kBAAkB,sBAAsB,kBAAkB,sBAAsB,iBAAiB,mBAAmB;AACrI,QAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC,EAAE,KAAK,UAAU;AACxE,QAAM,UAAU,CAAC,SAAiB,gBAAgB,KAAK,aAAa,MAAM,MAAM,CAAC;AACjF,MAAI,CAAC,QAAQ;AACX,SAAK,yBAAyB,2CAA2C;AAAA,EAC3E,WAAW,QAAQ,MAAM,GAAG;AAC1B,SAAK,kCAAkC;AAAA,EACzC,OAAO;AACL;AAAA,MACE;AAAA,MACA,GAAG,KAAK,SAAS,IAAI,KAAK,MAAM,CAAC;AAAA,IACnC;AAAA,EACF;AAGA,QAAM,aAAa,eAAe,IAAI,GAAG;AACzC,MAAI,CAAC,YAAY;AACf,SAAK,uBAAuB;AAAA,EAC9B,OAAO;AACL,UAAM,UAAU,aAAa,YAAY,MAAM;AAC/C,QAAI,qCAAqC,KAAK,OAAO,GAAG;AACtD;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI,mCAAmC,KAAK,OAAO,GAAG;AACpD,WAAK,GAAG,KAAK,SAAS,UAAU,CAAC,4BAA4B;AAAA,IAC/D,OAAO;AACL;AAAA,QACE,GAAG,KAAK,SAAS,UAAU,CAAC;AAAA,QAC5B;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,KAAK,KAAK,IAAI,KAAK,YAAY;AACjD,QAAM,aAAa,CAAC,cAAc,MAAM,EAAE,KAAK,CAAC,MAAM,WAAW,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC;AACvF,MAAI,YAAY;AACd,UAAM,UAAU,WAAW,SAAS,IAAI,aAAa,WAAW,MAAM,IAAI;AAC1E,QAAI,mCAAmC,KAAK,OAAO,KAAK,eAAe,KAAK,OAAO,GAAG;AACpF,WAAK,0BAA0B;AAAA,IACjC,OAAO;AACL,WAAK,gCAAgC,0CAA0C,kDAAkD;AAAA,IACnI;AAAA,EACF;AACF;AAKA,eAAe,eAA+C;AAC5D,MAAI;AACF,WAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,cAAc,KAAU,IAA6C;AAClF,QAAM,UAAU;AAChB,MAAI,CAAC,IAAI,OAAO,CAAC,IAAI,SAAS;AAC5B,SAAK,WAAW,2CAA2C;AAC3D,WAAO;AAAA,EACT;AACA,MAAI,CAAC,IAAI;AACP,SAAK,WAAW,wCAAwC;AACxD,WAAO;AAAA,EACT;AACA,QAAM,OAAO,IAAI,IAAI,QAAQ,OAAO,EAAE;AAGtC,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,IAAI,+CAA+C;AAAA,MAC5E,SAAS,EAAE,QAAQ,IAAI,SAAS,eAAe,UAAU,IAAI,OAAO,IAAI,kBAAkB,UAAU;AAAA,IACtG,CAAC;AACD,UAAM,OAAO,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK;AAC1C,QAAI,IAAI,IAAI;AACV,WAAK,IAAI,SAAS,kCAAkC;AAAA,IACtD,WAAW,2BAA2B,KAAK,IAAI,GAAG;AAChD;AAAA,QACE,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA;AAAA,MACF;AACA,aAAO;AAAA,IACT,WAAW,8CAA8C,KAAK,IAAI,GAAG;AACnE,WAAK,kCAAkC,sDAAsD,yCAAyC;AACtI,aAAO;AAAA,IACT,WAAW,IAAI,WAAW,OAAO,IAAI,WAAW,KAAK;AACnD,WAAK,6BAA6B,QAAQ,IAAI,MAAM,IAAI,kDAAkD;AAC1G,aAAO;AAAA,IACT,OAAO;AACL,WAAK,uCAAuC,QAAQ,IAAI,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AACvF,aAAO;AAAA,IACT;AAAA,EACF,SAAS,GAAG;AACV,SAAK,2BAA2B,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG,wDAAwD;AACpI,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,IAAI,qBAAqB,EAAE,SAAS,EAAE,QAAQ,IAAI,QAAQ,EAAE,CAAC;AACxF,QAAI,IAAI,IAAI;AACV,YAAM,IAAK,MAAM,IAAI,KAAK;AAK1B,UAAI,EAAE,UAAU,UAAU,OAAO;AAC/B,aAAK,yCAAyC,QAAW,qDAAqD;AAAA,MAChH,WAAW,EAAE,gBAAgB;AAC3B,aAAK,yBAAyB;AAAA,MAChC,WAAW,EAAE,oBAAoB;AAC/B;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AACL,aAAK,oCAAoC;AAAA,MAC3C;AAAA,IACF,OAAO;AACL,WAAK,gDAAgD,QAAQ,IAAI,MAAM,EAAE;AAAA,IAC3E;AAAA,EACF,QAAQ;AACN,SAAK,8CAA8C;AAAA,EACrD;AAEA,MAAI,CAAC,IAAI,YAAY;AACnB,SAAK,+BAA+B,+DAA+D;AACnG,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,GAAG,aAAa,IAAI,KAAK,IAAI,YAAY,EAAE,MAAM,EAAE,gBAAgB,OAAO,kBAAkB,MAAM,EAAE,CAAC;AACnH,QAAM,KAAK,MAAM,MAAM,OAAO,SAAS;AAEvC,aAAW,SAAS,CAAC,kBAAkB,cAAc,qBAAqB,gBAAgB,GAAG;AAC3F,UAAM,EAAE,OAAAC,OAAM,IAAI,MAAM,GAAG,EAAE,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,MAAM,OAAO,QAAQ,CAAC,EAAE,MAAM,CAAC;AAC5F,QAAI,CAACA,OAAO,MAAK,SAAS,KAAK,EAAE;AAAA,aACxB,UAAU,iBAAkB,MAAK,mCAAmC,qEAAqE,yCAAyC;AAAA,QACtL,MAAK,SAAS,KAAK,mBAAmBA,OAAM,SAAS,yCAAyC;AAAA,EACrG;AAGA,QAAM,SAA2D;AAAA,IAC/D,CAAC,sBAAsB,CAAC,GAAG,gCAAgC;AAAA,IAC3D,CAAC,mBAAmB,EAAE,aAAa,eAAe,GAAG,uBAAuB;AAAA,IAC5E,CAAC,oBAAoB,EAAE,eAAe,kBAAkB,GAAG,uBAAuB;AAAA,EACpF;AACA,aAAW,CAAC,IAAI,MAAM,KAAK,KAAK,QAAQ;AACtC,UAAM,EAAE,OAAAA,OAAM,IAAI,MAAM,GAAG,EAAE,IAAI,IAAI,IAAI;AACzC,QAAI,CAACA,UAASA,OAAM,SAAS,WAAW,qBAAqB,KAAKA,OAAM,OAAO,GAAG;AAChF,WAAK,KAAK;AAAA,IACZ,WAAWA,OAAM,SAAS,cAAcA,OAAM,SAAS,WAAW,+BAA+B,KAAKA,OAAM,OAAO,GAAG;AACpH,WAAK,GAAG,KAAK,eAAe,YAAY,EAAE,qBAAqB,iEAAiE;AAAA,IAClI,OAAO;AAEL,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,WAAW,UAAU,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AACzD,QAAM,EAAE,MAAM,MAAM,IAAI,MAAM,GAAG,EAAE,IAAI,eAAe,EAAE,QAAQ,gBAAgB,OAAO,UAAU,SAAS,KAAM,kBAAkB,GAAG,CAAC;AACtI,MAAI,CAAC,SAAS,MAAM;AAClB,SAAK,+BAA+B;AACpC,UAAM,GAAG,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,GAAG,UAAU,gBAAgB,QAAQ,EAAE;AAAA,EACpF,WAAW,OAAO,SAAS,cAAc,OAAO,SAAS,SAAS;AAChE,SAAK,kCAAkC,iFAAiF,iEAAiE;AAAA,EAC3L,WAAW,OAAO;AAChB,SAAK,8BAA8B,MAAM,OAAO;AAAA,EAClD;AAEA,OAAK,6CAA6C,qEAAqE;AACvH,SAAO;AACT;AAMA,SAAS,SAAS,OAAoB;AACpC,QAAM,MAAW,CAAC;AAClB,aAAW,QAAQ,MAAM,MAAM,GAAG,GAAG;AACnC,UAAM,CAAC,MAAM,GAAG,OAAO,IAAI,KAAK,KAAK,EAAE,MAAM,KAAK;AAClD,QAAI,KAAM,KAAI,KAAK,YAAY,CAAC,IAAI;AAAA,EACtC;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,KAAU,MAA+B;AACjE,SAAO,IAAI,IAAI,KAAK,IAAI,aAAa,KAAK;AAC5C;AAGA,SAAS,aAAa,SAA0B,QAAyB;AACvE,MAAI,YAAY,KAAM,QAAO;AAC7B,QAAM,SAAS,IAAI,IAAI,MAAM;AAC7B,SAAO,QAAQ,KAAK,CAAC,QAAQ;AAC3B,UAAM,MAAM,IAAI,YAAY;AAC5B,QAAI,QAAQ,IAAK,QAAO;AACxB,QAAI,QAAQ,GAAG,OAAO,QAAQ,GAAI,QAAO;AACzC,QAAI,QAAQ,OAAO,YAAY,EAAG,QAAO;AACzC,UAAM,OAAO,IAAI,MAAM,qCAAqC;AAC5D,QAAI,MAAM;AACR,YAAM,CAAC,EAAE,QAAQ,QAAQ,IAAI,IAAI;AACjC,aAAO,WAAW,OAAO,YAAY,OAAO,SAAS,SAAS,IAAI,MAAM,EAAE,MAAM,CAAC,QAAQ,SAAS,OAAO;AAAA,IAC3G;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAGA,SAAS,cAAc,aAA8B;AACnD,QAAM,KAAK,uBAAuB,cAAc,EAAE,YAAY,IAAI,CAAC,CAAC;AACpE,SAAO,UAAU;AAAA,IACf,eAAe,CAAC,QAAQ;AAAA,IACxB,cAAc,CAAC,UAAU,+BAA+B,kBAAkB;AAAA,IAC1E,aAAa,GAAG,WAAW;AAAA,IAC3B,WAAW,CAAC,GAAG,GAAG,SAAS,GAAI,OAAO;AAAA,IACtC,eAAe,GAAG,aAAa;AAAA,IAC/B,YAAY,CAAC,UAAU,OAAO;AAAA,IAC9B,cAAc,CAAC,QAAQ;AAAA,IACvB,YAAY,CAAC,QAAQ;AAAA,IACrB,eAAe,CAAC,QAAQ;AAAA,IACxB,mBAAmB,GAAG,iBAAiB;AAAA,EACzC,CAAC;AACH;AAEA,SAAS,gBAAgB,KAAgB;AACvC,UAAQ,IAAI;AAAA;AAAA,uCAEyB,cAAc,IAAI,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2DAUF,IAAI,MAAM,KAAK,8EAA8E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAOvJ;AACD;AAEA,eAAe,cAAc,QAAgB,KAAyB;AACpE,QAAM,WAAW;AACjB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,QAAQ,EAAE,UAAU,SAAS,CAAC;AAAA,EAClD,SAAS,GAAG;AACV,SAAK,kBAAkB,MAAM,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG,sBAAsB;AACnG;AAAA,EACF;AACA,MAAI,IAAI,UAAU,KAAK;AACrB,SAAK,GAAG,MAAM,kBAAkB,IAAI,MAAM,EAAE;AAC5C;AAAA,EACF;AACA,OAAK,GAAG,MAAM,kBAAkB,IAAI,MAAM,EAAE;AAE5C,QAAM,IAAI,IAAI;AACd,QAAM,MAAM,EAAE,IAAI,yBAAyB,KAAK;AAChD,MAAI,mBAAmB,KAAK,GAAG,KAAK,EAAE,IAAI,iBAAiB,EAAG,MAAK,iCAAiC;AAAA,MAC/F,MAAK,iCAAiC,iDAAiD,4DAA4D;AAExJ,MAAI,WAAW,KAAK,EAAE,IAAI,wBAAwB,KAAK,EAAE,EAAG,MAAK,iCAAiC;AAAA,MAC7F,MAAK,uCAAuC;AAEjD,MAAI,EAAE,IAAI,iBAAiB,EAAG,MAAK,oBAAoB,EAAE,IAAI,iBAAiB,CAAC,EAAE;AAAA,MAC5E,MAAK,gCAAgC;AAE1C,MAAI,IAAI,IAAI,MAAM,EAAE,aAAa,UAAU;AACzC,QAAI,EAAE,IAAI,2BAA2B,EAAG,MAAK,+BAA+B;AAAA,QACvE,MAAK,4CAA4C,+CAA+C;AAAA,EACvG,OAAO;AACL,SAAK,6BAA6B,6BAA6B;AAAA,EACjE;AAEA,QAAM,YAAY,OAAO,EAAE,iBAAiB,aAAa,EAAE,aAAa,IAAI,CAAC;AAC7E,QAAM,UAAU,UAAU,OAAO,CAAC,MAAM,qBAAqB,KAAK,CAAC,CAAC;AACpE,MAAI,QAAQ,OAAQ,MAAK,qDAAqD,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAK5H,MAAI,IAAI,WAAW,KAAK;AACtB,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,UAAM,SAAS;AACf,UAAM,SAAS,CAAC,GAAG,KAAK,SAAS,mCAAmC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,IAAI;AACjG,QAAI,QAAQ,OAAO,KAAK,MAAM;AAC9B,UAAM,QAAQ,CAAC,GAAG,KAAK,SAAS,4CAA4C,CAAC,EAC1E,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,wBAAwB,IAAI,CAAC,CAAC,EACpD,OAAO,CAAC,MAAmB,QAAQ,CAAC,CAAC,EACrC,MAAM,GAAG,CAAC;AACb,eAAW,QAAQ,OAAO;AACxB,UAAI,MAAO;AACX,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG,EAAE,QAAQ,YAAY,QAAQ,GAAI,EAAE,CAAC;AACpF,YAAI,IAAI,MAAM,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,EAAG,SAAQ;AAAA,MACvD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,QAAI,MAAO,MAAK,uCAAuC;AAAA,aAC9C,MAAM,WAAW,KAAK,CAAC,UAAU,KAAK,IAAI,EAAG,MAAK,0BAA0B,yCAAyC;AAAA,SACzH;AACH;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,EAAE,IAAI,yBAAyB;AAChD,QAAM,aAAa,EAAE,IAAI,qCAAqC;AAC9D,QAAM,SAAS,YAAY;AAC3B,MAAI,CAAC,QAAQ;AACX;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA;AAAA,EACF;AACA,QAAM,SAAS,SAAS,MAAM;AAC9B,QAAM,QAAQ,WAAW,KAAK;AAC9B,MAAI,CAAC,OAAO,aAAa,KAAK,CAAC,OAAO,YAAY,GAAG;AACnD,SAAK,qCAAqC,OAAO,KAAK,MAAM,EAAE,KAAK,IAAI,KAAK,SAAS,IAAI,0FAA0F;AACnL;AAAA,EACF;AACA,QAAM,QAAQ,iBAAiB,QAAQ,WAAW;AAClD,MAAI,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,SAAS,iBAAiB,GAAG;AAC1E,SAAK,mCAAmC,KAAK,IAAI,mHAA8G;AAAA,EACjK,OAAO;AACL,SAAK,SAAS,KAAK,sDAAsD;AAAA,EAC3E;AACA,MAAI,IAAI,KAAK;AACX,QAAI;AACJ,QAAI;AACF,iBAAW,IAAI,IAAI,IAAI,GAAG,EAAE;AAAA,IAC9B,QAAQ;AAAA,IAER;AACA,QAAI,YAAY,aAAa,IAAI,IAAI,MAAM,EAAE,QAAQ;AACnD,iBAAW,CAAC,WAAW,GAAG,KAAK;AAAA,QAC7B,CAAC,eAAe,gEAAgE;AAAA,QAChF,CAAC,WAAW,8EAA8E;AAAA,MAC5F,GAAY;AACV,YAAI,aAAa,iBAAiB,QAAQ,SAAS,GAAG,QAAQ,EAAG,MAAK,GAAG,SAAS,GAAG,KAAK,WAAW,QAAQ,EAAE;AAAA,YAC1G,MAAK,GAAG,SAAS,GAAG,KAAK,WAAW,QAAQ,IAAI,kBAAkB,GAAG,KAAK,OAAO,QAAQ,OAAO,SAAS,GAAG;AAAA,MACnH;AAAA,IACF;AAAA,EACF;AACF;AAKA,MAAM,WAAW,OAAO;AAAA,EACtB;AAAA,EACA;AACF;AAEA,eAAe,WAAW,KAAU,KAAgC;AAClE,QAAM,OAAO,IAAI,IAAI,IAAI,GAAI,EAAE;AAC/B,QAAM,QAAQ,YAAY,IAAI,IAAI,IAAI,GAAI,EAAE,QAAQ;AACpD,UAAQ;AAAA,IACN;AAAA,sFAAyF,IAAI;AAAA;AAAA;AAAA,EAC/F;AACA,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,YAAQ,IAAI,6DAA6D;AACzE,WAAO;AAAA,EACT;AACA,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,QAAM,UAAU,MAAM,GAAG,SAAS,GAAG,IAAI,4CAA4C,GAAG,KAAK,EAAE,YAAY;AAC3G,KAAG,MAAM;AACT,SAAO,WAAW,OAAO,WAAW;AACtC;AAEA,eAAe,OAAO,KAAU,IAAmC;AACjE,QAAM,YAAY;AAClB,QAAM,MAAM,IAAI;AAChB,QAAM,QAAQ,GAAG,aAAa,KAAK,IAAI,YAAa,EAAE,MAAM,EAAE,gBAAgB,OAAO,kBAAkB,MAAM,EAAE,CAAC;AAChH,QAAM,OAAO,GAAG,aAAa,KAAK,IAAI,SAAU,EAAE,MAAM,EAAE,gBAAgB,OAAO,kBAAkB,MAAM,EAAE,CAAC;AAC5G,QAAM,OAAO,GAAG,aAAa,KAAK,IAAI,SAAU,EAAE,MAAM,EAAE,gBAAgB,OAAO,kBAAkB,MAAM,EAAE,CAAC;AAC5G,QAAM,KAAK,CAAC,MAAsB,EAAE,OAAO,SAAS;AAEpD,QAAM,MAAM,YAAY,CAAC,EAAE,SAAS,KAAK;AACzC,QAAM,SAAS,UAAU,GAAG;AAC5B,QAAM,YAAY,UAAU,GAAG;AAC/B,QAAM,QAAQ,oBAAoB,GAAG;AACrC,QAAM,WAAW,YAAY,EAAE,EAAE,SAAS,WAAW;AACrD,QAAM,aAAa,GAAG,MAAM;AAC5B,MAAI;AACJ,MAAI,WAAW;AACf,MAAI,iBAAiB;AAIrB,QAAM,SAAS,CAAC,IAAa,OAAe,KAAc,MAAe,SACvE,KAAK,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,KAAK,IAAI;AAEhD,MAAI;AAEF,UAAM,UAAU,MAAM,MAAM,KAAK,MAAM,WAAW,EAAE,OAAO,UAAU,eAAe,KAAK,CAAC;AAC1F,QAAI,QAAQ,SAAS,CAAC,QAAQ,KAAK,MAAM;AACvC,WAAK,yBAAyB,QAAQ,OAAO,WAAW,qBAAqB,2DAA2D;AACxI;AAAA,IACF;AACA,aAAS,QAAQ,KAAK,KAAK;AAC3B,SAAK,gDAAgD;AAErD,UAAM,OAAO,MAAM,MAAM,KAAK,MAAM,aAAa,EAAE,MAAM,YAAY,MAAM,CAAC;AAC5E;AAAA,MACE,CAAC,KAAK,SAAS,QAAQ,KAAK,MAAM,YAAY,YAAY;AAAA,MAC1D;AAAA,MACA,KAAK,OAAO;AAAA,MACZ;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,GAAG,KAAK,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,SAAS,QAAQ,OAAO,MAAM,SAAS,QAAQ,UAAU,CAAC;AACzH,QAAI,OAAO,OAAO;AAChB,WAAK,+BAA+B,OAAO,MAAM,OAAO;AACxD;AAAA,IACF;AACA,UAAM,WAAW,MAAM,KAAK,KAAK,mBAAmB,EAAE,OAAO,SAAS,CAAC;AACvE,QAAI,SAAS,OAAO;AAClB,WAAK,2BAA2B,SAAS,MAAM,OAAO;AACtD;AAAA,IACF;AACA,SAAK,yBAAyB;AAE9B,UAAM,QAAQ,MAAM,GAAG,IAAI,EAAE,IAAI,mBAAmB,EAAE,aAAa,OAAO,CAAC;AAC3E,UAAM,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,gBAAgB,EAAE,OAAO,aAAa,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,SAAS,KAAK,EAAE,YAAY;AAC/H;AAAA,MACE,CAAC,MAAM,SAAS,MAAM,MAAM,WAAW,YAAY,MAAM,MAAM,SAAS;AAAA,MACxE;AAAA,MACA,MAAM,OAAO,YAAY,MAAM,OAAO,SAAY;AAAA,MAClD;AAAA,IACF;AAGA,UAAM,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,YAAY,EAAE,OAAO;AAAA,MACrD,SAAS;AAAA,MACT,UAAU;AAAA,MACV,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,QAAQ,MAAM,SAAS;AAAA,IACxC,CAAC;AACD,WAAO,CAAC,MAAM,OAAO,yCAAyC,MAAM,OAAO,OAAO;AAElF,UAAM,OAAO,MAAM,GAAG,IAAI,EAAE,KAAK,YAAY,EAAE,OAAO,UAAU,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,YAAY,cAAc;AACrH,WAAO,CAAC,KAAK,UAAU,KAAK,MAAM,UAAU,OAAO,GAAG,mCAAmC,KAAK,OAAO,OAAO;AAE5G,UAAM,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,YAAY,EAAE,OAAO;AAAA,MACrD,SAAS;AAAA,MACT,UAAU;AAAA,MACV,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,QAAQ,MAAM,OAAO;AAAA,IACtC,CAAC;AACD,WAAO,QAAQ,MAAM,KAAK,GAAG,qDAAqD,MAAM,QAAQ,SAAY,4EAAuE;AAEnL,UAAM,YAAY,MAAM,GAAG,IAAI,EAAE,KAAK,YAAY,EAAE,OAAO;AAAA,MACzD,SAAS;AAAA,MACT,UAAU;AAAA,MACV,MAAM;AAAA,MACN,OAAO,EAAE,MAAM,QAAQ,MAAM,OAAO;AAAA,IACtC,CAAC;AACD,WAAO,QAAQ,UAAU,KAAK,GAAG,gCAAgC,UAAU,QAAQ,SAAY,uCAAuC;AAGtI,UAAM,SAAS,MAAM,GAAG,IAAI,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,SAAS,KAAK;AACvH;AAAA,MACE,QAAQ,OAAO,KAAK;AAAA,MACpB;AAAA,MACA,OAAO,QAAQ,SAAY;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,GAAG,KAAK,EAAE,KAAK,gBAAgB,EAAE;AAAA,MACrC,EAAE,SAAS,QAAQ,OAAO,SAAS,QAAQ,MAAM,SAAS,QAAQ,SAAS;AAAA,MAC3E,EAAE,YAAY,gBAAgB;AAAA,IAChC;AACA,UAAM,SAAS,MAAM,GAAG,IAAI,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,SAAS,KAAK;AACrG,UAAM,aAAa,MAAM,GAAG,KAAK,EAAE,KAAK,gBAAgB,EAAE,OAAO,OAAO,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,SAAS,KAAK;AACjH;AAAA,MACE,QAAQ,OAAO,KAAK,MAAM,WAAW,MAAM,UAAU,OAAO;AAAA,MAC5D;AAAA,MACA,OAAO,QAAQ,SAAY;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,GAAG,KAAK,EAAE,KAAK,gBAAgB,EAAE;AAAA,MACrC,EAAE,SAAS,QAAQ,OAAO,SAAS,QAAQ,MAAM,SAAS,QAAQ,SAAS;AAAA,MAC3E,EAAE,YAAY,gBAAgB;AAAA,IAChC;AAGA,UAAM,OAAkB,CAAC;AACzB,QAAI;AACJ,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,IAAI,MAAM,GAAG,KAAK,EAAE,IAAI,eAAe,EAAE,QAAQ,cAAc,OAAO,QAAQ,SAAS,GAAG,kBAAkB,GAAG,CAAC;AACtH,UAAI,EAAE,OAAO;AACX,kBAAU,EAAE,MAAM;AAClB;AAAA,MACF;AACA,YAAM,MAAM,MAAM,QAAQ,EAAE,IAAI,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;AAClD,WAAK,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjC;AACA;AAAA,MACE,CAAC,WAAW,KAAK,KAAK,MAAM;AAAA,MAC5B;AAAA,MACA,YAAY,KAAK,SAAS,OAAO,KAAK,KAAK,IAAI,CAAC,kCAAkC;AAAA,MAClF;AAAA,MACA,WAAW,KAAK,OAAO,OAAO,EAAE,MAAM,OAAO,KAAK,MAAM;AAAA,IAC1D;AAGA,UAAM,UAAU,MAAM,MAAM,QAAQ,YAAY;AAChD,UAAM,cAAc,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AACjE,QAAI,CAAC,aAAa;AAChB,WAAK,WAAW,8EAAyE;AAAA,IAC3F,OAAO;AACL,uBAAiB;AACjB,YAAM,KAAK,MAAM,KAAK,QAAQ,KAAK,UAAU,EAAE,OAAO,YAAY,UAAU,EAAE,aAAa,YAAY,CAAC;AACxG,iBAAW,CAAC,GAAG;AACf,aAAO,CAAC,GAAG,OAAO,qDAAqD,GAAG,OAAO,SAAS,4DAA4D;AACtJ,UAAI,UAAU;AACZ,cAAM,EAAE,KAAK,IAAI,KAAK,QAAQ,KAAK,UAAU,EAAE,aAAa,UAAU;AACtE,cAAM,MAAM,MAAM,MAAM,KAAK,SAAS,EAAE,MAAM,MAAM,IAAI;AACxD,eAAO,QAAQ,KAAK,EAAE,GAAG,yCAAyC,MAAM,QAAQ,IAAI,MAAM,KAAK,mBAAmB,6DAA6D,QAAQ,KAAK,MAAM,EAAE;AAAA,MACtM;AACA,YAAM,YAAY,MAAM,KAAK,QAAQ,KAAK,UAAU,EAAE,OAAO,GAAG,SAAS,eAAe,UAAU,EAAE,aAAa,YAAY,CAAC;AAC9H,UAAI,CAAC,UAAU,MAAO,OAAM,MAAM,QAAQ,KAAK,UAAU,EAAE,OAAO,CAAC,GAAG,SAAS,aAAa,CAAC;AAC7F,aAAO,QAAQ,UAAU,KAAK,GAAG,kDAAkD,UAAU,QAAQ,SAAY,yBAAyB;AAAA,IAC5I;AAAA,EACF,SAAS,GAAG;AACV,SAAK,2CAA2C,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,EAC5F,UAAE;AAEA,UAAM,YAAsB,CAAC;AAC7B,UAAM,UAAU,OAAO,MAAc,QAAkE;AACrG,UAAI;AACF,cAAM,EAAE,MAAM,IAAI,MAAM,IAAI;AAC5B,YAAI,MAAO,WAAU,KAAK,GAAG,IAAI,KAAK,MAAM,OAAO,GAAG;AAAA,MACxD,SAAS,GAAG;AACV,kBAAU,KAAK,GAAG,IAAI,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,GAAG;AAAA,MAC1E;AAAA,IACF;AACA,QAAI,kBAAkB,SAAU,OAAM,QAAQ,kBAAkB,UAAU,IAAI,MAAM,MAAM,QAAQ,KAAK,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;AACvI,UAAM,QAAQ,gBAAgB,MAAM,GAAG,KAAK,EAAE,KAAK,YAAY,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,QAAQ,SAAS,CAAC,CAAC;AAC5G,UAAM,QAAQ,mBAAmB,MAAM,GAAG,KAAK,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,GAAG,WAAW,MAAM,CAAC;AACtG,UAAM,QAAQ,kBAAkB,MAAM,GAAG,KAAK,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,GAAG,UAAU,cAAc,MAAM,EAAE,CAAC;AACpH,QAAI,OAAQ,OAAM,QAAQ,gBAAgB,KAAK,IAAI,MAAM,MAAM,KAAK,MAAM,WAAW,MAAO,CAAC;AAC7F,QAAI,UAAU,QAAQ;AACpB,WAAK,iCAAiC,UAAU,KAAK,IAAI,GAAG,iCAAiC,MAAM,OAAO,KAAK,sBAAsB;AAAA,IACvI,OAAO;AACL,WAAK,wCAAwC;AAAA,IAC/C;AAAA,EACF;AACF;AAIA,MAAM,QAAQ,QAAQ,OAAO,SAAS,CAAC,QAAQ,IAAI;AACnD,MAAM,QAAQ,CAAC,MAAc,MAAe,QAAQ,QAAU,IAAI,IAAI,CAAC,YAAc;AACrF,MAAM,OAA+B;AAAA,EACnC,MAAM,MAAM,IAAI,QAAG;AAAA,EACnB,MAAM,MAAM,IAAI,GAAG;AAAA,EACnB,MAAM,MAAM,IAAI,QAAG;AAAA,EACnB,MAAM,MAAM,IAAI,QAAG;AAAA,EACnB,MAAM,MAAM,IAAI,GAAG;AACrB;AAEA,SAAS,cAAoB;AAC3B,MAAI,OAAO;AACX,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,UAAU,MAAM;AACpB,cAAQ,IAAI;AAAA,EAAK,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE;AACpC,aAAO,EAAE;AAAA,IACX;AACA,YAAQ,IAAI,KAAK,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;AAC5C,QAAI,EAAE,OAAQ,SAAQ,IAAI,SAAS,MAAM,IAAI,EAAE,MAAM,CAAC,EAAE;AACxD,QAAI,EAAE,SAAS,EAAE,WAAW,UAAU,EAAE,WAAW,QAAS,SAAQ,IAAI,SAAS,MAAM,IAAI,YAAO,EAAE,IAAI,CAAC,EAAE;AAAA,EAC7G;AACA,QAAM,QAAQ,CAAC,MAAc,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE;AACnE,QAAM,QAAQ,CAAC,GAAG,MAAM,MAAM,CAAC,WAAW,MAAM,MAAM,IAAI,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,YAAY,IAAI,MAAM,MAAM,MAAM,IAAI,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,OAAO,OAAO;AACrL,UAAQ,IAAI;AAAA,EAAK,MAAM,KAAK,IAAI,CAAC;AAAA,CAAI;AACvC;AAIA,eAAe,OAAwB;AACrC,QAAM,EAAE,OAAO,QAAQ,QAAQ,IAAI,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;AACnE,MAAI,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,GAAG;AACvC,YAAQ,IAAI,KAAK;AACjB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAQ;AAClB,YAAQ,MAAM,iBAAiB,QAAQ,WAAW,IAAI,KAAK,GAAG,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA,EAAO,KAAK,EAAE;AACnG,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,UAAU,aAAa,GAAG;AAChC,QAAM,MAA8B,EAAE,GAAG,QAAQ;AACjD,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,GAAG,EAAG,KAAI,EAAG,KAAI,CAAC,IAAI;AAElE,QAAM,MAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA,YAAY,MAAM,IAAI,YAAY;AAAA,IAClC,SAAS,MAAM,IAAI,SAAS;AAAA,IAC5B,KAAK,IAAI;AAAA,IACT,SAAS,IAAI;AAAA,IACb,YAAY,IAAI,6BAA6B,IAAI;AAAA,EACnD;AAEA,MAAI,MAAM,IAAI,KAAK,GAAG;AACpB,oBAAgB,GAAG;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,MAAI,CAAC,KAAM,SAAQ,IAAI,kBAAkB;AAEzC,MAAI,MAAM,IAAI,KAAK,GAAG;AACpB,kBAAc,GAAG;AACjB,gBAAY,GAAG;AACf,uBAAmB,GAAG;AACtB,UAAM,UAAU,WAAW,GAAG;AAC9B,QAAI,YAAY,UAAU;AACxB,cAAQ,KAAK,oHAAiH;AAAA,IAChI;AACA,QAAI,MAAM,IAAI,MAAM,GAAG;AACrB,UAAI,CAAC,cAAc,UAAU,GAAG;AAC9B,gBAAQ,KAAK,qHAAgH;AAAA,MAC/H,OAAO;AAGL,cAAM,QAAQ,IAAI,MAAM,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,QAAQ,IAAI;AACjE,cAAM,MAAM,QAAQ,CAAC,aAAa,MAAM,SAAS,IAAI,CAAC,MAAM,MAAM;AAClE,cAAM,MAAM,UAAU,YAAY,KAAK,EAAE,OAAO,OAAO,WAAW,UAAU,CAAC;AAC7E,gBAAQ;AAAA,UACN,IAAI,WAAW,IACX,sCAAsC,IAAI,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,OAC/D,QACE,yFACA;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,MAAM;AACT,cAAQ,IAAI;AAAA,EAAK,MAAM,GAAG,OAAO,CAAC,EAAE;AACpC,UAAI,QAAQ,WAAW,EAAG,SAAQ,IAAI,2BAA2B;AACjE,iBAAW,KAAK,QAAS,SAAQ,IAAI,YAAO,CAAC,EAAE;AAAA,IACjD;AAAA,EACF,WAAW,MAAM,IAAI,MAAM,GAAG;AAC5B,YAAQ,MAAM,0CAA0C;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,GAAG;AACtB,mBAAiB,GAAG;AACpB,aAAW,GAAG;AAEd,QAAM,KAAK,MAAM,aAAa;AAC9B,QAAM,cAAc,MAAM,cAAc,KAAK,EAAE;AAE/C,MAAI,OAAO,SAAS,EAAG,OAAM,cAAc,OAAO,SAAS,GAAG,GAAG;AAEjE,MAAI,MAAM,IAAI,KAAK,GAAG;AACpB,UAAM,YAAY;AAClB,QAAI,CAAC,eAAe,CAAC,IAAI;AACvB,WAAK,WAAW,+CAA+C;AAAA,IACjE,WAAW,CAAC,IAAI,YAAY;AAC1B,WAAK,WAAW,wFAAwF;AAAA,IAC1G,WAAW,MAAM,WAAW,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG;AAClD,YAAM,OAAO,KAAK,EAAE;AAAA,IACtB,OAAO;AACL,WAAK,WAAW,gBAAgB;AAAA,IAClC;AAAA,EACF;AAEA,MAAI,MAAM;AACR,YAAQ,IAAI,KAAK,UAAU,EAAE,SAAS,QAAQ,GAAG,MAAM,CAAC,CAAC;AAAA,EAC3D,OAAO;AACL,gBAAY;AACZ,QAAI,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM,GAAG;AAC5C,cAAQ,IAAI,4DAA4D;AAAA,IAC1E;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM;AACtD,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,MAAM;AACtD,SAAO,UAAW,MAAM,IAAI,QAAQ,KAAK,SAAU,IAAI;AACzD;AAEA,KAAK,EAAE;AAAA,EACL,CAAC,SAAS,QAAQ,KAAK,IAAI;AAAA,EAC3B,CAAC,QAAQ;AACP,YAAQ,MAAM,GAAG;AACjB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":["require","error"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * guardrail-doctor — checks that a project is set up the way this version of\n * Guardrail expects, and (with --fix) repairs what can be repaired locally.\n *\n * It ships inside @guard-rail/core on purpose, so the checks always match the\n * installed version: when a release adds a requirement (a migration, an\n * environment variable, a header), the same release adds the check for it here.\n *\n * Three layers, each opt-in beyond the first:\n * 1. Static + read-only (default): project files, environment, the database's\n * shape and the Supabase project's sign-up settings. Changes nothing.\n * 2. --app-url: asks a running copy of the site for its response headers.\n * 3. --e2e: creates a throwaway user and rows on the real database, exercises\n * sign-in, invite claiming, row-level security, the admin guard, rate\n * limiting and storage the way a signed-in editor would, then deletes it\n * all. Never runs without saying what it will touch.\n *\n * Standalone like the other CLIs: plain Node, local imports end in \".js\", and\n * nothing here imports React or Next.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n copyFileSync,\n readdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport { createInterface } from \"node:readline/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { GR_SCHEMA, GR_MEMBERS_TABLE } from \"../core/defs.js\";\nimport { cspString, guardrailCspDirectives } from \"../core/csp.js\";\n\n/* ------------------------------------------------------------ constants --- */\n\n/**\n * Oldest Next.js release known to carry the fixes for the advisories published\n * so far, by major line. Only the offline fallback: normally the doctor asks the\n * npm registry's advisory service about the installed version, which is always\n * current. Update alongside the dependency bumps; a major not listed gets a\n * \"check for security releases\" note instead of a verdict.\n */\nconst NEXT_MIN_PATCHED: Record<number, [number, number, number]> = {\n 15: [15, 5, 28],\n 16: [16, 4, 1],\n};\n\n/**\n * Kept in step with the block create-guardrail writes into a new project's\n * next.config.ts — the same headers, so a scaffolded and a repaired site match.\n */\nconst SECURITY_HEADERS_BLOCK = ` // Baseline response headers. frame-ancestors keeps another site from framing\n // this one (the editing toolbar included) in a clickjacking overlay. A full\n // Content-Security-Policy is specific to what your pages load, so add yours\n // alongside it.\n async headers() {\n return [\n {\n source: \"/:path*\",\n headers: [\n { key: \"Content-Security-Policy\", value: \"frame-ancestors 'self'\" },\n { key: \"X-Frame-Options\", value: \"SAMEORIGIN\" },\n { key: \"X-Content-Type-Options\", value: \"nosniff\" },\n { key: \"Referrer-Policy\", value: \"strict-origin-when-cross-origin\" },\n { key: \"Permissions-Policy\", value: \"camera=(), microphone=(), geolocation=()\" },\n // Only honored over HTTPS; browsers ignore it on plain HTTP.\n { key: \"Strict-Transport-Security\", value: \"max-age=63072000; includeSubDomains\" },\n ],\n },\n ];\n },\n`;\n\nconst SITE_URL_ENV_NOTE = `\n# The site's public address. Every emailed invite/reset link points here, and in\n# production nothing is sent without it (links are never built from the request).\n# GUARDRAIL_SITE_URL=https://example.com\n`;\n\nconst USAGE = `Usage:\n guardrail-doctor [options]\n\nChecks this project's Guardrail setup. Read-only unless you ask otherwise.\n\nOptions:\n --fix Repair what can be repaired in local files: copy missing\n migrations, add a GUARDRAIL_SITE_URL note to your env\n files, add baseline security headers to next.config.\n Never touches secrets or Supabase dashboard settings.\n --push With --fix: also run \\`supabase db push\\` after copying.\n --app-url=<url> Also check the response headers of a running copy of the\n site (e.g. --app-url=http://localhost:3000).\n --e2e Run the end-to-end suite against the database: creates a\n throwaway user and rows, exercises sign-in, invite claims,\n row-level security, the admin guard, rate limiting and\n storage, then deletes everything it made.\n --yes Don't ask before --e2e touches a non-local database.\n --offline Don't contact the npm registry. By default the installed\n versions of next, @supabase/* and @guard-rail/core are\n checked against its published security advisories (only\n package names and versions are sent, as \\`pnpm audit\\` does).\n --csp Print a starter Content-Security-Policy-Report-Only header\n for this site (built from SUPABASE_URL) and exit. Add it\n report-only first, watch the reports, then enforce.\n --production Treat production-only requirements (like\n GUARDRAIL_SITE_URL) as failures rather than warnings.\n --strict Exit non-zero on warnings too.\n --json Print results as JSON.\n -h, --help This text.\n\nReads SUPABASE_URL, SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY (or\nSERVICE_KEY) from the environment, then .env.local and .env.`;\n\n/* -------------------------------------------------------------- results --- */\n\ntype Status = \"pass\" | \"warn\" | \"fail\" | \"skip\" | \"info\";\n\ninterface Result {\n group: string;\n status: Status;\n title: string;\n detail?: string;\n hint?: string;\n}\n\nconst results: Result[] = [];\nlet currentGroup = \"\";\n\nfunction group(name: string): void {\n currentGroup = name;\n}\n\nfunction record(status: Status, title: string, detail?: string, hint?: string): void {\n results.push({ group: currentGroup, status, title, ...(detail ? { detail } : {}), ...(hint ? { hint } : {}) });\n}\n\nconst pass = (t: string, d?: string) => record(\"pass\", t, d);\nconst warn = (t: string, d?: string, h?: string) => record(\"warn\", t, d, h);\nconst fail = (t: string, d?: string, h?: string) => record(\"fail\", t, d, h);\nconst skip = (t: string, d?: string) => record(\"skip\", t, d);\nconst info = (t: string, d?: string) => record(\"info\", t, d);\n\n/* ------------------------------------------------------------- helpers --- */\n\nfunction parseFlags(argv: string[]) {\n const flags = new Set<string>();\n const values: Record<string, string> = {};\n const unknown: string[] = [];\n for (const arg of argv) {\n const m = arg.match(/^--([^=]+)=([\\s\\S]*)$/);\n if (m) {\n values[m[1]!] = m[2]!;\n } else if (arg.startsWith(\"-\")) {\n flags.add(arg.replace(/^-+/, \"\"));\n } else {\n unknown.push(arg);\n }\n }\n const known = new Set([\"fix\", \"push\", \"e2e\", \"yes\", \"production\", \"strict\", \"json\", \"offline\", \"csp\", \"help\", \"h\"]);\n for (const f of flags) if (!known.has(f)) unknown.push(`--${f}`);\n for (const k of Object.keys(values)) if (k !== \"app-url\") unknown.push(`--${k}`);\n return { flags, values, unknown };\n}\n\nfunction readEnvFiles(cwd: string): Record<string, string> {\n const out: Record<string, string> = {};\n // .env wins last-in, so read it first: .env.local overrides it, as in Next.\n for (const file of [\".env\", \".env.local\"]) {\n const p = path.join(cwd, file);\n if (!existsSync(p)) continue;\n for (const line of readFileSync(p, \"utf8\").split(/\\r?\\n/)) {\n const m = line.match(/^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*)$/);\n if (!m) continue;\n const value = m[2]!.trim().replace(/^(['\"])(.*)\\1$/, \"$2\");\n if (value) out[m[1]!] = value;\n }\n }\n return out;\n}\n\nfunction parseVersion(v: string): [number, number, number] | null {\n const m = v.match(/^(\\d+)\\.(\\d+)\\.(\\d+)/);\n return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;\n}\n\nfunction atLeast(v: [number, number, number], min: [number, number, number]): boolean {\n for (let i = 0; i < 3; i++) {\n if (v[i]! !== min[i]!) return v[i]! > min[i]!;\n }\n return true;\n}\n\n/** Decodes a JWT's payload without verifying it — just to read which role a key claims. */\nfunction jwtRole(key: string): string | null {\n const parts = key.split(\".\");\n if (parts.length !== 3) return null;\n try {\n const payload = JSON.parse(Buffer.from(parts[1]!, \"base64url\").toString(\"utf8\"));\n return typeof payload.role === \"string\" ? payload.role : null;\n } catch {\n return null;\n }\n}\n\nfunction isLocalHost(host: string): boolean {\n return host === \"localhost\" || host === \"127.0.0.1\" || host === \"::1\" || host === \"[::1]\" || host.endsWith(\".localhost\");\n}\n\nfunction commandExists(cmd: string): boolean {\n const res = process.platform === \"win32\" ? spawnSync(\"where\", [cmd]) : spawnSync(\"which\", [cmd]);\n return res.status === 0;\n}\n\nfunction findNextConfig(cwd: string): string | null {\n for (const name of [\"next.config.ts\", \"next.config.mjs\", \"next.config.js\", \"next.config.cjs\", \"next.config.mts\"]) {\n const p = path.join(cwd, name);\n if (existsSync(p)) return p;\n }\n return null;\n}\n\nfunction bundledMigrationsDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n return path.join(here, \"..\", \"..\", \"supabase\", \"migrations\");\n}\n\n/* ----------------------------------------------------------------- fixes --- */\n\nconst applied: string[] = [];\n\nfunction fixMigrations(cwd: string): void {\n const src = bundledMigrationsDir();\n if (!existsSync(src)) return;\n const dest = path.join(cwd, \"supabase\", \"migrations\");\n mkdirSync(dest, { recursive: true });\n const have = new Set(readdirSync(dest));\n for (const file of readdirSync(src)) {\n if (have.has(file)) continue;\n copyFileSync(path.join(src, file), path.join(dest, file));\n applied.push(`Copied supabase/migrations/${file}`);\n }\n}\n\nfunction fixEnvNotes(cwd: string): void {\n for (const file of [\".env.example\", \".env.local\"]) {\n const p = path.join(cwd, file);\n if (!existsSync(p)) continue;\n const content = readFileSync(p, \"utf8\");\n if (/GUARDRAIL_SITE_URL/.test(content)) continue;\n writeFileSync(p, content.replace(/\\n*$/, \"\\n\") + SITE_URL_ENV_NOTE);\n applied.push(`Added a GUARDRAIL_SITE_URL note to ${file}`);\n }\n}\n\nconst CACHE_COMPONENTS_LINE = /^[ \\t]*(?:cacheComponents|partialPrefetching)\\s*:\\s*true\\s*,?[ \\t]*\\r?\\n/gm;\n\n/** Removes Cache Components from next.config. Returns true if it changed the file. */\nfunction fixCacheComponents(cwd: string): boolean {\n const configPath = findNextConfig(cwd);\n if (!configPath) return false;\n const content = readFileSync(configPath, \"utf8\");\n const next = content.replace(CACHE_COMPONENTS_LINE, \"\");\n if (next === content) return false;\n writeFileSync(configPath, next);\n applied.push(`Turned off Cache Components in ${path.basename(configPath)}`);\n return true;\n}\n\n/** Inserts the baseline headers into the config object. Returns false if the file isn't a shape it can patch. */\nfunction fixHeaders(cwd: string): \"patched\" | \"present\" | \"manual\" | \"none\" {\n const configPath = findNextConfig(cwd);\n if (!configPath) return \"none\";\n const content = readFileSync(configPath, \"utf8\");\n if (/frame-ancestors|x-frame-options/i.test(content)) return \"present\";\n // An existing headers() can't be merged into safely from here.\n if (/\\bheaders\\s*\\(/.test(content)) return \"manual\";\n\n const openers = [\n /(const\\s+nextConfig\\s*(?::\\s*[A-Za-z<>[\\]| ]+)?=\\s*\\{[ \\t]*\\r?\\n)/,\n /(export\\s+default\\s*\\{[ \\t]*\\r?\\n)/,\n /(module\\.exports\\s*=\\s*\\{[ \\t]*\\r?\\n)/,\n ];\n for (const re of openers) {\n if (!re.test(content)) continue;\n writeFileSync(configPath, content.replace(re, `$1${SECURITY_HEADERS_BLOCK}`));\n applied.push(`Added baseline security headers to ${path.basename(configPath)}`);\n return \"patched\";\n }\n return \"manual\";\n}\n\n/* ------------------------------------------------------------ advisories --- */\n\ninterface Advisory {\n title: string;\n severity: string;\n url: string;\n}\n\n/**\n * Published security advisories for exact package versions, from the npm\n * registry's bulk advisory service (the one `pnpm audit` uses). Null when it\n * can't be reached, so callers can fall back instead of guessing.\n */\nasync function fetchAdvisories(versions: Record<string, string>): Promise<Record<string, Advisory[]> | null> {\n const body: Record<string, string[]> = {};\n for (const [name, v] of Object.entries(versions)) body[name] = [v];\n if (Object.keys(body).length === 0) return {};\n try {\n const res = await fetch(\"https://registry.npmjs.org/-/npm/v1/security/advisories/bulk\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(8000),\n });\n if (!res.ok) return null;\n return (await res.json()) as Record<string, Advisory[]>;\n } catch {\n return null;\n }\n}\n\n/* ---------------------------------------------------------------- checks --- */\n\ninterface Ctx {\n cwd: string;\n env: Record<string, string>;\n production: boolean;\n offline: boolean;\n url?: string;\n anonKey?: string;\n serviceKey?: string;\n}\n\nasync function checkProject(ctx: Ctx): Promise<void> {\n group(\"Project\");\n const pkgPath = path.join(ctx.cwd, \"package.json\");\n if (!existsSync(pkgPath)) {\n fail(\"No package.json here\", \"Run this from the root of your Next.js project.\");\n return;\n }\n\n const ownPkg = JSON.parse(readFileSync(path.join(bundledMigrationsDir(), \"..\", \"..\", \"package.json\"), \"utf8\"));\n pass(`@guard-rail/core ${ownPkg.version}`);\n\n const require = createRequire(pkgPath);\n const installed = (name: string): string | undefined => {\n try {\n return JSON.parse(readFileSync(require.resolve(`${name}/package.json`), \"utf8\")).version;\n } catch {\n return undefined;\n }\n };\n const nextVersion = installed(\"next\");\n if (!nextVersion) warn(\"Next.js isn't installed here\", \"Couldn't resolve `next` from this directory.\");\n\n // Ask the registry about what's actually installed; only fall back to the\n // built-in table for Next.js when that isn't possible.\n const checked: Record<string, string> = {};\n for (const name of [\"next\", \"@supabase/supabase-js\", \"@supabase/ssr\"]) {\n const v = installed(name);\n if (v) checked[name] = v;\n }\n checked[\"@guard-rail/core\"] = ownPkg.version;\n const advisories = ctx.offline ? null : await fetchAdvisories(checked);\n\n if (advisories) {\n let clean = 0;\n for (const [name, version] of Object.entries(checked)) {\n const found = advisories[name] ?? [];\n const serious = found.filter((a) => a.severity === \"critical\" || a.severity === \"high\");\n if (found.length === 0) {\n clean++;\n continue;\n }\n const lines = found.slice(0, 3).map((a) => `${a.severity}: ${a.title} (${a.url})`);\n if (found.length > 3) lines.push(`…and ${found.length - 3} more`);\n (serious.length ? fail : warn)(\n `${name} ${version} has ${found.length} known security advisor${found.length === 1 ? \"y\" : \"ies\"}`,\n lines.join(\"\\n \"),\n `Upgrade: pnpm add ${name}@latest`,\n );\n }\n if (clean) pass(`No known security advisories for ${clean} checked package${clean === 1 ? \"\" : \"s\"}`, Object.entries(checked).filter(([n]) => (advisories[n] ?? []).length === 0).map(([n, v]) => `${n} ${v}`).join(\", \"));\n } else if (nextVersion) {\n // Offline, or the registry couldn't be reached: the built-in table.\n const v = parseVersion(nextVersion);\n const floor = v ? NEXT_MIN_PATCHED[v[0]] : undefined;\n const why = ctx.offline ? \"offline\" : \"the advisory service couldn't be reached\";\n if (!v) {\n info(`Next.js ${nextVersion}`);\n } else if (floor && !atLeast(v, floor)) {\n fail(\n `Next.js ${nextVersion} has known security vulnerabilities`,\n `Releases before ${floor.join(\".\")} carry published advisories (checked against this tool's built-in list — ${why}).`,\n `Upgrade: pnpm add next@latest`,\n );\n } else if (floor) {\n pass(`Next.js ${nextVersion}`, `Checked against this tool's built-in list (${why}); it may not know about newer advisories.`);\n } else {\n warn(\n `Next.js ${nextVersion}`,\n `This tool doesn't know the patched release for the ${v[0]}.x line (${why}).`,\n `Make sure you're on its latest patch, or move to the current major.`,\n );\n }\n }\n\n for (const dep of [\"@supabase/supabase-js\", \"@supabase/ssr\"]) {\n try {\n require.resolve(`${dep}/package.json`);\n pass(`${dep} installed`);\n } catch {\n fail(`${dep} isn't installed`, undefined, `pnpm add ${dep}`);\n }\n }\n}\n\nfunction checkEnvironment(ctx: Ctx): void {\n group(\"Environment\");\n const { env } = ctx;\n\n if (!ctx.url) {\n fail(\"SUPABASE_URL isn't set\", \"Not in the environment, .env.local or .env.\");\n } else {\n try {\n const u = new URL(ctx.url);\n if (u.protocol !== \"https:\" && u.protocol !== \"http:\") throw new Error();\n if (u.protocol === \"http:\" && !isLocalHost(u.hostname)) {\n warn(\"SUPABASE_URL uses plain http\", \"Use the project's https:// address.\");\n } else {\n pass(\"SUPABASE_URL\", u.host);\n }\n } catch {\n fail(\"SUPABASE_URL isn't a valid URL\", \"Expected https://<project>.supabase.co.\");\n }\n }\n\n const anon = ctx.anonKey;\n if (!anon) {\n fail(\"SUPABASE_ANON_KEY isn't set\");\n } else {\n const role = jwtRole(anon);\n if (role === \"service_role\" || anon.startsWith(\"sb_secret_\")) {\n fail(\n \"SUPABASE_ANON_KEY holds the service-role (secret) key\",\n \"The anon key is meant to be public; the service-role key bypasses every access rule.\",\n \"Swap them: put the anon / publishable key here.\",\n );\n } else {\n pass(\"SUPABASE_ANON_KEY\");\n }\n }\n\n const svc = ctx.serviceKey;\n if (!svc) {\n warn(\n \"No service-role key\",\n \"Invites, password resets and database-backed rate limits need SUPABASE_SERVICE_ROLE_KEY (server-only).\",\n );\n } else {\n const role = jwtRole(svc);\n if (role === \"anon\" || svc.startsWith(\"sb_publishable_\")) {\n fail(\"SUPABASE_SERVICE_ROLE_KEY holds the anon (publishable) key\", undefined, \"Use the service-role / secret key here.\");\n } else {\n pass(\"Service-role key present\");\n }\n }\n\n // A secret behind NEXT_PUBLIC_ is compiled into the browser bundle.\n const leaked = Object.keys(env).filter((k) => /^NEXT_PUBLIC_.*(SERVICE|SECRET|RESEND|PRIVATE)/i.test(k));\n if (leaked.length) {\n fail(\n \"Secret behind a NEXT_PUBLIC_ name\",\n `${leaked.join(\", \")} would be published to every visitor's browser.`,\n \"Rename it without the NEXT_PUBLIC_ prefix and read it only in server code.\",\n );\n } else {\n pass(\"No secrets behind NEXT_PUBLIC_ names\");\n }\n\n const siteUrl = env.GUARDRAIL_SITE_URL;\n if (!siteUrl) {\n (ctx.production ? fail : warn)(\n \"GUARDRAIL_SITE_URL isn't set\",\n \"Production sends no invite or password-reset emails without it — and \\\"Forgot password?\\\" fails silently.\",\n \"Set it to your public address, e.g. https://example.com (or pass siteUrl to withGuardrail()).\",\n );\n } else {\n try {\n const u = new URL(siteUrl);\n if (u.protocol !== \"https:\" && u.protocol !== \"http:\") throw new Error();\n if (u.protocol === \"http:\" && !isLocalHost(u.hostname)) {\n warn(\"GUARDRAIL_SITE_URL uses plain http\", \"Emailed links should point at an https address.\");\n } else {\n pass(\"GUARDRAIL_SITE_URL\", u.origin);\n }\n } catch {\n fail(\"GUARDRAIL_SITE_URL isn't a valid http(s) URL\", siteUrl);\n }\n }\n\n const hasKey = Boolean(env.RESEND_API_KEY);\n const hasFrom = Boolean(env.GUARDRAIL_EMAIL_FROM);\n if (hasKey && hasFrom) {\n pass(\"Resend email configured\");\n } else if (hasKey !== hasFrom) {\n fail(\n `${hasKey ? \"GUARDRAIL_EMAIL_FROM\" : \"RESEND_API_KEY\"} is missing`,\n \"Email through Resend needs both RESEND_API_KEY and GUARDRAIL_EMAIL_FROM.\",\n );\n } else {\n info(\"No Resend settings in the environment\", \"Fine if you pass your own onInvite / onPasswordReset, or only use consoleEmails() in development.\");\n }\n\n if (env.GUARDRAIL_SITE_ID) pass(\"GUARDRAIL_SITE_ID\", env.GUARDRAIL_SITE_ID);\n else info(\"GUARDRAIL_SITE_ID isn't set\", 'Sites default to the id \"default\".');\n}\n\nfunction checkFiles(ctx: Ctx): void {\n group(\"Files\");\n\n // Migrations\n const src = bundledMigrationsDir();\n const dest = path.join(ctx.cwd, \"supabase\", \"migrations\");\n if (existsSync(src)) {\n const shipped = readdirSync(src);\n const have = existsSync(dest) ? new Set(readdirSync(dest)) : new Set<string>();\n const missing = shipped.filter((f) => !have.has(f));\n if (missing.length === 0) {\n pass(\"All Guardrail migrations are in supabase/migrations/\");\n } else {\n fail(\n `${missing.length} migration${missing.length === 1 ? \"\" : \"s\"} not copied into supabase/migrations/`,\n missing.join(\", \"),\n \"Run: pnpm exec guardrail-doctor --fix (or guardrail-init-db)\",\n );\n }\n }\n\n // Local Supabase config exposes the schema\n const configToml = path.join(ctx.cwd, \"supabase\", \"config.toml\");\n if (existsSync(configToml)) {\n const toml = readFileSync(configToml, \"utf8\");\n const schemas = toml.match(/\\[api\\][\\s\\S]*?schemas\\s*=\\s*\\[([^\\]]*)\\]/)?.[1] ?? \"\";\n if (schemas.includes('\"guardrail\"')) pass('supabase/config.toml exposes the \"guardrail\" schema');\n else fail('supabase/config.toml doesn\\'t expose the \"guardrail\" schema', undefined, \"Run: pnpm exec guardrail-init-db\");\n }\n\n // Root layout wiring\n const layouts = [\"app/layout.tsx\", \"src/app/layout.tsx\", \"app/layout.jsx\", \"src/app/layout.jsx\", \"app/layout.js\", \"src/app/layout.js\"];\n const layout = layouts.map((l) => path.join(ctx.cwd, l)).find(existsSync);\n const wiredIn = (file: string) => /withGuardrail/.test(readFileSync(file, \"utf8\"));\n if (!layout) {\n skip(\"Root layout not found\", \"Looked for app/layout and src/app/layout.\");\n } else if (wiredIn(layout)) {\n pass(\"Root layout uses withGuardrail()\");\n } else {\n warn(\n \"Root layout doesn't mention withGuardrail()\",\n `${path.relative(ctx.cwd, layout)} — fine if it's wrapped where the layout is exported from another file.`,\n );\n }\n\n // Security headers\n const configPath = findNextConfig(ctx.cwd);\n if (!configPath) {\n skip(\"next.config not found\");\n } else {\n const content = readFileSync(configPath, \"utf8\");\n if (/^[ \\t]*cacheComponents\\s*:\\s*true/m.test(content)) {\n fail(\n \"Cache Components is turned on\",\n \"Guardrail reads the session cookie to decide what to render, which Next's Cache Components mode refuses outside <Suspense> — the site won't pass `next build`.\",\n \"Run: pnpm exec guardrail-doctor --fix (removes `cacheComponents: true` and `partialPrefetching: true`)\",\n );\n }\n if (/frame-ancestors|x-frame-options/i.test(content)) {\n pass(`${path.basename(configPath)} sets anti-framing headers`);\n } else {\n warn(\n `${path.basename(configPath)} sets no security headers`,\n \"Without frame-ancestors, another site can frame yours — including the editing toolbar — under a clickjacking overlay.\",\n \"Run: pnpm exec guardrail-doctor --fix (skip if your host or middleware sets them; verify with --app-url)\",\n );\n }\n }\n\n // Env files must be ignored by git\n const gitignore = path.join(ctx.cwd, \".gitignore\");\n const hasEnvFile = [\".env.local\", \".env\"].some((f) => existsSync(path.join(ctx.cwd, f)));\n if (hasEnvFile) {\n const ignores = existsSync(gitignore) ? readFileSync(gitignore, \"utf8\") : \"\";\n if (/^\\s*\\.env(\\*|\\.local|\\.\\*)?\\s*$/m.test(ignores) || /^\\s*\\.env\\*/m.test(ignores)) {\n pass(\"Env files are gitignored\");\n } else {\n fail(\".env files aren't gitignored\", \"Your Supabase keys could be committed.\", \"Add `.env*` (and `!.env.example`) to .gitignore.\");\n }\n }\n}\n\ntype SupabaseModule = typeof import(\"@supabase/supabase-js\");\ntype SupabaseClient = import(\"@supabase/supabase-js\").SupabaseClient;\n\nasync function loadSupabase(): Promise<SupabaseModule | null> {\n try {\n return (await import(\"@supabase/supabase-js\")) as SupabaseModule;\n } catch {\n return null;\n }\n}\n\nasync function checkDatabase(ctx: Ctx, sb: SupabaseModule | null): Promise<boolean> {\n group(\"Database\");\n if (!ctx.url || !ctx.anonKey) {\n skip(\"Skipped\", \"Needs SUPABASE_URL and SUPABASE_ANON_KEY.\");\n return false;\n }\n if (!sb) {\n skip(\"Skipped\", \"@supabase/supabase-js isn't installed.\");\n return false;\n }\n const base = ctx.url.replace(/\\/$/, \"\");\n\n // Reachability + schema exposure, with the public key only\n try {\n const res = await fetch(`${base}/rest/v1/gr_content?select=path_key&limit=1`, {\n headers: { apikey: ctx.anonKey, Authorization: `Bearer ${ctx.anonKey}`, \"Accept-Profile\": GR_SCHEMA },\n });\n const body = res.ok ? \"\" : await res.text();\n if (res.ok) {\n pass(`\"${GR_SCHEMA}\" schema is exposed and readable`);\n } else if (/PGRST106|Invalid schema/i.test(body)) {\n fail(\n `The \"${GR_SCHEMA}\" schema isn't exposed`,\n \"PostgREST refuses requests for it.\",\n \"Supabase dashboard > Settings > API > Exposed schemas: add guardrail.\",\n );\n return false;\n } else if (/PGRST205|42P01|does not exist|schema cache/i.test(body)) {\n fail(\"Guardrail's tables don't exist\", \"The migration hasn't been applied to this project.\", \"Run: pnpm exec guardrail-init-db --push\");\n return false;\n } else if (res.status === 401 || res.status === 403) {\n fail(\"The anon key was rejected\", `HTTP ${res.status}`, \"Check SUPABASE_ANON_KEY belongs to this project.\");\n return false;\n } else {\n fail(\"Unexpected response reading content\", `HTTP ${res.status}: ${body.slice(0, 160)}`);\n return false;\n }\n } catch (e) {\n fail(\"Couldn't reach Supabase\", e instanceof Error ? e.message : String(e), \"Check SUPABASE_URL, and that the project isn't paused.\");\n return false;\n }\n\n // Sign-up settings: the one thing that decides whether an invite can be claimed by someone else\n try {\n const res = await fetch(`${base}/auth/v1/settings`, { headers: { apikey: ctx.anonKey } });\n if (res.ok) {\n const s = (await res.json()) as {\n disable_signup?: boolean;\n mailer_autoconfirm?: boolean;\n external?: { email?: boolean };\n };\n if (s.external?.email === false) {\n fail(\"Email sign-in is disabled in Supabase\", undefined, \"Authentication > Sign In / Providers: enable Email.\");\n } else if (s.disable_signup) {\n pass(\"Public sign-ups are off\");\n } else if (s.mailer_autoconfirm) {\n warn(\n \"Anyone can sign up, and addresses count as confirmed instantly\",\n \"Someone could register an invited person's address before they do, and claim the invitation.\",\n \"Authentication > Sign In / Providers > Email: turn on Confirm email (or turn off public sign-ups).\",\n );\n } else {\n pass(\"Sign-ups require a confirmed email\");\n }\n } else {\n skip(\"Couldn't read the project's sign-up settings\", `HTTP ${res.status}`);\n }\n } catch {\n skip(\"Couldn't read the project's sign-up settings\");\n }\n\n if (!ctx.serviceKey) {\n skip(\"Service-role checks skipped\", \"No service-role key to inspect the database's functions with.\");\n return true;\n }\n\n const admin = sb.createClient(ctx.url, ctx.serviceKey, { auth: { persistSession: false, autoRefreshToken: false } });\n const gr = () => admin.schema(GR_SCHEMA);\n\n for (const table of [GR_MEMBERS_TABLE, \"gr_content\", \"gr_repeater_items\", \"gr_rate_limits\", \"gr_content_history\"]) {\n // A real GET, not HEAD: supabase-js reports a HEAD request for a missing table\n // (an empty-bodied 404) as a success.\n const { error } = await gr().from(table).select(\"*\").limit(1);\n if (!error) pass(`Table ${table}`);\n else if (table === \"gr_rate_limits\") warn(\"Table gr_rate_limits is missing\", \"Rate limits are counted in memory until the migration is applied.\", \"Run: pnpm exec guardrail-init-db --push\");\n else if (table === \"gr_content_history\") warn(\"Table gr_content_history is missing\", \"Editors won't be offered a revert button until the migration is applied.\", \"Run: pnpm exec guardrail-init-db --push\");\n else fail(`Table ${table} isn't readable`, error.message, \"Run: pnpm exec guardrail-init-db --push\");\n }\n\n // Functions: a permission error means it exists but this role can't run it; \"not found\" means it's absent.\n const probes: Array<[string, Record<string, unknown>, string]> = [\n [\"gr_confirmed_email\", {}, \"Confirmed-email identity check\"],\n [\"gr_claim_invite\", { target_site: \"doctor-probe\" }, \"Invite-claim function\"],\n [\"gr_ensure_bucket\", { target_bucket: \"gr-doctor-probe\" }, \"Media bucket function\"],\n [\"gr_revert_content\", { p_site: \"doctor-probe\", p_key: \"doctor.probe\" }, \"Revert function\"],\n [\"gr_revert_info\", { p_site: \"doctor-probe\", p_key: \"doctor.probe\" }, \"Revert-info function\"],\n ];\n for (const [fn, args, label] of probes) {\n const { error } = await gr().rpc(fn, args);\n if (!error || error.code === \"42501\" || /permission denied/i.test(error.message)) {\n pass(label);\n } else if (error.code === \"PGRST202\" || error.code === \"42883\" || /could not find the function/i.test(error.message)) {\n fail(`${label} is missing`, `Function ${fn}() doesn't exist.`, \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n } else {\n // Exists, but failed for another reason with these dummy arguments.\n pass(label);\n }\n }\n\n const probeKey = `doctor-${randomBytes(3).toString(\"hex\")}`;\n const { data, error } = await gr().rpc(\"gr_rate_hit\", { p_name: \"doctor:probe\", p_key: probeKey, p_limit: 1000, p_window_seconds: 60 });\n if (!error && data) {\n pass(\"Database-backed rate limiting\");\n await gr().from(\"gr_rate_limits\").delete().eq(\"bucket\", `doctor:probe|${probeKey}`);\n } else if (error?.code === \"PGRST202\" || error?.code === \"42883\") {\n warn(\"Rate-limit function is missing\", \"Limits are counted in memory, which doesn't hold across serverless instances.\", \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n } else if (error) {\n warn(\"Rate-limit function failed\", error.message);\n }\n\n info(\"Admin guard (can't delete the last admin)\", \"Verified by --e2e; it's a trigger, which can't be probed read-only.\");\n return true;\n}\n\n/* -------------------------------------------------------------------- csp --- */\n\ntype Csp = Record<string, string[]>;\n\nfunction parseCsp(value: string): Csp {\n const out: Csp = {};\n for (const part of value.split(\";\")) {\n const [name, ...sources] = part.trim().split(/\\s+/);\n if (name) out[name.toLowerCase()] = sources;\n }\n return out;\n}\n\n/** The sources a directive effectively allows: its own list, else default-src's; null if neither (unrestricted). */\nfunction effectiveSources(csp: Csp, name: string): string[] | null {\n return csp[name] ?? csp[\"default-src\"] ?? null;\n}\n\n/** Whether a source list lets the page reach `origin` (a different origin from the site's own). */\nfunction allowsOrigin(sources: string[] | null, origin: string): boolean {\n if (sources === null) return true;\n const target = new URL(origin);\n return sources.some((raw) => {\n const src = raw.toLowerCase();\n if (src === \"*\") return true;\n if (src === `${target.protocol}`) return true; // scheme only, e.g. https:\n if (src === origin.toLowerCase()) return true;\n const wild = src.match(/^(https?:)\\/\\/\\*\\.(.+?)(?::(\\d+))?$/);\n if (wild) {\n const [, scheme, domain, port] = wild;\n return scheme === target.protocol && target.hostname.endsWith(`.${domain}`) && (!port || port === target.port);\n }\n return false;\n });\n}\n\n/** A starter policy for this site: Guardrail's needs plus a conservative baseline for everything else. */\nfunction starterPolicy(supabaseUrl?: string): string {\n const gr = guardrailCspDirectives(supabaseUrl ? { supabaseUrl } : {});\n return cspString({\n \"default-src\": [\"'self'\"],\n \"script-src\": [\"'self'\", \"'nonce-REPLACE_PER_REQUEST'\", \"'strict-dynamic'\"],\n \"style-src\": gr[\"style-src\"]!,\n \"img-src\": [...gr[\"img-src\"]!, \"data:\"],\n \"connect-src\": gr[\"connect-src\"]!,\n \"font-src\": [\"'self'\", \"data:\"],\n \"object-src\": [\"'none'\"],\n \"base-uri\": [\"'self'\"],\n \"form-action\": [\"'self'\"],\n \"frame-ancestors\": gr[\"frame-ancestors\"]!,\n });\n}\n\nfunction printStarterCsp(ctx: Ctx): void {\n console.log(`Starter Content-Security-Policy\n\nContent-Security-Policy-Report-Only: ${starterPolicy(ctx.url)}\n\nHow to use it\n - Send it as Content-Security-Policy-Report-Only first. Browsers report what it\n would block without blocking anything; tune it for the things your site\n loads itself (analytics, fonts, embeds), then switch the header name to\n Content-Security-Policy.\n - Guardrail needs nothing beyond 'self' for styles and scripts. Its stylesheet is\n a real file and it renders no inline scripts, so it needs no 'unsafe-inline' and no\n nonce. The only extras are your Supabase origin in connect-src (image\n uploads) and img-src (the media picker's thumbnails).${ctx.url ? \"\" : \"\\n (Set SUPABASE_URL and run this again to include your Supabase origin.)\"}\n - script-src above uses a per-request nonce, which Next.js sets from a proxy\n (see \"Content Security Policy\" in the Next.js docs). With only a static header\n in next.config you can't use a nonce — use 'self' 'unsafe-inline' for scripts\n instead, which is a weaker policy but still a useful one.\n - guardrailCspDirectives() from @guard-rail/core returns Guardrail's directives\n if you build the header in code.\n`);\n}\n\nasync function checkLiveSite(appUrl: string, ctx: Ctx): Promise<void> {\n group(\"Live site\");\n let res: Response;\n try {\n res = await fetch(appUrl, { redirect: \"manual\" });\n } catch (e) {\n fail(`Couldn't reach ${appUrl}`, e instanceof Error ? e.message : String(e), \"Is the site running?\");\n return;\n }\n if (res.status >= 500) {\n fail(`${appUrl} answered HTTP ${res.status}`);\n return;\n }\n pass(`${appUrl} answered HTTP ${res.status}`);\n\n const h = res.headers;\n const csp = h.get(\"content-security-policy\") ?? \"\";\n if (/frame-ancestors/i.test(csp) || h.get(\"x-frame-options\")) pass(\"Can't be framed by another site\");\n else fail(\"Can be framed by another site\", \"No frame-ancestors or X-Frame-Options header.\", \"Add the baseline headers: pnpm exec guardrail-doctor --fix\");\n\n if (/nosniff/i.test(h.get(\"x-content-type-options\") ?? \"\")) pass(\"X-Content-Type-Options: nosniff\");\n else warn(\"X-Content-Type-Options header missing\");\n\n if (h.get(\"referrer-policy\")) pass(`Referrer-Policy: ${h.get(\"referrer-policy\")}`);\n else warn(\"Referrer-Policy header missing\");\n\n if (new URL(appUrl).protocol === \"https:\") {\n if (h.get(\"strict-transport-security\")) pass(\"Strict-Transport-Security set\");\n else warn(\"Strict-Transport-Security header missing\", \"Browsers won't insist on HTTPS for this site.\");\n } else {\n skip(\"Strict-Transport-Security\", \"Only meaningful over HTTPS.\");\n }\n\n const setCookie = typeof h.getSetCookie === \"function\" ? h.getSetCookie() : [];\n const session = setCookie.filter((c) => /^sb-.*-auth-token/i.test(c));\n if (session.length) fail(\"A session cookie was set for an anonymous request\", session.map((c) => c.split(\"=\")[0]).join(\", \"));\n\n // Guardrail's stylesheet: the chrome is styled by a CSS file the package imports\n // itself. A bundler that doesn't pick up CSS from node_modules would leave it\n // silently unstyled, so confirm the CSS actually reached the page.\n if (res.status === 200) {\n const html = await res.text().catch(() => \"\");\n const MARKER = /--gr-bg\\b|\\.gr-banner\\b/;\n const inline = [...html.matchAll(/<style[^>]*>([\\s\\S]*?)<\\/style>/gi)].map((m) => m[1]).join(\"\\n\");\n let found = MARKER.test(inline);\n const hrefs = [...html.matchAll(/<link\\b[^>]*rel=[\"']stylesheet[\"'][^>]*>/gi)]\n .map((m) => m[0].match(/href=[\"']([^\"']+)[\"']/i)?.[1])\n .filter((x): x is string => Boolean(x))\n .slice(0, 8);\n for (const href of hrefs) {\n if (found) break;\n try {\n const css = await fetch(new URL(href, appUrl), { signal: AbortSignal.timeout(8000) });\n if (css.ok && MARKER.test(await css.text())) found = true;\n } catch {\n // an unreachable stylesheet is the site's own problem; keep looking\n }\n }\n if (found) pass(\"Guardrail's stylesheet is on the page\");\n else if (hrefs.length === 0 && !/<style/i.test(html)) skip(\"Guardrail's stylesheet\", \"The page has no stylesheets to inspect.\");\n else {\n warn(\n \"Guardrail's stylesheet isn't on the page\",\n \"Its editing chrome would render unstyled. The package imports its own CSS, which your bundler may not be picking up.\",\n 'Import it once in your root layout: import \"@guard-rail/core/styles.css\";',\n );\n }\n }\n\n // Content-Security-Policy: is there a real one, and does it let Guardrail work?\n const enforced = h.get(\"content-security-policy\");\n const reportOnly = h.get(\"content-security-policy-report-only\");\n const policy = enforced ?? reportOnly;\n if (!policy) {\n warn(\n \"No Content-Security-Policy\",\n \"Nothing limits where scripts and styles can load from, so an injected script would run.\",\n \"Run: pnpm exec guardrail-doctor --csp (prints a starter report-only policy)\",\n );\n return;\n }\n const parsed = parseCsp(policy);\n const label = enforced ? \"\" : \" (report-only)\";\n if (!parsed[\"default-src\"] && !parsed[\"script-src\"]) {\n info(`Content-Security-Policy only sets ${Object.keys(parsed).join(\", \") || \"nothing\"}`, \"It doesn't restrict scripts. Run: pnpm exec guardrail-doctor --csp for a fuller starter.\");\n return;\n }\n const style = effectiveSources(parsed, \"style-src\");\n if (style && style.map((x) => x.toLowerCase()).includes(\"'unsafe-inline'\")) {\n info(`style-src allows 'unsafe-inline'${label}`, \"Guardrail no longer needs it — its chrome is a stylesheet. Remove it unless something else on the site does.\");\n } else {\n pass(`Policy${label} doesn't need 'unsafe-inline' for Guardrail's styles`);\n }\n if (ctx.url) {\n let supabase: string | undefined;\n try {\n supabase = new URL(ctx.url).origin;\n } catch {\n // reported under Environment\n }\n if (supabase && supabase !== new URL(appUrl).origin) {\n for (const [directive, why] of [\n [\"connect-src\", \"image uploads go straight from the browser to Supabase storage\"],\n [\"img-src\", \"uploaded images and the media picker's thumbnails load from Supabase storage\"],\n ] as const) {\n if (allowsOrigin(effectiveSources(parsed, directive), supabase)) pass(`${directive}${label} allows ${supabase}`);\n else warn(`${directive}${label} blocks ${supabase}`, `Needed because ${why}.`, `Add ${supabase} to ${directive}.`);\n }\n }\n }\n}\n\n/* ------------------------------------------------------------------ e2e --- */\n\n// A 1x1 transparent PNG.\nconst TINY_PNG = Buffer.from(\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==\",\n \"base64\",\n);\n\nasync function confirmE2E(ctx: Ctx, yes: boolean): Promise<boolean> {\n const host = new URL(ctx.url!).host;\n const local = isLocalHost(new URL(ctx.url!).hostname);\n console.log(\n `\\nThe end-to-end suite will create a throwaway user, a few rows and one test image on ${host},\\nexercise them as a signed-in editor would, then delete everything it made.\\n`,\n );\n if (local || yes) return true;\n if (!process.stdin.isTTY) {\n console.log(\"That's not a local database. Re-run with --yes to allow it.\");\n return false;\n }\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const answer = (await rl.question(`${host} is not a local database. Continue? [y/N] `)).trim().toLowerCase();\n rl.close();\n return answer === \"y\" || answer === \"yes\";\n}\n\nasync function runE2E(ctx: Ctx, sb: SupabaseModule): Promise<void> {\n group(\"End-to-end\");\n const url = ctx.url!;\n const admin = sb.createClient(url, ctx.serviceKey!, { auth: { persistSession: false, autoRefreshToken: false } });\n const user = sb.createClient(url, ctx.anonKey!, { auth: { persistSession: false, autoRefreshToken: false } });\n const anon = sb.createClient(url, ctx.anonKey!, { auth: { persistSession: false, autoRefreshToken: false } });\n const gr = (c: SupabaseClient) => c.schema(GR_SCHEMA);\n\n const tag = randomBytes(4).toString(\"hex\");\n const siteId = `doctor-${tag}`;\n const otherSite = `doctor-${tag}-other`;\n const email = `guardrail-doctor-${tag}@example.com`;\n const password = randomBytes(18).toString(\"base64url\");\n const objectPath = `${siteId}/doctor.png`;\n let userId: string | undefined;\n let uploaded = false;\n let storageChecked = false;\n\n // `why` explains a failure and is only shown when the check fails; `note` is\n // shown when it passes.\n const expect = (ok: boolean, title: string, why?: string, hint?: string, note?: string) =>\n ok ? pass(title, note) : fail(title, why, hint);\n\n try {\n // 1. A throwaway account, and a link the way an invite or reset would mint one\n const created = await admin.auth.admin.createUser({ email, password, email_confirm: true });\n if (created.error || !created.data.user) {\n fail(\"Create a test account\", created.error?.message ?? \"No user returned.\", \"Needs a valid service-role key and Email sign-in enabled.\");\n return;\n }\n userId = created.data.user.id;\n pass(\"Create a test account (service-role key works)\");\n\n const link = await admin.auth.admin.generateLink({ type: \"recovery\", email });\n expect(\n !link.error && Boolean(link.data?.properties?.hashed_token),\n \"Mint a password-reset link\",\n link.error?.message,\n \"Invites and resets need this to work; check Email sign-in is enabled.\",\n );\n\n // 2. Invite claim: a pending membership becomes active for the signed-in person\n const seeded = await gr(admin).from(GR_MEMBERS_TABLE).insert({ site_id: siteId, email, role: \"admin\", status: \"pending\" });\n if (seeded.error) {\n fail(\"Record a pending invitation\", seeded.error.message);\n return;\n }\n const signedIn = await user.auth.signInWithPassword({ email, password });\n if (signedIn.error) {\n fail(\"Sign in with a password\", signedIn.error.message);\n return;\n }\n pass(\"Sign in with a password\");\n\n const claim = await gr(user).rpc(\"gr_claim_invite\", { target_site: siteId });\n const after = await gr(user).from(GR_MEMBERS_TABLE).select(\"status,role\").eq(\"site_id\", siteId).eq(\"email\", email).maybeSingle();\n expect(\n !claim.error && after.data?.status === \"active\" && after.data?.role === \"admin\",\n \"Claim an invitation\",\n claim.error?.message ?? (after.data ? undefined : \"The membership row wasn't visible after claiming.\"),\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n );\n\n // 3. Content: an admin can write their own site, anyone can read it, nobody can write another site\n const write = await gr(user).from(\"gr_content\").insert({\n site_id: siteId,\n path_key: \"doctor.check\",\n type: \"text\",\n value: { type: \"text\", text: \"doctor\" },\n });\n expect(!write.error, \"Editor can save content to their site\", write.error?.message);\n\n const read = await gr(anon).from(\"gr_content\").select(\"path_key\").eq(\"site_id\", siteId).eq(\"path_key\", \"doctor.check\");\n expect(!read.error && (read.data?.length ?? 0) === 1, \"Visitors can read saved content\", read.error?.message);\n\n const cross = await gr(user).from(\"gr_content\").insert({\n site_id: otherSite,\n path_key: \"doctor.check\",\n type: \"text\",\n value: { type: \"text\", text: \"nope\" },\n });\n expect(Boolean(cross.error), \"Editor can't write to a site they don't belong to\", cross.error ? undefined : \"The write was allowed — row-level security is not protecting content.\");\n\n const anonWrite = await gr(anon).from(\"gr_content\").insert({\n site_id: siteId,\n path_key: \"doctor.anon\",\n type: \"text\",\n value: { type: \"text\", text: \"nope\" },\n });\n expect(Boolean(anonWrite.error), \"Visitors can't write content\", anonWrite.error ? undefined : \"An unauthenticated write was allowed.\");\n\n // 3b. History and one-step revert\n const histCount = async () => {\n const r = await gr(admin).from(\"gr_content_history\").select(\"id\", { count: \"exact\", head: true }).eq(\"site_id\", siteId).eq(\"path_key\", \"doctor.check\");\n return r.error ? -1 : (r.count ?? 0);\n };\n const setText = (text: string) =>\n gr(user).from(\"gr_content\").update({ value: { type: \"text\", text }, updated_at: new Date().toISOString() }).eq(\"site_id\", siteId).eq(\"path_key\", \"doctor.check\");\n const current = async () => {\n const r = await gr(user).from(\"gr_content\").select(\"value\").eq(\"site_id\", siteId).eq(\"path_key\", \"doctor.check\").maybeSingle();\n return (r.data?.value as { text?: string } | null)?.text ?? null;\n };\n const revertRpc = async (site = siteId) => {\n const r = await gr(user).rpc(\"gr_revert_content\", { p_site: site, p_key: \"doctor.check\" });\n return { error: r.error, data: r.data as { action?: string; value?: { text?: string } } | null };\n };\n\n await setText(\"v1\");\n await setText(\"v1\"); // saving the same value again must not add history\n const afterTwo = await histCount();\n expect(afterTwo === 1, \"A save keeps the old value in history, and re-saving the same value doesn't\", `Expected 1 history entry, found ${afterTwo}.`, \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n\n const info1 = await gr(user).rpc(\"gr_revert_info\", { p_site: siteId, p_key: \"doctor.check\" });\n expect((info1.data as { kind?: string } | null)?.kind === \"previous\", \"The editor is told a previous version exists\", info1.error?.message);\n\n const peek = await gr(anon).from(\"gr_content_history\").select(\"id\").eq(\"site_id\", siteId);\n expect(Boolean(peek.error) || (peek.data?.length ?? 0) === 0, \"Visitors can't read content history\", peek.error ? undefined : \"An unauthenticated read returned history rows.\");\n\n const foreign = await revertRpc(otherSite);\n expect(Boolean(foreign.error), \"An editor can't revert another site's content\", foreign.error ? undefined : \"The revert was allowed.\");\n\n const r1 = await revertRpc();\n expect(!r1.error && r1.data?.action === \"restored\" && (await current()) === \"doctor\" && (await histCount()) === 0, \"Revert steps back one version\", r1.error?.message ?? `Got ${JSON.stringify(r1.data)}.`, \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n\n const r2 = await revertRpc();\n expect(!r2.error && r2.data?.action === \"reset\" && (await current()) === null, \"Revert with nothing earlier returns the field to its default\", r2.error?.message ?? `Got ${JSON.stringify(r2.data)}.`);\n\n // Retention: 22 edits leave only the newest 20 prior versions.\n await gr(user).from(\"gr_content\").insert({ site_id: siteId, path_key: \"doctor.check\", type: \"text\", value: { type: \"text\", text: \"n0\" } });\n for (let i = 1; i <= 22; i++) await setText(`n${i}`);\n const kept = await histCount();\n expect(kept === 20, \"History keeps the newest 20 versions of a field\", `Expected 20, found ${kept}.`, \"Apply the latest migrations: pnpm exec guardrail-init-db --push\");\n\n // 4. Admin guard: the last admin can't be demoted or removed through the API\n const demote = await gr(user).from(GR_MEMBERS_TABLE).update({ role: \"editor\" }).eq(\"site_id\", siteId).eq(\"email\", email);\n expect(\n Boolean(demote.error),\n \"The last admin can't be demoted\",\n demote.error ? undefined : \"The demotion succeeded — the admin guard trigger isn't installed.\",\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n );\n // Put the admin back first, so this check doesn't depend on the last one\n // having been blocked.\n await gr(admin).from(GR_MEMBERS_TABLE).upsert(\n { site_id: siteId, email, user_id: userId, role: \"admin\", status: \"active\" },\n { onConflict: \"site_id,email\" },\n );\n const remove = await gr(user).from(GR_MEMBERS_TABLE).delete().eq(\"site_id\", siteId).eq(\"email\", email);\n const stillThere = await gr(admin).from(GR_MEMBERS_TABLE).select(\"email\").eq(\"site_id\", siteId).eq(\"email\", email);\n expect(\n Boolean(remove.error) || (stillThere.data?.length ?? 0) === 1,\n \"The last admin can't be removed\",\n remove.error ? undefined : \"The row was deleted — the admin guard trigger isn't installed.\",\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n );\n\n // Whatever the last two checks did, the storage checks below need the admin back.\n await gr(admin).from(GR_MEMBERS_TABLE).upsert(\n { site_id: siteId, email, user_id: userId, role: \"admin\", status: \"active\" },\n { onConflict: \"site_id,email\" },\n );\n\n // 5. Rate limiting counts and then refuses\n const hits: boolean[] = [];\n let rateErr: string | undefined;\n for (let i = 0; i < 3; i++) {\n const r = await gr(admin).rpc(\"gr_rate_hit\", { p_name: \"doctor:e2e\", p_key: siteId, p_limit: 2, p_window_seconds: 60 });\n if (r.error) {\n rateErr = r.error.message;\n break;\n }\n const row = Array.isArray(r.data) ? r.data[0] : r.data;\n hits.push(Boolean(row?.allowed));\n }\n expect(\n !rateErr && hits.join() === \"true,true,false\",\n \"Rate limiter allows up to the limit, then refuses\",\n rateErr ?? (hits.length ? `Got ${hits.join(\", \")} instead of true, true, false` : undefined),\n \"Apply the latest migrations: pnpm exec guardrail-init-db --push\",\n `Allowed ${hits.filter(Boolean).length} of ${hits.length} attempts at a limit of 2`,\n );\n\n // 6. Storage, when the site uses it\n const buckets = await admin.storage.listBuckets();\n const mediaBucket = buckets.data?.find((b) => b.id === \"gr-media\");\n if (!mediaBucket) {\n skip(\"Storage\", 'No \"gr-media\" bucket — skipped (this site may not use editable images).');\n } else {\n storageChecked = true;\n const up = await user.storage.from(\"gr-media\").upload(objectPath, TINY_PNG, { contentType: \"image/png\" });\n uploaded = !up.error;\n expect(!up.error, \"Editor can upload an image to their site's folder\", up.error?.message, \"Check the storage policies from the migration are applied.\");\n if (uploaded) {\n const { data } = user.storage.from(\"gr-media\").getPublicUrl(objectPath);\n const res = await fetch(data.publicUrl).catch(() => null);\n expect(Boolean(res?.ok), \"Uploaded images are publicly readable\", res ? `HTTP ${res.status}` : \"Request failed.\", \"next/image also needs the storage host in remotePatterns.\", `HTTP ${res?.status}`);\n }\n const elsewhere = await user.storage.from(\"gr-media\").upload(`${otherSite}/doctor.png`, TINY_PNG, { contentType: \"image/png\" });\n if (!elsewhere.error) await admin.storage.from(\"gr-media\").remove([`${otherSite}/doctor.png`]);\n expect(Boolean(elsewhere.error), \"Editor can't upload into another site's folder\", elsewhere.error ? undefined : \"The upload was allowed.\");\n }\n } catch (e) {\n fail(\"The end-to-end run stopped unexpectedly\", e instanceof Error ? e.message : String(e));\n } finally {\n // Always clean up, and say so if something is left behind.\n const leftovers: string[] = [];\n const attempt = async (what: string, run: () => PromiseLike<{ error: { message: string } | null }>) => {\n try {\n const { error } = await run();\n if (error) leftovers.push(`${what} (${error.message})`);\n } catch (e) {\n leftovers.push(`${what} (${e instanceof Error ? e.message : String(e)})`);\n }\n };\n if (storageChecked && uploaded) await attempt(`storage object ${objectPath}`, () => admin.storage.from(\"gr-media\").remove([objectPath]));\n await attempt(\"content rows\", () => gr(admin).from(\"gr_content\").delete().in(\"site_id\", [siteId, otherSite]));\n await attempt(\"membership rows\", () => gr(admin).from(GR_MEMBERS_TABLE).delete().eq(\"site_id\", siteId));\n await attempt(\"rate-limit row\", () => gr(admin).from(\"gr_rate_limits\").delete().eq(\"bucket\", `doctor:e2e|${siteId}`));\n if (userId) await attempt(`test account ${email}`, () => admin.auth.admin.deleteUser(userId!));\n if (leftovers.length) {\n warn(\"Cleanup left something behind\", leftovers.join(\"; \"), `Everything it made is tagged \"${siteId}\" / ${email}; delete it by hand.`);\n } else {\n pass(\"Cleaned up everything the test created\");\n }\n }\n}\n\n/* --------------------------------------------------------------- output --- */\n\nconst COLOR = process.stdout.isTTY && !process.env.NO_COLOR;\nconst paint = (code: number, s: string) => (COLOR ? `\\u001b[${code}m${s}\\u001b[0m` : s);\nconst ICON: Record<Status, string> = {\n pass: paint(32, \"✓\"),\n warn: paint(33, \"!\"),\n fail: paint(31, \"✗\"),\n skip: paint(90, \"–\"),\n info: paint(36, \"i\"),\n};\n\nfunction printReport(): void {\n let last = \"\";\n for (const r of results) {\n if (r.group !== last) {\n console.log(`\\n${paint(1, r.group)}`);\n last = r.group;\n }\n console.log(` ${ICON[r.status]} ${r.title}`);\n if (r.detail) console.log(` ${paint(90, r.detail)}`);\n if (r.hint && (r.status === \"fail\" || r.status === \"warn\")) console.log(` ${paint(90, \"→ \" + r.hint)}`);\n }\n const count = (s: Status) => results.filter((r) => r.status === s).length;\n const parts = [`${count(\"pass\")} passed`, count(\"warn\") ? paint(33, `${count(\"warn\")} to review`) : null, count(\"fail\") ? paint(31, `${count(\"fail\")} failed`) : null].filter(Boolean);\n console.log(`\\n${parts.join(\", \")}\\n`);\n}\n\n/* ----------------------------------------------------------------- main --- */\n\nasync function main(): Promise<number> {\n const { flags, values, unknown } = parseFlags(process.argv.slice(2));\n if (flags.has(\"help\") || flags.has(\"h\")) {\n console.log(USAGE);\n return 0;\n }\n if (unknown.length) {\n console.error(`Unknown option${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}\\n\\n${USAGE}`);\n return 2;\n }\n\n const cwd = process.cwd();\n const fileEnv = readEnvFiles(cwd);\n const env: Record<string, string> = { ...fileEnv };\n for (const [k, v] of Object.entries(process.env)) if (v) env[k] = v;\n\n const ctx: Ctx = {\n cwd,\n env,\n production: flags.has(\"production\"),\n offline: flags.has(\"offline\"),\n url: env.SUPABASE_URL,\n anonKey: env.SUPABASE_ANON_KEY,\n serviceKey: env.SUPABASE_SERVICE_ROLE_KEY ?? env.SERVICE_KEY,\n };\n\n if (flags.has(\"csp\")) {\n printStarterCsp(ctx);\n return 0;\n }\n\n const json = flags.has(\"json\");\n if (!json) console.log(\"Guardrail doctor\");\n\n if (flags.has(\"fix\")) {\n fixMigrations(cwd);\n fixEnvNotes(cwd);\n fixCacheComponents(cwd);\n const headers = fixHeaders(cwd);\n if (headers === \"manual\") {\n applied.push(\"next.config has a shape I can't patch safely — add the headers from the README's \\\"Before you deploy\\\" yourself\");\n }\n if (flags.has(\"push\")) {\n if (!commandExists(\"supabase\")) {\n applied.push(\"Supabase CLI not found — apply the migrations yourself (`supabase db push`, or paste them into the SQL editor)\");\n } else {\n // A local Docker stack takes pending migrations with `migration up`;\n // `db push` is for a linked hosted project.\n const local = ctx.url ? isLocalHost(new URL(ctx.url).hostname) : false;\n const cmd = local ? [\"migration\", \"up\", \"--local\"] : [\"db\", \"push\"];\n const res = spawnSync(\"supabase\", cmd, { stdio: json ? \"ignore\" : \"inherit\" });\n applied.push(\n res.status === 0\n ? `Applied migrations with \\`supabase ${cmd.slice(0, 2).join(\" \")}\\``\n : local\n ? \"`supabase migration up` failed — is the local stack running? (`supabase start`)\"\n : \"`supabase db push` failed — if the project isn't linked yet, run `supabase link` first\",\n );\n }\n }\n if (!json) {\n console.log(`\\n${paint(1, \"Fixes\")}`);\n if (applied.length === 0) console.log(\" Nothing to fix locally.\");\n for (const a of applied) console.log(` • ${a}`);\n }\n } else if (flags.has(\"push\")) {\n console.error(\"--push only applies together with --fix.\");\n return 2;\n }\n\n await checkProject(ctx);\n checkEnvironment(ctx);\n checkFiles(ctx);\n\n const sb = await loadSupabase();\n const dbReachable = await checkDatabase(ctx, sb);\n\n if (values[\"app-url\"]) await checkLiveSite(values[\"app-url\"], ctx);\n\n if (flags.has(\"e2e\")) {\n group(\"End-to-end\");\n if (!dbReachable || !sb) {\n skip(\"Skipped\", \"The database checks above need to pass first.\");\n } else if (!ctx.serviceKey) {\n skip(\"Skipped\", \"The end-to-end suite needs the service-role key to create and remove its test account.\");\n } else if (await confirmE2E(ctx, flags.has(\"yes\"))) {\n await runE2E(ctx, sb);\n } else {\n skip(\"Skipped\", \"Not confirmed.\");\n }\n }\n\n if (json) {\n console.log(JSON.stringify({ applied, results }, null, 2));\n } else {\n printReport();\n if (results.some((r) => r.status === \"fail\")) {\n console.log(\"Fix the failures above, then run guardrail-doctor again.\\n\");\n }\n }\n\n const failed = results.some((r) => r.status === \"fail\");\n const warned = results.some((r) => r.status === \"warn\");\n return failed || (flags.has(\"strict\") && warned) ? 1 : 0;\n}\n\nmain().then(\n (code) => process.exit(code),\n (err) => {\n console.error(err);\n process.exit(1);\n },\n);\n"],"mappings":"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","names":["require","error"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { OverrideRow, GRMember, GRRole, GRAsset, GRRepeaterItem } from './types.js';
|
|
2
|
+
import { OverrideRow, OverrideValue, GRMember, GRRole, GRAsset, GRRepeaterItem } from './types.js';
|
|
3
3
|
|
|
4
4
|
interface GRStorageRow {
|
|
5
5
|
pathKey: string;
|
|
@@ -30,6 +30,18 @@ interface GRDBAdapter {
|
|
|
30
30
|
* row is still removed, just not its old field values.
|
|
31
31
|
*/
|
|
32
32
|
deleteOverrides?: (pathKeyPrefix: string, siteId: string) => Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* What a revert would do for this field right now, so the editor can offer —
|
|
35
|
+
* and label — it. Optional: an adapter without history simply never shows the
|
|
36
|
+
* revert control.
|
|
37
|
+
*/
|
|
38
|
+
getRevertInfo?: (pathKey: string, siteId: string) => Promise<GRRevertInfo>;
|
|
39
|
+
/**
|
|
40
|
+
* One step back for a field: restore its previous saved value, or, with none
|
|
41
|
+
* left, remove the saved value so the default written in the page shows.
|
|
42
|
+
* Optional, like getRevertInfo.
|
|
43
|
+
*/
|
|
44
|
+
revertOverride?: (pathKey: string, siteId: string) => Promise<GRRevertResult>;
|
|
33
45
|
/**
|
|
34
46
|
* List a site's rows without naming keys — what makes <GRail> usable inside
|
|
35
47
|
* Client Components, which cannot await a per-field query. One round trip.
|
|
@@ -38,16 +50,40 @@ interface GRDBAdapter {
|
|
|
38
50
|
* client-rendered fields just show their JSX fallback. `limit` is a ceiling,
|
|
39
51
|
* not a page size — return `truncated: true` if rows beyond it existed, so
|
|
40
52
|
* the caller knows the list is partial. `prefix`, when given, restricts to
|
|
41
|
-
* keys starting with any of those strings
|
|
53
|
+
* keys starting with any of those strings; `exclude` leaves out keys starting
|
|
54
|
+
* with any of them (page settings, which would otherwise crowd the preload).
|
|
55
|
+
* An adapter that ignores `exclude` still works — it just preloads more.
|
|
42
56
|
*/
|
|
43
57
|
listOverrides?: (siteId: string, opts: {
|
|
44
58
|
prefix?: string[];
|
|
45
59
|
limit: number;
|
|
60
|
+
exclude?: string[];
|
|
46
61
|
}) => Promise<{
|
|
47
62
|
rows: OverrideRow[];
|
|
48
63
|
truncated: boolean;
|
|
49
64
|
}>;
|
|
50
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* What reverting a field would do: "previous" (an earlier saved version exists,
|
|
68
|
+
* saved at `at`), "default" (no earlier version — it returns to the default in
|
|
69
|
+
* the page), or "none" (nothing saved, nothing to revert).
|
|
70
|
+
*/
|
|
71
|
+
interface GRRevertInfo {
|
|
72
|
+
kind: "previous" | "default" | "none";
|
|
73
|
+
/** ISO timestamp of the version a "previous" revert goes back to. */
|
|
74
|
+
at?: string;
|
|
75
|
+
}
|
|
76
|
+
type GRRevertResult = {
|
|
77
|
+
success: true;
|
|
78
|
+
action: "restored";
|
|
79
|
+
value: OverrideValue;
|
|
80
|
+
} | {
|
|
81
|
+
success: true;
|
|
82
|
+
action: "reset" | "none";
|
|
83
|
+
} | {
|
|
84
|
+
success: false;
|
|
85
|
+
error: string;
|
|
86
|
+
};
|
|
51
87
|
/** How much content to preload for <GRail>s inside Client Components. */
|
|
52
88
|
interface GRPrefetch {
|
|
53
89
|
/** Only preload keys starting with one of these (e.g. `["home.", "header."]`). */
|
|
@@ -423,4 +459,4 @@ interface GRConfig {
|
|
|
423
459
|
prefetch?: false | GRPrefetch;
|
|
424
460
|
}
|
|
425
461
|
|
|
426
|
-
export type { AuthRedirectParams, AuthResult, GRAuthAdapter, GRConfig, GRDBAdapter, GRInviteEvent, GRMembershipAdapter, GRPasswordResetEvent, GRPrefetch, GRRateLimitHit, GRRateLimitName, GRRateLimiter, GRRepeaterAdapter, GRSession, GRStorageAdapter, GRStorageRow, GRUploadTarget };
|
|
462
|
+
export type { AuthRedirectParams, AuthResult, GRAuthAdapter, GRConfig, GRDBAdapter, GRInviteEvent, GRMembershipAdapter, GRPasswordResetEvent, GRPrefetch, GRRateLimitHit, GRRateLimitName, GRRateLimiter, GRRepeaterAdapter, GRRevertInfo, GRRevertResult, GRSession, GRStorageAdapter, GRStorageRow, GRUploadTarget };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { GRRevertInfo } from './adapter-types.js';
|
|
2
|
+
import 'react';
|
|
3
|
+
import './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Page settings — the title, description, social image and "hide from search
|
|
7
|
+
* engines" flag an editor can set for a page, kept as ordinary content rows so
|
|
8
|
+
* they get the same storage, permissions and revert history as everything else.
|
|
9
|
+
*
|
|
10
|
+
* Pure and client-safe: the path/key rules here are shared by the drawer (which
|
|
11
|
+
* keys off the browser's current path) and guardrailMetadata() (which is handed
|
|
12
|
+
* the path by the page), and the two must agree exactly.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Reserved namespace for page settings. Kept out of the client preload — one row per page would crowd it. */
|
|
16
|
+
declare const SEO_PREFIX = "seo.";
|
|
17
|
+
type SeoField = "title" | "description" | "image" | "noindex";
|
|
18
|
+
declare const SEO_FIELDS: readonly SeoField[];
|
|
19
|
+
/** Server-side caps. The drawer's counters are guidance; these are the limit. */
|
|
20
|
+
declare const SEO_LIMITS: {
|
|
21
|
+
readonly title: 300;
|
|
22
|
+
readonly description: 1000;
|
|
23
|
+
readonly path: 150;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* One canonical form per page: a leading slash, no query string or hash, no
|
|
27
|
+
* doubled or trailing slashes (except the root). Null if it isn't a usable path.
|
|
28
|
+
*/
|
|
29
|
+
declare function normalizePagePath(raw: unknown): string | null;
|
|
30
|
+
/** The content key for one setting of one page, e.g. `seo./about.title`. */
|
|
31
|
+
declare function seoKey(path: string, field: SeoField): string;
|
|
32
|
+
interface GRPageImage {
|
|
33
|
+
src: string;
|
|
34
|
+
alt: string;
|
|
35
|
+
width?: number;
|
|
36
|
+
height?: number;
|
|
37
|
+
}
|
|
38
|
+
/** What the drawer loads: the saved values, which of them are saved, and what a revert would do to each. */
|
|
39
|
+
interface GRPageSettings {
|
|
40
|
+
path: string;
|
|
41
|
+
title: string;
|
|
42
|
+
description: string;
|
|
43
|
+
image: GRPageImage;
|
|
44
|
+
noindex: boolean;
|
|
45
|
+
/** Which settings have a saved value (as opposed to using the page's own default). */
|
|
46
|
+
saved: Record<SeoField, boolean>;
|
|
47
|
+
revert: Record<SeoField, GRRevertInfo>;
|
|
48
|
+
}
|
|
49
|
+
/** Only the settings that changed. `image: null` clears the saved image. */
|
|
50
|
+
interface GRPageSettingsPatch {
|
|
51
|
+
title?: string;
|
|
52
|
+
description?: string;
|
|
53
|
+
image?: GRPageImage | null;
|
|
54
|
+
noindex?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** The page's own defaults, used wherever an editor hasn't saved a value. */
|
|
57
|
+
interface GRMetadataFallback {
|
|
58
|
+
title?: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
image?: string | {
|
|
61
|
+
url: string;
|
|
62
|
+
width?: number;
|
|
63
|
+
height?: number;
|
|
64
|
+
alt?: string;
|
|
65
|
+
};
|
|
66
|
+
noindex?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { type GRMetadataFallback, type GRPageImage, type GRPageSettings, type GRPageSettingsPatch, SEO_FIELDS, SEO_LIMITS, SEO_PREFIX, type SeoField, normalizePagePath, seoKey };
|
package/dist/core/seo.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const SEO_PREFIX = "seo.";
|
|
2
|
+
const SEO_FIELDS = ["title", "description", "image", "noindex"];
|
|
3
|
+
const SEO_LIMITS = { title: 300, description: 1e3, path: 150 };
|
|
4
|
+
function normalizePagePath(raw) {
|
|
5
|
+
if (typeof raw !== "string") return null;
|
|
6
|
+
let path = raw.trim().split("#")[0].split("?")[0];
|
|
7
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
8
|
+
path = path.replace(/\/{2,}/g, "/");
|
|
9
|
+
if (path.length > 1) path = path.replace(/\/+$/, "");
|
|
10
|
+
if (path.length > SEO_LIMITS.path || /[\u0000-\u001f\u007f]/.test(path)) return null;
|
|
11
|
+
return path;
|
|
12
|
+
}
|
|
13
|
+
function seoKey(path, field) {
|
|
14
|
+
return `${SEO_PREFIX}${path}.${field}`;
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
SEO_FIELDS,
|
|
18
|
+
SEO_LIMITS,
|
|
19
|
+
SEO_PREFIX,
|
|
20
|
+
normalizePagePath,
|
|
21
|
+
seoKey
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=seo.js.map
|