dineway 0.1.4 → 0.1.6

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 (193) hide show
  1. package/README.md +6 -3
  2. package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
  3. package/dist/astro/index.d.mts +18 -9
  4. package/dist/astro/index.mjs +238 -16
  5. package/dist/astro/middleware/auth.d.mts +16 -5
  6. package/dist/astro/middleware/auth.mjs +74 -37
  7. package/dist/astro/middleware/redirect.mjs +24 -8
  8. package/dist/astro/middleware/request-context.mjs +18 -5
  9. package/dist/astro/middleware/setup.mjs +1 -1
  10. package/dist/astro/middleware.mjs +411 -169
  11. package/dist/astro/types.d.mts +25 -8
  12. package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
  13. package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
  14. package/dist/cache-BdSY-gQN.mjs +42 -0
  15. package/dist/chunks--4F8ddV4.mjs +18 -0
  16. package/dist/cli/index.mjs +935 -15
  17. package/dist/client/external-auth-headers.d.mts +1 -1
  18. package/dist/client/index.d.mts +11 -3
  19. package/dist/client/index.mjs +4 -3
  20. package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
  21. package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
  22. package/dist/database/instrumentation.d.mts +34 -0
  23. package/dist/database/instrumentation.mjs +53 -0
  24. package/dist/db/index.d.mts +3 -3
  25. package/dist/db/index.mjs +2 -2
  26. package/dist/db/libsql.d.mts +1 -1
  27. package/dist/db/libsql.mjs +11 -5
  28. package/dist/db/postgres.d.mts +1 -1
  29. package/dist/db/sqlite.d.mts +1 -1
  30. package/dist/db/sqlite.mjs +7 -1
  31. package/dist/db-errors-CEqD7qH9.mjs +23 -0
  32. package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
  33. package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
  34. package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
  35. package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
  36. package/dist/index.d.mts +11 -11
  37. package/dist/index.mjs +24 -22
  38. package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
  39. package/dist/media/index.d.mts +1 -1
  40. package/dist/media/index.mjs +1 -1
  41. package/dist/media/local-runtime.d.mts +7 -7
  42. package/dist/page/index.d.mts +10 -2
  43. package/dist/page/index.mjs +22 -1
  44. package/dist/patterns-CrCYkMBb.mjs +92 -0
  45. package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
  46. package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
  47. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  48. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  49. package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
  50. package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
  51. package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
  52. package/dist/request-cache-Dk5qPSOx.mjs +66 -0
  53. package/dist/request-context.d.mts +4 -16
  54. package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
  55. package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
  56. package/dist/runtime.d.mts +6 -6
  57. package/dist/runtime.mjs +2 -2
  58. package/dist/{search-BNruJHDL.mjs → search-BApX1xhM.mjs} +570 -424
  59. package/dist/seed/index.d.mts +2 -2
  60. package/dist/seed/index.mjs +11 -10
  61. package/dist/seo/index.d.mts +1 -1
  62. package/dist/storage/local.d.mts +1 -1
  63. package/dist/storage/local.mjs +1 -1
  64. package/dist/storage/s3.d.mts +11 -3
  65. package/dist/storage/s3.mjs +78 -15
  66. package/dist/taxonomies-1s5PaS_8.mjs +266 -0
  67. package/dist/transaction-Cn2rjY78.mjs +27 -0
  68. package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
  69. package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
  70. package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
  71. package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
  72. package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
  73. package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
  74. package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
  75. package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
  76. package/dist/version-hmtC3Cmv.mjs +6 -0
  77. package/package.json +49 -38
  78. package/src/astro/routes/admin.astro +25 -9
  79. package/src/astro/routes/api/admin/api-tokens/[id].ts +4 -0
  80. package/src/astro/routes/api/admin/api-tokens/index.ts +24 -2
  81. package/src/astro/routes/api/admin/briefing.ts +76 -0
  82. package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -0
  83. package/src/astro/routes/api/admin/bylines/index.ts +2 -0
  84. package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
  85. package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
  86. package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
  87. package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
  88. package/src/astro/routes/api/admin/context/diff.ts +35 -0
  89. package/src/astro/routes/api/admin/context/index.ts +69 -0
  90. package/src/astro/routes/api/admin/context/stale.ts +35 -0
  91. package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
  92. package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
  93. package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
  94. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +58 -17
  95. package/src/astro/routes/api/admin/oauth-clients/[id].ts +28 -1
  96. package/src/astro/routes/api/admin/oauth-clients/index.ts +25 -1
  97. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +54 -2
  98. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +54 -2
  99. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +51 -1
  100. package/src/astro/routes/api/admin/plugins/[id]/update.ts +98 -3
  101. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +72 -1
  102. package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
  103. package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
  104. package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
  105. package/src/astro/routes/api/admin/users/[id]/disable.ts +26 -23
  106. package/src/astro/routes/api/admin/users/[id]/index.ts +41 -21
  107. package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
  108. package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
  109. package/src/astro/routes/api/auth/passkey/options.ts +2 -1
  110. package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
  111. package/src/astro/routes/api/auth/signup/request.ts +20 -8
  112. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +3 -4
  113. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
  114. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +16 -2
  115. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +16 -0
  116. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +9 -0
  117. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
  118. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +45 -1
  119. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +12 -2
  120. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
  121. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +24 -0
  122. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +3 -0
  123. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +20 -0
  124. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +13 -0
  125. package/src/astro/routes/api/content/[collection]/[id].ts +36 -0
  126. package/src/astro/routes/api/content/[collection]/index.ts +48 -4
  127. package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
  128. package/src/astro/routes/api/health.ts +54 -0
  129. package/src/astro/routes/api/import/wordpress/analyze.ts +2 -10
  130. package/src/astro/routes/api/import/wordpress/execute.ts +40 -6
  131. package/src/astro/routes/api/import/wordpress/prepare.ts +36 -5
  132. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +33 -1
  133. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +3 -3
  134. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +57 -15
  135. package/src/astro/routes/api/manifest.ts +13 -1
  136. package/src/astro/routes/api/mcp.ts +1 -0
  137. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
  138. package/src/astro/routes/api/media/upload-url.ts +11 -2
  139. package/src/astro/routes/api/media.ts +9 -7
  140. package/src/astro/routes/api/menus/[name]/items.ts +124 -5
  141. package/src/astro/routes/api/menus/[name]/reorder.ts +47 -1
  142. package/src/astro/routes/api/menus/[name].ts +84 -4
  143. package/src/astro/routes/api/menus/index.ts +46 -2
  144. package/src/astro/routes/api/oauth/authorize.ts +21 -8
  145. package/src/astro/routes/api/oauth/device/code.ts +2 -1
  146. package/src/astro/routes/api/oauth/device/token.ts +2 -1
  147. package/src/astro/routes/api/oauth/register.ts +182 -0
  148. package/src/astro/routes/api/oauth/token.ts +18 -7
  149. package/src/astro/routes/api/openapi.json.ts +3 -2
  150. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +21 -4
  151. package/src/astro/routes/api/redirects/[id].ts +103 -4
  152. package/src/astro/routes/api/redirects/index.ts +50 -2
  153. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +28 -0
  154. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +15 -0
  155. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +13 -0
  156. package/src/astro/routes/api/schema/collections/[slug]/index.ts +27 -0
  157. package/src/astro/routes/api/schema/collections/index.ts +14 -0
  158. package/src/astro/routes/api/search/index.ts +1 -0
  159. package/src/astro/routes/api/search/suggest.ts +1 -0
  160. package/src/astro/routes/api/sections/[slug].ts +123 -4
  161. package/src/astro/routes/api/sections/index.ts +57 -2
  162. package/src/astro/routes/api/settings.ts +51 -2
  163. package/src/astro/routes/api/setup/admin-verify.ts +25 -5
  164. package/src/astro/routes/api/setup/admin.ts +16 -8
  165. package/src/astro/routes/api/setup/index.ts +3 -2
  166. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +141 -4
  167. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +64 -2
  168. package/src/astro/routes/api/taxonomies/index.ts +57 -2
  169. package/src/astro/routes/api/well-known/auth.ts +3 -1
  170. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +8 -5
  171. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
  172. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +58 -16
  173. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +124 -38
  174. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +66 -20
  175. package/src/astro/routes/api/widget-areas/[name].ts +55 -7
  176. package/src/astro/routes/api/widget-areas/index.ts +56 -6
  177. package/src/components/DinewayHead.astro +15 -7
  178. package/src/components/DinewayMedia.astro +1 -1
  179. package/src/components/InlinePortableTextEditor.tsx +1 -1
  180. package/src/components/Table.astro +68 -41
  181. package/src/components/index.ts +2 -12
  182. package/src/components/marks.ts +19 -0
  183. package/LICENSE +0 -9
  184. /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
  185. /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
  186. /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
  187. /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
  188. /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
  189. /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
  190. /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
  191. /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
  192. /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
  193. /package/dist/{types-DShnjzb6.mjs → types-griIBQOQ.mjs} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dineway
