@notionx/core 0.1.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.
Files changed (208) hide show
  1. package/dist/admin/index.d.ts +137 -0
  2. package/dist/admin/index.js +206 -0
  3. package/dist/admin/index.js.map +1 -0
  4. package/dist/admin/pages/index.d.ts +324 -0
  5. package/dist/admin/pages/index.js +827 -0
  6. package/dist/admin/pages/index.js.map +1 -0
  7. package/dist/auth/auth-pages/forgot-password.d.ts +20 -0
  8. package/dist/auth/auth-pages/forgot-password.js +70 -0
  9. package/dist/auth/auth-pages/forgot-password.js.map +1 -0
  10. package/dist/auth/auth-pages/index.d.ts +6 -0
  11. package/dist/auth/auth-pages/index.js +342 -0
  12. package/dist/auth/auth-pages/index.js.map +1 -0
  13. package/dist/auth/auth-pages/login.d.ts +30 -0
  14. package/dist/auth/auth-pages/login.js +125 -0
  15. package/dist/auth/auth-pages/login.js.map +1 -0
  16. package/dist/auth/auth-pages/register.d.ts +17 -0
  17. package/dist/auth/auth-pages/register.js +81 -0
  18. package/dist/auth/auth-pages/register.js.map +1 -0
  19. package/dist/auth/auth-pages/reset-password.d.ts +18 -0
  20. package/dist/auth/auth-pages/reset-password.js +72 -0
  21. package/dist/auth/auth-pages/reset-password.js.map +1 -0
  22. package/dist/auth/index.d.ts +72 -0
  23. package/dist/auth/index.js +1011 -0
  24. package/dist/auth/index.js.map +1 -0
  25. package/dist/auth/passwords.d.ts +6 -0
  26. package/dist/auth/passwords.js +79 -0
  27. package/dist/auth/passwords.js.map +1 -0
  28. package/dist/auth/rate-limit.d.ts +28 -0
  29. package/dist/auth/rate-limit.js +245 -0
  30. package/dist/auth/rate-limit.js.map +1 -0
  31. package/dist/auth/routes/google-callback.d.ts +6 -0
  32. package/dist/auth/routes/google-callback.js +404 -0
  33. package/dist/auth/routes/google-callback.js.map +1 -0
  34. package/dist/auth/routes/google.d.ts +6 -0
  35. package/dist/auth/routes/google.js +250 -0
  36. package/dist/auth/routes/google.js.map +1 -0
  37. package/dist/auth/routes/index.d.ts +22 -0
  38. package/dist/auth/routes/index.js +619 -0
  39. package/dist/auth/routes/index.js.map +1 -0
  40. package/dist/auth/routes/verify-email.d.ts +6 -0
  41. package/dist/auth/routes/verify-email.js +317 -0
  42. package/dist/auth/routes/verify-email.js.map +1 -0
  43. package/dist/auth/routes/viewer.d.ts +6 -0
  44. package/dist/auth/routes/viewer.js +372 -0
  45. package/dist/auth/routes/viewer.js.map +1 -0
  46. package/dist/auth/session.d.ts +9 -0
  47. package/dist/auth/session.js +1 -0
  48. package/dist/auth/session.js.map +1 -0
  49. package/dist/auth/turnstile.d.ts +20 -0
  50. package/dist/auth/turnstile.js +301 -0
  51. package/dist/auth/turnstile.js.map +1 -0
  52. package/dist/auth/user-session.d.ts +42 -0
  53. package/dist/auth/user-session.js +419 -0
  54. package/dist/auth/user-session.js.map +1 -0
  55. package/dist/auth/users.d.ts +112 -0
  56. package/dist/auth/users.js +558 -0
  57. package/dist/auth/users.js.map +1 -0
  58. package/dist/bootstrap-CN2g76M6.d.ts +67 -0
  59. package/dist/cache/index.d.ts +6 -0
  60. package/dist/cache/index.js +47 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/content/admin-summary.d.ts +24 -0
  63. package/dist/content/admin-summary.js +36 -0
  64. package/dist/content/admin-summary.js.map +1 -0
  65. package/dist/content/index.d.ts +9 -0
  66. package/dist/content/index.js +473 -0
  67. package/dist/content/index.js.map +1 -0
  68. package/dist/content/models.d.ts +69 -0
  69. package/dist/content/models.js +24 -0
  70. package/dist/content/models.js.map +1 -0
  71. package/dist/content/prewarm.d.ts +28 -0
  72. package/dist/content/prewarm.js +56 -0
  73. package/dist/content/prewarm.js.map +1 -0
  74. package/dist/content/revalidate.d.ts +37 -0
  75. package/dist/content/revalidate.js +170 -0
  76. package/dist/content/revalidate.js.map +1 -0
  77. package/dist/content/search-index.d.ts +54 -0
  78. package/dist/content/search-index.js +172 -0
  79. package/dist/content/search-index.js.map +1 -0
  80. package/dist/content/search.d.ts +8 -0
  81. package/dist/content/search.js +57 -0
  82. package/dist/content/search.js.map +1 -0
  83. package/dist/doctor/cli.d.ts +1 -0
  84. package/dist/doctor/cli.js +360 -0
  85. package/dist/doctor/cli.js.map +1 -0
  86. package/dist/doctor/index.d.ts +139 -0
  87. package/dist/doctor/index.js +289 -0
  88. package/dist/doctor/index.js.map +1 -0
  89. package/dist/email/index.d.ts +38 -0
  90. package/dist/email/index.js +126 -0
  91. package/dist/email/index.js.map +1 -0
  92. package/dist/env-C5qu-0R-.d.ts +35 -0
  93. package/dist/hooks/index.d.ts +2 -0
  94. package/dist/hooks/index.js +1 -0
  95. package/dist/hooks/index.js.map +1 -0
  96. package/dist/i18n/index.d.ts +26 -0
  97. package/dist/i18n/index.js +73 -0
  98. package/dist/i18n/index.js.map +1 -0
  99. package/dist/index.d.ts +8 -0
  100. package/dist/index.js +1281 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/internal/admin/index.d.ts +75 -0
  103. package/dist/internal/admin/index.js +365 -0
  104. package/dist/internal/admin/index.js.map +1 -0
  105. package/dist/media/index.d.ts +24 -0
  106. package/dist/media/index.js +86 -0
  107. package/dist/media/index.js.map +1 -0
  108. package/dist/media/routes/index.d.ts +1 -0
  109. package/dist/media/routes/index.js +585 -0
  110. package/dist/media/routes/index.js.map +1 -0
  111. package/dist/media/routes/notion-media.d.ts +19 -0
  112. package/dist/media/routes/notion-media.js +588 -0
  113. package/dist/media/routes/notion-media.js.map +1 -0
  114. package/dist/middleware.d.ts +95 -0
  115. package/dist/middleware.js +79 -0
  116. package/dist/middleware.js.map +1 -0
  117. package/dist/notion/block-text.d.ts +5 -0
  118. package/dist/notion/block-text.js +37 -0
  119. package/dist/notion/block-text.js.map +1 -0
  120. package/dist/notion/blocks.d.ts +24 -0
  121. package/dist/notion/blocks.js +46 -0
  122. package/dist/notion/blocks.js.map +1 -0
  123. package/dist/notion/client.d.ts +7 -0
  124. package/dist/notion/client.js +13 -0
  125. package/dist/notion/client.js.map +1 -0
  126. package/dist/notion/config.d.ts +25 -0
  127. package/dist/notion/config.js +147 -0
  128. package/dist/notion/config.js.map +1 -0
  129. package/dist/notion/content-cache.d.ts +45 -0
  130. package/dist/notion/content-cache.js +166 -0
  131. package/dist/notion/content-cache.js.map +1 -0
  132. package/dist/notion/generic-source.d.ts +61 -0
  133. package/dist/notion/generic-source.js +408 -0
  134. package/dist/notion/generic-source.js.map +1 -0
  135. package/dist/notion/index.d.ts +13 -0
  136. package/dist/notion/index.js +1278 -0
  137. package/dist/notion/index.js.map +1 -0
  138. package/dist/notion/mappers.d.ts +1 -0
  139. package/dist/notion/mappers.js +152 -0
  140. package/dist/notion/mappers.js.map +1 -0
  141. package/dist/notion/media.d.ts +22 -0
  142. package/dist/notion/media.js +209 -0
  143. package/dist/notion/media.js.map +1 -0
  144. package/dist/notion/property-mappers.d.ts +24 -0
  145. package/dist/notion/property-mappers.js +152 -0
  146. package/dist/notion/property-mappers.js.map +1 -0
  147. package/dist/notion/routes/index.d.ts +8 -0
  148. package/dist/notion/routes/index.js +428 -0
  149. package/dist/notion/routes/index.js.map +1 -0
  150. package/dist/notion/routes/webhook.d.ts +98 -0
  151. package/dist/notion/routes/webhook.js +428 -0
  152. package/dist/notion/routes/webhook.js.map +1 -0
  153. package/dist/notion/types.d.ts +152 -0
  154. package/dist/notion/types.js +1 -0
  155. package/dist/notion/types.js.map +1 -0
  156. package/dist/notion/webhook.d.ts +83 -0
  157. package/dist/notion/webhook.js +490 -0
  158. package/dist/notion/webhook.js.map +1 -0
  159. package/dist/platform/capabilities.d.ts +34 -0
  160. package/dist/platform/capabilities.js +42 -0
  161. package/dist/platform/capabilities.js.map +1 -0
  162. package/dist/platform/current.d.ts +13 -0
  163. package/dist/platform/current.js +181 -0
  164. package/dist/platform/current.js.map +1 -0
  165. package/dist/platform/index.d.ts +5 -0
  166. package/dist/platform/index.js +269 -0
  167. package/dist/platform/index.js.map +1 -0
  168. package/dist/platform/runtime.d.ts +118 -0
  169. package/dist/platform/runtime.js +160 -0
  170. package/dist/platform/runtime.js.map +1 -0
  171. package/dist/platform/selection.d.ts +10 -0
  172. package/dist/platform/selection.js +22 -0
  173. package/dist/platform/selection.js.map +1 -0
  174. package/dist/storage/index.d.ts +17 -0
  175. package/dist/storage/index.js +218 -0
  176. package/dist/storage/index.js.map +1 -0
  177. package/dist/storage/routes/cdn.d.ts +19 -0
  178. package/dist/storage/routes/cdn.js +289 -0
  179. package/dist/storage/routes/cdn.js.map +1 -0
  180. package/dist/storage/routes/files.d.ts +27 -0
  181. package/dist/storage/routes/files.js +216 -0
  182. package/dist/storage/routes/files.js.map +1 -0
  183. package/dist/storage/routes/index.d.ts +2 -0
  184. package/dist/storage/routes/index.js +352 -0
  185. package/dist/storage/routes/index.js.map +1 -0
  186. package/dist/types-BsAcZSNX.d.ts +94 -0
  187. package/dist/types.d.ts +78 -0
  188. package/dist/types.js +1 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/index.d.ts +18 -0
  191. package/dist/util/index.js +48 -0
  192. package/dist/util/index.js.map +1 -0
  193. package/dist/worker/index.d.ts +6 -0
  194. package/dist/worker/index.js +1026 -0
  195. package/dist/worker/index.js.map +1 -0
  196. package/dist/worker/routes/content-prewarm.d.ts +34 -0
  197. package/dist/worker/routes/content-prewarm.js +38 -0
  198. package/dist/worker/routes/content-prewarm.js.map +1 -0
  199. package/dist/worker/routes/content-revalidate.d.ts +81 -0
  200. package/dist/worker/routes/content-revalidate.js +64 -0
  201. package/dist/worker/routes/content-revalidate.js.map +1 -0
  202. package/dist/worker/routes/health.d.ts +14 -0
  203. package/dist/worker/routes/health.js +278 -0
  204. package/dist/worker/routes/health.js.map +1 -0
  205. package/dist/worker/routes/index.d.ts +6 -0
  206. package/dist/worker/routes/index.js +373 -0
  207. package/dist/worker/routes/index.js.map +1 -0
  208. package/package.json +124 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/doctor/cli.ts","../../src/platform/capabilities.ts","../../src/platform/selection.ts","../../src/doctor/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n// Foundation doctor CLI.\n// Reads wrangler.jsonc and .env/.dev.vars from the current working\n// directory, builds a report via the package's doctor module, and\n// prints it to stdout. Exits with status 1 when required configuration\n// is missing.\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n} from \"./index\";\n\nconst projectRoot = process.cwd();\n\nfunction parseArgs(argv: string[]) {\n const result = {\n json: false,\n };\n\n for (const arg of argv) {\n if (arg === \"--json\") {\n result.json = true;\n continue;\n }\n throw new Error(`Unknown argument: ${arg}`);\n }\n\n return result;\n}\n\nfunction stripJsonComments(source: string) {\n let output = \"\";\n let inString = false;\n let escaped = false;\n let lineComment = false;\n let blockComment = false;\n\n for (let index = 0; index < source.length; index += 1) {\n const char = source[index];\n const next = source[index + 1];\n\n if (lineComment) {\n if (char === \"\\n\") {\n lineComment = false;\n output += char;\n }\n continue;\n }\n\n if (blockComment) {\n if (char === \"*\" && next === \"/\") {\n blockComment = false;\n index += 1;\n }\n continue;\n }\n\n if (inString) {\n output += char;\n if (escaped) {\n escaped = false;\n } else if (char === \"\\\\\") {\n escaped = true;\n } else if (char === \"\\\"\") {\n inString = false;\n }\n continue;\n }\n\n if (char === \"\\\"\") {\n inString = true;\n output += char;\n continue;\n }\n\n if (char === \"/\" && next === \"/\") {\n lineComment = true;\n index += 1;\n continue;\n }\n\n if (char === \"/\" && next === \"*\") {\n blockComment = true;\n index += 1;\n continue;\n }\n\n output += char;\n }\n\n return output;\n}\n\nfunction readJsonc(filePath: string) {\n if (!fs.existsSync(filePath)) return null;\n return JSON.parse(stripJsonComments(fs.readFileSync(filePath, \"utf8\")));\n}\n\nfunction readDotEnvFile(filePath: string) {\n if (!fs.existsSync(filePath)) return {};\n const env: Record<string, string> = {};\n for (const line of fs.readFileSync(filePath, \"utf8\").split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const equalIndex = trimmed.indexOf(\"=\");\n if (equalIndex === -1) continue;\n\n const name = trimmed.slice(0, equalIndex).trim();\n let value = trimmed.slice(equalIndex + 1).trim();\n if (\n (value.startsWith(\"\\\"\") && value.endsWith(\"\\\"\")) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n env[name] = value;\n }\n return env;\n}\n\nfunction mergedEnv(wranglerConfig: { vars?: Record<string, string | undefined> } | null) {\n return {\n ...wranglerConfig?.vars,\n ...readDotEnvFile(path.join(projectRoot, \".env.local\")),\n ...readDotEnvFile(path.join(projectRoot, \".dev.vars\")),\n ...process.env,\n };\n}\n\nasync function main() {\n const args = parseArgs(process.argv.slice(2));\n const wranglerConfig = readJsonc(path.join(projectRoot, \"wrangler.jsonc\"));\n const report = buildNextionDoctorReport({\n env: mergedEnv(wranglerConfig),\n wranglerConfig,\n });\n\n if (args.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n console.log(formatNextionDoctorReport(report));\n }\n\n if (report.overall.status === \"missing\") {\n process.exitCode = 1;\n }\n}\n\nif (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? \"\")) {\n main().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n });\n}\n","import type { RuntimeId } from \"./selection\";\n\nexport type RuntimeCapability =\n | \"server-rendering\"\n | \"edge-cache\"\n | \"relational-storage\"\n | \"object-storage\"\n | \"image-optimization\"\n | \"secrets\"\n | \"observability\";\n\nexport type RuntimeAdapterDefinition = {\n id: RuntimeId;\n label: string;\n status: \"active\" | \"partial\" | \"planned\";\n services: {\n compute: string;\n relationalStorage: string;\n objectStorage: string;\n imageOptimization: string;\n cache: string;\n authStorage: string;\n };\n capabilities: readonly RuntimeCapability[];\n};\n\nexport type RuntimeServiceStatus = {\n database: boolean;\n objectStorage: boolean;\n imageTransformer: boolean;\n publicCache: boolean;\n};\n\nexport const cloudflareWorkersAdapter: RuntimeAdapterDefinition = {\n id: \"cloudflare-workers\",\n label: \"Cloudflare Workers + D1\",\n status: \"active\",\n services: {\n compute: \"Cloudflare Workers via vinext\",\n relationalStorage: \"D1 through the runtime SQL adapter\",\n objectStorage: \"R2\",\n imageOptimization: \"Cloudflare Images\",\n cache: \"vinext CDN/data adapters and caches.default for media\",\n authStorage: \"D1 users and signed cookies\",\n },\n capabilities: [\n \"server-rendering\",\n \"edge-cache\",\n \"relational-storage\",\n \"object-storage\",\n \"image-optimization\",\n \"secrets\",\n \"observability\",\n ],\n};\n\nexport const runtimeAdapters = [cloudflareWorkersAdapter] as const;\n\nexport function getRuntimeAdapter(id: RuntimeAdapterDefinition[\"id\"]) {\n return runtimeAdapters.find((adapter) => adapter.id === id);\n}\n\nexport function runtimeServiceStatus(\n platform: {\n database: unknown;\n objectStorage: unknown;\n imageTransformer: unknown;\n publicCache: unknown;\n }\n): RuntimeServiceStatus {\n return {\n database: Boolean(platform.database),\n objectStorage: Boolean(platform.objectStorage),\n imageTransformer: Boolean(platform.imageTransformer),\n publicCache: Boolean(platform.publicCache),\n };\n}\n","export type RuntimeId = \"cloudflare-workers\";\n\nexport type RuntimeKind = \"cloudflare\";\n\nexport interface RuntimeSelection {\n kind: RuntimeKind;\n runtimeId: RuntimeId;\n}\n\nfunction hasCloudflareBindings(env: unknown): boolean {\n if (!env || typeof env !== \"object\") return false;\n const record = env as Record<string, unknown>;\n return (\n \"DB\" in record ||\n \"ASSETS_BUCKET\" in record ||\n \"R2\" in record ||\n \"IMAGES\" in record ||\n \"CONTENT_CACHE\" in record\n );\n}\n\nexport function selectRuntime(env: unknown): RuntimeSelection {\n if (hasCloudflareBindings(env)) {\n return { kind: \"cloudflare\", runtimeId: \"cloudflare-workers\" };\n }\n throw new Error(\n \"No supported runtime detected. Expected Cloudflare Workers bindings \" +\n \"(DB, ASSETS_BUCKET, R2, IMAGES, or CONTENT_CACHE).\",\n );\n}\n\nexport function currentRuntimeId(): RuntimeId {\n return \"cloudflare-workers\";\n}\n","import type { ContentModelDefinition, NotionFieldMap } from \"./model\";\nimport {\n getRuntimeAdapter,\n type RuntimeAdapterDefinition,\n} from \"../platform/capabilities\";\nimport { currentRuntimeId, type RuntimeId } from \"../platform/selection\";\n\nexport type NextionDoctorStatus = \"ok\" | \"warn\" | \"missing\";\n\nexport type EnvLike = Record<string, string | undefined>;\n\nexport type WranglerConfigLike = {\n vars?: EnvLike;\n d1_databases?: Array<{ binding?: string }>;\n r2_buckets?: Array<{ binding?: string }>;\n images?: { binding?: string } | Array<{ binding?: string }>;\n observability?: { enabled?: boolean };\n};\n\nexport type NextionDoctorCheck = {\n id: string;\n label: string;\n status: NextionDoctorStatus;\n required: boolean;\n detail: string;\n action?: string;\n};\n\nexport type NextionDoctorModel = {\n id: string;\n public: boolean;\n admin: boolean;\n listPath: string;\n detailPath: string;\n publicApiPath?: string;\n dataSourceEnv: string;\n dataSourceStatus: NextionDoctorStatus;\n dataSourceSource: \"env\" | \"default\" | \"missing\";\n};\n\nexport type NextionDoctorReport = {\n overall: {\n status: NextionDoctorStatus;\n summary: string;\n };\n runtime: {\n id: RuntimeId;\n label: string;\n adapterStatus: RuntimeAdapterDefinition[\"status\"];\n };\n checks: NextionDoctorCheck[];\n models: NextionDoctorModel[];\n nextSteps: string[];\n};\n\ntype BuildNextionDoctorReportOptions = {\n env?: EnvLike;\n runtimeId?: RuntimeId;\n wranglerConfig?: WranglerConfigLike | null;\n models?: readonly ContentModelDefinition<NotionFieldMap>[];\n};\n\nfunction envValue(env: EnvLike, name: string) {\n const value = String(env[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction hasEnv(env: EnvLike, name: string) {\n return Boolean(envValue(env, name));\n}\n\nfunction hasD1Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.d1_databases?.some((item) => item.binding === binding));\n}\n\nfunction hasR2Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.r2_buckets?.some((item) => item.binding === binding));\n}\n\nfunction hasImagesBinding(\n config: WranglerConfigLike | null | undefined,\n binding: string\n) {\n const images = config?.images;\n if (Array.isArray(images)) {\n return images.some((item) => item.binding === binding);\n }\n return images?.binding === binding;\n}\n\nfunction statusSummary(status: NextionDoctorStatus) {\n if (status === \"ok\") return \"ready\";\n if (status === \"warn\") return \"usable with warnings\";\n return \"missing required configuration\";\n}\n\nfunction overallStatus(\n checks: readonly NextionDoctorCheck[],\n models: readonly NextionDoctorModel[]\n): NextionDoctorStatus {\n if (\n checks.some((check) => check.status === \"missing\") ||\n models.some((model) => model.dataSourceStatus === \"missing\")\n ) {\n return \"missing\";\n }\n\n if (\n checks.some((check) => check.status === \"warn\") ||\n models.some((model) => model.dataSourceStatus === \"warn\")\n ) {\n return \"warn\";\n }\n\n return \"ok\";\n}\n\nfunction cloudflareChecks(\n config: WranglerConfigLike | null | undefined\n): NextionDoctorCheck[] {\n return [\n {\n id: \"runtime.database\",\n label: \"SQL database\",\n status: hasD1Binding(config, \"DB\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasD1Binding(config, \"DB\")\n ? \"wrangler D1 binding DB is declared\"\n : \"wrangler D1 binding DB is missing\",\n action: \"Add a DB binding under d1_databases in wrangler.jsonc.\",\n },\n {\n id: \"runtime.objectStorage\",\n label: \"Object storage\",\n status: hasR2Binding(config, \"ASSETS_BUCKET\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasR2Binding(config, \"ASSETS_BUCKET\")\n ? \"wrangler R2 binding ASSETS_BUCKET is declared\"\n : \"uploads and persistent media cache need ASSETS_BUCKET\",\n action: \"Add an ASSETS_BUCKET R2 binding for uploads and media cache.\",\n },\n {\n id: \"runtime.imageTransformer\",\n label: \"Image transformation\",\n status: hasImagesBinding(config, \"IMAGES\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasImagesBinding(config, \"IMAGES\")\n ? \"wrangler Images binding IMAGES is declared\"\n : \"Notion media optimization will fall back without IMAGES\",\n action: \"Add a Cloudflare Images binding named IMAGES.\",\n },\n {\n id: \"runtime.publicCache\",\n label: \"Public cache\",\n status: \"ok\",\n required: true,\n detail: \"vinext CDN adapter handles page cache; caches.default remains for media\",\n },\n {\n id: \"runtime.observability\",\n label: \"Observability\",\n status: config?.observability?.enabled ? \"ok\" : \"warn\",\n required: false,\n detail: config?.observability?.enabled\n ? \"wrangler observability is enabled\"\n : \"wrangler observability is not enabled\",\n action: \"Enable observability in wrangler.jsonc for production debugging.\",\n },\n ];\n}\n\nfunction notionChecks(env: EnvLike): NextionDoctorCheck[] {\n return [\n {\n id: \"notion.token\",\n label: \"Notion token\",\n status: hasEnv(env, \"NOTION_TOKEN\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasEnv(env, \"NOTION_TOKEN\")\n ? \"NOTION_TOKEN is configured\"\n : \"NOTION_TOKEN is missing\",\n action: \"Set NOTION_TOKEN to an internal integration token.\",\n },\n {\n id: \"notion.webhook\",\n label: \"Notion webhook verification\",\n status: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\")\n ? \"NOTION_WEBHOOK_VERIFICATION_TOKEN is configured\"\n : \"instant content invalidation needs NOTION_WEBHOOK_VERIFICATION_TOKEN\",\n action:\n \"Set NOTION_WEBHOOK_VERIFICATION_TOKEN after creating the Notion webhook.\",\n },\n ];\n}\n\nfunction modelDoctorStatus(\n env: EnvLike,\n model: ContentModelDefinition<NotionFieldMap>\n): Pick<NextionDoctorModel, \"dataSourceStatus\" | \"dataSourceSource\"> {\n const hasConfiguredEnv = hasEnv(env, model.source.dataSourceEnv);\n const hasDefault = Boolean(model.source.defaultDataSourceId);\n const dataSourceSource = hasConfiguredEnv\n ? \"env\"\n : hasDefault\n ? \"default\"\n : \"missing\";\n const dataSourceStatus = dataSourceSource === \"missing\" ? \"missing\" : \"ok\";\n\n return {\n dataSourceStatus,\n dataSourceSource,\n };\n}\n\nfunction modelChecks(\n env: EnvLike,\n models: readonly ContentModelDefinition<NotionFieldMap>[]\n): NextionDoctorModel[] {\n return models.map((model) => ({\n id: model.id,\n public: model.visibility.public,\n admin: model.visibility.admin,\n listPath: model.routes.listPath,\n detailPath: model.routes.detailPath,\n publicApiPath: model.routes.publicApiPath,\n dataSourceEnv: model.source.dataSourceEnv,\n ...modelDoctorStatus(env, model),\n }));\n}\n\nfunction uniqueActions(checks: readonly NextionDoctorCheck[]) {\n return Array.from(\n new Set(\n checks\n .filter((check) => check.status !== \"ok\" && check.action)\n .map((check) => check.action as string)\n )\n );\n}\n\nfunction omitResolvedActions(check: NextionDoctorCheck): NextionDoctorCheck {\n if (check.status !== \"ok\") return check;\n return {\n ...check,\n action: undefined,\n };\n}\n\nexport function buildNextionDoctorReport(\n options: BuildNextionDoctorReportOptions = {}\n): NextionDoctorReport {\n const env = options.env ?? process.env;\n const runtimeId = options.runtimeId ?? currentRuntimeId();\n const adapter = getRuntimeAdapter(runtimeId);\n const checks = [\n ...cloudflareChecks(options.wranglerConfig),\n ...notionChecks(env),\n ].map(omitResolvedActions);\n const models = modelChecks(env, options.models ?? []);\n const status = overallStatus(checks, models);\n const modelActions = models\n .filter((model) => model.dataSourceStatus === \"missing\")\n .map(\n (model) =>\n `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`\n );\n\n return {\n overall: {\n status,\n summary: statusSummary(status),\n },\n runtime: {\n id: runtimeId,\n label: adapter?.label ?? runtimeId,\n adapterStatus: adapter?.status ?? \"planned\",\n },\n checks,\n models,\n nextSteps: [...uniqueActions(checks), ...modelActions],\n };\n}\n\nfunction formatCheck(check: NextionDoctorCheck) {\n const required = check.required ? \"required\" : \"optional\";\n return ` [${check.status}] ${check.label} (${required}) - ${check.detail}`;\n}\n\nfunction formatModel(model: NextionDoctorModel) {\n const visibility = [\n model.public ? \"public\" : \"\",\n model.admin ? \"admin\" : \"\",\n ].filter(Boolean);\n const source =\n model.dataSourceSource === \"env\"\n ? model.dataSourceEnv\n : model.dataSourceSource === \"default\"\n ? `${model.dataSourceEnv} or model default`\n : model.dataSourceEnv;\n\n return [\n ` [${model.dataSourceStatus}] ${model.id} (${visibility.join(\", \") || \"private\"})`,\n ` routes: ${model.listPath}, ${model.detailPath}${\n model.publicApiPath ? `, ${model.publicApiPath}` : \"\"\n }`,\n ` notion: ${source}`,\n ].join(\"\\n\");\n}\n\nexport function formatNextionDoctorReport(report: NextionDoctorReport) {\n const lines = [\n \"vinext nextion doctor\",\n \"\",\n `Overall: [${report.overall.status}] ${report.overall.summary}`,\n `Runtime: ${report.runtime.label} (${report.runtime.id}, ${report.runtime.adapterStatus})`,\n \"\",\n \"Checks:\",\n ...report.checks.map(formatCheck),\n \"\",\n \"Content models:\",\n ...report.models.map(formatModel),\n ];\n\n if (report.nextSteps.length > 0) {\n lines.push(\"\", \"Next steps:\");\n for (const step of report.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;AAOA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;;;ACwBvB,IAAM,2BAAqD;AAAA,EAChE,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,IACR,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,mBAAmB;AAAA,IACnB,OAAO;AAAA,IACP,aAAa;AAAA,EACf;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,kBAAkB,CAAC,wBAAwB;AAEjD,SAAS,kBAAkB,IAAoC;AACpE,SAAO,gBAAgB,KAAK,CAAC,YAAY,QAAQ,OAAO,EAAE;AAC5D;;;AC7BO,SAAS,mBAA8B;AAC5C,SAAO;AACT;;;AC6BA,SAAS,SAAS,KAAc,MAAc;AAC5C,QAAM,QAAQ,OAAO,IAAI,IAAI,KAAK,EAAE,EAAE,KAAK;AAC3C,SAAO,SAAS;AAClB;AAEA,SAAS,OAAO,KAAc,MAAc;AAC1C,SAAO,QAAQ,SAAS,KAAK,IAAI,CAAC;AACpC;AAEA,SAAS,aAAa,QAA+C,SAAiB;AACpF,SAAO,QAAQ,QAAQ,cAAc,KAAK,CAAC,SAAS,KAAK,YAAY,OAAO,CAAC;AAC/E;AAEA,SAAS,aAAa,QAA+C,SAAiB;AACpF,SAAO,QAAQ,QAAQ,YAAY,KAAK,CAAC,SAAS,KAAK,YAAY,OAAO,CAAC;AAC7E;AAEA,SAAS,iBACP,QACA,SACA;AACA,QAAM,SAAS,QAAQ;AACvB,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO,OAAO,KAAK,CAAC,SAAS,KAAK,YAAY,OAAO;AAAA,EACvD;AACA,SAAO,QAAQ,YAAY;AAC7B;AAEA,SAAS,cAAc,QAA6B;AAClD,MAAI,WAAW,KAAM,QAAO;AAC5B,MAAI,WAAW,OAAQ,QAAO;AAC9B,SAAO;AACT;AAEA,SAAS,cACP,QACA,QACqB;AACrB,MACE,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,SAAS,KACjD,OAAO,KAAK,CAAC,UAAU,MAAM,qBAAqB,SAAS,GAC3D;AACA,WAAO;AAAA,EACT;AAEA,MACE,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,MAAM,KAC9C,OAAO,KAAK,CAAC,UAAU,MAAM,qBAAqB,MAAM,GACxD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,iBACP,QACsB;AACtB,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,aAAa,QAAQ,IAAI,IAAI,OAAO;AAAA,MAC5C,UAAU;AAAA,MACV,QAAQ,aAAa,QAAQ,IAAI,IAC7B,uCACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,aAAa,QAAQ,eAAe,IAAI,OAAO;AAAA,MACvD,UAAU;AAAA,MACV,QAAQ,aAAa,QAAQ,eAAe,IACxC,kDACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,OAAO;AAAA,MACpD,UAAU;AAAA,MACV,QAAQ,iBAAiB,QAAQ,QAAQ,IACrC,+CACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,QAAQ,eAAe,UAAU,OAAO;AAAA,MAChD,UAAU;AAAA,MACV,QAAQ,QAAQ,eAAe,UAC3B,sCACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEA,SAAS,aAAa,KAAoC;AACxD,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,OAAO,KAAK,cAAc,IAAI,OAAO;AAAA,MAC7C,UAAU;AAAA,MACV,QAAQ,OAAO,KAAK,cAAc,IAC9B,+BACA;AAAA,MACJ,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,QAAQ,OAAO,KAAK,mCAAmC,IAAI,OAAO;AAAA,MAClE,UAAU;AAAA,MACV,QAAQ,OAAO,KAAK,mCAAmC,IACnD,oDACA;AAAA,MACJ,QACE;AAAA,IACJ;AAAA,EACF;AACF;AAEA,SAAS,kBACP,KACA,OACmE;AACnE,QAAM,mBAAmB,OAAO,KAAK,MAAM,OAAO,aAAa;AAC/D,QAAM,aAAa,QAAQ,MAAM,OAAO,mBAAmB;AAC3D,QAAM,mBAAmB,mBACrB,QACA,aACE,YACA;AACN,QAAM,mBAAmB,qBAAqB,YAAY,YAAY;AAEtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,YACP,KACA,QACsB;AACtB,SAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5B,IAAI,MAAM;AAAA,IACV,QAAQ,MAAM,WAAW;AAAA,IACzB,OAAO,MAAM,WAAW;AAAA,IACxB,UAAU,MAAM,OAAO;AAAA,IACvB,YAAY,MAAM,OAAO;AAAA,IACzB,eAAe,MAAM,OAAO;AAAA,IAC5B,eAAe,MAAM,OAAO;AAAA,IAC5B,GAAG,kBAAkB,KAAK,KAAK;AAAA,EACjC,EAAE;AACJ;AAEA,SAAS,cAAc,QAAuC;AAC5D,SAAO,MAAM;AAAA,IACX,IAAI;AAAA,MACF,OACG,OAAO,CAAC,UAAU,MAAM,WAAW,QAAQ,MAAM,MAAM,EACvD,IAAI,CAAC,UAAU,MAAM,MAAgB;AAAA,IAC1C;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,OAA+C;AAC1E,MAAI,MAAM,WAAW,KAAM,QAAO;AAClC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ;AAAA,EACV;AACF;AAEO,SAAS,yBACd,UAA2C,CAAC,GACvB;AACrB,QAAM,MAAM,QAAQ,OAAO,QAAQ;AACnC,QAAM,YAAY,QAAQ,aAAa,iBAAiB;AACxD,QAAM,UAAU,kBAAkB,SAAS;AAC3C,QAAM,SAAS;AAAA,IACb,GAAG,iBAAiB,QAAQ,cAAc;AAAA,IAC1C,GAAG,aAAa,GAAG;AAAA,EACrB,EAAE,IAAI,mBAAmB;AACzB,QAAM,SAAS,YAAY,KAAK,QAAQ,UAAU,CAAC,CAAC;AACpD,QAAM,SAAS,cAAc,QAAQ,MAAM;AAC3C,QAAM,eAAe,OAClB,OAAO,CAAC,UAAU,MAAM,qBAAqB,SAAS,EACtD;AAAA,IACC,CAAC,UACC,OAAO,MAAM,aAAa,YAAY,MAAM,EAAE;AAAA,EAClD;AAEF,SAAO;AAAA,IACL,SAAS;AAAA,MACP;AAAA,MACA,SAAS,cAAc,MAAM;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,MACP,IAAI;AAAA,MACJ,OAAO,SAAS,SAAS;AAAA,MACzB,eAAe,SAAS,UAAU;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,CAAC,GAAG,cAAc,MAAM,GAAG,GAAG,YAAY;AAAA,EACvD;AACF;AAEA,SAAS,YAAY,OAA2B;AAC9C,QAAM,WAAW,MAAM,WAAW,aAAa;AAC/C,SAAO,MAAM,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,QAAQ,OAAO,MAAM,MAAM;AAC3E;AAEA,SAAS,YAAY,OAA2B;AAC9C,QAAM,aAAa;AAAA,IACjB,MAAM,SAAS,WAAW;AAAA,IAC1B,MAAM,QAAQ,UAAU;AAAA,EAC1B,EAAE,OAAO,OAAO;AAChB,QAAM,SACJ,MAAM,qBAAqB,QACvB,MAAM,gBACN,MAAM,qBAAqB,YACzB,GAAG,MAAM,aAAa,sBACtB,MAAM;AAEd,SAAO;AAAA,IACL,MAAM,MAAM,gBAAgB,KAAK,MAAM,EAAE,KAAK,WAAW,KAAK,IAAI,KAAK,SAAS;AAAA,IAChF,iBAAiB,MAAM,QAAQ,KAAK,MAAM,UAAU,GAClD,MAAM,gBAAgB,KAAK,MAAM,aAAa,KAAK,EACrD;AAAA,IACA,iBAAiB,MAAM;AAAA,EACzB,EAAE,KAAK,IAAI;AACb;AAEO,SAAS,0BAA0B,QAA6B;AACrE,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,aAAa,OAAO,QAAQ,MAAM,KAAK,OAAO,QAAQ,OAAO;AAAA,IAC7D,YAAY,OAAO,QAAQ,KAAK,KAAK,OAAO,QAAQ,EAAE,KAAK,OAAO,QAAQ,aAAa;AAAA,IACvF;AAAA,IACA;AAAA,IACA,GAAG,OAAO,OAAO,IAAI,WAAW;AAAA,IAChC;AAAA,IACA;AAAA,IACA,GAAG,OAAO,OAAO,IAAI,WAAW;AAAA,EAClC;AAEA,MAAI,OAAO,UAAU,SAAS,GAAG;AAC/B,UAAM,KAAK,IAAI,aAAa;AAC5B,eAAW,QAAQ,OAAO,WAAW;AACnC,YAAM,KAAK,OAAO,IAAI,EAAE;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;AH9TA,IAAM,cAAc,QAAQ,IAAI;AAEhC,SAAS,UAAU,MAAgB;AACjC,QAAM,SAAS;AAAA,IACb,MAAM;AAAA,EACR;AAEA,aAAW,OAAO,MAAM;AACtB,QAAI,QAAQ,UAAU;AACpB,aAAO,OAAO;AACd;AAAA,IACF;AACA,UAAM,IAAI,MAAM,qBAAqB,GAAG,EAAE;AAAA,EAC5C;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAgB;AACzC,MAAI,SAAS;AACb,MAAI,WAAW;AACf,MAAI,UAAU;AACd,MAAI,cAAc;AAClB,MAAI,eAAe;AAEnB,WAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;AACrD,UAAM,OAAO,OAAO,KAAK;AACzB,UAAM,OAAO,OAAO,QAAQ,CAAC;AAE7B,QAAI,aAAa;AACf,UAAI,SAAS,MAAM;AACjB,sBAAc;AACd,kBAAU;AAAA,MACZ;AACA;AAAA,IACF;AAEA,QAAI,cAAc;AAChB,UAAI,SAAS,OAAO,SAAS,KAAK;AAChC,uBAAe;AACf,iBAAS;AAAA,MACX;AACA;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,gBAAU;AACV,UAAI,SAAS;AACX,kBAAU;AAAA,MACZ,WAAW,SAAS,MAAM;AACxB,kBAAU;AAAA,MACZ,WAAW,SAAS,KAAM;AACxB,mBAAW;AAAA,MACb;AACA;AAAA,IACF;AAEA,QAAI,SAAS,KAAM;AACjB,iBAAW;AACX,gBAAU;AACV;AAAA,IACF;AAEA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,oBAAc;AACd,eAAS;AACT;AAAA,IACF;AAEA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,qBAAe;AACf,eAAS;AACT;AAAA,IACF;AAEA,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,UAAkB;AACnC,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO;AACrC,SAAO,KAAK,MAAM,kBAAkB,GAAG,aAAa,UAAU,MAAM,CAAC,CAAC;AACxE;AAEA,SAAS,eAAe,UAAkB;AACxC,MAAI,CAAC,GAAG,WAAW,QAAQ,EAAG,QAAO,CAAC;AACtC,QAAM,MAA8B,CAAC;AACrC,aAAW,QAAQ,GAAG,aAAa,UAAU,MAAM,EAAE,MAAM,OAAO,GAAG;AACnE,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,GAAI;AAEvB,UAAM,OAAO,QAAQ,MAAM,GAAG,UAAU,EAAE,KAAK;AAC/C,QAAI,QAAQ,QAAQ,MAAM,aAAa,CAAC,EAAE,KAAK;AAC/C,QACG,MAAM,WAAW,GAAI,KAAK,MAAM,SAAS,GAAI,KAC7C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,GAC5C;AACA,cAAQ,MAAM,MAAM,GAAG,EAAE;AAAA,IAC3B;AACA,QAAI,IAAI,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAEA,SAAS,UAAU,gBAAsE;AACvF,SAAO;AAAA,IACL,GAAG,gBAAgB;AAAA,IACnB,GAAG,eAAe,KAAK,KAAK,aAAa,YAAY,CAAC;AAAA,IACtD,GAAG,eAAe,KAAK,KAAK,aAAa,WAAW,CAAC;AAAA,IACrD,GAAG,QAAQ;AAAA,EACb;AACF;AAEA,eAAe,OAAO;AACpB,QAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC5C,QAAM,iBAAiB,UAAU,KAAK,KAAK,aAAa,gBAAgB,CAAC;AACzE,QAAM,SAAS,yBAAyB;AAAA,IACtC,KAAK,UAAU,cAAc;AAAA,IAC7B;AAAA,EACF,CAAC;AAED,MAAI,KAAK,MAAM;AACb,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC7C,OAAO;AACL,YAAQ,IAAI,0BAA0B,MAAM,CAAC;AAAA,EAC/C;AAEA,MAAI,OAAO,QAAQ,WAAW,WAAW;AACvC,YAAQ,WAAW;AAAA,EACrB;AACF;AAEA,IAAI,cAAc,YAAY,GAAG,MAAM,KAAK,QAAQ,QAAQ,KAAK,CAAC,KAAK,EAAE,GAAG;AAC1E,OAAK,EAAE,MAAM,CAAC,UAAU;AACtB,YAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,YAAQ,WAAW;AAAA,EACrB,CAAC;AACH;","names":[]}
