dineway 0.1.48 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activity-events-C9u37D5z.mjs → activity-events-y6eT8UW6.mjs} +6 -38
- package/dist/actor-CrObAPV0.mjs +37 -0
- package/dist/after-BfniwgIf.mjs +9 -0
- package/dist/{allowed-origins-DhipQNLb.mjs → allowed-origins-_oeyP6x-.mjs} +1 -1
- package/dist/api/route-utils.d.mts +10 -10
- package/dist/api/route-utils.mjs +17 -14
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -4
- package/dist/{api-Eiy8_zVZ.mjs → api-ByrU2q-Z.mjs} +61 -37
- package/dist/{api-tokens-DtvdtIkm.mjs → api-tokens-CjVMQT-X.mjs} +6 -2
- package/dist/{apply-CX3no7XE.mjs → apply-bZswmZPm.mjs} +117 -77
- package/dist/astro/image-endpoint.d.mts +7 -0
- package/dist/astro/image-endpoint.mjs +54 -0
- package/dist/astro/index.d.mts +35 -11
- package/dist/astro/index.mjs +157 -23
- package/dist/astro/integration/shims/use-sync-external-store-with-selector.js +71 -0
- package/dist/astro/integration/shims/use-sync-external-store.js +1 -0
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.mjs +42 -24
- package/dist/astro/middleware/redirect.mjs +14 -5
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/seed.d.mts +1 -1
- package/dist/astro/middleware/seed.mjs +2 -2
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.mjs +334 -193
- package/dist/astro/routes/admin.astro +6 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -6
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -6
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
- package/dist/astro/routes/api/admin/briefing.mjs +22 -18
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +9 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +9 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +9 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +18 -15
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +18 -15
- package/dist/astro/routes/api/admin/bylines/index.mjs +18 -15
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +15 -13
- package/dist/astro/routes/api/admin/comments/_id_.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +12 -10
- package/dist/astro/routes/api/admin/comments/counts.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/index.mjs +12 -10
- package/dist/astro/routes/api/admin/context/_id_/history.mjs +27 -25
- package/dist/astro/routes/api/admin/context/_id_/index.mjs +27 -25
- package/dist/astro/routes/api/admin/context/_id_/review.mjs +32 -29
- package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +32 -29
- package/dist/astro/routes/api/admin/context/diff.mjs +31 -28
- package/dist/astro/routes/api/admin/context/index.mjs +32 -29
- package/dist/astro/routes/api/admin/context/stale.mjs +31 -28
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +27 -25
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +31 -28
- package/dist/astro/routes/api/admin/hitl-requests/index.mjs +31 -28
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +28 -26
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +6 -6
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +56 -52
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +56 -52
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +48 -44
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +54 -50
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +53 -49
- package/dist/astro/routes/api/admin/plugins/index.mjs +48 -44
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +47 -43
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +53 -49
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +47 -43
- package/dist/astro/routes/api/admin/plugins/updates.mjs +47 -43
- package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +6 -6
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +32 -28
- package/dist/astro/routes/api/admin/review-requests/index.mjs +11 -10
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +47 -43
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +47 -43
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +8 -7
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +4 -4
- package/dist/astro/routes/api/admin/users/index.mjs +7 -6
- package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +12 -11
- package/dist/astro/routes/api/auth/invite/index.mjs +9 -8
- package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -10
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +11 -10
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +10 -9
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +12 -11
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +10 -9
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +11 -10
- package/dist/astro/routes/api/auth/passkey/verify.mjs +11 -10
- package/dist/astro/routes/api/auth/setup-token/index.mjs +6 -6
- package/dist/astro/routes/api/auth/setup-token/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/signup/complete.mjs +12 -11
- package/dist/astro/routes/api/auth/signup/request.mjs +11 -10
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +16 -14
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.d.mts +8 -0
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +160 -0
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +12 -11
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +17 -15
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -6
- package/dist/astro/routes/api/dashboard.mjs +10 -9
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/health.mjs +1 -1
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +23 -11
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +9 -6
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +19 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +85 -38
- package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +20 -14
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +36 -33
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +18 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +121 -101
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +25 -13
- package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +56 -2
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +284 -56
- package/dist/astro/routes/api/manifest.mjs +14 -5
- package/dist/astro/routes/api/mcp.mjs +266 -70
- package/dist/astro/routes/api/media/_id_/confirm.mjs +39 -11
- package/dist/astro/routes/api/media/_id_.mjs +11 -7
- package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +12 -11
- package/dist/astro/routes/api/media.mjs +28 -35
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +32 -29
- package/dist/astro/routes/api/menus/_name_/items.mjs +32 -29
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +32 -29
- package/dist/astro/routes/api/menus/_name_/translations.mjs +32 -29
- package/dist/astro/routes/api/menus/_name_.mjs +32 -29
- package/dist/astro/routes/api/menus/index.mjs +32 -29
- package/dist/astro/routes/api/oauth/authorize.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +9 -9
- package/dist/astro/routes/api/oauth/device/token.mjs +8 -8
- package/dist/astro/routes/api/oauth/register.mjs +4 -4
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +4 -3
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +30 -30
- package/dist/astro/routes/api/redirects/404s/index.mjs +12 -11
- package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -11
- package/dist/astro/routes/api/redirects/_id_.mjs +33 -30
- package/dist/astro/routes/api/redirects/index.mjs +33 -30
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +48 -44
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +48 -44
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +48 -44
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +48 -44
- package/dist/astro/routes/api/schema/collections/index.mjs +48 -44
- package/dist/astro/routes/api/schema/index.mjs +13 -14
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +47 -43
- package/dist/astro/routes/api/schema/orphans/index.mjs +47 -43
- package/dist/astro/routes/api/search/enable.mjs +12 -11
- package/dist/astro/routes/api/search/index.mjs +11 -10
- package/dist/astro/routes/api/search/rebuild.mjs +12 -11
- package/dist/astro/routes/api/search/stats.mjs +7 -7
- package/dist/astro/routes/api/search/suggest.mjs +11 -10
- package/dist/astro/routes/api/sections/_slug_.mjs +32 -29
- package/dist/astro/routes/api/sections/index.mjs +32 -29
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +32 -29
- package/dist/astro/routes/api/setup/admin-verify.mjs +14 -13
- package/dist/astro/routes/api/setup/admin.mjs +12 -11
- package/dist/astro/routes/api/setup/dev-bypass.mjs +32 -26
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +30 -26
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +9 -8
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +35 -32
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +35 -32
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +35 -32
- package/dist/astro/routes/api/taxonomies/index.mjs +35 -32
- package/dist/astro/routes/api/themes/preview.mjs +6 -6
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +3 -3
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +3 -3
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +3 -3
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +31 -28
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +32 -29
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +32 -29
- package/dist/astro/routes/api/widget-areas/_name_.mjs +29 -27
- package/dist/astro/routes/api/widget-areas/index.mjs +32 -29
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +9 -8
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +24 -15
- package/dist/astro/routes/sitemap.xml.mjs +10 -9
- package/dist/astro/session-driver.d.mts +1 -1
- package/dist/astro/session-driver.mjs +3 -3
- package/dist/astro/types.d.mts +21 -13
- package/dist/{audit-D1cbG7xH.mjs → audit-ByVKIwWF.mjs} +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{auth-control-guard-Dr14I98m.mjs → auth-control-guard-CqeUmQsv.mjs} +1 -1
- package/dist/{authorize-BLO0m7fW.mjs → authorize-CcBxcHtz.mjs} +1 -1
- package/dist/{briefing-CObXGOGz.mjs → briefing-BJaJI8dm.mjs} +2 -1
- package/dist/{briefing-gKWiGLt1.mjs → briefing-CawZ59CY.mjs} +90 -28
- package/dist/bundle-7C_DpkEq.mjs +602 -0
- package/dist/{byline-1ntDwv8c.mjs → byline-e51-fWRs.mjs} +30 -62
- package/dist/{byline-fields-BX7rWKNU.d.mts → byline-fields-CUvbslGc.d.mts} +49 -6
- package/dist/{byline-fields-C9_iaPaM.mjs → byline-fields-D-N5qVIG.mjs} +1 -1
- package/dist/{byline-fields-B-ZkoVft.mjs → byline-fields-DOJO16Gm.mjs} +3 -3
- package/dist/{byline-registry-CUSvkasJ.mjs → byline-registry-87nNnYl4.mjs} +2 -2
- package/dist/{bylines-bC15ZqAv.mjs → bylines-CMQNCNFh.mjs} +9 -3
- package/dist/{bylines-DtxChhN_.mjs → bylines-y4LO-dTj.mjs} +5 -5
- package/dist/{cache-DbKyr60Q.mjs → cache-C-FNTFYK.mjs} +2 -2
- package/dist/{chunks-C5zp2xir.mjs → chunks-CxBt-GxR.mjs} +1 -1
- package/dist/cli/index.mjs +820 -1276
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +2 -1
- package/dist/{comment-CXDOQ93N.mjs → comment-DlA4iChg.mjs} +13 -3
- package/dist/comment-reaction-DfFao1ST.mjs +58 -0
- package/dist/{comments-hK-rdzJ9.mjs → comments-DApFGMsP.mjs} +1 -1
- package/dist/{content-QaryevFO.mjs → content-Bh4BDjv8.mjs} +51 -34
- package/dist/content-refresh-CgutSUfO.mjs +1576 -0
- package/dist/{context-CoYb_fvK.mjs → context-C5W3G18y.mjs} +111 -50
- package/dist/{context-DBL1ib4v.mjs → context-CK4ym2Sf.mjs} +2 -2
- package/dist/{context-route-helpers-CYQVmbIm.mjs → context-route-helpers-MASyGgWf.mjs} +2 -1
- package/dist/{cron-DxCAU_Sn.mjs → cron-cKcNTix_.mjs} +7 -3
- package/dist/{dashboard-DNlqsknJ.mjs → dashboard-DQkVT7Ei.mjs} +4 -4
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{device-flow-DygsFPGL.mjs → device-flow-BnqRH-T9.mjs} +5 -5
- package/dist/{placeholder-BDo81Is5.mjs → enrich-DIFIFA8O.mjs} +36 -9
- package/dist/{error-DCSuUAZW.mjs → error-CytFlgcd.mjs} +5 -0
- package/dist/field-defs-cache-DRNdhBJv.mjs +49 -0
- package/dist/{fts-manager-CIZlScg5.mjs → fts-manager-Crzypjqu.mjs} +1 -1
- package/dist/hitl-Hw1RmWOq.mjs +677 -0
- package/dist/{hitl-requests-CGQwOEiq.mjs → hitl-requests-B4Vbcmaq.mjs} +2 -2
- package/dist/{hitl-route-helpers-GSDLNPpN.mjs → hitl-route-helpers-hEVzvP8l.mjs} +5 -4
- package/dist/import-Bh9biJfx.mjs +1354 -0
- package/dist/{index-CwRuZ-u8.d.mts → index-D4y6NGiy.d.mts} +75 -3
- package/dist/index.d.mts +17 -16
- package/dist/index.mjs +81 -69
- package/dist/{jsonld-DZ67souc.d.mts → jsonld-Dq0gsnOH.d.mts} +8 -4
- package/dist/{load-xE01Jm0V.mjs → load-DmYeniXl.mjs} +1 -1
- package/dist/{loader-tg63WSY2.mjs → loader-CjWqjweO.mjs} +117 -31
- package/dist/{manifest-schema-qh2P7RDX.mjs → manifest-schema-BxVPYMJR.mjs} +34 -1
- package/dist/mcp-reviewed-executor-TwVbMzG6.mjs +555 -0
- package/dist/media/image-endpoint.d.mts +32 -0
- package/dist/media/image-endpoint.mjs +111 -0
- package/dist/media/index.d.mts +27 -2
- package/dist/media/index.mjs +5 -3
- package/dist/media/local-runtime.d.mts +16 -11
- package/dist/media/local-runtime.mjs +10 -8
- package/dist/{media-DUAjMfvR.mjs → media-DMj0grzC.mjs} +3 -1
- package/dist/{media-allowlist-BsXkT9RK.mjs → media-allowlist-BlVCSzHx.mjs} +1 -1
- package/dist/media-url-lEKSrpr9.mjs +20 -0
- package/dist/{menus-WpFRMXjz.mjs → menus-TaW1gKnk.mjs} +19 -5
- package/dist/{menus-DU1eZqw_.mjs → menus-xvsnVZXw.mjs} +80 -15
- package/dist/{normalize-DUnoXAa9.mjs → normalize-5ek_z7de.mjs} +8 -1
- package/dist/{oauth-authorization-D-1p52Je.mjs → oauth-authorization-DiaGvRVz.mjs} +5 -5
- package/dist/{oauth-clients-CzeMGyUU.mjs → oauth-clients-Dl9E8dEM.mjs} +2 -2
- package/dist/object-cache/memory.d.mts +14 -0
- package/dist/object-cache/memory.mjs +55 -0
- package/dist/object-cache-D51qOseS.mjs +419 -0
- package/dist/page/index.d.mts +4 -3
- package/dist/page/index.mjs +2 -2
- package/dist/{parse-CneSgl7-.mjs → parse-CbymENaR.mjs} +1 -1
- package/dist/{parser-DxfsARXL.mjs → parser-C1nYd-81.mjs} +151 -9
- package/dist/{placeholder-CXfoCtCd.d.mts → placeholder-uvNRZBX9.d.mts} +12 -4
- package/dist/plugin-utils.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +11 -2
- package/dist/{plugins-C57YQi4l.mjs → plugins-BolVa-tl.mjs} +832 -64
- package/dist/{transport-LSdZVXHF.mjs → portable-text-dA_UlJwW.mjs} +1 -134
- package/dist/{preview-D5rvDhRt.mjs → preview-vSVg6fwG.mjs} +4 -3
- package/dist/process-cache-7xgAO9JY.mjs +106 -0
- package/dist/{provider-loader-9it4vd6L.d.mts → provider-loader-MD8zAbnH.d.mts} +1 -1
- package/dist/{public-url-DLUM5OOv.mjs → public-url-BEz_sM9n.mjs} +24 -18
- package/dist/{public-url-B5HaoGzj.d.mts → public-url-gsp3oUbu.d.mts} +5 -4
- package/dist/{query-BYc3oruA.mjs → query-CfRCdaL3.mjs} +281 -76
- package/dist/{query-DKEwzobh.mjs → query-jaAmw8E1.mjs} +11 -6
- package/dist/{rate-limit-i0G3IAOX.mjs → rate-limit-ZLdyWoZF.mjs} +2 -2
- package/dist/{redirect-C2zXRj3c.mjs → redirect-BAjt4Z6b.mjs} +2 -2
- package/dist/{redirects-YZo_znWG.mjs → redirects-BTdwpQtE.mjs} +47 -24
- package/dist/{redirects-ocRJmQHP.mjs → redirects-CA-xylPG.mjs} +19 -11
- package/dist/{registry-CTkbIDB9.mjs → registry-CtyVedcN.mjs} +130 -77
- package/dist/request-context.d.mts +2 -0
- package/dist/{request-meta-DAb6xaP9.mjs → request-meta-CUypToDJ.mjs} +1 -1
- package/dist/{review-requests-C0un5kdV.mjs → review-requests-C9Ff11Vq.mjs} +1 -1
- package/dist/{review-requests-gGMyYYhy.mjs → review-requests-DUyEmtmG.mjs} +1 -1
- package/dist/{runner-D4FL9usy.d.mts → runner-1tC2tIRV.d.mts} +1 -1
- package/dist/runner-DB7CRfOF.mjs +6740 -0
- package/dist/{runtime-DEWQpZ-4.d.mts → runtime-BOeiQsCv.d.mts} +2028 -1564
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +4 -4
- package/dist/schema-DXdYj4R3.mjs +8 -0
- package/dist/{search-axGQ1KY3.mjs → search-DSaLgEFI.mjs} +5 -3
- package/dist/{secrets-062Izo8i.mjs → secrets-CDZo2k8L.mjs} +15 -12
- package/dist/{sections-OLKgXL10.mjs → sections-PVVXHGfp.mjs} +2 -2
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +22 -19
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/{seo-Cd45Po8L.mjs → seo-CQG3GCHQ.mjs} +5 -2
- package/dist/{seo-contributions-CqVvReEj.mjs → seo-contributions-C1EnikHB.mjs} +11 -3
- package/dist/{seo-5lJOtB5J.mjs → seo-qa7Dn0jK.mjs} +22 -4
- package/dist/{service-Bwad8s5h.mjs → service-DiN4ZuPp.mjs} +2 -2
- package/dist/session-user-CAKImna3.mjs +22 -0
- package/dist/{settings-yLjz_I4T.mjs → settings-BbqgHWpU.mjs} +28 -55
- package/dist/{settings-lAHjaUit.mjs → settings-i4VPP8bT.mjs} +2 -2
- package/dist/{setup-complete--PradA4R.mjs → setup-complete-B8fl-mjV.mjs} +1 -1
- package/dist/{site-activity-D7Xyropw.mjs → site-activity-Dy3FROs0.mjs} +1 -1
- package/dist/{site-context-DaN9Z91j.mjs → site-context-DQiL8Fl5.mjs} +23 -652
- package/dist/{site-url-DpGZo9Z6.mjs → site-url-BML3GQYm.mjs} +1 -1
- package/dist/status-DANd8ERo.mjs +21 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-uXVEoQso.mjs → taxonomies-9s-v03H-.mjs} +36 -29
- package/dist/taxonomies-DRiixp_E.mjs +443 -0
- package/dist/{taxonomy-CBmkmMuU.mjs → taxonomy-Dvsw6BOs.mjs} +44 -9
- package/dist/{tokens-CjSgb0AC.mjs → tokens-1RK1n6ED.mjs} +1 -1
- package/dist/transport-BNmCqLu7.mjs +134 -0
- package/dist/types-4garmdWa.mjs +66 -0
- package/dist/{types-DSPGWjxO.d.mts → types-C8XW6L19.d.mts} +225 -4
- package/dist/{types-GaCtZ1k9.d.mts → types-CDpH6MA4.d.mts} +18 -0
- package/dist/{types-DRIKLmJI.d.mts → types-C_ly5eTp.d.mts} +17 -2
- package/dist/types-DF7ZuGGk.d.mts +118 -0
- package/dist/types-DU0bU2Wu.d.mts +2392 -0
- package/dist/{types-CwawBUTa.d.mts → types-DW_-sFyz.d.mts} +2 -1
- package/dist/{types-DfuD0Afl.mjs → types-Dq1qT19x.mjs} +2 -2
- package/dist/ui/server-runtime.d.mts +15 -15
- package/dist/ui/server-runtime.mjs +25 -21
- package/dist/url-Cac7a9QZ.mjs +33 -0
- package/dist/{user-DtNmjNYt.mjs → user-BIZPzCb9.mjs} +2 -2
- package/dist/{utils-wv-zvC-Y.mjs → utils-CMDhr5Xd.mjs} +129 -7
- package/dist/{validate-BMs11tan.d.mts → validate-CZvb-BKB.d.mts} +10 -7
- package/dist/{validate-7SYRbz2f.mjs → validate-DNs0pTSc.mjs} +11 -6
- package/dist/version-BTvfRGc6.mjs +6 -0
- package/dist/{widgets-DR8EHuRO.mjs → widgets-f0ibZUIq.mjs} +46 -43
- package/dist/{wordpress-slugs-Dvhxh4cl.mjs → wordpress-slugs-BhHJX8XE.mjs} +1 -1
- package/dist/{zod-generator-BS9-umbk.mjs → zod-generator-B7TMjyFJ.mjs} +41 -13
- package/package.json +31 -8
- package/src/components/Block.astro +50 -0
- package/src/components/Comments.astro +143 -1
- package/src/components/DinewayHead.astro +19 -2
- package/src/components/DinewayImage.astro +66 -33
- package/src/components/DinewayMedia.astro +1 -2
- package/src/components/Image.astro +85 -40
- package/src/components/InlinePortableTextEditor.tsx +12 -2
- package/src/components/JsonLdScript.astro +9 -0
- package/src/components/LiveSearch.astro +40 -4
- package/src/components/index.ts +4 -0
- package/src/components/live-search-routing.ts +30 -0
- package/src/components/portable-text-text-align.ts +15 -0
- package/src/ui.ts +2 -0
- package/dist/entity-aliases-Egjmqc7I.mjs +0 -51
- package/dist/import-CvHY6sf5.mjs +0 -684
- package/dist/runner-B-tbV751.mjs +0 -2741
- package/dist/schema-B6zyB-Xn.mjs +0 -8
- package/dist/taxonomies-CNOGeC-3.mjs +0 -327
- package/dist/types-eu5UDgHN.d.mts +0 -652
- package/dist/version-xlzMSXF2.mjs +0 -6
- /package/dist/{adapters-OQCImOL7.d.mts → adapters-NVSs4UH7.d.mts} +0 -0
- /package/dist/{api-tokens-CYUoFGo7.mjs → api-tokens-2r7OWi_I.mjs} +0 -0
- /package/dist/{base64-C1Q9yr0B.mjs → base64-F8-DUraK.mjs} +0 -0
- /package/dist/{challenge-store-CRSz-Xim.mjs → challenge-store-Bjlepa2E.mjs} +0 -0
- /package/dist/{components--pg6zBvu.mjs → components-Bclf98Be.mjs} +0 -0
- /package/dist/{context-types-DQh_XKkn.mjs → context-types-DqTo6C79.mjs} +0 -0
- /package/dist/{default-B6iaLvP7.mjs → default--K40jqm0.mjs} +0 -0
- /package/dist/{email-console-BOVqIYpB.mjs → email-console-ChgKFO8-.mjs} +0 -0
- /package/dist/{escape-BDgouFFY.mjs → escape-Bd4P_xKg.mjs} +0 -0
- /package/dist/{experimental-workflows-Dh4DUoD_.mjs → experimental-workflows-BMsUnIAw.mjs} +0 -0
- /package/dist/{mime-8pAB8-77.mjs → mime-CA7Lgovc.mjs} +0 -0
- /package/dist/{mode-CpyEz8pe.mjs → mode-C4CIyjtR.mjs} +0 -0
- /package/dist/{oauth-state-store-qncPB_IU.mjs → oauth-state-store-CNoXafF7.mjs} +0 -0
- /package/dist/{oauth-user-lookup-BFjWF8wK.mjs → oauth-user-lookup-Ygw4bNyt.mjs} +0 -0
- /package/dist/{options-BvJoSSJR.mjs → options-DQPTH5n0.mjs} +0 -0
- /package/dist/{passkey-config-DzHQK90v.mjs → passkey-config-BUwu8BA3.mjs} +0 -0
- /package/dist/{patterns-r-0n824Y.mjs → patterns-BhAvqO0S.mjs} +0 -0
- /package/dist/{provider-loader-DzUxejk7.mjs → provider-loader-D0w9dqjm.mjs} +0 -0
- /package/dist/{redirect-BjIBtDWY.mjs → redirect-wphRBv4y.mjs} +0 -0
- /package/dist/{request-cache-CYMbwZc6.mjs → request-cache-D9GQBxMP.mjs} +0 -0
- /package/dist/{resolve-DTuDyYKs.mjs → resolve-Cb6Dbz-C.mjs} +0 -0
- /package/dist/{setup-nonce-pyGfc9hV.mjs → setup-nonce-CoyjILvW.mjs} +0 -0
- /package/dist/{sidecar-client-mJxwayYN.mjs → sidecar-client-C3mqXg4C.mjs} +0 -0
- /package/dist/{slugify-BzGxlOFx.mjs → slugify-KSsVD_D3.mjs} +0 -0
- /package/dist/{ssrf-DiKqWd3N.mjs → ssrf-DWe8jwAp.mjs} +0 -0
- /package/dist/{transaction-CShMWIej.mjs → transaction-C2EOMTJA.mjs} +0 -0
- /package/dist/{transport-BnsNxh2l.d.mts → transport-CcJu3Co8.d.mts} +0 -0
- /package/dist/{trusted-proxy-BgIIcbsN.mjs → trusted-proxy-DTXQ2IMv.mjs} +0 -0
- /package/dist/{types-BO8TGjS8.mjs → types-B3t5cxhU.mjs} +0 -0
- /package/dist/{types-nP9vGUFq.d.mts → types-C46EvNdc.d.mts} +0 -0
- /package/dist/{types-Cg276-3z.mjs → types-C5CVgwsV.mjs} +0 -0
- /package/dist/{types-DZpT-PnG.d.mts → types-POH8PQZh.d.mts} +0 -0
- /package/dist/{types-Dryvv7LH.d.mts → types-Rc3gQqqw.d.mts} +0 -0
- /package/dist/{url-w7Mp5qAD.mjs → url-CpyFDFpO.mjs} +0 -0
|
@@ -0,0 +1,1354 @@
|
|
|
1
|
+
import { n as slugify } from "./slugify-KSsVD_D3.mjs";
|
|
2
|
+
import { t as TaxonomyRepository } from "./taxonomy-Dvsw6BOs.mjs";
|
|
3
|
+
import { t as OptionsRepository } from "./options-DQPTH5n0.mjs";
|
|
4
|
+
import { o as invalidateSiteSettingsCache } from "./settings-BbqgHWpU.mjs";
|
|
5
|
+
import { i as resolveLocaleChain } from "./resolve-Cb6Dbz-C.mjs";
|
|
6
|
+
import { l as invalidateTermCache } from "./taxonomies-DRiixp_E.mjs";
|
|
7
|
+
import { n as parseWxrString } from "./parser-C1nYd-81.mjs";
|
|
8
|
+
import { a as validateExternalUrl, n as resolveAndValidateExternalUrl, r as ssrfSafeFetch } from "./ssrf-DWe8jwAp.mjs";
|
|
9
|
+
import { _ as sanitizeFieldSlug, a as getFilenameFromUrl, c as isInternalMetaKey, d as mapMetaKeyToField, f as mapPostTypeToCollection, h as relativizeContentLinks, i as checkSchemaCompatibility, l as isInternalPostType, m as normalizeUrl$1, n as FEATURED_IMAGE_FIELD, o as guessMimeType, p as mapWpStatus, r as buildAttachmentMap, s as inferMetaType, t as BASE_REQUIRED_FIELDS, u as isPluginBookkeepingMeta } from "./utils-CMDhr5Xd.mjs";
|
|
10
|
+
import { ulid } from "ulidx";
|
|
11
|
+
import { gutenbergToPortableText } from "@dineway-ai/gutenberg-to-portable-text";
|
|
12
|
+
|
|
13
|
+
//#region src/import/menus.ts
|
|
14
|
+
/**
|
|
15
|
+
* Import navigation menus from Plugin API
|
|
16
|
+
*
|
|
17
|
+
* @param menus - Menus from plugin API
|
|
18
|
+
* @param db - Database connection
|
|
19
|
+
* @param contentIdMap - Map from WP post ID to Dineway content ID
|
|
20
|
+
* @returns Import result with counts and ID mapping
|
|
21
|
+
*/
|
|
22
|
+
async function importMenusFromPlugin(menus, db, contentIdMap) {
|
|
23
|
+
const result = {
|
|
24
|
+
menusCreated: 0,
|
|
25
|
+
itemsCreated: 0,
|
|
26
|
+
menuIdMap: /* @__PURE__ */ new Map(),
|
|
27
|
+
errors: []
|
|
28
|
+
};
|
|
29
|
+
for (const menu of menus) try {
|
|
30
|
+
const existing = await db.selectFrom("_dineway_menus").select("id").where("name", "=", menu.name).executeTakeFirst();
|
|
31
|
+
if (existing) {
|
|
32
|
+
result.menuIdMap.set(menu.name, existing.id);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const menuId = ulid();
|
|
36
|
+
await db.insertInto("_dineway_menus").values({
|
|
37
|
+
id: menuId,
|
|
38
|
+
name: menu.name,
|
|
39
|
+
label: menu.label,
|
|
40
|
+
translation_group: menuId
|
|
41
|
+
}).execute();
|
|
42
|
+
result.menusCreated++;
|
|
43
|
+
result.menuIdMap.set(menu.name, menuId);
|
|
44
|
+
const itemsCreated = await importPluginMenuItems(menu.items, menuId, db, contentIdMap);
|
|
45
|
+
result.itemsCreated += itemsCreated;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
result.errors.push({
|
|
48
|
+
menu: menu.name,
|
|
49
|
+
error: error instanceof Error ? error.message : String(error)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Import menu items from Plugin API format
|
|
56
|
+
*/
|
|
57
|
+
async function importPluginMenuItems(items, menuId, db, contentIdMap) {
|
|
58
|
+
const itemIdMap = /* @__PURE__ */ new Map();
|
|
59
|
+
let count = 0;
|
|
60
|
+
const sortedItems = items.toSorted((a, b) => a.sort_order - b.sort_order);
|
|
61
|
+
for (const item of sortedItems) {
|
|
62
|
+
const itemId = ulid();
|
|
63
|
+
itemIdMap.set(item.id, itemId);
|
|
64
|
+
const { type, collection, referenceId, customUrl } = mapPluginMenuItem(item, contentIdMap);
|
|
65
|
+
await db.insertInto("_dineway_menu_items").values({
|
|
66
|
+
id: itemId,
|
|
67
|
+
menu_id: menuId,
|
|
68
|
+
parent_id: null,
|
|
69
|
+
sort_order: item.sort_order,
|
|
70
|
+
type,
|
|
71
|
+
reference_collection: collection,
|
|
72
|
+
reference_id: referenceId,
|
|
73
|
+
custom_url: customUrl,
|
|
74
|
+
label: item.title,
|
|
75
|
+
title_attr: null,
|
|
76
|
+
target: item.target || null,
|
|
77
|
+
css_classes: item.classes || null,
|
|
78
|
+
translation_group: itemId
|
|
79
|
+
}).execute();
|
|
80
|
+
count++;
|
|
81
|
+
}
|
|
82
|
+
for (const item of sortedItems) if (item.parent_id) {
|
|
83
|
+
const itemId = itemIdMap.get(item.id);
|
|
84
|
+
const parentId = itemIdMap.get(item.parent_id);
|
|
85
|
+
if (itemId && parentId) await db.updateTable("_dineway_menu_items").set({ parent_id: parentId }).where("id", "=", itemId).execute();
|
|
86
|
+
}
|
|
87
|
+
return count;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Map Plugin menu item to Dineway format
|
|
91
|
+
*/
|
|
92
|
+
function mapPluginMenuItem(item, contentIdMap) {
|
|
93
|
+
switch (item.type) {
|
|
94
|
+
case "custom": return {
|
|
95
|
+
type: "custom",
|
|
96
|
+
collection: null,
|
|
97
|
+
referenceId: null,
|
|
98
|
+
customUrl: item.url || "#"
|
|
99
|
+
};
|
|
100
|
+
case "post_type": {
|
|
101
|
+
const collection = mapObjectToCollection(item.object);
|
|
102
|
+
const referenceId = item.object_id ? contentIdMap.get(item.object_id) || null : null;
|
|
103
|
+
if (!referenceId && item.url) return {
|
|
104
|
+
type: "custom",
|
|
105
|
+
collection: null,
|
|
106
|
+
referenceId: null,
|
|
107
|
+
customUrl: item.url
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
type: collection === "pages" ? "page" : "post",
|
|
111
|
+
collection,
|
|
112
|
+
referenceId,
|
|
113
|
+
customUrl: null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
case "taxonomy": return {
|
|
117
|
+
type: "custom",
|
|
118
|
+
collection: null,
|
|
119
|
+
referenceId: null,
|
|
120
|
+
customUrl: item.url || "#"
|
|
121
|
+
};
|
|
122
|
+
default: return {
|
|
123
|
+
type: "custom",
|
|
124
|
+
collection: null,
|
|
125
|
+
referenceId: null,
|
|
126
|
+
customUrl: item.url || "#"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Map WordPress object type to Dineway collection name
|
|
132
|
+
*/
|
|
133
|
+
function mapObjectToCollection(objectType) {
|
|
134
|
+
if (!objectType) return "posts";
|
|
135
|
+
return {
|
|
136
|
+
post: "posts",
|
|
137
|
+
page: "pages",
|
|
138
|
+
product: "products",
|
|
139
|
+
portfolio: "portfolio"
|
|
140
|
+
}[objectType] || objectType;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/import/comments.ts
|
|
145
|
+
/**
|
|
146
|
+
* Import comments from the plugin API.
|
|
147
|
+
*
|
|
148
|
+
* Preserves original timestamps and threading. Comments whose post was
|
|
149
|
+
* not imported (no entry in `contentIdMap`) are skipped. Re-running the
|
|
150
|
+
* import is idempotent: a comment with the same post, author email,
|
|
151
|
+
* timestamp, and body is not created twice.
|
|
152
|
+
*
|
|
153
|
+
* @param comments - Comments from the plugin API (all pages, flat)
|
|
154
|
+
* @param db - Database connection
|
|
155
|
+
* @param contentIdMap - WP post ID -> Dineway content ID
|
|
156
|
+
* @param collectionMap - WP post ID -> Dineway collection slug
|
|
157
|
+
*/
|
|
158
|
+
async function importCommentsFromPlugin(comments, db, contentIdMap, collectionMap) {
|
|
159
|
+
const result = {
|
|
160
|
+
imported: 0,
|
|
161
|
+
skipped: 0,
|
|
162
|
+
errors: []
|
|
163
|
+
};
|
|
164
|
+
const rootIdMap = /* @__PURE__ */ new Map();
|
|
165
|
+
const sorted = comments.toSorted((a, b) => a.id - b.id);
|
|
166
|
+
for (const comment of sorted) {
|
|
167
|
+
const label = `${comment.author_name || "Anonymous"} (${comment.date_gmt})`;
|
|
168
|
+
try {
|
|
169
|
+
const contentId = contentIdMap.get(comment.post_id);
|
|
170
|
+
const collection = collectionMap.get(comment.post_id);
|
|
171
|
+
if (!contentId || !collection) {
|
|
172
|
+
result.skipped++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const parsed = new Date(comment.date_gmt);
|
|
176
|
+
const createdAt = Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : parsed.toISOString();
|
|
177
|
+
const existing = await db.selectFrom("_dineway_comments").select("id").where("content_id", "=", contentId).where("author_email", "=", comment.author_email).where("created_at", "=", createdAt).where("body", "=", comment.body).executeTakeFirst();
|
|
178
|
+
const parentId = comment.parent_id !== null ? rootIdMap.get(comment.parent_id) ?? null : null;
|
|
179
|
+
if (existing) {
|
|
180
|
+
rootIdMap.set(comment.id, parentId ?? existing.id);
|
|
181
|
+
result.skipped++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const id = ulid();
|
|
185
|
+
await db.insertInto("_dineway_comments").values({
|
|
186
|
+
id,
|
|
187
|
+
collection,
|
|
188
|
+
content_id: contentId,
|
|
189
|
+
parent_id: parentId,
|
|
190
|
+
author_name: comment.author_name || "Anonymous",
|
|
191
|
+
author_email: comment.author_email,
|
|
192
|
+
author_user_id: null,
|
|
193
|
+
body: comment.body,
|
|
194
|
+
status: comment.status,
|
|
195
|
+
ip_hash: null,
|
|
196
|
+
user_agent: null,
|
|
197
|
+
moderation_metadata: null,
|
|
198
|
+
created_at: createdAt,
|
|
199
|
+
updated_at: createdAt
|
|
200
|
+
}).execute();
|
|
201
|
+
rootIdMap.set(comment.id, parentId ?? id);
|
|
202
|
+
result.imported++;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
result.errors.push({
|
|
205
|
+
comment: label,
|
|
206
|
+
error: error instanceof Error ? error.message : String(error)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/import/sections.ts
|
|
215
|
+
/**
|
|
216
|
+
* Import reusable blocks (wp_block post type) from WXR as sections
|
|
217
|
+
*
|
|
218
|
+
* @param posts - All posts from WXR (will filter to wp_block)
|
|
219
|
+
* @param db - Database connection
|
|
220
|
+
* @returns Import result with counts
|
|
221
|
+
*/
|
|
222
|
+
async function importReusableBlocksAsSections(posts, db) {
|
|
223
|
+
const result = {
|
|
224
|
+
sectionsCreated: 0,
|
|
225
|
+
sectionsSkipped: 0,
|
|
226
|
+
errors: []
|
|
227
|
+
};
|
|
228
|
+
const reusableBlocks = posts.filter((post) => post.postType === "wp_block");
|
|
229
|
+
if (reusableBlocks.length === 0) return result;
|
|
230
|
+
for (const block of reusableBlocks) try {
|
|
231
|
+
const slug = block.postName || slugify(block.title || `block-${block.id || Date.now()}`);
|
|
232
|
+
if (await db.selectFrom("_dineway_sections").select("id").where("slug", "=", slug).executeTakeFirst()) {
|
|
233
|
+
result.sectionsSkipped++;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const content = block.content ? gutenbergToPortableText(block.content) : [];
|
|
237
|
+
const id = ulid();
|
|
238
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
239
|
+
await db.insertInto("_dineway_sections").values({
|
|
240
|
+
id,
|
|
241
|
+
slug,
|
|
242
|
+
title: block.title || "Untitled Block",
|
|
243
|
+
description: null,
|
|
244
|
+
keywords: null,
|
|
245
|
+
content: JSON.stringify(content),
|
|
246
|
+
preview_media_id: null,
|
|
247
|
+
source: "import",
|
|
248
|
+
theme_id: null,
|
|
249
|
+
created_at: now,
|
|
250
|
+
updated_at: now
|
|
251
|
+
}).execute();
|
|
252
|
+
result.sectionsCreated++;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
result.errors.push({
|
|
255
|
+
title: block.title || "Untitled Block",
|
|
256
|
+
error: error instanceof Error ? error.message : String(error)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/import/settings.ts
|
|
264
|
+
/** Options key prefix used by the site settings module (`settings/index.ts`). */
|
|
265
|
+
const SETTINGS_PREFIX = "site:";
|
|
266
|
+
/**
|
|
267
|
+
* Import site settings from analysis into Dineway site settings.
|
|
268
|
+
*
|
|
269
|
+
* Writes the `site:*` options consumed by `getSiteSettings()` and invalidates
|
|
270
|
+
* the settings cache. Logo/favicon are only applied when the caller resolved
|
|
271
|
+
* them to Dineway media IDs.
|
|
272
|
+
*
|
|
273
|
+
* @param settings - Site settings analysis
|
|
274
|
+
* @param db - Database connection
|
|
275
|
+
* @param overwrite - Whether to overwrite existing settings
|
|
276
|
+
* @param media - Resolved media IDs for logo/favicon
|
|
277
|
+
*/
|
|
278
|
+
async function importSiteSettings(settings, db, overwrite = false, media = {}) {
|
|
279
|
+
const result = {
|
|
280
|
+
applied: [],
|
|
281
|
+
skipped: [],
|
|
282
|
+
errors: []
|
|
283
|
+
};
|
|
284
|
+
const updates = [];
|
|
285
|
+
if (settings.title) updates.push({
|
|
286
|
+
setting: "title",
|
|
287
|
+
value: settings.title
|
|
288
|
+
});
|
|
289
|
+
if (settings.tagline) updates.push({
|
|
290
|
+
setting: "tagline",
|
|
291
|
+
value: settings.tagline
|
|
292
|
+
});
|
|
293
|
+
if (media.logoMediaId) updates.push({
|
|
294
|
+
setting: "logo",
|
|
295
|
+
value: { mediaId: media.logoMediaId }
|
|
296
|
+
});
|
|
297
|
+
if (media.faviconMediaId) updates.push({
|
|
298
|
+
setting: "favicon",
|
|
299
|
+
value: { mediaId: media.faviconMediaId }
|
|
300
|
+
});
|
|
301
|
+
if (updates.length === 0) return result;
|
|
302
|
+
const options = new OptionsRepository(db);
|
|
303
|
+
try {
|
|
304
|
+
for (const { setting, value } of updates) try {
|
|
305
|
+
const key = `${SETTINGS_PREFIX}${setting}`;
|
|
306
|
+
if (!overwrite) {
|
|
307
|
+
if (await options.get(key) !== null) {
|
|
308
|
+
result.skipped.push(setting);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
await options.set(key, value);
|
|
313
|
+
result.applied.push(setting);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
result.errors.push({
|
|
316
|
+
setting,
|
|
317
|
+
error: error instanceof Error ? error.message : String(error)
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
} finally {
|
|
321
|
+
if (result.applied.length > 0) invalidateSiteSettingsCache();
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Parse site settings from WordPress plugin options response.
|
|
327
|
+
*/
|
|
328
|
+
function parseSiteSettingsFromPlugin(options) {
|
|
329
|
+
const settings = {};
|
|
330
|
+
if (typeof options.blogname === "string" && options.blogname.trim() !== "") settings.title = options.blogname;
|
|
331
|
+
if (typeof options.blogdescription === "string" && options.blogdescription.trim() !== "") settings.tagline = options.blogdescription;
|
|
332
|
+
if (typeof options.custom_logo_url === "string" && options.custom_logo_url !== "") settings.logo = {
|
|
333
|
+
url: options.custom_logo_url,
|
|
334
|
+
id: typeof options.custom_logo === "number" ? options.custom_logo : void 0
|
|
335
|
+
};
|
|
336
|
+
if (typeof options.site_icon_url === "string" && options.site_icon_url !== "") settings.favicon = {
|
|
337
|
+
url: options.site_icon_url,
|
|
338
|
+
id: typeof options.site_icon === "number" ? options.site_icon : void 0
|
|
339
|
+
};
|
|
340
|
+
return settings;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/import/registry.ts
|
|
345
|
+
/**
|
|
346
|
+
* Import source registry
|
|
347
|
+
*
|
|
348
|
+
* Manages available import sources and provides URL probing.
|
|
349
|
+
*/
|
|
350
|
+
const TRAILING_SLASHES_PATTERN = /\/+$/;
|
|
351
|
+
/** Registered import sources */
|
|
352
|
+
const sources = /* @__PURE__ */ new Map();
|
|
353
|
+
/**
|
|
354
|
+
* Register an import source
|
|
355
|
+
*/
|
|
356
|
+
function registerSource(source) {
|
|
357
|
+
sources.set(source.id, source);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get a source by ID
|
|
361
|
+
*/
|
|
362
|
+
function getSource(id) {
|
|
363
|
+
return sources.get(id);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get all registered sources
|
|
367
|
+
*/
|
|
368
|
+
function getAllSources() {
|
|
369
|
+
return [...sources.values()];
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Get sources that can handle file uploads
|
|
373
|
+
*/
|
|
374
|
+
function getFileSources() {
|
|
375
|
+
return getAllSources().filter((s) => s.requiresFile);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get sources that can probe URLs
|
|
379
|
+
*/
|
|
380
|
+
function getUrlSources() {
|
|
381
|
+
return getAllSources().filter((s) => s.canProbe);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Probe a URL against all registered sources
|
|
385
|
+
*
|
|
386
|
+
* Returns probe results sorted by confidence (definite > likely > possible)
|
|
387
|
+
*/
|
|
388
|
+
async function probeUrl(url) {
|
|
389
|
+
let normalizedUrl = url.trim();
|
|
390
|
+
if (!normalizedUrl.startsWith("http")) normalizedUrl = `https://${normalizedUrl}`;
|
|
391
|
+
normalizedUrl = normalizedUrl.replace(TRAILING_SLASHES_PATTERN, "");
|
|
392
|
+
await resolveAndValidateExternalUrl(normalizedUrl);
|
|
393
|
+
const results = [];
|
|
394
|
+
const probePromises = getUrlSources().map(async (source) => {
|
|
395
|
+
try {
|
|
396
|
+
const result = await source.probe?.(normalizedUrl);
|
|
397
|
+
if (result) return result;
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.debug(`Probe failed for ${source.id}:`, error);
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
});
|
|
403
|
+
const probeResults = await Promise.allSettled(probePromises);
|
|
404
|
+
for (const result of probeResults) if (result.status === "fulfilled" && result.value) results.push(result.value);
|
|
405
|
+
const confidenceOrder = {
|
|
406
|
+
definite: 0,
|
|
407
|
+
likely: 1,
|
|
408
|
+
possible: 2
|
|
409
|
+
};
|
|
410
|
+
results.sort((a, b) => confidenceOrder[a.confidence] - confidenceOrder[b.confidence]);
|
|
411
|
+
return {
|
|
412
|
+
url: normalizedUrl,
|
|
413
|
+
isWordPress: results.length > 0,
|
|
414
|
+
bestMatch: results[0] ?? null,
|
|
415
|
+
allMatches: results
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Clear all registered sources (useful for testing)
|
|
420
|
+
*/
|
|
421
|
+
function clearSources() {
|
|
422
|
+
sources.clear();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/import/sources/wxr.ts
|
|
427
|
+
/**
|
|
428
|
+
* WXR (WordPress eXtended RSS) import source
|
|
429
|
+
*
|
|
430
|
+
* Handles WordPress export file uploads (.xml).
|
|
431
|
+
* This wraps the existing WXR parsing and analysis logic.
|
|
432
|
+
*/
|
|
433
|
+
const wxrSource = {
|
|
434
|
+
id: "wxr",
|
|
435
|
+
name: "WordPress Export File",
|
|
436
|
+
description: "Upload a WordPress export file (.xml)",
|
|
437
|
+
icon: "upload",
|
|
438
|
+
requiresFile: true,
|
|
439
|
+
canProbe: false,
|
|
440
|
+
async analyze(input, context) {
|
|
441
|
+
if (input.type !== "file") throw new Error("WXR source requires a file input");
|
|
442
|
+
return analyzeWxrData(await parseWxrString(await input.file.text()), context.getExistingCollections ? await context.getExistingCollections() : /* @__PURE__ */ new Map());
|
|
443
|
+
},
|
|
444
|
+
async *fetchContent(input, options) {
|
|
445
|
+
if (input.type !== "file") throw new Error("WXR source requires a file input");
|
|
446
|
+
const wxr = await parseWxrString(await input.file.text());
|
|
447
|
+
const attachmentMap = buildAttachmentMap(wxr.attachments);
|
|
448
|
+
let count = 0;
|
|
449
|
+
for (const post of wxr.posts) {
|
|
450
|
+
const postType = post.postType || "post";
|
|
451
|
+
if (!options.postTypes.includes(postType)) continue;
|
|
452
|
+
if (isInternalPostType(postType)) continue;
|
|
453
|
+
if (!options.includeDrafts && post.status !== "publish") continue;
|
|
454
|
+
yield wxrPostToNormalizedItem(post, attachmentMap, wxr.site.link || "");
|
|
455
|
+
count++;
|
|
456
|
+
if (options.limit && count >= options.limit) break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
/**
|
|
461
|
+
* Analyze WXR data and return normalized ImportAnalysis
|
|
462
|
+
*/
|
|
463
|
+
function analyzeWxrData(wxr, existingCollections) {
|
|
464
|
+
const postTypeCounts = /* @__PURE__ */ new Map();
|
|
465
|
+
const postTypesWithThumbnails = /* @__PURE__ */ new Set();
|
|
466
|
+
const metaKeys = /* @__PURE__ */ new Map();
|
|
467
|
+
const authorPostCounts = /* @__PURE__ */ new Map();
|
|
468
|
+
for (const post of wxr.posts) {
|
|
469
|
+
const type = post.postType || "post";
|
|
470
|
+
postTypeCounts.set(type, (postTypeCounts.get(type) || 0) + 1);
|
|
471
|
+
if (post.creator) authorPostCounts.set(post.creator, (authorPostCounts.get(post.creator) || 0) + 1);
|
|
472
|
+
if (post.meta.has("_thumbnail_id")) postTypesWithThumbnails.add(type);
|
|
473
|
+
for (const [key, value] of post.meta) {
|
|
474
|
+
const existing = metaKeys.get(key);
|
|
475
|
+
if (existing) {
|
|
476
|
+
existing.count++;
|
|
477
|
+
if (existing.samples.length < 3 && value) existing.samples.push(value.slice(0, 100));
|
|
478
|
+
} else metaKeys.set(key, {
|
|
479
|
+
count: 1,
|
|
480
|
+
samples: value ? [value.slice(0, 100)] : [],
|
|
481
|
+
isInternal: isInternalMetaKey(key)
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const customFields = [...metaKeys.entries()].filter(([_, info]) => !info.isInternal).map(([key, info]) => ({
|
|
486
|
+
key,
|
|
487
|
+
count: info.count,
|
|
488
|
+
samples: info.samples,
|
|
489
|
+
suggestedField: mapMetaKeyToField(key),
|
|
490
|
+
suggestedType: inferMetaType(key, info.samples[0]),
|
|
491
|
+
isInternal: info.isInternal
|
|
492
|
+
})).toSorted((a, b) => b.count - a.count);
|
|
493
|
+
const postTypes = [...postTypeCounts.entries()].filter(([type]) => !isInternalPostType(type)).map(([name, count]) => {
|
|
494
|
+
const suggestedCollection = mapPostTypeToCollection(name);
|
|
495
|
+
const existingCollection = existingCollections.get(suggestedCollection);
|
|
496
|
+
const requiredFields = [...BASE_REQUIRED_FIELDS];
|
|
497
|
+
if (postTypesWithThumbnails.has(name)) requiredFields.push(FEATURED_IMAGE_FIELD);
|
|
498
|
+
return {
|
|
499
|
+
name,
|
|
500
|
+
count,
|
|
501
|
+
suggestedCollection,
|
|
502
|
+
requiredFields,
|
|
503
|
+
schemaStatus: checkSchemaCompatibility(requiredFields, existingCollection)
|
|
504
|
+
};
|
|
505
|
+
}).toSorted((a, b) => b.count - a.count);
|
|
506
|
+
const attachmentItems = wxr.attachments.map((att) => {
|
|
507
|
+
const filename = att.url ? getFilenameFromUrl(att.url) : void 0;
|
|
508
|
+
const mimeType = filename ? guessMimeType(filename) : void 0;
|
|
509
|
+
return {
|
|
510
|
+
id: att.id,
|
|
511
|
+
title: att.title,
|
|
512
|
+
url: att.url,
|
|
513
|
+
filename,
|
|
514
|
+
mimeType
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
const navMenus = wxr.navMenus.map((menu) => ({
|
|
518
|
+
name: menu.name,
|
|
519
|
+
label: menu.label,
|
|
520
|
+
itemCount: menu.items.length
|
|
521
|
+
}));
|
|
522
|
+
const taxonomyMap = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const term of wxr.terms) {
|
|
524
|
+
if (term.taxonomy === "category" || term.taxonomy === "post_tag" || term.taxonomy === "nav_menu") continue;
|
|
525
|
+
const existing = taxonomyMap.get(term.taxonomy);
|
|
526
|
+
if (existing) {
|
|
527
|
+
existing.count++;
|
|
528
|
+
if (existing.samples.length < 3) existing.samples.push(term.name);
|
|
529
|
+
} else taxonomyMap.set(term.taxonomy, {
|
|
530
|
+
count: 1,
|
|
531
|
+
samples: [term.name]
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const customTaxonomies = Array.from(taxonomyMap.entries(), ([slug, info]) => ({
|
|
535
|
+
slug,
|
|
536
|
+
termCount: info.count,
|
|
537
|
+
sampleTerms: info.samples
|
|
538
|
+
})).toSorted((a, b) => b.termCount - a.termCount);
|
|
539
|
+
const reusableBlocks = wxr.posts.filter((post) => post.postType === "wp_block").map((post) => ({
|
|
540
|
+
id: post.id || 0,
|
|
541
|
+
title: post.title || "Untitled Block",
|
|
542
|
+
slug: post.postName || slugify(post.title || `block-${post.id || Date.now()}`)
|
|
543
|
+
}));
|
|
544
|
+
return {
|
|
545
|
+
sourceId: "wxr",
|
|
546
|
+
site: {
|
|
547
|
+
title: wxr.site.title || "WordPress Site",
|
|
548
|
+
url: wxr.site.link || ""
|
|
549
|
+
},
|
|
550
|
+
postTypes,
|
|
551
|
+
attachments: {
|
|
552
|
+
count: wxr.attachments.length,
|
|
553
|
+
items: attachmentItems
|
|
554
|
+
},
|
|
555
|
+
categories: wxr.categories.length,
|
|
556
|
+
tags: wxr.tags.length,
|
|
557
|
+
authors: wxr.authors.map((a) => ({
|
|
558
|
+
id: a.id,
|
|
559
|
+
login: a.login,
|
|
560
|
+
email: a.email,
|
|
561
|
+
displayName: a.displayName || a.login || "Unknown",
|
|
562
|
+
postCount: a.login ? authorPostCounts.get(a.login) || 0 : 0
|
|
563
|
+
})),
|
|
564
|
+
navMenus: navMenus.length > 0 ? navMenus : void 0,
|
|
565
|
+
customTaxonomies: customTaxonomies.length > 0 ? customTaxonomies : void 0,
|
|
566
|
+
reusableBlocks: reusableBlocks.length > 0 ? reusableBlocks : void 0,
|
|
567
|
+
customFields
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Convert a WXR post to a normalized item
|
|
572
|
+
*/
|
|
573
|
+
function wxrPostToNormalizedItem(post, attachmentMap, siteUrl = "") {
|
|
574
|
+
const content = post.content ? gutenbergToPortableText(post.content) : [];
|
|
575
|
+
if (siteUrl) relativizeContentLinks(content, siteUrl);
|
|
576
|
+
const thumbnailId = post.meta.get("_thumbnail_id");
|
|
577
|
+
const featuredImage = thumbnailId ? attachmentMap.get(String(thumbnailId)) : void 0;
|
|
578
|
+
let customTaxonomies;
|
|
579
|
+
if (post.customTaxonomies && post.customTaxonomies.size > 0) {
|
|
580
|
+
const filtered = Object.fromEntries([...post.customTaxonomies].filter(([taxonomy]) => taxonomy !== "language"));
|
|
581
|
+
if (Object.keys(filtered).length > 0) customTaxonomies = filtered;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
sourceId: post.id || 0,
|
|
585
|
+
postType: post.postType || "post",
|
|
586
|
+
status: mapWpStatus(post.status),
|
|
587
|
+
slug: post.postName || slugify(post.title || `post-${post.id || Date.now()}`),
|
|
588
|
+
title: post.title || "Untitled",
|
|
589
|
+
content,
|
|
590
|
+
excerpt: post.excerpt,
|
|
591
|
+
date: parseWxrDate(post.postDateGmt, post.pubDate, post.postDate) ?? /* @__PURE__ */ new Date(),
|
|
592
|
+
modified: parseWxrDate(post.postModifiedGmt, void 0, post.postModified),
|
|
593
|
+
author: post.creator,
|
|
594
|
+
categories: post.categories,
|
|
595
|
+
tags: post.tags,
|
|
596
|
+
meta: Object.fromEntries(post.meta),
|
|
597
|
+
featuredImage,
|
|
598
|
+
parentId: post.postParent && post.postParent !== 0 ? post.postParent : void 0,
|
|
599
|
+
menuOrder: post.menuOrder,
|
|
600
|
+
customTaxonomies,
|
|
601
|
+
locale: post.locale,
|
|
602
|
+
translationGroup: post.translationGroup
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* WordPress uses "0000-00-00 00:00:00" as a sentinel for missing GMT dates
|
|
607
|
+
* (e.g. unpublished drafts). This must be treated as absent.
|
|
608
|
+
*/
|
|
609
|
+
const WXR_ZERO_DATE = "0000-00-00 00:00:00";
|
|
610
|
+
/**
|
|
611
|
+
* Parse a WXR date with the correct fallback chain:
|
|
612
|
+
* 1. GMT date (always UTC, most reliable)
|
|
613
|
+
* 2. pubDate (RFC 2822, includes timezone offset)
|
|
614
|
+
* 3. Site-local date (MySQL datetime without timezone, imprecise but best available)
|
|
615
|
+
*
|
|
616
|
+
* Returns undefined when none of the inputs yield a valid date.
|
|
617
|
+
* Callers that need a guaranteed Date should use `?? new Date()`.
|
|
618
|
+
*/
|
|
619
|
+
function parseWxrDate(gmtDate, pubDate, localDate) {
|
|
620
|
+
if (gmtDate && gmtDate !== WXR_ZERO_DATE) return /* @__PURE__ */ new Date(gmtDate.replace(" ", "T") + "Z");
|
|
621
|
+
if (pubDate) {
|
|
622
|
+
const d = new Date(pubDate);
|
|
623
|
+
if (!isNaN(d.getTime())) return d;
|
|
624
|
+
}
|
|
625
|
+
if (localDate) {
|
|
626
|
+
const d = new Date(localDate.replace(" ", "T"));
|
|
627
|
+
if (!isNaN(d.getTime())) return d;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/import/wxr-taxonomies.ts
|
|
633
|
+
var WxrTaxonomyConflictError = class extends Error {
|
|
634
|
+
publicMessage;
|
|
635
|
+
constructor(publicMessage, options) {
|
|
636
|
+
super(publicMessage, options);
|
|
637
|
+
this.name = "WxrTaxonomyConflictError";
|
|
638
|
+
this.publicMessage = publicMessage;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
function isWxrTaxonomyConflictError(error) {
|
|
642
|
+
return error instanceof WxrTaxonomyConflictError;
|
|
643
|
+
}
|
|
644
|
+
function makeState() {
|
|
645
|
+
return { plan: {
|
|
646
|
+
termsCreated: {},
|
|
647
|
+
termsReused: {},
|
|
648
|
+
missingTaxonomies: [],
|
|
649
|
+
termIdByNameAndSlug: /* @__PURE__ */ new Map(),
|
|
650
|
+
collectionsByTaxonomy: /* @__PURE__ */ new Map(),
|
|
651
|
+
translationGroupByTermId: /* @__PURE__ */ new Map()
|
|
652
|
+
} };
|
|
653
|
+
}
|
|
654
|
+
function bump(record, key) {
|
|
655
|
+
record[key] = (record[key] ?? 0) + 1;
|
|
656
|
+
}
|
|
657
|
+
function rememberTerm(state, taxonomyName, slug, termId) {
|
|
658
|
+
let bySlug = state.plan.termIdByNameAndSlug.get(taxonomyName);
|
|
659
|
+
if (!bySlug) {
|
|
660
|
+
bySlug = /* @__PURE__ */ new Map();
|
|
661
|
+
state.plan.termIdByNameAndSlug.set(taxonomyName, bySlug);
|
|
662
|
+
}
|
|
663
|
+
bySlug.set(slug, termId);
|
|
664
|
+
}
|
|
665
|
+
function parseDefCollections(raw) {
|
|
666
|
+
if (!raw) return [];
|
|
667
|
+
try {
|
|
668
|
+
const parsed = JSON.parse(raw);
|
|
669
|
+
if (Array.isArray(parsed)) return parsed.filter((collection) => typeof collection === "string");
|
|
670
|
+
} catch {}
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
673
|
+
async function findTaxonomyDef(db, name, locale) {
|
|
674
|
+
const chain = resolveLocaleChain(locale);
|
|
675
|
+
if (chain.length === 0) {
|
|
676
|
+
const row = await db.selectFrom("_dineway_taxonomy_defs").selectAll().where("name", "=", name).orderBy("locale", "asc").executeTakeFirst();
|
|
677
|
+
return row ? {
|
|
678
|
+
id: row.id,
|
|
679
|
+
collections: parseDefCollections(row.collections)
|
|
680
|
+
} : null;
|
|
681
|
+
}
|
|
682
|
+
for (const tryLocale of chain) {
|
|
683
|
+
const row = await db.selectFrom("_dineway_taxonomy_defs").selectAll().where("name", "=", name).where("locale", "=", tryLocale).executeTakeFirst();
|
|
684
|
+
if (row) return {
|
|
685
|
+
id: row.id,
|
|
686
|
+
collections: parseDefCollections(row.collections)
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
async function ensureTerm(repo, state, taxonomyName, slug, label, description, locale) {
|
|
692
|
+
const cached = state.plan.termIdByNameAndSlug.get(taxonomyName)?.get(slug);
|
|
693
|
+
if (cached) return cached;
|
|
694
|
+
const existing = await repo.findBySlug(taxonomyName, slug, locale);
|
|
695
|
+
if (existing) {
|
|
696
|
+
bump(state.plan.termsReused, taxonomyName);
|
|
697
|
+
rememberTerm(state, taxonomyName, slug, existing.id);
|
|
698
|
+
return existing.id;
|
|
699
|
+
}
|
|
700
|
+
const translationOf = (await repo.findBySlug(taxonomyName, slug))?.id;
|
|
701
|
+
const created = await repo.create({
|
|
702
|
+
name: taxonomyName,
|
|
703
|
+
slug,
|
|
704
|
+
label,
|
|
705
|
+
data: description ? { description } : void 0,
|
|
706
|
+
locale,
|
|
707
|
+
translationOf
|
|
708
|
+
});
|
|
709
|
+
bump(state.plan.termsCreated, taxonomyName);
|
|
710
|
+
rememberTerm(state, taxonomyName, slug, created.id);
|
|
711
|
+
return created.id;
|
|
712
|
+
}
|
|
713
|
+
function labelFor(post, taxonomy, slug) {
|
|
714
|
+
const key = `${taxonomy}\u0000${slug}`;
|
|
715
|
+
return post.taxonomyLabels?.get(key) ?? slug;
|
|
716
|
+
}
|
|
717
|
+
async function preImportWxrTaxonomies(db, posts, categories, tags, terms, locale) {
|
|
718
|
+
const state = makeState();
|
|
719
|
+
const repo = new TaxonomyRepository(db);
|
|
720
|
+
const defCache = /* @__PURE__ */ new Map();
|
|
721
|
+
const lookupDef = async (name) => {
|
|
722
|
+
if (defCache.has(name)) return defCache.get(name) ?? null;
|
|
723
|
+
const def = await findTaxonomyDef(db, name, locale);
|
|
724
|
+
defCache.set(name, def);
|
|
725
|
+
if (def) state.plan.collectionsByTaxonomy.set(name, new Set(def.collections));
|
|
726
|
+
return def;
|
|
727
|
+
};
|
|
728
|
+
const categoryDef = await lookupDef("category");
|
|
729
|
+
if (categoryDef) for (const category of categories) {
|
|
730
|
+
const slug = category.nicename;
|
|
731
|
+
const label = category.name;
|
|
732
|
+
if (!slug || !label) continue;
|
|
733
|
+
await ensureTerm(repo, state, "category", slug, label, category.description, locale);
|
|
734
|
+
}
|
|
735
|
+
else if (categories.length > 0) state.plan.missingTaxonomies.push("category");
|
|
736
|
+
const tagDef = await lookupDef("tag");
|
|
737
|
+
if (tagDef) for (const tag of tags) {
|
|
738
|
+
const slug = tag.slug;
|
|
739
|
+
const label = tag.name;
|
|
740
|
+
if (!slug || !label) continue;
|
|
741
|
+
await ensureTerm(repo, state, "tag", slug, label, tag.description, locale);
|
|
742
|
+
}
|
|
743
|
+
else if (tags.length > 0) state.plan.missingTaxonomies.push("tag");
|
|
744
|
+
for (const term of terms) {
|
|
745
|
+
if (term.taxonomy === "nav_menu" || term.taxonomy === "language") continue;
|
|
746
|
+
const taxonomyName = term.taxonomy === "post_tag" ? "tag" : term.taxonomy;
|
|
747
|
+
if (!await lookupDef(taxonomyName)) {
|
|
748
|
+
if (!state.plan.missingTaxonomies.includes(taxonomyName)) state.plan.missingTaxonomies.push(taxonomyName);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
await ensureTerm(repo, state, taxonomyName, term.slug, term.name, term.description, locale);
|
|
752
|
+
}
|
|
753
|
+
let recordedMissingCategoryFromPosts = false;
|
|
754
|
+
let recordedMissingTagFromPosts = false;
|
|
755
|
+
for (const post of posts) {
|
|
756
|
+
for (const slug of post.categories) {
|
|
757
|
+
if (!categoryDef) {
|
|
758
|
+
if (!recordedMissingCategoryFromPosts && !state.plan.missingTaxonomies.includes("category")) {
|
|
759
|
+
state.plan.missingTaxonomies.push("category");
|
|
760
|
+
recordedMissingCategoryFromPosts = true;
|
|
761
|
+
}
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
if (state.plan.termIdByNameAndSlug.get("category")?.has(slug)) continue;
|
|
765
|
+
await ensureTerm(repo, state, "category", slug, labelFor(post, "category", slug), void 0, locale);
|
|
766
|
+
}
|
|
767
|
+
for (const slug of post.tags) {
|
|
768
|
+
if (!tagDef) {
|
|
769
|
+
if (!recordedMissingTagFromPosts && !state.plan.missingTaxonomies.includes("tag")) {
|
|
770
|
+
state.plan.missingTaxonomies.push("tag");
|
|
771
|
+
recordedMissingTagFromPosts = true;
|
|
772
|
+
}
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
if (state.plan.termIdByNameAndSlug.get("tag")?.has(slug)) continue;
|
|
776
|
+
await ensureTerm(repo, state, "tag", slug, labelFor(post, "tag", slug), void 0, locale);
|
|
777
|
+
}
|
|
778
|
+
if (!post.customTaxonomies) continue;
|
|
779
|
+
for (const [rawName, slugs] of post.customTaxonomies) {
|
|
780
|
+
if (rawName === "nav_menu" || rawName === "language") continue;
|
|
781
|
+
const taxonomyName = rawName === "post_tag" ? "tag" : rawName;
|
|
782
|
+
if (!await lookupDef(taxonomyName)) {
|
|
783
|
+
if (!state.plan.missingTaxonomies.includes(taxonomyName)) state.plan.missingTaxonomies.push(taxonomyName);
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
for (const slug of slugs) {
|
|
787
|
+
if (state.plan.termIdByNameAndSlug.get(taxonomyName)?.has(slug)) continue;
|
|
788
|
+
await ensureTerm(repo, state, taxonomyName, slug, labelFor(post, taxonomyName, slug), void 0, locale);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
invalidateTermCache();
|
|
793
|
+
return state.plan;
|
|
794
|
+
}
|
|
795
|
+
function resolvePostTermAssignments(collection, post, plan) {
|
|
796
|
+
const result = /* @__PURE__ */ new Map();
|
|
797
|
+
const seen = /* @__PURE__ */ new Set();
|
|
798
|
+
const tryResolve = (taxonomyName, slug) => {
|
|
799
|
+
const termId = plan.termIdByNameAndSlug.get(taxonomyName)?.get(slug);
|
|
800
|
+
if (!termId) return;
|
|
801
|
+
const collectionFilter = plan.collectionsByTaxonomy.get(taxonomyName);
|
|
802
|
+
if (collectionFilter && collectionFilter.size > 0 && !collectionFilter.has(collection)) return;
|
|
803
|
+
const dedupeKey = `${taxonomyName}\u0000${termId}`;
|
|
804
|
+
if (seen.has(dedupeKey)) return;
|
|
805
|
+
seen.add(dedupeKey);
|
|
806
|
+
const existing = result.get(taxonomyName);
|
|
807
|
+
if (existing) existing.push(termId);
|
|
808
|
+
else result.set(taxonomyName, [termId]);
|
|
809
|
+
};
|
|
810
|
+
for (const slug of post.categories) tryResolve("category", slug);
|
|
811
|
+
for (const slug of post.tags) tryResolve("tag", slug);
|
|
812
|
+
if (post.customTaxonomies) for (const [rawName, slugs] of post.customTaxonomies) {
|
|
813
|
+
if (rawName === "nav_menu" || rawName === "language") continue;
|
|
814
|
+
const taxonomyName = rawName === "post_tag" ? "tag" : rawName;
|
|
815
|
+
for (const slug of slugs) tryResolve(taxonomyName, slug);
|
|
816
|
+
}
|
|
817
|
+
return result;
|
|
818
|
+
}
|
|
819
|
+
async function attachPostTaxonomies(db, collection, entryId, post, plan) {
|
|
820
|
+
const repo = new TaxonomyRepository(db);
|
|
821
|
+
const resolved = resolvePostTermAssignments(collection, post, plan);
|
|
822
|
+
let attached = 0;
|
|
823
|
+
for (const [, termIds] of resolved) for (const termId of termIds) if (await attachToEntryCountingInserts(db, repo, plan, collection, entryId, termId)) attached++;
|
|
824
|
+
return attached;
|
|
825
|
+
}
|
|
826
|
+
async function setPostTermAssignmentsReplacing(db, collection, entryId, post, plan) {
|
|
827
|
+
const repo = new TaxonomyRepository(db);
|
|
828
|
+
const resolved = resolvePostTermAssignments(collection, post, plan);
|
|
829
|
+
let attached = 0;
|
|
830
|
+
for (const [taxonomyName, termIds] of resolved) {
|
|
831
|
+
await repo.setTermsForEntry(collection, entryId, taxonomyName, termIds);
|
|
832
|
+
attached += termIds.length;
|
|
833
|
+
}
|
|
834
|
+
return attached;
|
|
835
|
+
}
|
|
836
|
+
async function termTranslationGroup(repo, plan, termId) {
|
|
837
|
+
const cached = plan.translationGroupByTermId.get(termId);
|
|
838
|
+
if (cached !== void 0) return cached;
|
|
839
|
+
const group = (await repo.findById(termId))?.translationGroup ?? null;
|
|
840
|
+
plan.translationGroupByTermId.set(termId, group);
|
|
841
|
+
return group;
|
|
842
|
+
}
|
|
843
|
+
async function attachToEntryCountingInserts(db, repo, plan, collection, entryId, termId) {
|
|
844
|
+
const group = await termTranslationGroup(repo, plan, termId);
|
|
845
|
+
if (!group) return false;
|
|
846
|
+
if (await db.selectFrom("content_taxonomies").select("collection").where("collection", "=", collection).where("entry_id", "=", entryId).where("taxonomy_id", "=", group).executeTakeFirst()) return false;
|
|
847
|
+
await repo.attachToEntry(collection, entryId, termId);
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
async function mirrorTermsToLocales(db, plan, postLocales, canonicalLocale) {
|
|
851
|
+
const localeSet = /* @__PURE__ */ new Set();
|
|
852
|
+
for (const locale of postLocales) {
|
|
853
|
+
if (!locale || locale === canonicalLocale) continue;
|
|
854
|
+
localeSet.add(locale);
|
|
855
|
+
}
|
|
856
|
+
if (localeSet.size === 0) return;
|
|
857
|
+
const repo = new TaxonomyRepository(db);
|
|
858
|
+
for (const [taxonomyName, bySlug] of plan.termIdByNameAndSlug) for (const [slug, canonicalTermId] of bySlug) {
|
|
859
|
+
const canonicalGroup = await termTranslationGroup(repo, plan, canonicalTermId);
|
|
860
|
+
if (!canonicalGroup) continue;
|
|
861
|
+
for (const locale of localeSet) {
|
|
862
|
+
const existing = await repo.findBySlug(taxonomyName, slug, locale);
|
|
863
|
+
if (existing) {
|
|
864
|
+
if (existing.translationGroup !== canonicalGroup) throw new WxrTaxonomyConflictError(`Cannot import: term "${taxonomyName}/${slug}" already exists at locale "${locale}" in a different translation group than the canonical row at this import's locale. Reconcile the rows in the admin and retry.`);
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
try {
|
|
868
|
+
await repo.create({
|
|
869
|
+
name: taxonomyName,
|
|
870
|
+
slug,
|
|
871
|
+
label: slug,
|
|
872
|
+
locale,
|
|
873
|
+
translationOf: canonicalTermId
|
|
874
|
+
});
|
|
875
|
+
} catch (error) {
|
|
876
|
+
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
877
|
+
if (!(message.includes("unique constraint failed") || message.includes("duplicate key"))) throw error;
|
|
878
|
+
const winner = await repo.findBySlug(taxonomyName, slug, locale);
|
|
879
|
+
if (!winner) throw new WxrTaxonomyConflictError(`Cannot import: term "${taxonomyName}/${slug}" raced UNIQUE at locale "${locale}" but no row is visible afterwards. Retry the import.`, { cause: error });
|
|
880
|
+
if (winner.translationGroup !== canonicalGroup) throw new WxrTaxonomyConflictError(`Cannot import: term "${taxonomyName}/${slug}" raced UNIQUE at locale "${locale}" with a different translation group. Reconcile the rows in the admin and retry.`, { cause: error });
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
//#endregion
|
|
887
|
+
//#region src/import/sources/wordpress-rest.ts
|
|
888
|
+
/**
|
|
889
|
+
* WordPress REST API probe
|
|
890
|
+
*
|
|
891
|
+
* Probes self-hosted WordPress sites to detect capabilities.
|
|
892
|
+
* This source is probe-only - it tells users what's available
|
|
893
|
+
* and suggests next steps (usually: upload WXR file).
|
|
894
|
+
*/
|
|
895
|
+
const TRAILING_SLASHES = /\/+$/;
|
|
896
|
+
const WP_JSON_SUFFIX = /\/wp-json\/?$/;
|
|
897
|
+
const wordpressRestSource = {
|
|
898
|
+
id: "wordpress-rest",
|
|
899
|
+
name: "WordPress Site",
|
|
900
|
+
description: "Connect to a self-hosted WordPress site",
|
|
901
|
+
icon: "globe",
|
|
902
|
+
requiresFile: false,
|
|
903
|
+
canProbe: true,
|
|
904
|
+
async probe(url) {
|
|
905
|
+
try {
|
|
906
|
+
const siteUrl = normalizeUrl(url);
|
|
907
|
+
validateExternalUrl(siteUrl);
|
|
908
|
+
const response = await ssrfSafeFetch(`${siteUrl}/wp-json/`, {
|
|
909
|
+
headers: { Accept: "application/json" },
|
|
910
|
+
signal: AbortSignal.timeout(1e4)
|
|
911
|
+
});
|
|
912
|
+
if (!response.ok) {
|
|
913
|
+
if (!(await ssrfSafeFetch(`${siteUrl}/?rest_route=/`, {
|
|
914
|
+
headers: { Accept: "application/json" },
|
|
915
|
+
signal: AbortSignal.timeout(1e4)
|
|
916
|
+
})).ok) return null;
|
|
917
|
+
}
|
|
918
|
+
const data = await response.json();
|
|
919
|
+
if (!data.namespaces?.includes("wp/v2")) return null;
|
|
920
|
+
const preview = await getPublicContentCounts(siteUrl);
|
|
921
|
+
const hasAppPasswords = !!data.authentication?.["application-passwords"];
|
|
922
|
+
return {
|
|
923
|
+
sourceId: "wordpress-rest",
|
|
924
|
+
confidence: "definite",
|
|
925
|
+
detected: {
|
|
926
|
+
platform: "wordpress",
|
|
927
|
+
siteTitle: data.name,
|
|
928
|
+
siteUrl: data.url || data.home || siteUrl
|
|
929
|
+
},
|
|
930
|
+
capabilities: {
|
|
931
|
+
publicContent: true,
|
|
932
|
+
privateContent: false,
|
|
933
|
+
customPostTypes: false,
|
|
934
|
+
allMeta: false,
|
|
935
|
+
mediaStream: true
|
|
936
|
+
},
|
|
937
|
+
auth: hasAppPasswords ? {
|
|
938
|
+
type: "password",
|
|
939
|
+
instructions: "To import drafts and private content, create an Application Password in WordPress → Users → Your Profile → Application Passwords"
|
|
940
|
+
} : void 0,
|
|
941
|
+
preview,
|
|
942
|
+
suggestedAction: {
|
|
943
|
+
type: "upload",
|
|
944
|
+
instructions: "For a complete import including drafts, custom post types, and all metadata, export your content from WordPress (Tools → Export) and upload the file here."
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
} catch {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
async analyze(_input, _context) {
|
|
952
|
+
throw new Error("Direct REST API import not implemented. Please upload a WXR export file.");
|
|
953
|
+
},
|
|
954
|
+
async *fetchContent(_input, _options) {
|
|
955
|
+
throw new Error("Direct REST API import not implemented. Please upload a WXR export file.");
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
/**
|
|
959
|
+
* Normalize a URL for API requests
|
|
960
|
+
*/
|
|
961
|
+
function normalizeUrl(url) {
|
|
962
|
+
let normalized = url.trim();
|
|
963
|
+
if (!normalized.startsWith("http")) normalized = `https://${normalized}`;
|
|
964
|
+
normalized = normalized.replace(TRAILING_SLASHES, "");
|
|
965
|
+
normalized = normalized.replace(WP_JSON_SUFFIX, "");
|
|
966
|
+
return normalized;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Get public content counts from REST API
|
|
970
|
+
*/
|
|
971
|
+
async function getPublicContentCounts(siteUrl) {
|
|
972
|
+
const result = {};
|
|
973
|
+
try {
|
|
974
|
+
const [postsRes, pagesRes, mediaRes] = await Promise.allSettled([
|
|
975
|
+
ssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/posts?per_page=1`, { signal: AbortSignal.timeout(5e3) }),
|
|
976
|
+
ssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/pages?per_page=1`, { signal: AbortSignal.timeout(5e3) }),
|
|
977
|
+
ssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/media?per_page=1`, { signal: AbortSignal.timeout(5e3) })
|
|
978
|
+
]);
|
|
979
|
+
if (postsRes.status === "fulfilled" && postsRes.value.ok) {
|
|
980
|
+
const total = postsRes.value.headers.get("X-WP-Total");
|
|
981
|
+
if (total) result.posts = parseInt(total, 10);
|
|
982
|
+
}
|
|
983
|
+
if (pagesRes.status === "fulfilled" && pagesRes.value.ok) {
|
|
984
|
+
const total = pagesRes.value.headers.get("X-WP-Total");
|
|
985
|
+
if (total) result.pages = parseInt(total, 10);
|
|
986
|
+
}
|
|
987
|
+
if (mediaRes.status === "fulfilled" && mediaRes.value.ok) {
|
|
988
|
+
const total = mediaRes.value.headers.get("X-WP-Total");
|
|
989
|
+
if (total) result.media = parseInt(total, 10);
|
|
990
|
+
}
|
|
991
|
+
} catch {}
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
//#endregion
|
|
996
|
+
//#region src/import/sources/wordpress-plugin.ts
|
|
997
|
+
/**
|
|
998
|
+
* WordPress Plugin (Dineway Exporter) import source
|
|
999
|
+
*
|
|
1000
|
+
* Connects to self-hosted WordPress sites running the Dineway Exporter plugin.
|
|
1001
|
+
* Provides full access to all content including drafts, custom post types, and ACF fields.
|
|
1002
|
+
*/
|
|
1003
|
+
const LEADING_SLASHES = /^\/+/;
|
|
1004
|
+
const TRAILING_SLASH = /\/$/;
|
|
1005
|
+
const FIELD_KEY_SEPARATORS = /[_-]+/;
|
|
1006
|
+
const CORE_TAXONOMY_NAMES = new Set([
|
|
1007
|
+
"category",
|
|
1008
|
+
"categories",
|
|
1009
|
+
"post_tag",
|
|
1010
|
+
"tags"
|
|
1011
|
+
]);
|
|
1012
|
+
const VALID_INFERRED_TYPES = new Set([
|
|
1013
|
+
"string",
|
|
1014
|
+
"text",
|
|
1015
|
+
"number",
|
|
1016
|
+
"integer",
|
|
1017
|
+
"boolean",
|
|
1018
|
+
"datetime",
|
|
1019
|
+
"json",
|
|
1020
|
+
"reference"
|
|
1021
|
+
]);
|
|
1022
|
+
const wordpressPluginSource = {
|
|
1023
|
+
id: "wordpress-plugin",
|
|
1024
|
+
name: "WordPress (Dineway Exporter)",
|
|
1025
|
+
description: "Import from WordPress sites with the Dineway Exporter plugin installed",
|
|
1026
|
+
icon: "plug",
|
|
1027
|
+
requiresFile: false,
|
|
1028
|
+
canProbe: true,
|
|
1029
|
+
async probe(url) {
|
|
1030
|
+
try {
|
|
1031
|
+
const siteUrl = normalizeUrl$1(url);
|
|
1032
|
+
validateExternalUrl(siteUrl);
|
|
1033
|
+
const response = await fetchPluginApi(siteUrl, "probe", {}, { Accept: "application/json" }, 1e4);
|
|
1034
|
+
if (!response.ok) return null;
|
|
1035
|
+
const data = await response.json();
|
|
1036
|
+
if (!data.dineway_exporter) return null;
|
|
1037
|
+
return {
|
|
1038
|
+
sourceId: "wordpress-plugin",
|
|
1039
|
+
confidence: "definite",
|
|
1040
|
+
detected: {
|
|
1041
|
+
platform: "wordpress",
|
|
1042
|
+
version: data.wordpress_version,
|
|
1043
|
+
siteTitle: data.site.title,
|
|
1044
|
+
siteUrl: data.site.url
|
|
1045
|
+
},
|
|
1046
|
+
capabilities: {
|
|
1047
|
+
publicContent: true,
|
|
1048
|
+
privateContent: true,
|
|
1049
|
+
customPostTypes: true,
|
|
1050
|
+
allMeta: true,
|
|
1051
|
+
mediaStream: true
|
|
1052
|
+
},
|
|
1053
|
+
auth: data.capabilities.application_passwords ? {
|
|
1054
|
+
type: "password",
|
|
1055
|
+
instructions: data.auth_instructions.instructions
|
|
1056
|
+
} : void 0,
|
|
1057
|
+
preview: {
|
|
1058
|
+
posts: data.post_types.find((p) => p.name === "post")?.count,
|
|
1059
|
+
pages: data.post_types.find((p) => p.name === "page")?.count,
|
|
1060
|
+
media: data.media_count
|
|
1061
|
+
},
|
|
1062
|
+
suggestedAction: { type: "proceed" },
|
|
1063
|
+
i18n: pluginI18nToDetection(data.i18n)
|
|
1064
|
+
};
|
|
1065
|
+
} catch {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
async analyze(input, context) {
|
|
1070
|
+
const { siteUrl, headers } = getRequestConfig(input);
|
|
1071
|
+
const response = await fetchPluginApi(siteUrl, "analyze", {}, headers, 3e4);
|
|
1072
|
+
if (!response.ok) {
|
|
1073
|
+
const error = await response.json().catch(() => ({}));
|
|
1074
|
+
throw new Error(error.message || `Failed to analyze site: ${response.statusText}`);
|
|
1075
|
+
}
|
|
1076
|
+
const data = await response.json();
|
|
1077
|
+
const existingCollections = context.getExistingCollections ? await context.getExistingCollections() : /* @__PURE__ */ new Map();
|
|
1078
|
+
const postTypes = data.post_types.filter((pt) => pt.total > 0).map((pt) => {
|
|
1079
|
+
const suggestedCollection = mapPostTypeToCollection(pt.name);
|
|
1080
|
+
const existingCollection = existingCollections.get(suggestedCollection);
|
|
1081
|
+
const requiredFields = pt.supports && "thumbnail" in pt.supports ? [...BASE_REQUIRED_FIELDS, FEATURED_IMAGE_FIELD] : [...BASE_REQUIRED_FIELDS];
|
|
1082
|
+
const knownSlugs = new Set(requiredFields.map((field) => field.slug));
|
|
1083
|
+
for (const customField of pt.custom_fields ?? []) {
|
|
1084
|
+
if (isPluginBookkeepingMeta(customField.key)) continue;
|
|
1085
|
+
const slug = sanitizeFieldSlug(customField.key);
|
|
1086
|
+
if (knownSlugs.has(slug)) continue;
|
|
1087
|
+
knownSlugs.add(slug);
|
|
1088
|
+
requiredFields.push({
|
|
1089
|
+
slug,
|
|
1090
|
+
label: fieldLabelFromKey(customField.key),
|
|
1091
|
+
type: mapInferredFieldType(customField.inferred_type),
|
|
1092
|
+
required: false
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return {
|
|
1096
|
+
name: pt.name,
|
|
1097
|
+
count: pt.total,
|
|
1098
|
+
suggestedCollection,
|
|
1099
|
+
requiredFields,
|
|
1100
|
+
schemaStatus: checkSchemaCompatibility(requiredFields, existingCollection)
|
|
1101
|
+
};
|
|
1102
|
+
});
|
|
1103
|
+
const attachments = [];
|
|
1104
|
+
if (data.attachments.count > 0) try {
|
|
1105
|
+
let page = 1;
|
|
1106
|
+
let totalPages = 1;
|
|
1107
|
+
while (page <= totalPages) {
|
|
1108
|
+
const mediaResponse = await fetchPluginApi(siteUrl, "media", {
|
|
1109
|
+
page,
|
|
1110
|
+
per_page: 500
|
|
1111
|
+
}, headers, 3e4);
|
|
1112
|
+
if (!mediaResponse.ok) break;
|
|
1113
|
+
const mediaData = await mediaResponse.json();
|
|
1114
|
+
totalPages = Math.max(1, mediaData.pages || 1);
|
|
1115
|
+
for (const item of mediaData.items) attachments.push({
|
|
1116
|
+
id: item.id,
|
|
1117
|
+
url: item.url,
|
|
1118
|
+
filename: item.filename,
|
|
1119
|
+
mimeType: item.mime_type,
|
|
1120
|
+
title: item.title,
|
|
1121
|
+
alt: item.alt,
|
|
1122
|
+
caption: item.caption,
|
|
1123
|
+
width: item.width,
|
|
1124
|
+
height: item.height
|
|
1125
|
+
});
|
|
1126
|
+
page++;
|
|
1127
|
+
}
|
|
1128
|
+
} catch (e) {
|
|
1129
|
+
console.warn("Failed to fetch media list:", e);
|
|
1130
|
+
}
|
|
1131
|
+
const categoryTaxonomy = data.taxonomies.find((t) => t.name === "category");
|
|
1132
|
+
const tagTaxonomy = data.taxonomies.find((t) => t.name === "post_tag");
|
|
1133
|
+
return {
|
|
1134
|
+
sourceId: "wordpress-plugin",
|
|
1135
|
+
site: {
|
|
1136
|
+
title: data.site.title,
|
|
1137
|
+
url: data.site.url
|
|
1138
|
+
},
|
|
1139
|
+
postTypes,
|
|
1140
|
+
attachments: {
|
|
1141
|
+
count: data.attachments.count,
|
|
1142
|
+
items: attachments
|
|
1143
|
+
},
|
|
1144
|
+
categories: categoryTaxonomy?.term_count ?? 0,
|
|
1145
|
+
tags: tagTaxonomy?.term_count ?? 0,
|
|
1146
|
+
authors: data.authors.map((a) => ({
|
|
1147
|
+
id: a.id,
|
|
1148
|
+
login: a.login,
|
|
1149
|
+
email: a.email,
|
|
1150
|
+
displayName: a.display_name,
|
|
1151
|
+
postCount: a.post_count
|
|
1152
|
+
})),
|
|
1153
|
+
i18n: pluginI18nToDetection(data.i18n)
|
|
1154
|
+
};
|
|
1155
|
+
},
|
|
1156
|
+
async *fetchContent(input, options) {
|
|
1157
|
+
const { siteUrl, headers } = getRequestConfig(input);
|
|
1158
|
+
for (const postType of options.postTypes) {
|
|
1159
|
+
let page = 1;
|
|
1160
|
+
let totalPages = 1;
|
|
1161
|
+
let yielded = 0;
|
|
1162
|
+
while (page <= totalPages) {
|
|
1163
|
+
const status = options.includeDrafts ? "any" : "publish";
|
|
1164
|
+
const response = await fetchPluginApi(siteUrl, "content", {
|
|
1165
|
+
page,
|
|
1166
|
+
per_page: 100,
|
|
1167
|
+
post_type: postType,
|
|
1168
|
+
status
|
|
1169
|
+
}, headers, 6e4);
|
|
1170
|
+
if (!response.ok) throw new Error(`Failed to fetch ${postType}: ${response.statusText}`);
|
|
1171
|
+
const data = await response.json();
|
|
1172
|
+
totalPages = data.pages;
|
|
1173
|
+
for (const post of data.items) {
|
|
1174
|
+
yield pluginPostToNormalizedItem(post, siteUrl);
|
|
1175
|
+
yielded++;
|
|
1176
|
+
if (options.limit && yielded >= options.limit) return;
|
|
1177
|
+
}
|
|
1178
|
+
page++;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
async fetchMedia(url, _input) {
|
|
1183
|
+
validateExternalUrl(url);
|
|
1184
|
+
const response = await ssrfSafeFetch(url);
|
|
1185
|
+
if (!response.ok) throw new Error(`Failed to fetch media: ${response.statusText}`);
|
|
1186
|
+
return response.blob();
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
/**
|
|
1190
|
+
* Convert plugin i18n info to the shared I18nDetection type.
|
|
1191
|
+
* Returns undefined when no multilingual plugin is detected.
|
|
1192
|
+
*/
|
|
1193
|
+
function pluginI18nToDetection(i18n) {
|
|
1194
|
+
if (!i18n) return void 0;
|
|
1195
|
+
return {
|
|
1196
|
+
plugin: i18n.plugin,
|
|
1197
|
+
defaultLocale: i18n.default_locale,
|
|
1198
|
+
locales: i18n.locales
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
function mapInferredFieldType(inferredType) {
|
|
1202
|
+
return VALID_INFERRED_TYPES.has(inferredType) ? inferredType : "string";
|
|
1203
|
+
}
|
|
1204
|
+
function fieldLabelFromKey(key) {
|
|
1205
|
+
return key.split(FIELD_KEY_SEPARATORS).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
1206
|
+
}
|
|
1207
|
+
function pluginApiUrl(siteUrl, endpoint, params = {}, restRoute = false) {
|
|
1208
|
+
const cleanEndpoint = endpoint.replace(LEADING_SLASHES, "");
|
|
1209
|
+
const url = restRoute ? new URL(`${siteUrl.replace(TRAILING_SLASH, "")}/`) : new URL(`${siteUrl.replace(TRAILING_SLASH, "")}/wp-json/dineway/v1/${cleanEndpoint}`);
|
|
1210
|
+
if (restRoute) url.searchParams.set("rest_route", `/dineway/v1/${cleanEndpoint}`);
|
|
1211
|
+
for (const [key, value] of Object.entries(params)) if (value !== void 0) url.searchParams.set(key, String(value));
|
|
1212
|
+
return url.toString();
|
|
1213
|
+
}
|
|
1214
|
+
async function fetchPluginApi(siteUrl, endpoint, params, headers, timeoutMs) {
|
|
1215
|
+
try {
|
|
1216
|
+
const response = await ssrfSafeFetch(pluginApiUrl(siteUrl, endpoint, params), {
|
|
1217
|
+
headers,
|
|
1218
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1219
|
+
});
|
|
1220
|
+
if (response.status !== 404) return response;
|
|
1221
|
+
} catch {}
|
|
1222
|
+
return ssrfSafeFetch(pluginApiUrl(siteUrl, endpoint, params, true), {
|
|
1223
|
+
headers,
|
|
1224
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Get request configuration from input
|
|
1229
|
+
*/
|
|
1230
|
+
function getRequestConfig(input) {
|
|
1231
|
+
if (input.type === "url") {
|
|
1232
|
+
const siteUrl = normalizeUrl$1(input.url);
|
|
1233
|
+
validateExternalUrl(siteUrl);
|
|
1234
|
+
const headers = { Accept: "application/json" };
|
|
1235
|
+
if (input.token) headers["Authorization"] = `Basic ${input.token}`;
|
|
1236
|
+
return {
|
|
1237
|
+
siteUrl,
|
|
1238
|
+
headers
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
if (input.type === "oauth") {
|
|
1242
|
+
const oauthSiteUrl = normalizeUrl$1(input.url);
|
|
1243
|
+
validateExternalUrl(oauthSiteUrl);
|
|
1244
|
+
return {
|
|
1245
|
+
siteUrl: oauthSiteUrl,
|
|
1246
|
+
headers: {
|
|
1247
|
+
Accept: "application/json",
|
|
1248
|
+
Authorization: `Bearer ${input.accessToken}`
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
throw new Error("WordPress plugin source requires URL or OAuth input");
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Convert plugin post to normalized item
|
|
1256
|
+
*/
|
|
1257
|
+
function pluginPostToNormalizedItem(post, siteUrl) {
|
|
1258
|
+
const content = post.content ? gutenbergToPortableText(post.content) : [];
|
|
1259
|
+
relativizeContentLinks(content, siteUrl);
|
|
1260
|
+
const categories = post.taxonomies?.category?.map((c) => c.slug) ?? post.taxonomies?.categories?.map((c) => c.slug) ?? [];
|
|
1261
|
+
const tags = post.taxonomies?.post_tag?.map((t) => t.slug) ?? post.taxonomies?.tags?.map((t) => t.slug) ?? [];
|
|
1262
|
+
const customTaxonomies = {};
|
|
1263
|
+
for (const [name, terms] of Object.entries(post.taxonomies ?? {})) {
|
|
1264
|
+
if (CORE_TAXONOMY_NAMES.has(name)) continue;
|
|
1265
|
+
if (terms.length > 0) customTaxonomies[name] = terms.map((term) => term.slug);
|
|
1266
|
+
}
|
|
1267
|
+
const meta = { ...post.meta };
|
|
1268
|
+
if (post.acf) meta._acf = post.acf;
|
|
1269
|
+
if (post.yoast) meta._yoast = post.yoast;
|
|
1270
|
+
if (post.rankmath) meta._rankmath = post.rankmath;
|
|
1271
|
+
return {
|
|
1272
|
+
sourceId: post.id,
|
|
1273
|
+
postType: post.post_type,
|
|
1274
|
+
status: mapWpStatus(post.status),
|
|
1275
|
+
slug: post.slug,
|
|
1276
|
+
title: post.title,
|
|
1277
|
+
content,
|
|
1278
|
+
excerpt: post.excerpt || void 0,
|
|
1279
|
+
date: new Date(post.date_gmt || post.date),
|
|
1280
|
+
modified: post.modified_gmt ? new Date(post.modified_gmt) : new Date(post.modified),
|
|
1281
|
+
author: post.author?.login,
|
|
1282
|
+
categories,
|
|
1283
|
+
tags,
|
|
1284
|
+
customTaxonomies: Object.keys(customTaxonomies).length > 0 ? customTaxonomies : void 0,
|
|
1285
|
+
meta,
|
|
1286
|
+
featuredImage: post.featured_image?.url,
|
|
1287
|
+
locale: post.locale,
|
|
1288
|
+
translationGroup: post.translation_group
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Fetch navigation menus from plugin API.
|
|
1293
|
+
* Returns an empty array when the endpoint doesn't exist on older plugins.
|
|
1294
|
+
*/
|
|
1295
|
+
async function fetchPluginMenus(siteUrl, authToken) {
|
|
1296
|
+
const normalizedSiteUrl = normalizeUrl$1(siteUrl);
|
|
1297
|
+
validateExternalUrl(normalizedSiteUrl);
|
|
1298
|
+
const response = await fetchPluginApi(normalizedSiteUrl, "menus", {}, {
|
|
1299
|
+
Accept: "application/json",
|
|
1300
|
+
Authorization: `Basic ${authToken}`
|
|
1301
|
+
}, 3e4);
|
|
1302
|
+
if (response.status === 404) return [];
|
|
1303
|
+
if (!response.ok) throw new Error(`Failed to fetch menus: ${response.statusText}`);
|
|
1304
|
+
return response.json();
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Fetch site options from plugin API.
|
|
1308
|
+
*/
|
|
1309
|
+
async function fetchPluginOptions(siteUrl, authToken) {
|
|
1310
|
+
const normalizedSiteUrl = normalizeUrl$1(siteUrl);
|
|
1311
|
+
validateExternalUrl(normalizedSiteUrl);
|
|
1312
|
+
const response = await fetchPluginApi(normalizedSiteUrl, "options", {}, {
|
|
1313
|
+
Accept: "application/json",
|
|
1314
|
+
Authorization: `Basic ${authToken}`
|
|
1315
|
+
}, 3e4);
|
|
1316
|
+
if (!response.ok) throw new Error(`Failed to fetch options: ${response.statusText}`);
|
|
1317
|
+
return response.json();
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Fetch all comments from plugin API, paginating through every page.
|
|
1321
|
+
* Returns an empty array when the endpoint doesn't exist on older plugins.
|
|
1322
|
+
*/
|
|
1323
|
+
async function fetchPluginComments(siteUrl, authToken) {
|
|
1324
|
+
const normalizedSiteUrl = normalizeUrl$1(siteUrl);
|
|
1325
|
+
validateExternalUrl(normalizedSiteUrl);
|
|
1326
|
+
const comments = [];
|
|
1327
|
+
let page = 1;
|
|
1328
|
+
let totalPages = 1;
|
|
1329
|
+
while (page <= totalPages) {
|
|
1330
|
+
const response = await fetchPluginApi(normalizedSiteUrl, "comments", {
|
|
1331
|
+
page,
|
|
1332
|
+
per_page: 500
|
|
1333
|
+
}, {
|
|
1334
|
+
Accept: "application/json",
|
|
1335
|
+
Authorization: `Basic ${authToken}`
|
|
1336
|
+
}, 3e4);
|
|
1337
|
+
if (response.status === 404) return [];
|
|
1338
|
+
if (!response.ok) throw new Error(`Failed to fetch comments: ${response.statusText}`);
|
|
1339
|
+
const data = await response.json();
|
|
1340
|
+
totalPages = Math.max(1, data.pages || 1);
|
|
1341
|
+
comments.push(...data.items);
|
|
1342
|
+
page++;
|
|
1343
|
+
}
|
|
1344
|
+
return comments;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region src/import/index.ts
|
|
1349
|
+
registerSource(wordpressPluginSource);
|
|
1350
|
+
registerSource(wordpressRestSource);
|
|
1351
|
+
registerSource(wxrSource);
|
|
1352
|
+
|
|
1353
|
+
//#endregion
|
|
1354
|
+
export { importMenusFromPlugin as C, importCommentsFromPlugin as S, probeUrl as _, attachPostTaxonomies as a, parseSiteSettingsFromPlugin as b, preImportWxrTaxonomies as c, wxrSource as d, clearSources as f, getUrlSources as g, getSource as h, wordpressRestSource as i, setPostTermAssignmentsReplacing as l, getFileSources as m, fetchPluginMenus as n, isWxrTaxonomyConflictError as o, getAllSources as p, fetchPluginOptions as r, mirrorTermsToLocales as s, fetchPluginComments as t, parseWxrDate as u, registerSource as v, importReusableBlocksAsSections as x, importSiteSettings as y };
|