2
2
 
3
- The core Dineway Agentic Web builder package - an agentic website builder for your restaurants or local businesses.
3
+ The core Dineway package the Agentic Website builder for restaurants and local businesses. Combines structured content modeling with the Model Context Protocol (MCP) to create intelligent, AI-powered sites.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,7 +10,11 @@ npm install dineway
10
10
 
11
11
  ## Features
12
12
 
13
- - **Content Management** - Collections, fields, Live Collections integration
13
+ - **MCP Server** - AI agents interact with your site's content, schema, and context through the open Model Context Protocol
14
+ - **Site Context Engine** - Versioned operational knowledge (brand voice, seasonal strategy, policies) that agents consult before acting
15
+ - **Content Management** - Structured collections, fields, Live Collections integration
16
+ - **Entity Resolution** - Natural-language references resolve to exact content items
17
+ - **Review Requests** - Human-in-the-loop approval for AI-generated changes
14
18
  - **Media Library** - Upload via signed URLs, S3-compatible storage
15
19
  - **Full-Text Search** - FTS5 with Porter stemming, per-collection config
16
20
  - **Navigation Menus** - Hierarchical menus with URL resolution
@@ -18,7 +22,6 @@ npm install dineway
18
22
  - **Widget Areas** - Content, menu, and component widgets