@@ -0,0 +1,139 @@
1
+ import { RuntimeAdapterDefinition } from '../platform/capabilities.js';
2
+ import { RuntimeId } from '../platform/selection.js';
3
+
4
+ type NotionSortDirection = "ascending" | "descending";
5
+ type NotionSort = {
6
+ property: string;
7
+ direction: NotionSortDirection;
8
+ };
9
+ type NotionFieldMap = Record<string, string | readonly string[]>;
10
+ type ContentModelDefinition<TFields extends NotionFieldMap = NotionFieldMap> = {
11
+ id: string;
12
+ kind: "article" | "catalog" | "directory";
13
+ visibility: {
14
+ public: boolean;
15
+ admin: boolean;
16
+ };
17
+ source: {
18
+ type: "notion";
19
+ tokenEnv: "NOTION_TOKEN";
20
+ dataSourceEnv: string;
21
+ defaultDataSourceId?: string;
22
+ fields: TFields;
23
+ query: {
24
+ pageSize: number;
25
+ sorts?: readonly NotionSort[];
26
+ filterProperties?: readonly string[];
27
+ };
28
+ };
29
+ routes: {
30
+ listPath: string;
31
+ detailPath: string;
32
+ detailParam: string;
33
+ publicApiPath?: string;
34
+ };
35
+ ui: {
36
+ name: string;
37
+ pluralName: string;
38
+ navLabel: string;
39
+ listTitle: string;
40
+ listDescription: string;
41
+ emptyState: string;
42
+ };
43
+ capabilities: {
44
+ richBlocks: boolean;
45
+ coverImages: boolean;
46
+ gatedAssets: boolean;
47
+ };
48
+ };
49
+
50
+ type NextionDoctorStatus = "ok" | "warn" | "missing";
51
+ type EnvLike = Record<string, string | undefined>;
52
+ type WranglerConfigLike = {
53
+ vars?: EnvLike;
54
+ d1_databases?: Array<{
55
+ binding?: string;
56
+ }>;
57
+ r2_buckets?: Array<{
58
+ binding?: string;
59
+ }>;
60
+ images?: {
61
+ binding?: string;
62
+ } | Array<{
63
+ binding?: string;
64
+ }>;
65
+ observability?: {
66
+ enabled?: boolean;
67
+ };
68
+ };
69
+ type NextionDoctorCheck = {
70
+ id: string;
71
+ label: string;
72
+ status: NextionDoctorStatus;
73
+ required: boolean;
74
+ detail: string;
75
+ action?: string;
76
+ };
77
+ type NextionDoctorModel = {
78
+ id: string;
79
+ public: boolean;
80
+ admin: boolean;
81
+ listPath: string;
82
+ detailPath: string;
83
+ publicApiPath?: string;
84
+ dataSourceEnv: string;
85
+ dataSourceStatus: NextionDoctorStatus;
86
+ dataSourceSource: "env" | "default" | "missing";
87
+ };
88
+ type NextionDoctorReport = {
89
+ overall: {
90
+ status: NextionDoctorStatus;
91
+ summary: string;
92
+ };
93
+ runtime: {
94
+ id: RuntimeId;
95
+ label: string;
96
+ adapterStatus: RuntimeAdapterDefinition["status"];
97
+ };
98
+ checks: NextionDoctorCheck[];
99
+ models: NextionDoctorModel[];
100
+ nextSteps: string[];
101
+ };
102
+ type BuildNextionDoctorReportOptions = {
103
+ env?: EnvLike;
104
+ runtimeId?: RuntimeId;
105
+ wranglerConfig?: WranglerConfigLike | null;
106
+ models?: readonly ContentModelDefinition<NotionFieldMap>[];
107
+ };
108
+ declare function buildNextionDoctorReport(options?: BuildNextionDoctorReportOptions): NextionDoctorReport;
109
+ declare function formatNextionDoctorReport(report: NextionDoctorReport): string;
110
+
111
+ /**
112
+ * Public, runtime-driven doctor API. Consumers (the worker bootstrap,
113
+ * scripts, CI) pass a runtime that exposes binding lookups; the doctor
114
+ * derives a synthetic wrangler config from it and runs the full report.
115
+ *
116
+ * `sources` is a list of project content sources. For now, the doctor
117
+ * ignores their detailed shape; future phases will surface per-source
118
+ * data-source status as findings.
119
+ */
120
+ type DoctorFinding = {
121
+ code: string;
122
+ message: string;
123
+ severity: "info" | "warning" | "error";
124
+ };
125
+ type RuntimeLike = {
126
+ getBinding(name: string): unknown;
127
+ };
128
+ type RunNextionDoctorOptions = {
129
+ env: EnvLike;
130
+ runtime: RuntimeLike;
131
+ sources: readonly unknown[];
132
+ wranglerConfig?: WranglerConfigLike | null;
133
+ };
134
+ type NextionDoctorFindingsReport = NextionDoctorReport & {
135
+ findings: DoctorFinding[];
136
+ };
137
+ declare function runNextionDoctor(options: RunNextionDoctorOptions): NextionDoctorFindingsReport;
138
+
139
+ export { type ContentModelDefinition, type DoctorFinding, type EnvLike, type NextionDoctorCheck, type NextionDoctorFindingsReport, type NextionDoctorModel, type NextionDoctorReport, type NextionDoctorStatus, type NotionFieldMap, type RunNextionDoctorOptions, type RuntimeLike, type WranglerConfigLike, buildNextionDoctorReport, formatNextionDoctorReport as formatDoctorReport, formatNextionDoctorReport, runNextionDoctor };
@@ -0,0 +1,289 @@
1
+ // src/platform/capabilities.ts
2
+ var cloudflareWorkersAdapter = {
3
+ id: "cloudflare-workers",
4
+ label: "Cloudflare Workers + D1",
5
+ status: "active",
6
+ services: {
7
+ compute: "Cloudflare Workers via vinext",
8
+ relationalStorage: "D1 through the runtime SQL adapter",
9
+ objectStorage: "R2",
10
+ imageOptimization: "Cloudflare Images",
11
+ cache: "vinext CDN/data adapters and caches.default for media",
12
+ authStorage: "D1 users and signed cookies"
13
+ },
14
+ capabilities: [
15
+ "server-rendering",
16
+ "edge-cache",
17
+ "relational-storage",
18
+ "object-storage",
19
+ "image-optimization",
20
+ "secrets",
21
+ "observability"
22
+ ]
23
+ };
24
+ var runtimeAdapters = [cloudflareWorkersAdapter];
25
+ function getRuntimeAdapter(id) {
26
+ return runtimeAdapters.find((adapter) => adapter.id === id);
27
+ }
28
+
29
+ // src/platform/selection.ts
30
+ function currentRuntimeId() {
31
+ return "cloudflare-workers";
32
+ }
33
+
34
+ // src/doctor/doctor.ts
35
+ function envValue(env, name) {
36
+ const value = String(env[name] ?? "").trim();
37
+ return value || void 0;
38
+ }
39
+ function hasEnv(env, name) {
40
+ return Boolean(envValue(env, name));
41
+ }
42
+ function hasD1Binding(config, binding) {
43
+ return Boolean(config?.d1_databases?.some((item) => item.binding === binding));
44
+ }
45
+ function hasR2Binding(config, binding) {
46
+ return Boolean(config?.r2_buckets?.some((item) => item.binding === binding));
47
+ }
48
+ function hasImagesBinding(config, binding) {
49
+ const images = config?.images;
50
+ if (Array.isArray(images)) {
51
+ return images.some((item) => item.binding === binding);
52
+ }
53
+ return images?.binding === binding;
54
+ }
55
+ function statusSummary(status) {
56
+ if (status === "ok") return "ready";
57
+ if (status === "warn") return "usable with warnings";
58
+ return "missing required configuration";
59
+ }
60
+ function overallStatus(checks, models) {
61
+ if (checks.some((check) => check.status === "missing") || models.some((model) => model.dataSourceStatus === "missing")) {
62
+ return "missing";
63
+ }
64
+ if (checks.some((check) => check.status === "warn") || models.some((model) => model.dataSourceStatus === "warn")) {
65
+ return "warn";
66
+ }
67
+ return "ok";
68
+ }
69
+ function cloudflareChecks(config) {
70
+ return [
71
+ {
72
+ id: "runtime.database",
73
+ label: "SQL database",
74
+ status: hasD1Binding(config, "DB") ? "ok" : "missing",
75
+ required: true,
76
+ detail: hasD1Binding(config, "DB") ? "wrangler D1 binding DB is declared" : "wrangler D1 binding DB is missing",
77
+ action: "Add a DB binding under d1_databases in wrangler.jsonc."
78
+ },
79
+ {
80
+ id: "runtime.objectStorage",
81
+ label: "Object storage",
82
+ status: hasR2Binding(config, "ASSETS_BUCKET") ? "ok" : "warn",
83
+ required: false,
84
+ detail: hasR2Binding(config, "ASSETS_BUCKET") ? "wrangler R2 binding ASSETS_BUCKET is declared" : "uploads and persistent media cache need ASSETS_BUCKET",
85
+ action: "Add an ASSETS_BUCKET R2 binding for uploads and media cache."
86
+ },
87
+ {
88
+ id: "runtime.imageTransformer",
89
+ label: "Image transformation",
90
+ status: hasImagesBinding(config, "IMAGES") ? "ok" : "warn",
91
+ required: false,
92
+ detail: hasImagesBinding(config, "IMAGES") ? "wrangler Images binding IMAGES is declared" : "Notion media optimization will fall back without IMAGES",
93
+ action: "Add a Cloudflare Images binding named IMAGES."
94
+ },
95
+ {
96
+ id: "runtime.publicCache",
97
+ label: "Public cache",
98
+ status: "ok",
99
+ required: true,
100
+ detail: "vinext CDN adapter handles page cache; caches.default remains for media"
101
+ },
102
+ {
103
+ id: "runtime.observability",
104
+ label: "Observability",
105
+ status: config?.observability?.enabled ? "ok" : "warn",
106
+ required: false,
107
+ detail: config?.observability?.enabled ? "wrangler observability is enabled" : "wrangler observability is not enabled",
108
+ action: "Enable observability in wrangler.jsonc for production debugging."
109
+ }
110
+ ];
111
+ }
112
+ function notionChecks(env) {
113
+ return [
114
+ {
115
+ id: "notion.token",
116
+ label: "Notion token",
117
+ status: hasEnv(env, "NOTION_TOKEN") ? "ok" : "missing",
118
+ required: true,
119
+ detail: hasEnv(env, "NOTION_TOKEN") ? "NOTION_TOKEN is configured" : "NOTION_TOKEN is missing",
120
+ action: "Set NOTION_TOKEN to an internal integration token."
121
+ },
122
+ {
123
+ id: "notion.webhook",
124
+ label: "Notion webhook verification",
125
+ status: hasEnv(env, "NOTION_WEBHOOK_VERIFICATION_TOKEN") ? "ok" : "warn",
126
+ required: false,
127
+ detail: hasEnv(env, "NOTION_WEBHOOK_VERIFICATION_TOKEN") ? "NOTION_WEBHOOK_VERIFICATION_TOKEN is configured" : "instant content invalidation needs NOTION_WEBHOOK_VERIFICATION_TOKEN",
128
+ action: "Set NOTION_WEBHOOK_VERIFICATION_TOKEN after creating the Notion webhook."
129
+ }
130
+ ];
131
+ }
132
+ function modelDoctorStatus(env, model) {
133
+ const hasConfiguredEnv = hasEnv(env, model.source.dataSourceEnv);
134
+ const hasDefault = Boolean(model.source.defaultDataSourceId);
135
+ const dataSourceSource = hasConfiguredEnv ? "env" : hasDefault ? "default" : "missing";
136
+ const dataSourceStatus = dataSourceSource === "missing" ? "missing" : "ok";
137
+ return {
138
+ dataSourceStatus,
139
+ dataSourceSource
140
+ };
141
+ }
142
+ function modelChecks(env, models) {
143
+ return models.map((model) => ({
144
+ id: model.id,
145
+ public: model.visibility.public,
146
+ admin: model.visibility.admin,
147
+ listPath: model.routes.listPath,
148
+ detailPath: model.routes.detailPath,
149
+ publicApiPath: model.routes.publicApiPath,
150
+ dataSourceEnv: model.source.dataSourceEnv,
151
+ ...modelDoctorStatus(env, model)
152
+ }));
153
+ }
154
+ function uniqueActions(checks) {
155
+ return Array.from(
156
+ new Set(
157
+ checks.filter((check) => check.status !== "ok" && check.action).map((check) => check.action)
158
+ )
159
+ );
160
+ }
161
+ function omitResolvedActions(check) {
162
+ if (check.status !== "ok") return check;
163
+ return {
164
+ ...check,
165
+ action: void 0
166
+ };
167
+ }
168
+ function buildNextionDoctorReport(options = {}) {
169
+ const env = options.env ?? process.env;
170
+ const runtimeId = options.runtimeId ?? currentRuntimeId();
171
+ const adapter = getRuntimeAdapter(runtimeId);
172
+ const checks = [
173
+ ...cloudflareChecks(options.wranglerConfig),
174
+ ...notionChecks(env)
175
+ ].map(omitResolvedActions);
176
+ const models = modelChecks(env, options.models ?? []);
177
+ const status = overallStatus(checks, models);
178
+ const modelActions = models.filter((model) => model.dataSourceStatus === "missing").map(
179
+ (model) => `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`
180
+ );
181
+ return {
182
+ overall: {
183
+ status,
184
+ summary: statusSummary(status)
185
+ },
186
+ runtime: {
187
+ id: runtimeId,
188
+ label: adapter?.label ?? runtimeId,
189
+ adapterStatus: adapter?.status ?? "planned"
190
+ },
191
+ checks,
192
+ models,
193
+ nextSteps: [...uniqueActions(checks), ...modelActions]
194
+ };
195
+ }
196
+ function formatCheck(check) {
197
+ const required = check.required ? "required" : "optional";
198
+ return ` [${check.status}] ${check.label} (${required}) - ${check.detail}`;
199
+ }
200
+ function formatModel(model) {
201
+ const visibility = [
202
+ model.public ? "public" : "",
203
+ model.admin ? "admin" : ""
204
+ ].filter(Boolean);
205
+ const source = model.dataSourceSource === "env" ? model.dataSourceEnv : model.dataSourceSource === "default" ? `${model.dataSourceEnv} or model default` : model.dataSourceEnv;
206
+ return [
207
+ ` [${model.dataSourceStatus}] ${model.id} (${visibility.join(", ") || "private"})`,
208
+ ` routes: ${model.listPath}, ${model.detailPath}${model.publicApiPath ? `, ${model.publicApiPath}` : ""}`,
209
+ ` notion: ${source}`
210
+ ].join("\n");
211
+ }
212
+ function formatNextionDoctorReport(report) {
213
+ const lines = [
214
+ "vinext nextion doctor",
215
+ "",
216
+ `Overall: [${report.overall.status}] ${report.overall.summary}`,
217
+ `Runtime: ${report.runtime.label} (${report.runtime.id}, ${report.runtime.adapterStatus})`,
218
+ "",
219
+ "Checks:",
220
+ ...report.checks.map(formatCheck),
221
+ "",
222
+ "Content models:",
223
+ ...report.models.map(formatModel)
224
+ ];
225
+ if (report.nextSteps.length > 0) {
226
+ lines.push("", "Next steps:");
227
+ for (const step of report.nextSteps) {
228
+ lines.push(` - ${step}`);
229
+ }
230
+ }
231
+ return lines.join("\n");
232
+ }
233
+
234
+ // src/doctor/index.ts
235
+ function deriveWranglerConfig(runtime) {
236
+ return {
237
+ d1_databases: runtime.getBinding("DB") ? [{ binding: "DB" }] : [],
238
+ r2_buckets: runtime.getBinding("ASSETS_BUCKET") ? [{ binding: "ASSETS_BUCKET" }] : [],
239
+ images: runtime.getBinding("IMAGES") ? { binding: "IMAGES" } : void 0
240
+ };
241
+ }
242
+ function checkToFindingCode(checkId) {
243
+ if (checkId === "runtime.database") return "missing-db-binding";
244
+ if (checkId === "runtime.objectStorage") return "missing-r2-binding";
245
+ if (checkId === "runtime.imageTransformer") return "missing-images-binding";
246
+ if (checkId === "runtime.observability") return "observability-disabled";
247
+ if (checkId === "notion.token") return "missing-notion-token";
248
+ if (checkId === "notion.webhook") return "missing-notion-webhook";
249
+ return checkId;
250
+ }
251
+ function checkToFinding(check) {
252
+ const severity = check.status === "missing" ? "error" : check.status === "warn" ? "warning" : "info";
253
+ return {
254
+ code: checkToFindingCode(check.id),
255
+ message: check.action ?? check.detail,
256
+ severity
257
+ };
258
+ }
259
+ function modelToFinding(model) {
260
+ if (model.dataSourceStatus !== "missing") return null;
261
+ return {
262
+ code: `missing-data-source:${model.id}`,
263
+ message: `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`,
264
+ severity: "error"
265
+ };
266
+ }
267
+ function runNextionDoctor(options) {
268
+ const wranglerConfig = options.wranglerConfig ?? deriveWranglerConfig(options.runtime);
269
+ const report = buildNextionDoctorReport({
270
+ env: options.env,
271
+ wranglerConfig,
272
+ // Phase 6 will map `sources` (ContentSource[]) to the detailed model
273
+ // shape used by `buildNextionDoctorReport`. For now, treat the
274
+ // empty array as the default and rely on binding/env checks.
275
+ models: []
276
+ });
277
+ const findings = [
278
+ ...report.checks.filter((check) => check.status !== "ok").map(checkToFinding),
279
+ ...report.models.map(modelToFinding).filter((finding) => finding !== null)
280
+ ];
281
+ return { ...report, findings };
282
+ }
283
+ export {
284
+ buildNextionDoctorReport,
285
+ formatNextionDoctorReport as formatDoctorReport,
286
+ formatNextionDoctorReport,
287
+ runNextionDoctor
288
+ };
289
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/platform/capabilities.ts","../../src/platform/selection.ts","../../src/doctor/doctor.ts","../../src/doctor/index.ts"],"sourcesContent":["import type { RuntimeId } from \"./selection\";\n\nexport type RuntimeCapability =\n | \"server-rendering\"\n | \"edge-cache\"\n | \"relational-storage\"\n | \"object-storage\"\n | \"image-optimization\"\n | \"secrets\"\n | \"observability\";\n\nexport type RuntimeAdapterDefinition = {\n id: RuntimeId;\n label: string;\n status: \"active\" | \"partial\" | \"planned\";\n services: {\n compute: string;\n relationalStorage: string;\n objectStorage: string;\n imageOptimization: string;\n cache: string;\n authStorage: string;\n };\n capabilities: readonly RuntimeCapability[];\n};\n\nexport type RuntimeServiceStatus = {\n database: boolean;\n objectStorage: boolean;\n imageTransformer: boolean;\n publicCache: boolean;\n};\n\nexport const cloudflareWorkersAdapter: RuntimeAdapterDefinition = {\n id: \"cloudflare-workers\",\n label: \"Cloudflare Workers + D1\",\n status: \"active\",\n services: {\n compute: \"Cloudflare Workers via vinext\",\n relationalStorage: \"D1 through the runtime SQL adapter\",\n objectStorage: \"R2\",\n imageOptimization: \"Cloudflare Images\",\n cache: \"vinext CDN/data adapters and caches.default for media\",\n authStorage: \"D1 users and signed cookies\",\n },\n capabilities: [\n \"server-rendering\",\n \"edge-cache\",\n \"relational-storage\",\n \"object-storage\",\n \"image-optimization\",\n \"secrets\",\n \"observability\",\n ],\n};\n\nexport const runtimeAdapters = [cloudflareWorkersAdapter] as const;\n\nexport function getRuntimeAdapter(id: RuntimeAdapterDefinition[\"id\"]) {\n return runtimeAdapters.find((adapter) => adapter.id === id);\n}\n\nexport function runtimeServiceStatus(\n platform: {\n database: unknown;\n objectStorage: unknown;\n imageTransformer: unknown;\n publicCache: unknown;\n }\n): RuntimeServiceStatus {\n return {\n database: Boolean(platform.database),\n objectStorage: Boolean(platform.objectStorage),\n imageTransformer: Boolean(platform.imageTransformer),\n publicCache: Boolean(platform.publicCache),\n };\n}\n","export type RuntimeId = \"cloudflare-workers\";\n\nexport type RuntimeKind = \"cloudflare\";\n\nexport interface RuntimeSelection {\n kind: RuntimeKind;\n runtimeId: RuntimeId;\n}\n\nfunction hasCloudflareBindings(env: unknown): boolean {\n if (!env || typeof env !== \"object\") return false;\n const record = env as Record<string, unknown>;\n return (\n \"DB\" in record ||\n \"ASSETS_BUCKET\" in record ||\n \"R2\" in record ||\n \"IMAGES\" in record ||\n \"CONTENT_CACHE\" in record\n );\n}\n\nexport function selectRuntime(env: unknown): RuntimeSelection {\n if (hasCloudflareBindings(env)) {\n return { kind: \"cloudflare\", runtimeId: \"cloudflare-workers\" };\n }\n throw new Error(\n \"No supported runtime detected. Expected Cloudflare Workers bindings \" +\n \"(DB, ASSETS_BUCKET, R2, IMAGES, or CONTENT_CACHE).\",\n );\n}\n\nexport function currentRuntimeId(): RuntimeId {\n return \"cloudflare-workers\";\n}\n","import type { ContentModelDefinition, NotionFieldMap } from \"./model\";\nimport {\n getRuntimeAdapter,\n type RuntimeAdapterDefinition,\n} from \"../platform/capabilities\";\nimport { currentRuntimeId, type RuntimeId } from \"../platform/selection\";\n\nexport type NextionDoctorStatus = \"ok\" | \"warn\" | \"missing\";\n\nexport type EnvLike = Record<string, string | undefined>;\n\nexport type WranglerConfigLike = {\n vars?: EnvLike;\n d1_databases?: Array<{ binding?: string }>;\n r2_buckets?: Array<{ binding?: string }>;\n images?: { binding?: string } | Array<{ binding?: string }>;\n observability?: { enabled?: boolean };\n};\n\nexport type NextionDoctorCheck = {\n id: string;\n label: string;\n status: NextionDoctorStatus;\n required: boolean;\n detail: string;\n action?: string;\n};\n\nexport type NextionDoctorModel = {\n id: string;\n public: boolean;\n admin: boolean;\n listPath: string;\n detailPath: string;\n publicApiPath?: string;\n dataSourceEnv: string;\n dataSourceStatus: NextionDoctorStatus;\n dataSourceSource: \"env\" | \"default\" | \"missing\";\n};\n\nexport type NextionDoctorReport = {\n overall: {\n status: NextionDoctorStatus;\n summary: string;\n };\n runtime: {\n id: RuntimeId;\n label: string;\n adapterStatus: RuntimeAdapterDefinition[\"status\"];\n };\n checks: NextionDoctorCheck[];\n models: NextionDoctorModel[];\n nextSteps: string[];\n};\n\ntype BuildNextionDoctorReportOptions = {\n env?: EnvLike;\n runtimeId?: RuntimeId;\n wranglerConfig?: WranglerConfigLike | null;\n models?: readonly ContentModelDefinition<NotionFieldMap>[];\n};\n\nfunction envValue(env: EnvLike, name: string) {\n const value = String(env[name] ?? \"\").trim();\n return value || undefined;\n}\n\nfunction hasEnv(env: EnvLike, name: string) {\n return Boolean(envValue(env, name));\n}\n\nfunction hasD1Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.d1_databases?.some((item) => item.binding === binding));\n}\n\nfunction hasR2Binding(config: WranglerConfigLike | null | undefined, binding: string) {\n return Boolean(config?.r2_buckets?.some((item) => item.binding === binding));\n}\n\nfunction hasImagesBinding(\n config: WranglerConfigLike | null | undefined,\n binding: string\n) {\n const images = config?.images;\n if (Array.isArray(images)) {\n return images.some((item) => item.binding === binding);\n }\n return images?.binding === binding;\n}\n\nfunction statusSummary(status: NextionDoctorStatus) {\n if (status === \"ok\") return \"ready\";\n if (status === \"warn\") return \"usable with warnings\";\n return \"missing required configuration\";\n}\n\nfunction overallStatus(\n checks: readonly NextionDoctorCheck[],\n models: readonly NextionDoctorModel[]\n): NextionDoctorStatus {\n if (\n checks.some((check) => check.status === \"missing\") ||\n models.some((model) => model.dataSourceStatus === \"missing\")\n ) {\n return \"missing\";\n }\n\n if (\n checks.some((check) => check.status === \"warn\") ||\n models.some((model) => model.dataSourceStatus === \"warn\")\n ) {\n return \"warn\";\n }\n\n return \"ok\";\n}\n\nfunction cloudflareChecks(\n config: WranglerConfigLike | null | undefined\n): NextionDoctorCheck[] {\n return [\n {\n id: \"runtime.database\",\n label: \"SQL database\",\n status: hasD1Binding(config, \"DB\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasD1Binding(config, \"DB\")\n ? \"wrangler D1 binding DB is declared\"\n : \"wrangler D1 binding DB is missing\",\n action: \"Add a DB binding under d1_databases in wrangler.jsonc.\",\n },\n {\n id: \"runtime.objectStorage\",\n label: \"Object storage\",\n status: hasR2Binding(config, \"ASSETS_BUCKET\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasR2Binding(config, \"ASSETS_BUCKET\")\n ? \"wrangler R2 binding ASSETS_BUCKET is declared\"\n : \"uploads and persistent media cache need ASSETS_BUCKET\",\n action: \"Add an ASSETS_BUCKET R2 binding for uploads and media cache.\",\n },\n {\n id: \"runtime.imageTransformer\",\n label: \"Image transformation\",\n status: hasImagesBinding(config, \"IMAGES\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasImagesBinding(config, \"IMAGES\")\n ? \"wrangler Images binding IMAGES is declared\"\n : \"Notion media optimization will fall back without IMAGES\",\n action: \"Add a Cloudflare Images binding named IMAGES.\",\n },\n {\n id: \"runtime.publicCache\",\n label: \"Public cache\",\n status: \"ok\",\n required: true,\n detail: \"vinext CDN adapter handles page cache; caches.default remains for media\",\n },\n {\n id: \"runtime.observability\",\n label: \"Observability\",\n status: config?.observability?.enabled ? \"ok\" : \"warn\",\n required: false,\n detail: config?.observability?.enabled\n ? \"wrangler observability is enabled\"\n : \"wrangler observability is not enabled\",\n action: \"Enable observability in wrangler.jsonc for production debugging.\",\n },\n ];\n}\n\nfunction notionChecks(env: EnvLike): NextionDoctorCheck[] {\n return [\n {\n id: \"notion.token\",\n label: \"Notion token\",\n status: hasEnv(env, \"NOTION_TOKEN\") ? \"ok\" : \"missing\",\n required: true,\n detail: hasEnv(env, \"NOTION_TOKEN\")\n ? \"NOTION_TOKEN is configured\"\n : \"NOTION_TOKEN is missing\",\n action: \"Set NOTION_TOKEN to an internal integration token.\",\n },\n {\n id: \"notion.webhook\",\n label: \"Notion webhook verification\",\n status: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\") ? \"ok\" : \"warn\",\n required: false,\n detail: hasEnv(env, \"NOTION_WEBHOOK_VERIFICATION_TOKEN\")\n ? \"NOTION_WEBHOOK_VERIFICATION_TOKEN is configured\"\n : \"instant content invalidation needs NOTION_WEBHOOK_VERIFICATION_TOKEN\",\n action:\n \"Set NOTION_WEBHOOK_VERIFICATION_TOKEN after creating the Notion webhook.\",\n },\n ];\n}\n\nfunction modelDoctorStatus(\n env: EnvLike,\n model: ContentModelDefinition<NotionFieldMap>\n): Pick<NextionDoctorModel, \"dataSourceStatus\" | \"dataSourceSource\"> {\n const hasConfiguredEnv = hasEnv(env, model.source.dataSourceEnv);\n const hasDefault = Boolean(model.source.defaultDataSourceId);\n const dataSourceSource = hasConfiguredEnv\n ? \"env\"\n : hasDefault\n ? \"default\"\n : \"missing\";\n const dataSourceStatus = dataSourceSource === \"missing\" ? \"missing\" : \"ok\";\n\n return {\n dataSourceStatus,\n dataSourceSource,\n };\n}\n\nfunction modelChecks(\n env: EnvLike,\n models: readonly ContentModelDefinition<NotionFieldMap>[]\n): NextionDoctorModel[] {\n return models.map((model) => ({\n id: model.id,\n public: model.visibility.public,\n admin: model.visibility.admin,\n listPath: model.routes.listPath,\n detailPath: model.routes.detailPath,\n publicApiPath: model.routes.publicApiPath,\n dataSourceEnv: model.source.dataSourceEnv,\n ...modelDoctorStatus(env, model),\n }));\n}\n\nfunction uniqueActions(checks: readonly NextionDoctorCheck[]) {\n return Array.from(\n new Set(\n checks\n .filter((check) => check.status !== \"ok\" && check.action)\n .map((check) => check.action as string)\n )\n );\n}\n\nfunction omitResolvedActions(check: NextionDoctorCheck): NextionDoctorCheck {\n if (check.status !== \"ok\") return check;\n return {\n ...check,\n action: undefined,\n };\n}\n\nexport function buildNextionDoctorReport(\n options: BuildNextionDoctorReportOptions = {}\n): NextionDoctorReport {\n const env = options.env ?? process.env;\n const runtimeId = options.runtimeId ?? currentRuntimeId();\n const adapter = getRuntimeAdapter(runtimeId);\n const checks = [\n ...cloudflareChecks(options.wranglerConfig),\n ...notionChecks(env),\n ].map(omitResolvedActions);\n const models = modelChecks(env, options.models ?? []);\n const status = overallStatus(checks, models);\n const modelActions = models\n .filter((model) => model.dataSourceStatus === \"missing\")\n .map(\n (model) =>\n `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`\n );\n\n return {\n overall: {\n status,\n summary: statusSummary(status),\n },\n runtime: {\n id: runtimeId,\n label: adapter?.label ?? runtimeId,\n adapterStatus: adapter?.status ?? \"planned\",\n },\n checks,\n models,\n nextSteps: [...uniqueActions(checks), ...modelActions],\n };\n}\n\nfunction formatCheck(check: NextionDoctorCheck) {\n const required = check.required ? \"required\" : \"optional\";\n return ` [${check.status}] ${check.label} (${required}) - ${check.detail}`;\n}\n\nfunction formatModel(model: NextionDoctorModel) {\n const visibility = [\n model.public ? \"public\" : \"\",\n model.admin ? \"admin\" : \"\",\n ].filter(Boolean);\n const source =\n model.dataSourceSource === \"env\"\n ? model.dataSourceEnv\n : model.dataSourceSource === \"default\"\n ? `${model.dataSourceEnv} or model default`\n : model.dataSourceEnv;\n\n return [\n ` [${model.dataSourceStatus}] ${model.id} (${visibility.join(\", \") || \"private\"})`,\n ` routes: ${model.listPath}, ${model.detailPath}${\n model.publicApiPath ? `, ${model.publicApiPath}` : \"\"\n }`,\n ` notion: ${source}`,\n ].join(\"\\n\");\n}\n\nexport function formatNextionDoctorReport(report: NextionDoctorReport) {\n const lines = [\n \"vinext nextion doctor\",\n \"\",\n `Overall: [${report.overall.status}] ${report.overall.summary}`,\n `Runtime: ${report.runtime.label} (${report.runtime.id}, ${report.runtime.adapterStatus})`,\n \"\",\n \"Checks:\",\n ...report.checks.map(formatCheck),\n \"\",\n \"Content models:\",\n ...report.models.map(formatModel),\n ];\n\n if (report.nextSteps.length > 0) {\n lines.push(\"\", \"Next steps:\");\n for (const step of report.nextSteps) {\n lines.push(` - ${step}`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n","// Re-export the detailed report builder used by the CLI and starter tests.\nexport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n} from \"./doctor\";\nexport type {\n NextionDoctorCheck,\n NextionDoctorModel,\n NextionDoctorReport,\n NextionDoctorStatus,\n EnvLike,\n WranglerConfigLike,\n} from \"./doctor\";\nexport type { ContentModelDefinition, NotionFieldMap } from \"./model\";\n\nimport {\n buildNextionDoctorReport,\n formatNextionDoctorReport,\n type EnvLike,\n type NextionDoctorReport,\n type WranglerConfigLike,\n} from \"./doctor\";\n\n/**\n * Public, runtime-driven doctor API. Consumers (the worker bootstrap,\n * scripts, CI) pass a runtime that exposes binding lookups; the doctor\n * derives a synthetic wrangler config from it and runs the full report.\n *\n * `sources` is a list of project content sources. For now, the doctor\n * ignores their detailed shape; future phases will surface per-source\n * data-source status as findings.\n */\nexport type DoctorFinding = {\n code: string;\n message: string;\n severity: \"info\" | \"warning\" | \"error\";\n};\n\nexport type RuntimeLike = {\n getBinding(name: string): unknown;\n};\n\nexport type RunNextionDoctorOptions = {\n env: EnvLike;\n runtime: RuntimeLike;\n sources: readonly unknown[];\n wranglerConfig?: WranglerConfigLike | null;\n};\n\nexport type NextionDoctorFindingsReport = NextionDoctorReport & {\n findings: DoctorFinding[];\n};\n\nfunction deriveWranglerConfig(runtime: RuntimeLike): WranglerConfigLike {\n return {\n d1_databases: runtime.getBinding(\"DB\") ? [{ binding: \"DB\" }] : [],\n r2_buckets: runtime.getBinding(\"ASSETS_BUCKET\")\n ? [{ binding: \"ASSETS_BUCKET\" }]\n : [],\n images: runtime.getBinding(\"IMAGES\") ? { binding: \"IMAGES\" } : undefined,\n };\n}\n\nfunction checkToFindingCode(checkId: string): string {\n if (checkId === \"runtime.database\") return \"missing-db-binding\";\n if (checkId === \"runtime.objectStorage\") return \"missing-r2-binding\";\n if (checkId === \"runtime.imageTransformer\") return \"missing-images-binding\";\n if (checkId === \"runtime.observability\") return \"observability-disabled\";\n if (checkId === \"notion.token\") return \"missing-notion-token\";\n if (checkId === \"notion.webhook\") return \"missing-notion-webhook\";\n return checkId;\n}\n\nfunction checkToFinding(check: import(\"./doctor\").NextionDoctorCheck): DoctorFinding {\n const severity: DoctorFinding[\"severity\"] =\n check.status === \"missing\" ? \"error\" : check.status === \"warn\" ? \"warning\" : \"info\";\n return {\n code: checkToFindingCode(check.id),\n message: check.action ?? check.detail,\n severity,\n };\n}\n\nfunction modelToFinding(\n model: import(\"./doctor\").NextionDoctorModel\n): DoctorFinding | null {\n if (model.dataSourceStatus !== \"missing\") return null;\n return {\n code: `missing-data-source:${model.id}`,\n message: `Set ${model.dataSourceEnv} for the ${model.id} content model or add a model defaultDataSourceId.`,\n severity: \"error\",\n };\n}\n\nexport function runNextionDoctor(\n options: RunNextionDoctorOptions\n): NextionDoctorFindingsReport {\n const wranglerConfig = options.wranglerConfig ?? deriveWranglerConfig(options.runtime);\n const report = buildNextionDoctorReport({\n env: options.env,\n wranglerConfig,\n // Phase 6 will map `sources` (ContentSource[]) to the detailed model\n // shape used by `buildNextionDoctorReport`. For now, treat the\n // empty array as the default and rely on binding/env checks.\n models: [],\n });\n const findings: DoctorFinding[] = [\n ...report.checks\n .filter((check) => check.status !== \"ok\")\n .map(checkToFinding),\n ...report.models\n .map(modelToFinding)\n .filter((finding): finding is DoctorFinding => finding !== null),\n ];\n return { ...report, findings };\n}\n\nexport { formatNextionDoctorReport as formatDoctorReport };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,38 @@
1
+ type SendArgs = {
2
+ to: string | string[];
3
+ subject: string;
4
+ html: string;
5
+ text?: string;
6
+ replyTo?: string;
7
+ };
8
+ /**
9
+ * Send an email. When RESEND_API_KEY is not set (the dev default), the
10
+ * call is silently skipped with a log line instead of failing. Returns
11
+ * the Resend message id, or null when no-op. Throws on a Resend error.
12
+ */
13
+ declare function sendEmail(args: SendArgs): Promise<string | null>;
14
+ /** Welcome email for new newsletter subscribers. */
15
+ declare function welcomeEmailHtml(opts: {
16
+ email: string;
17
+ unsubscribeUrl: string;
18
+ siteUrl: string;
19
+ }): string;
20
+ declare function resetPasswordHtml(opts: {
21
+ resetUrl: string;
22
+ siteUrl: string;
23
+ email: string;
24
+ }): string;
25
+ declare function verifyEmailHtml(opts: {
26
+ verifyUrl: string;
27
+ siteUrl: string;
28
+ email: string;
29
+ }): string;
30
+ /** New post notification email. */
31
+ declare function newPostEmailHtml(opts: {
32
+ title: string;
33
+ description: string;
34
+ url: string;
35
+ unsubscribeUrl: string;
36
+ }): string;
37
+
38
+ export { newPostEmailHtml, resetPasswordHtml, sendEmail, verifyEmailHtml, welcomeEmailHtml };