dineway 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/{apply-CAPvMfoU.mjs → apply-iVSqz2qs.mjs} +132 -39
- package/dist/astro/index.d.mts +18 -9
- package/dist/astro/index.mjs +238 -16
- package/dist/astro/middleware/auth.d.mts +16 -5
- package/dist/astro/middleware/auth.mjs +74 -37
- package/dist/astro/middleware/redirect.mjs +24 -8
- package/dist/astro/middleware/request-context.mjs +18 -5
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +411 -169
- package/dist/astro/types.d.mts +25 -8
- package/dist/{byline-DeWCMU_i.mjs → byline-OhH2dlRu.mjs} +6 -21
- package/dist/{bylines-DyqBV9EQ.mjs → bylines-BGpD9_hy.mjs} +16 -6
- package/dist/cache-BdSY-gQN.mjs +42 -0
- package/dist/chunks--4F8ddV4.mjs +18 -0
- package/dist/cli/index.mjs +935 -15
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +11 -3
- package/dist/client/index.mjs +4 -3
- package/dist/{connection-C9pxzuag.mjs → connection-BCNICDWN.mjs} +22 -5
- package/dist/{content-zSgdNmnt.mjs → content-DWi4d0rT.mjs} +41 -2
- package/dist/database/instrumentation.d.mts +34 -0
- package/dist/database/instrumentation.mjs +53 -0
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.mjs +11 -5
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.mjs +7 -1
- package/dist/db-errors-CEqD7qH9.mjs +23 -0
- package/dist/{default-WYlzADZL.mjs → default-VjJyuuG9.mjs} +2 -0
- package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +3 -0
- package/dist/{error-DrxtnGPg.mjs → error-BmL6QipT.mjs} +7 -3
- package/dist/{index-C-jx21qs.d.mts → index-yvc6E_17.d.mts} +157 -30
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +24 -22
- package/dist/{loader-qKmo0wAY.mjs → loader-sMG4TZ-u.mjs} +9 -3
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/page/index.d.mts +10 -2
- package/dist/page/index.mjs +22 -1
- package/dist/patterns-CrCYkMBb.mjs +92 -0
- package/dist/{placeholder-bOx1xCTY.d.mts → placeholder--wOi4TbO.d.mts} +1 -1
- package/dist/{placeholder-B3knXwNc.mjs → placeholder-Cp8g5Emj.mjs} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-BiaPl_g2.mjs → query-kDmwCsHh.mjs} +118 -50
- package/dist/{redirect-JPqLAbxa.mjs → redirect-DnEWAkVg.mjs} +43 -99
- package/dist/{registry-DSd1GWB8.mjs → registry-C0zjeB9P.mjs} +191 -123
- package/dist/request-cache-Dk5qPSOx.mjs +66 -0
- package/dist/request-context.d.mts +4 -16
- package/dist/{runner-B5l1JfOj.d.mts → runner-CFI6B6J2.d.mts} +1 -1
- package/dist/{runner-BGUGywgG.mjs → runner-DWZm2KQm.mjs} +589 -137
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +2 -2
- package/dist/{search-BNruJHDL.mjs → search-ByRGV2pq.mjs} +570 -424
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +11 -10
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +11 -3
- package/dist/storage/s3.mjs +78 -15
- package/dist/taxonomies-1s5PaS_8.mjs +266 -0
- package/dist/transaction-Cn2rjY78.mjs +27 -0
- package/dist/{types-BgQeVaPj.d.mts → types-BuMDPy5C.d.mts} +52 -3
- package/dist/{types-DuNbGKjF.mjs → types-COeOq9nK.mjs} +6 -1
- package/dist/{types-ju-_ORz7.d.mts → types-CWbdtiux.d.mts} +13 -5
- package/dist/{types-D38djUXv.d.mts → types-Cj0KMIZV.d.mts} +16 -3
- package/dist/{types-DkvMXalq.d.mts → types-DOrVigru.d.mts} +159 -0
- package/dist/{validate-CXnRKfJK.mjs → validate-BZ5wnLLp.mjs} +2 -1
- package/dist/{validate-DVKJJ-M_.d.mts → validate-IPf8n4Fj.d.mts} +4 -51
- package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +10 -10
- package/dist/version-BKXPsfmJ.mjs +6 -0
- package/package.json +49 -38
- package/src/astro/routes/admin.astro +25 -9
- package/src/astro/routes/api/admin/api-tokens/[id].ts +4 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +24 -2
- package/src/astro/routes/api/admin/briefing.ts +76 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +3 -0
- package/src/astro/routes/api/admin/bylines/index.ts +2 -0
- package/src/astro/routes/api/admin/context/[id]/history.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/context/[id]/review.ts +57 -0
- package/src/astro/routes/api/admin/context/[id]/supersede.ts +58 -0
- package/src/astro/routes/api/admin/context/diff.ts +35 -0
- package/src/astro/routes/api/admin/context/index.ts +69 -0
- package/src/astro/routes/api/admin/context/stale.ts +35 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/hitl-requests/[id]/resolve.ts +54 -0
- package/src/astro/routes/api/admin/hitl-requests/index.ts +38 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +58 -17
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +28 -1
- package/src/astro/routes/api/admin/oauth-clients/index.ts +25 -1
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +54 -2
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +51 -1
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +98 -3
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +72 -1
- package/src/astro/routes/api/admin/review-requests/[id]/index.ts +35 -0
- package/src/astro/routes/api/admin/review-requests/[id]/resolve.ts +52 -0
- package/src/astro/routes/api/admin/review-requests/index.ts +35 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +26 -23
- package/src/astro/routes/api/admin/users/[id]/index.ts +41 -21
- package/src/astro/routes/api/auth/invite/register-options.ts +73 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +2 -1
- package/src/astro/routes/api/auth/passkey/options.ts +2 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +5 -1
- package/src/astro/routes/api/auth/signup/request.ts +20 -8
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +3 -4
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +16 -2
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +16 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +9 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +45 -1
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +12 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +1 -1
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +24 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +3 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +20 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +13 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +36 -0
- package/src/astro/routes/api/content/[collection]/index.ts +48 -4
- package/src/astro/routes/api/content/[collection]/trash.ts +1 -1
- package/src/astro/routes/api/health.ts +54 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +2 -10
- package/src/astro/routes/api/import/wordpress/execute.ts +40 -6
- package/src/astro/routes/api/import/wordpress/prepare.ts +36 -5
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +33 -1
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +3 -3
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +57 -15
- package/src/astro/routes/api/manifest.ts +13 -1
- package/src/astro/routes/api/mcp.ts +1 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
- package/src/astro/routes/api/media/upload-url.ts +11 -2
- package/src/astro/routes/api/media.ts +9 -7
- package/src/astro/routes/api/menus/[name]/items.ts +124 -5
- package/src/astro/routes/api/menus/[name]/reorder.ts +47 -1
- package/src/astro/routes/api/menus/[name].ts +84 -4
- package/src/astro/routes/api/menus/index.ts +46 -2
- package/src/astro/routes/api/oauth/authorize.ts +21 -8
- package/src/astro/routes/api/oauth/device/code.ts +2 -1
- package/src/astro/routes/api/oauth/device/token.ts +2 -1
- package/src/astro/routes/api/oauth/register.ts +182 -0
- package/src/astro/routes/api/oauth/token.ts +18 -7
- package/src/astro/routes/api/openapi.json.ts +3 -2
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +21 -4
- package/src/astro/routes/api/redirects/[id].ts +103 -4
- package/src/astro/routes/api/redirects/index.ts +50 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +28 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +15 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +13 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +27 -0
- package/src/astro/routes/api/schema/collections/index.ts +14 -0
- package/src/astro/routes/api/search/index.ts +1 -0
- package/src/astro/routes/api/search/suggest.ts +1 -0
- package/src/astro/routes/api/sections/[slug].ts +123 -4
- package/src/astro/routes/api/sections/index.ts +57 -2
- package/src/astro/routes/api/settings.ts +51 -2
- package/src/astro/routes/api/setup/admin-verify.ts +25 -5
- package/src/astro/routes/api/setup/admin.ts +16 -8
- package/src/astro/routes/api/setup/index.ts +3 -2
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +141 -4
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +64 -2
- package/src/astro/routes/api/taxonomies/index.ts +57 -2
- package/src/astro/routes/api/well-known/auth.ts +3 -1
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +8 -5
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +58 -16
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +124 -38
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +66 -20
- package/src/astro/routes/api/widget-areas/[name].ts +55 -7
- package/src/astro/routes/api/widget-areas/index.ts +56 -6
- package/src/components/DinewayHead.astro +15 -7
- package/src/components/DinewayMedia.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +1 -1
- package/src/components/Table.astro +68 -41
- package/src/components/index.ts +2 -12
- package/src/components/marks.ts +19 -0
- package/LICENSE +0 -9
- /package/dist/{adapters-BlzWJG82.d.mts → adapters-C2ypTrZZ.d.mts} +0 -0
- /package/dist/{config-Cq8H0SfX.mjs → config-BXwuX8Bx.mjs} +0 -0
- /package/dist/{load-C6FCD1FU.mjs → load-Coc9HpHH.mjs} +0 -0
- /package/dist/{manifest-schema-CTSEyIJ3.mjs → manifest-schema-D1MSVnoI.mjs} +0 -0
- /package/dist/{mode-BlyYtIFO.mjs → mode-47goXBBK.mjs} +0 -0
- /package/dist/{tokens-4vgYuXsZ.mjs → tokens-CJz9ubV6.mjs} +0 -0
- /package/dist/{transport-C5FYnid7.mjs → transport-DB5eDN4x.mjs} +0 -0
- /package/dist/{transport-gIL-e43D.d.mts → transport-Wge_IzKl.d.mts} +0 -0
- /package/dist/{types-CLLdsG3g.d.mts → types-BzcUjoqg.d.mts} +0 -0
- /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
|
|
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
|
-
- **
|
|
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-
|
|
2
|
+
import { r as RevisionRepository, t as ContentRepository } from "./content-DWi4d0rT.mjs";
|
|
3
3
|
import { t as MediaRepository } from "./media-DMTr80Gv.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { t as RedirectRepository } from "./redirect-
|
|
6
|
-
import { t as BylineRepository } from "./byline-
|
|
7
|
-
import { n as
|
|
8
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
528
|
-
|
|
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 =
|
|
531
|
-
const num = parseIpv4(v4Match ? v4Match[1] :
|
|
532
|
-
if (num === null) return
|
|
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
|
-
*
|
|
554
|
-
*
|
|
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
|
|
571
|
-
if (BLOCKED_HOSTNAMES.has(hostname
|
|
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
|
-
|
|
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
|
|
883
|
-
|
|
884
|
-
|
|
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
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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 {
|
|
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 };
|
package/dist/astro/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "../types-
|
|
2
|
-
import { Cn as DinewayConfig, Dn as S3StorageConfig, En as LocalStorageConfig, On as StorageDescriptor, Tn as getStoredConfig,
|
|
3
|
-
import "../runner-
|
|
4
|
-
import { r as ContentItem } from "../types-
|
|
5
|
-
import {
|
|
6
|
-
import "../validate-
|
|
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
|
|
36
|
+
declare function s3(config?: Partial<S3StorageConfig>): StorageDescriptor;
|
|
28
37
|
/**
|
|
29
38
|
* Local filesystem storage adapter
|
|
30
39
|
*
|