19
23
  - **Sections** - Reusable content blocks
20
24
  - **Plugin System** - Hooks, storage, settings, admin pages
21
- - **WordPress Import** - WXR, REST API, WordPress.com
22
25
 
23
26
  ## Quick Start
24
27
 
@@ -1,14 +1,17 @@
1
1
  import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
2
- import { t as ContentRepository } from "./content-zSgdNmnt.mjs";
2
+ import { r as RevisionRepository, t as ContentRepository } from "./content-DWi4d0rT.mjs";
3
3
  import { t as MediaRepository } from "./media-DMTr80Gv.mjs";
4
- import { i as FTSManager, n as SchemaRegistry } from "./registry-DSd1GWB8.mjs";
5
- import { t as RedirectRepository } from "./redirect-JPqLAbxa.mjs";
6
- import { t as BylineRepository } from "./byline-DeWCMU_i.mjs";
7
- import { n as getDb } from "./loader-qKmo0wAY.mjs";
8
- import { t as validateSeed } from "./validate-CXnRKfJK.mjs";
4
+ import { t as withTransaction } from "./transaction-Cn2rjY78.mjs";
5
+ import { t as RedirectRepository } from "./redirect-DnEWAkVg.mjs";
6
+ import { t as BylineRepository } from "./byline-OhH2dlRu.mjs";
7
+ import { i as FTSManager, n as SchemaRegistry } from "./registry-C0zjeB9P.mjs";
8
+ import { n as getDb } from "./loader-sMG4TZ-u.mjs";
9
+ import { n as requestCached, t as peekRequestCache } from "./request-cache-Dk5qPSOx.mjs";
10
+ import { t as validateSeed } from "./validate-BZ5wnLLp.mjs";
9
11
  import { sql } from "kysely";
10
12
  import { ulid } from "ulidx";
11
13
  import { imageSize } from "image-size";
14
+ import { lookup } from "node:dns/promises";
12
15
  import mime from "mime/lite";
13
16
 
14
17
  //#region src/database/repositories/taxonomy.ts
@@ -199,6 +202,19 @@ var OptionsRepository = class {
199
202
  await this.db.insertInto("options").values(row).onConflict((oc) => oc.column("name").doUpdateSet({ value: row.value })).execute();
200
203
  }
201
204
  /**
205
+ * Set an option value only if no row with that name exists.
206
+ *
207
+ * Returns true when the row was inserted, false when a row already existed,
208
+ * regardless of its value.
209
+ */
210
+ async setIfAbsent(name, value) {
211
+ const row = {
212
+ name,
213
+ value: JSON.stringify(value)
214
+ };
215
+ return ((await this.db.insertInto("options").values(row).onConflict((oc) => oc.column("name").doNothing()).executeTakeFirst()).numInsertedOrUpdatedRows ?? 0n) > 0n;
216
+ }
217
+ /**
202
218
  * Delete an option
203
219
  */
204
220
  async delete(name) {
@@ -300,7 +316,11 @@ async function resolveMediaReference(mediaRef, db, _storage) {
300
316
  * ```
301
317
  */
302
318
  async function getSiteSetting(key) {
303
- return getSiteSettingWithDb(key, await getDb());
319
+ const primed = peekRequestCache("siteSettings");
320
+ if (primed) return (await primed)[key];
321
+ return requestCached(`siteSetting:${key}`, async () => {
322
+ return getSiteSettingWithDb(key, await getDb());
323
+ });
304
324
  }
305
325
  /**
306
326
  * Get a single site setting by key (with explicit db)
@@ -330,7 +350,9 @@ async function getSiteSettingWithDb(key, db, storage = null) {
330
350
  * ```
331
351
  */
332
352
  async function getSiteSettings() {
333
- return getSiteSettingsWithDb(await getDb());
353
+ return requestCached("siteSettings", async () => {
354
+ return getSiteSettingsWithDb(await getDb());
355
+ });
334
356
  }
335
357
  /**
336
358
  * Get all site settings (with explicit db)
@@ -432,6 +454,9 @@ const IPV4_MAPPED_IPV6_DOTTED_PATTERN = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i;
432
454
  const IPV4_MAPPED_IPV6_HEX_PATTERN = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
433
455
  const IPV4_TRANSLATED_HEX_PATTERN = /^::ffff:0:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
434
456
  const IPV6_EXPANDED_MAPPED_PATTERN = /^0{0,4}:0{0,4}:0{0,4}:0{0,4}:0{0,4}:ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
457
+ const IPV6_ULA_FC_PATTERN = /^fc[0-9a-f]{2}:/;
458
+ const IPV6_ULA_FD_PATTERN = /^fd[0-9a-f]{2}:/;
459
+ const TRAILING_DOT_PATTERN = /\.+$/;
435
460
  /**
436
461
  * IPv4-compatible (deprecated) addresses: ::XXXX:XXXX
437
462
  *
@@ -487,10 +512,19 @@ const BLOCKED_HOSTNAMES = new Set([
487
512
  "localhost",
488
513
  "metadata.google.internal",
489
514
  "metadata.google",
490
- "[::1]"
515
+ "::1"
491
516
  ]);
517
+ const BLOCKED_HOSTNAME_SUFFIXES = [
518
+ "nip.io",
519
+ "sslip.io",
520
+ "xip.io",
521
+ "traefik.me",
522
+ "lvh.me",
523
+ "localtest.me"
524
+ ];
492
525
  /** Blocked URL schemes */
493
526
  const ALLOWED_SCHEMES = new Set(["http:", "https:"]);
527
+ let defaultDnsResolver = null;
494
528
  function ip4ToNum(a, b, c, d) {
495
529
  return (a << 24 | b << 16 | c << 8 | d) >>> 0;
496
530
  }
@@ -501,6 +535,9 @@ function parseIpv4(ip) {
501
535
  if (nums.some((n) => isNaN(n) || n < 0 || n > 255)) return null;
502
536
  return ip4ToNum(nums[0], nums[1], nums[2], nums[3]);
503
537
  }
538
+ function getNormalizedHostname(parsed) {
539
+ return parsed.hostname.replace(IPV6_BRACKET_PATTERN, "").toLowerCase().replace(TRAILING_DOT_PATTERN, "");
540
+ }
504
541
  /**
505
542
  * Convert IPv4-mapped/translated IPv6 addresses from hex form back to IPv4.
506
543
  *
@@ -524,14 +561,18 @@ function normalizeIPv6MappedToIPv4(ip) {
524
561
  return null;
525
562
  }
526
563
  function isPrivateIp(ip) {
527
- if (ip === "::1" || ip === "::ffff:127.0.0.1") return true;
528
- const hexIpv4 = normalizeIPv6MappedToIPv4(ip);
564
+ const normalized = ip.toLowerCase();
565
+ if (normalized === "::1" || normalized === "::ffff:127.0.0.1") return true;
566
+ const hexIpv4 = normalizeIPv6MappedToIPv4(normalized);
529
567
  if (hexIpv4) return isPrivateIp(hexIpv4);
530
- const v4Match = ip.match(IPV4_MAPPED_IPV6_DOTTED_PATTERN);
531
- const num = parseIpv4(v4Match ? v4Match[1] : ip);
532
- if (num === null) return ip.startsWith("fe80:") || ip.startsWith("fc") || ip.startsWith("fd");
568
+ const v4Match = normalized.match(IPV4_MAPPED_IPV6_DOTTED_PATTERN);
569
+ const num = parseIpv4(v4Match ? v4Match[1] : normalized);
570
+ if (num === null) return normalized.startsWith("fe80:") || IPV6_ULA_FC_PATTERN.test(normalized) || IPV6_ULA_FD_PATTERN.test(normalized);
533
571
  return BLOCKED_PATTERNS.some((range) => num >= range.start && num <= range.end);
534
572
  }
573
+ function isIpLiteral(hostname) {
574
+ return parseIpv4(hostname) !== null || hostname.includes(":");
575
+ }
535
576
  /**
536
577
  * Error thrown when SSRF protection blocks a URL.
537
578
  */
@@ -550,10 +591,8 @@ var SsrfError = class extends Error {
550
591
  * 2. Hostname is not a known internal name (localhost, metadata endpoints)
551
592
  * 3. If hostname is an IP literal, it's not in a private range
552
593
  *
553
- * Note: DNS rebinding attacks are not fully mitigated (hostname could resolve
554
- * to a private IP). Full protection requires resolving DNS and checking the IP
555
- * before connecting, which needs a custom fetch implementation. This covers
556
- * the most common SSRF vectors.
594
+ * Use resolveAndValidateExternalUrl() or ssrfSafeFetch() before network I/O so
595
+ * hostnames are also checked after DNS resolution.
557
596
  *
558
597
  * @throws SsrfError if the URL targets an internal address
559
598
  */
@@ -567,11 +606,40 @@ function validateExternalUrl(url) {
567
606
  throw new SsrfError("Invalid URL");
568
607
  }
569
608
  if (!ALLOWED_SCHEMES.has(parsed.protocol)) throw new SsrfError(`Scheme '${parsed.protocol}' is not allowed`);
570
- const hostname = parsed.hostname.replace(IPV6_BRACKET_PATTERN, "");
571
- if (BLOCKED_HOSTNAMES.has(hostname.toLowerCase())) throw new SsrfError("URLs targeting internal hosts are not allowed");
609
+ const hostname = getNormalizedHostname(parsed);
610
+ if (BLOCKED_HOSTNAMES.has(hostname)) throw new SsrfError("URLs targeting internal hosts are not allowed");
611
+ for (const suffix of BLOCKED_HOSTNAME_SUFFIXES) if (hostname === suffix || hostname.endsWith(`.${suffix}`)) throw new SsrfError("URLs targeting wildcard DNS services are not allowed");
572
612
  if (isPrivateIp(hostname)) throw new SsrfError("URLs targeting private IP addresses are not allowed");
573
613
  return parsed;
574
614
  }
615
+ const nodeDnsResolver = async (hostname) => {
616
+ return (await lookup(hostname, { all: true })).map((entry) => entry.address);
617
+ };
618
+ /**
619
+ * Validate a URL and resolve hostnames before network I/O.
620
+ *
621
+ * Fails closed if DNS resolution fails, returns no addresses, or any resolved
622
+ * address targets a private/internal range.
623
+ */
624
+ async function resolveAndValidateExternalUrl(url, options = {}) {
625
+ const parsed = validateExternalUrl(url);
626
+ const hostname = getNormalizedHostname(parsed);
627
+ if (isIpLiteral(hostname)) return parsed;
628
+ const resolver = options.resolver ?? defaultDnsResolver ?? nodeDnsResolver;
629
+ let addresses;
630
+ try {
631
+ addresses = await resolver(hostname);
632
+ } catch {
633
+ throw new SsrfError(`Could not resolve hostname: ${hostname}`);
634
+ }
635
+ if (addresses.length === 0) throw new SsrfError("Hostname resolved to no addresses");
636
+ for (const address of addresses) {
637
+ const normalizedAddress = address.replace(IPV6_BRACKET_PATTERN, "").toLowerCase();
638
+ if (!isIpLiteral(normalizedAddress)) throw new SsrfError("Hostname resolved to an invalid address");
639
+ if (isPrivateIp(normalizedAddress)) throw new SsrfError("Hostname resolves to a private IP address");
640
+ }
641
+ return parsed;
642
+ }
575
643
  /**
576
644
  * Fetch a URL with SSRF protection on redirects.
577
645
  *
@@ -588,11 +656,11 @@ const CREDENTIAL_HEADERS = [
588
656
  "cookie",
589
657
  "proxy-authorization"
590
658
  ];
591
- async function ssrfSafeFetch(url, init) {
659
+ async function ssrfSafeFetch(url, init, options = {}) {
592
660
  let currentUrl = url;
593
661
  let currentInit = init;
594
662
  for (let i = 0; i <= MAX_REDIRECTS; i++) {
595
- validateExternalUrl(currentUrl);
663
+ await resolveAndValidateExternalUrl(currentUrl, options);
596
664
  const response = await globalThis.fetch(currentUrl, {
597
665
  ...currentInit,
598
666
  redirect: "manual"
@@ -871,7 +939,6 @@ async function applySeed(db, seed, options = {}) {
871
939
  }
872
940
  if (includeContent && seed.content) {
873
941
  const contentRepo = new ContentRepository(db);
874
- const bylineRepo = new BylineRepository(db);
875
942
  for (const [collectionSlug, entries] of Object.entries(seed.content)) for (const entry of entries) {
876
943
  const existing = await contentRepo.findBySlug(collectionSlug, entry.slug, entry.locale);
877
944
  if (existing) {
@@ -879,14 +946,28 @@ async function applySeed(db, seed, options = {}) {
879
946
  if (onConflict === "update") {
880
947
  const resolvedData = await resolveReferences(entry.data, seedIdMap, mediaContext, result);
881
948
  const status = entry.status || "published";
882
- await contentRepo.update(collectionSlug, existing.id, {
883
- status,
884
- data: resolvedData
949
+ await withTransaction(db, async (trx) => {
950
+ const trxContentRepo = new ContentRepository(trx);
951
+ const trxBylineRepo = new BylineRepository(trx);
952
+ const trxRevisionRepo = new RevisionRepository(trx);
953
+ await trxContentRepo.update(collectionSlug, existing.id, {
954
+ status,
955
+ data: resolvedData
956
+ });
957
+ await applyContentBylines(trxBylineRepo, collectionSlug, existing.id, entry, seedBylineIdMap, true);
958
+ await applyContentTaxonomies(trx, collectionSlug, existing.id, entry, true);
959
+ if (status === "published") {
960
+ const draft = await trxRevisionRepo.create({
961
+ collection: collectionSlug,
962
+ entryId: existing.id,
963
+ data: resolvedData
964
+ });
965
+ await trxContentRepo.setDraftRevision(collectionSlug, existing.id, draft.id);
966
+ await trxContentRepo.publish(collectionSlug, existing.id);
967
+ }
885
968
  });
886
969
  seedIdMap.set(entry.id, existing.id);
887
970
  result.content.updated++;
888
- await applyContentBylines(bylineRepo, collectionSlug, existing.id, entry, seedBylineIdMap, true);
889
- await applyContentTaxonomies(db, collectionSlug, existing.id, entry, true);
890
971
  continue;
891
972
  }
892
973
  result.content.skipped++;
@@ -901,19 +982,25 @@ async function applySeed(db, seed, options = {}) {
901
982
  else translationOf = sourceId;
902
983
  }
903
984
  const status = entry.status || "published";
904
- const created = await contentRepo.create({
905
- type: collectionSlug,
906
- slug: entry.slug,
907
- status,
908
- data: resolvedData,
909
- locale: entry.locale,
910
- translationOf,
911
- publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : null
985
+ const created = await withTransaction(db, async (trx) => {
986
+ const trxContentRepo = new ContentRepository(trx);
987
+ const trxBylineRepo = new BylineRepository(trx);
988
+ const item = await trxContentRepo.create({
989
+ type: collectionSlug,
990
+ slug: entry.slug,
991
+ status,
992
+ data: resolvedData,
993
+ locale: entry.locale,
994
+ translationOf,
995
+ publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : null
996
+ });
997
+ await applyContentBylines(trxBylineRepo, collectionSlug, item.id, entry, seedBylineIdMap);
998
+ await applyContentTaxonomies(trx, collectionSlug, item.id, entry, false);
999
+ if (status === "published") await trxContentRepo.publish(collectionSlug, item.id);
1000
+ return item;
912
1001
  });
913
1002
  seedIdMap.set(entry.id, created.id);
914
1003
  result.content.created++;
915
- await applyContentBylines(bylineRepo, collectionSlug, created.id, entry, seedBylineIdMap);
916
- await applyContentTaxonomies(db, collectionSlug, created.id, entry, false);
917
1004
  }
918
1005
  }
919
1006
  if (seed.menus) for (const menu of seed.menus) {
@@ -1033,6 +1120,12 @@ async function applySeed(db, seed, options = {}) {
1033
1120
  }
1034
1121
  }
1035
1122
  }
1123
+ const { invalidateBylineCache } = await import("./bylines-BGpD9_hy.mjs").then((n) => n.t);
1124
+ const { invalidateUrlPatternCache } = await import("./query-kDmwCsHh.mjs").then((n) => n.o);
1125
+ const { invalidateRedirectCache } = await import("./cache-BdSY-gQN.mjs").then((n) => n.t);
1126
+ invalidateBylineCache();
1127
+ invalidateUrlPatternCache();
1128
+ invalidateRedirectCache();
1036
1129
  return result;
1037
1130
  }
1038
1131
  /**
@@ -1336,4 +1429,4 @@ function getImageDimensions(buffer) {
1336
1429
  }
1337
1430
 
1338
1431
  //#endregion
1339
- export { stripCredentialHeaders as a, getPluginSettings as c, setSiteSettings as d, OptionsRepository as f, ssrfSafeFetch as i, getSiteSetting as l, apply_exports as n, validateExternalUrl as o, TaxonomyRepository as p, SsrfError as r, getPluginSetting as s, applySeed as t, getSiteSettings as u };
1432
+ export { ssrfSafeFetch as a, getPluginSetting as c, getSiteSettings as d, setSiteSettings as f, resolveAndValidateExternalUrl as i, getPluginSettings as l, TaxonomyRepository as m, apply_exports as n, stripCredentialHeaders as o, OptionsRepository as p, SsrfError as r, validateExternalUrl as s, applySeed as t, getSiteSetting as u };
@@ -1,9 +1,9 @@
1
- import "../types-DkvMXalq.mjs";
2
- import { Cn as DinewayConfig, Dn as S3StorageConfig, En as LocalStorageConfig, On as StorageDescriptor, Tn as getStoredConfig, la as MediaItem } from "../index-C-jx21qs.mjs";
3
- import "../runner-B5l1JfOj.mjs";
4
- import { r as ContentItem } from "../types-CLLdsG3g.mjs";
5
- import { J as ResolvedPlugin } from "../types-D38djUXv.mjs";
6
- import "../validate-DVKJJ-M_.mjs";
1
+ import "../types-DOrVigru.mjs";
2
+ import { Cn as DinewayConfig, Dn as S3StorageConfig, En as LocalStorageConfig, On as StorageDescriptor, Tn as getStoredConfig, fa as MediaItem } from "../index-yvc6E_17.mjs";
3
+ import "../runner-CFI6B6J2.mjs";
4
+ import { r as ContentItem } from "../types-BzcUjoqg.mjs";
5
+ import { X as ResolvedPlugin } from "../types-Cj0KMIZV.mjs";
6
+ import "../validate-IPf8n4Fj.mjs";
7
7
  import { DinewayHandlers, DinewayManifest, ManifestCollection } from "./types.mjs";
8
8
  import { AstroIntegration } from "astro";
9
9
 
@@ -12,19 +12,28 @@ import { AstroIntegration } from "astro";
12
12
  * S3-compatible storage adapter
13
13
  *
14
14
  * Works with AWS S3, MinIO, and other S3-compatible object stores.
15
+ * Any omitted field is resolved from the matching `S3_*` environment
16
+ * variable when the Node process starts. Explicit values take precedence.
15
17
  *
16
18
  * @example
17
19
  * ```ts
20
+ * // All fields from runtime env
21
+ * storage: s3()
22
+ *
23
+ * // Mix explicit CDN config with endpoint/bucket/credentials from env
24
+ * storage: s3({ publicUrl: "https://cdn.example.com" })
25
+ *
26
+ * // All explicit values
18
27
  * storage: s3({
19
28
  * endpoint: process.env.S3_ENDPOINT!,
20
29
  * bucket: "media",
21
- * accessKeyId: process.env.S3_ACCESS_KEY_ID!,
22
- * secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
30
+ * accessKeyId: process.env.S3_ACCESS_KEY_ID,
31
+ * secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
23
32
  * publicUrl: "https://cdn.example.com", // optional CDN
24
33
  * })
25
34
  * ```
26
35
  */
27
- declare function s3(config: S3StorageConfig): StorageDescriptor;
36
+ declare function s3(config?: Partial<S3StorageConfig>): StorageDescriptor;
28
37
  /**
29
38
  * Local filesystem storage adapter
30
39
  *