dineway 0.1.49 → 0.2.1
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-DUaz_w7p.mjs +79 -0
- package/dist/{activity-events-CwM7yxPW.mjs → activity-events-CJDvE2uc.mjs} +5 -113
- package/dist/actor-0QJ1Gbtf.mjs +37 -0
- package/dist/after-BfniwgIf.mjs +9 -0
- package/dist/{allowed-origins-CyGnbgBd.mjs → allowed-origins--OwHHiY8.mjs} +1 -1
- package/dist/api/route-utils.d.mts +9 -10
- package/dist/api/route-utils.mjs +16 -13
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +5 -4
- package/dist/{api-BMIxRIEN.mjs → api-B4PHIF4V.mjs} +93 -41
- package/dist/{api-tokens-DtvdtIkm.mjs → api-tokens-tbK_sORH.mjs} +6 -2
- package/dist/{apply-D_RlTYtC.mjs → apply-DjTqq6Ae.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 +34 -11
- package/dist/astro/index.mjs +163 -24
- 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 +8 -18
- package/dist/astro/middleware/auth.mjs +42 -24
- package/dist/astro/middleware/redirect.mjs +13 -4
- package/dist/astro/middleware/request-context.mjs +8 -5
- 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 +389 -206
- 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 +21 -17
- 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 +17 -14
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -14
- package/dist/astro/routes/api/admin/bylines/index.mjs +17 -14
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +14 -12
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +11 -9
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +11 -9
- package/dist/astro/routes/api/admin/context/_id_/history.mjs +27 -24
- package/dist/astro/routes/api/admin/context/_id_/index.mjs +27 -24
- package/dist/astro/routes/api/admin/context/_id_/review.mjs +32 -28
- package/dist/astro/routes/api/admin/context/_id_/supersede.mjs +32 -28
- package/dist/astro/routes/api/admin/context/diff.mjs +31 -27
- package/dist/astro/routes/api/admin/context/index.mjs +32 -28
- package/dist/astro/routes/api/admin/context/stale.mjs +31 -27
- package/dist/astro/routes/api/admin/hitl-requests/_id_/index.mjs +27 -24
- package/dist/astro/routes/api/admin/hitl-requests/_id_/resolve.mjs +31 -27
- package/dist/astro/routes/api/admin/hitl-requests/index.mjs +31 -27
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +28 -25
- 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 -51
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +56 -51
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +47 -43
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +54 -49
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +53 -48
- package/dist/astro/routes/api/admin/plugins/index.mjs +47 -43
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +46 -42
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +53 -48
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +46 -42
- package/dist/astro/routes/api/admin/plugins/updates.mjs +46 -42
- package/dist/astro/routes/api/admin/review-requests/_id_/index.mjs +5 -5
- package/dist/astro/routes/api/admin/review-requests/_id_/resolve.mjs +32 -63
- package/dist/astro/routes/api/admin/review-requests/index.d.mts +2 -1
- package/dist/astro/routes/api/admin/review-requests/index.mjs +58 -10
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +46 -42
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +46 -42
- 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 +9 -8
- 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 +5 -5
- 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 +15 -13
- 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 -5
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +11 -10
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +49 -20
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +67 -32
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +16 -14
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +11 -9
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +11 -9
- package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -6
- package/dist/astro/routes/api/dashboard.mjs +9 -8
- 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 +22 -10
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +9 -6
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +18 -1
- package/dist/astro/routes/api/import/wordpress/execute.mjs +85 -37
- package/dist/astro/routes/api/import/wordpress/media.d.mts +12 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +20 -14
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +36 -32
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +17 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +121 -100
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +24 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +55 -2
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +284 -55
- package/dist/astro/routes/api/manifest.mjs +14 -5
- package/dist/astro/routes/api/mcp.mjs +753 -170
- package/dist/astro/routes/api/media/_id_/confirm.mjs +38 -10
- 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 +11 -10
- package/dist/astro/routes/api/media.mjs +28 -35
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_/items.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_/translations.mjs +32 -28
- package/dist/astro/routes/api/menus/_name_.mjs +32 -28
- package/dist/astro/routes/api/menus/index.mjs +32 -28
- 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 +46 -31
- package/dist/astro/routes/api/redirects/404s/index.mjs +11 -10
- package/dist/astro/routes/api/redirects/404s/summary.mjs +11 -10
- package/dist/astro/routes/api/redirects/_id_.mjs +33 -29
- package/dist/astro/routes/api/redirects/index.mjs +33 -29
- 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 -43
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +48 -43
- package/dist/astro/routes/api/schema/collections/index.mjs +48 -43
- package/dist/astro/routes/api/schema/index.mjs +14 -15
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +46 -42
- package/dist/astro/routes/api/schema/orphans/index.mjs +46 -42
- package/dist/astro/routes/api/search/enable.mjs +11 -10
- package/dist/astro/routes/api/search/index.mjs +10 -9
- package/dist/astro/routes/api/search/rebuild.mjs +11 -10
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +10 -9
- package/dist/astro/routes/api/sections/_slug_.mjs +32 -28
- package/dist/astro/routes/api/sections/index.mjs +32 -28
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +32 -28
- 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 +31 -25
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +29 -25
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +8 -7
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +35 -31
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +35 -31
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +35 -31
- package/dist/astro/routes/api/taxonomies/index.mjs +35 -31
- package/dist/astro/routes/api/themes/preview.mjs +5 -5
- 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 -27
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +32 -28
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +32 -28
- package/dist/astro/routes/api/widget-areas/_name_.mjs +29 -26
- package/dist/astro/routes/api/widget-areas/index.mjs +32 -28
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +8 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +23 -14
- package/dist/astro/routes/sitemap.xml.mjs +9 -8
- package/dist/astro/session-driver.d.mts +1 -1
- package/dist/astro/session-driver.mjs +2 -2
- package/dist/astro/types.d.mts +22 -13
- package/dist/{audit-BeNsSyfU.mjs → audit-B2pRxZ20.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-D71jourk.mjs} +1 -1
- package/dist/{authorize-BLO0m7fW.mjs → authorize-ZXPGPwKn.mjs} +1 -1
- package/dist/{briefing-CkTee1ex.mjs → briefing-0rf-su3-.mjs} +90 -28
- package/dist/{briefing-DJX6irto.mjs → briefing-kRhegBoF.mjs} +2 -1
- package/dist/bundle-CEcdUa1s.mjs +602 -0
- package/dist/{byline-BVC1lV6G.mjs → byline-7xR185Ly.mjs} +30 -62
- package/dist/{byline-fields-Cj39WhXC.mjs → byline-fields-BKzA0fJU.mjs} +1 -1
- package/dist/{byline-fields-uJlygdzM.d.mts → byline-fields-Ckeqd1OY.d.mts} +88 -35
- package/dist/{byline-fields-cNrLJVRs.mjs → byline-fields-WN7RQT4q.mjs} +10 -4
- package/dist/{byline-registry-4v6tXMNX.mjs → byline-registry-Ba54M74c.mjs} +2 -2
- package/dist/{bylines-LN9XISRo.mjs → bylines-C-EL6dh3.mjs} +9 -3
- package/dist/{bylines-DwokOFj6.mjs → bylines-CmZFCMr0.mjs} +5 -5
- package/dist/{cache-Bac05gBd.mjs → cache-DkVCKGio.mjs} +2 -2
- package/dist/{chunks-CuUt7K50.mjs → chunks-ChGNDY8r.mjs} +1 -1
- package/dist/cli/index.d.mts +29 -1
- package/dist/cli/index.mjs +1466 -1763
- package/dist/cli/plugin-kit.d.mts +75 -0
- package/dist/cli/plugin-kit.mjs +4 -0
- package/dist/client/external-auth-headers.d.mts +1 -1
- package/dist/client/index.d.mts +27 -52
- package/dist/client/index.mjs +34 -6
- package/dist/client-factory-BDD9azPJ.mjs +233 -0
- package/dist/{comment-Beue8-15.mjs → comment-DFALgsJD.mjs} +13 -3
- package/dist/comment-reaction-C37bf99u.mjs +58 -0
- package/dist/{comments-DcjFIfqX.mjs → comments-B3NwDBxl.mjs} +1 -1
- package/dist/{content-CBxYGWfL.mjs → content-DJcAYFhz.mjs} +51 -34
- package/dist/content-refresh-B4ZCmxgd.mjs +1576 -0
- package/dist/{context-Df1xVYyr.mjs → context-DnpXcp6Y.mjs} +2 -2
- package/dist/{context-CQVNZaR8.mjs → context-fSCYLL3c.mjs} +125 -53
- package/dist/{context-route-helpers-B9wj8Rmh.mjs → context-route-helpers-CI8XzcsK.mjs} +2 -1
- package/dist/{cron-DxCAU_Sn.mjs → cron-B1SL_lsb.mjs} +7 -3
- package/dist/{dashboard-B7ZUNuyF.mjs → dashboard-D0nj5oWg.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-BZVGftqi.mjs → device-flow-BJe6VEhP.mjs} +5 -5
- package/dist/{placeholder-d54Qr_7n.mjs → enrich-Ck9tfMqe.mjs} +36 -9
- package/dist/{error-DCSuUAZW.mjs → error-CSVaQNZy.mjs} +5 -0
- package/dist/{experimental-workflows-CSMffP6q.mjs → experimental-workflows-GVhUgzEs.mjs} +1 -1
- package/dist/field-defs-cache-DL-T2XVS.mjs +49 -0
- package/dist/forgeway-seo-client-G5ddhVHd.mjs +456 -0
- package/dist/{fts-manager-B5l_qwrA.mjs → fts-manager-ewTGohf4.mjs} +1 -1
- package/dist/{hitl-requests-7NlzVeUA.mjs → hitl-requests-DEFj8e6u.mjs} +2 -2
- package/dist/{hitl-route-helpers-k---_H3-.mjs → hitl-route-helpers-C23JHaWz.mjs} +5 -4
- package/dist/import-CXXL5pip.mjs +1354 -0
- package/dist/index-DcnOCD1e.d.mts +375 -0
- package/dist/index.d.mts +15 -16
- package/dist/index.mjs +85 -70
- package/dist/{jsonld-DZ67souc.d.mts → jsonld-DzKbhRZ4.d.mts} +8 -4
- package/dist/{load-D9OiGr2m.mjs → load-CFtJRT3c.mjs} +1 -1
- package/dist/{loader--GOYux8M.mjs → loader-aWkkc1_o.mjs} +117 -31
- package/dist/{manifest-schema-qh2P7RDX.mjs → manifest-schema-uA6pRFXi.mjs} +38 -1
- package/dist/mcp-reviewed-executor-Dn52L_yY.mjs +558 -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 +15 -11
- package/dist/media/local-runtime.mjs +9 -7
- package/dist/{media-CbKyYukG.mjs → media-CVMOoywq.mjs} +3 -1
- package/dist/{media-allowlist-BYxJIcTO.mjs → media-allowlist-zDNJmXCo.mjs} +1 -1
- package/dist/media-url-BdoLPvg8.mjs +20 -0
- package/dist/{menus-C7F0sCrC.mjs → menus-CCG2z5um.mjs} +80 -15
- package/dist/{menus-BZbXf-ej.mjs → menus-CmyIkRG5.mjs} +19 -5
- package/dist/{normalize-ByemhhfW.mjs → normalize-Cyt7JdF7.mjs} +8 -1
- package/dist/{oauth-authorization-DF-LCsIA.mjs → oauth-authorization-B9H7X85L.mjs} +5 -5
- package/dist/{oauth-clients-Du-V0fEm.mjs → oauth-clients-Dwl32-Uj.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/output-DhBe5_Pi.mjs +68 -0
- package/dist/page/index.d.mts +10 -3
- package/dist/page/index.mjs +2 -2
- package/dist/{parse-CneSgl7-.mjs → parse-i15EFSAE.mjs} +1 -1
- package/dist/{parser-DxfsARXL.mjs → parser-06LLLL4E.mjs} +151 -9
- package/dist/{placeholder-CXfoCtCd.d.mts → placeholder-DkWkXfDI.d.mts} +12 -4
- package/dist/plugin-utils.d.mts +8 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +8 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +13 -2
- package/dist/{plugins-DzfqRREq.mjs → plugins-CPA2PtfY.mjs} +919 -66
- package/dist/{transport-LSdZVXHF.mjs → portable-text-BASHceim.mjs} +1 -134
- package/dist/portable-text-E79LyjGo.d.mts +45 -0
- package/dist/{preview-C-3YTX2U.mjs → preview-BjedZm8G.mjs} +4 -3
- package/dist/process-cache-CCDgti7j.mjs +106 -0
- package/dist/project-env-DKibmuP8.mjs +95 -0
- package/dist/{provider-loader-9it4vd6L.d.mts → provider-loader-DC2JdWeO.d.mts} +1 -1
- package/dist/{public-url-B4xV4rGa.d.mts → public-url-DQJCnYi9.d.mts} +1 -1
- package/dist/publication-authorization-RKiHXKtq.mjs +985 -0
- package/dist/{query-CZ3TH_Uu.mjs → query-C2U5u5_p.mjs} +11 -6
- package/dist/{query-Ds0EnMFX.mjs → query-DEv5x6yB.mjs} +281 -76
- package/dist/{rate-limit-i0G3IAOX.mjs → rate-limit-DglsAJSw.mjs} +2 -2
- package/dist/{redirect-CEACczxo.mjs → redirect-ubhEqQCQ.mjs} +2 -2
- package/dist/{redirects-ocRJmQHP.mjs → redirects-Bhpm8bRe.mjs} +35 -19
- package/dist/{redirects-DYj1yPxs.mjs → redirects-xs2yahLq.mjs} +47 -24
- package/dist/{registry-D7HACema.mjs → registry-BYYyb2tF.mjs} +148 -81
- package/dist/request-context.d.mts +2 -0
- package/dist/{request-meta-DAb6xaP9.mjs → request-meta-CFe1tW2M.mjs} +1 -1
- package/dist/review-payload-BysRWpxS.mjs +111 -0
- package/dist/{review-requests-BW2NxY-l.mjs → review-requests-CTWGnWUt.mjs} +1 -1
- package/dist/review-requests-D42VEP1l.mjs +142 -0
- package/dist/review-route-helpers-C31vO6SI.mjs +41 -0
- package/dist/runner-BgmttQmD.mjs +7444 -0
- package/dist/{runner-09sTqtv0.d.mts → runner-D6hzs8L9.d.mts} +1 -1
- package/dist/runtime.d.mts +9 -10
- package/dist/runtime.mjs +3 -3
- package/dist/schema-DUtnZxjZ.mjs +8 -0
- package/dist/{search-Gdrqj582.mjs → search-DUKgZUlm.mjs} +5 -3
- package/dist/{secrets-CeGpBFV2.mjs → secrets-ZV5rd8yV.mjs} +14 -11
- package/dist/{sections-BOjU13Y-.mjs → sections-DvFgsnda.mjs} +2 -2
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +21 -18
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/{seo-5lJOtB5J.mjs → seo-BLszyJxd.mjs} +22 -4
- package/dist/{seo-CLj8lS7Q.mjs → seo-BU8MLCS9.mjs} +5 -2
- package/dist/{seo-contributions-CqVvReEj.mjs → seo-contributions-GnCuL1ML.mjs} +11 -3
- package/dist/{service-jGlhrICs.mjs → service-FL3fB0Rx.mjs} +2 -2
- package/dist/session-user-Ct6GBLvn.mjs +22 -0
- package/dist/{settings-CUXOxzsT.mjs → settings-BECcAIRH.mjs} +28 -55
- package/dist/{settings-DlBsT2VS.mjs → settings-EzADu-dd.mjs} +2 -2
- package/dist/{setup-complete-BRrdPYjZ.mjs → setup-complete-Da0axZEE.mjs} +1 -1
- package/dist/{site-activity-DONFrMBJ.mjs → site-activity-h8smOlYI.mjs} +1 -1
- package/dist/{site-context-C8Az_P_I.mjs → site-context-B8T1kpUP.mjs} +337 -530
- package/dist/{site-url-DIMD68go.mjs → site-url-DJEid0ml.mjs} +1 -1
- package/dist/stable-hash-DshcXvRE.mjs +19 -0
- package/dist/status-NqWOSFAD.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-D9Sw4wWM.mjs → taxonomies-BiM9NtgW.mjs} +36 -29
- package/dist/taxonomies-DSnY5nOa.mjs +443 -0
- package/dist/{taxonomy-B169Nm6B.mjs → taxonomy-0k_vRVsD.mjs} +44 -9
- package/dist/transport-C46KHLMP.mjs +134 -0
- package/dist/types-A8fPOJnl.mjs +69 -0
- package/dist/types-BLQCz46p.d.mts +118 -0
- package/dist/{types-CwawBUTa.d.mts → types-B_FlxIIl.d.mts} +4 -1
- package/dist/{types-CSgR9nQB.mjs → types-ChxYDRvx.mjs} +1 -1
- package/dist/{types-GaCtZ1k9.d.mts → types-Cw0PeCT_.d.mts} +18 -0
- package/dist/types-DuQn1G7-.d.mts +2973 -0
- package/dist/{runtime-C4-2lSFg.d.mts → types-DwoUifGE.d.mts} +5668 -3000
- package/dist/{types-DRIKLmJI.d.mts → types-opjX_kz6.d.mts} +17 -2
- package/dist/ui/server-runtime.d.mts +13 -15
- package/dist/ui/server-runtime.mjs +24 -20
- package/dist/url-guoXQQcV.mjs +33 -0
- package/dist/{user-DTLoZs16.mjs → user-BH-n01y3.mjs} +2 -2
- package/dist/{utils-B8hBJ9Oz.mjs → utils-D1SrKGtP.mjs} +129 -7
- package/dist/{validate-DzjCdy6v.d.mts → validate-CbVFoJgc.d.mts} +65 -8
- package/dist/{validate-DWmPO6oj.mjs → validate-D1HL-oLR.mjs} +11 -6
- package/dist/version-C3iVRuaF.mjs +6 -0
- package/dist/{widgets-BpJ8EpPd.mjs → widgets-BZrLrNgS.mjs} +46 -43
- package/dist/{wordpress-slugs-DHNR9LpG.mjs → wordpress-slugs-C3PWcowq.mjs} +1 -1
- package/dist/{zod-generator-K91-g665.mjs → zod-generator-C7AsLpSB.mjs} +42 -14
- package/package.json +38 -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-4y93Yjuc.mjs +0 -51
- package/dist/import-4YBXbDkI.mjs +0 -684
- package/dist/index-CZiWCQDp.d.mts +0 -227
- package/dist/review-requests-Qum4713s.mjs +0 -79
- package/dist/runner-DzIpHGND.mjs +0 -2836
- package/dist/schema-Bsi-VpYv.mjs +0 -8
- package/dist/taxonomies-CZ-_DitR.mjs +0 -327
- package/dist/types-CuJOqHfM.d.mts +0 -758
- package/dist/types-DSPGWjxO.d.mts +0 -1247
- package/dist/types-nP9vGUFq.d.mts +0 -56
- package/dist/version-C4gfn0PJ.mjs +0 -6
- /package/dist/{adapters-OQCImOL7.d.mts → adapters-BiRymx02.d.mts} +0 -0
- /package/dist/{api-tokens-CYUoFGo7.mjs → api-tokens-Ypaly90d.mjs} +0 -0
- /package/dist/{challenge-store-DFxX0_15.mjs → challenge-store-D5pSY2S1.mjs} +0 -0
- /package/dist/{components--pg6zBvu.mjs → components-DImzlLhb.mjs} +0 -0
- /package/dist/{context-types-r8oFvyOq.mjs → context-types-eBuMDAg-.mjs} +0 -0
- /package/dist/{default-B6iaLvP7.mjs → default-D4Vygcy_.mjs} +0 -0
- /package/dist/{email-console-BOVqIYpB.mjs → email-console-Bp0r68iF.mjs} +0 -0
- /package/dist/{escape-BDgouFFY.mjs → escape-DN4Qmjsl.mjs} +0 -0
- /package/dist/{hash-BPmX6P8I.mjs → hash-mxxx2oPW.mjs} +0 -0
- /package/dist/{mime-CbKsKDCB.mjs → mime-C0vOHFm7.mjs} +0 -0
- /package/dist/{mode-CZqCF9dZ.mjs → mode-C8yZnIlR.mjs} +0 -0
- /package/dist/{oauth-state-store-qncPB_IU.mjs → oauth-state-store-CndT35iw.mjs} +0 -0
- /package/dist/{oauth-user-lookup-BFjWF8wK.mjs → oauth-user-lookup-CMehAaKs.mjs} +0 -0
- /package/dist/{options-LsPjZlqD.mjs → options-DQPTH5n0.mjs} +0 -0
- /package/dist/{passkey-config-DzHQK90v.mjs → passkey-config-NYukYGed.mjs} +0 -0
- /package/dist/{patterns-DKAX6oqN.mjs → patterns-CoqlXUvk.mjs} +0 -0
- /package/dist/{provider-loader-DzUxejk7.mjs → provider-loader-BtfrrzfD.mjs} +0 -0
- /package/dist/{public-url-B_m9IwdV.mjs → public-url-Cp6hyjVR.mjs} +0 -0
- /package/dist/{redirect-BjIBtDWY.mjs → redirect-DrbEmU9a.mjs} +0 -0
- /package/dist/{request-cache-ho99schz.mjs → request-cache-CBFjCwSt.mjs} +0 -0
- /package/dist/{resolve-DTuDyYKs.mjs → resolve-DE71WcL9.mjs} +0 -0
- /package/dist/{setup-nonce-pyGfc9hV.mjs → setup-nonce-BtkKnvxQ.mjs} +0 -0
- /package/dist/{sidecar-client-mJxwayYN.mjs → sidecar-client-Ctzfyyp-.mjs} +0 -0
- /package/dist/{slugify-BqebcqBg.mjs → slugify-KSsVD_D3.mjs} +0 -0
- /package/dist/{ssrf-DiKqWd3N.mjs → ssrf-FJqwQzj4.mjs} +0 -0
- /package/dist/{tokens-SLmgowfd.mjs → tokens-BDQU29Vd.mjs} +0 -0
- /package/dist/{transaction-CFLhEOuH.mjs → transaction-0LEAAy8R.mjs} +0 -0
- /package/dist/{transport-BnsNxh2l.d.mts → transport-D9pKAzFP.d.mts} +0 -0
- /package/dist/{trusted-proxy-BgIIcbsN.mjs → trusted-proxy-Dm7Mxvt6.mjs} +0 -0
- /package/dist/{types-CJ8DkL0q.mjs → types-B7TDDFMG.mjs} +0 -0
- /package/dist/{types-Dryvv7LH.d.mts → types-g0zMtraY.d.mts} +0 -0
- /package/dist/{types-Cg276-3z.mjs → types-mVVgRPaU.mjs} +0 -0
- /package/dist/{types-DZpT-PnG.d.mts → types-wFu80FrO.d.mts} +0 -0
- /package/dist/{url-w7Mp5qAD.mjs → url-EMAguGVk.mjs} +0 -0
package/dist/cli/index.mjs
CHANGED
|
@@ -1,150 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { i as runMigrations, n as getMigrationStatus, t as getMigrationFingerprint } from "../runner-BgmttQmD.mjs";
|
|
3
3
|
import { t as createDatabase } from "../connection-CiJWiWya.mjs";
|
|
4
4
|
import { t as validateIdentifier } from "../validate-VPnKoIzW.mjs";
|
|
5
5
|
import { l as listTablesLike } from "../dialect-helpers-fk43GbDb.mjs";
|
|
6
6
|
import { n as getI18nConfig, r as isI18nEnabled } from "../config-CVssduLe.mjs";
|
|
7
7
|
import { i as encodeBase64url } from "../base64-F8-DUraK.mjs";
|
|
8
|
-
import "../types-
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
13
|
-
import { t as
|
|
14
|
-
import { t as
|
|
15
|
-
import "../
|
|
16
|
-
import "../
|
|
17
|
-
import "../
|
|
18
|
-
import
|
|
19
|
-
import "../
|
|
20
|
-
import {
|
|
21
|
-
import "../
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
import "../
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { n as
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
8
|
+
import "../types-ChxYDRvx.mjs";
|
|
9
|
+
import { o as isMissingTableError } from "../loader-aWkkc1_o.mjs";
|
|
10
|
+
import "../object-cache-D51qOseS.mjs";
|
|
11
|
+
import { n as slugify } from "../slugify-KSsVD_D3.mjs";
|
|
12
|
+
import { t as ContentRepository } from "../content-DJcAYFhz.mjs";
|
|
13
|
+
import { t as MediaRepository } from "../media-CVMOoywq.mjs";
|
|
14
|
+
import { t as TaxonomyRepository } from "../taxonomy-0k_vRVsD.mjs";
|
|
15
|
+
import { t as OptionsRepository } from "../options-DQPTH5n0.mjs";
|
|
16
|
+
import "../redirect-ubhEqQCQ.mjs";
|
|
17
|
+
import "../request-cache-CBFjCwSt.mjs";
|
|
18
|
+
import "../byline-registry-Ba54M74c.mjs";
|
|
19
|
+
import "../field-defs-cache-DL-T2XVS.mjs";
|
|
20
|
+
import { t as BylineRepository } from "../byline-7xR185Ly.mjs";
|
|
21
|
+
import "../content-refresh-B4ZCmxgd.mjs";
|
|
22
|
+
import "../fts-manager-ewTGohf4.mjs";
|
|
23
|
+
import { n as SchemaRegistry } from "../registry-BYYyb2tF.mjs";
|
|
24
|
+
import "../settings-BECcAIRH.mjs";
|
|
25
|
+
import { n as VERSION } from "../version-C3iVRuaF.mjs";
|
|
26
|
+
import { a as reconcileManifestAccess, i as pluginManifestSchema } from "../manifest-schema-uA6pRFXi.mjs";
|
|
27
|
+
import { n as resolveAndValidateExternalUrl, t as SsrfError } from "../ssrf-FJqwQzj4.mjs";
|
|
28
|
+
import { n as fingerprintKey, r as generateEncryptionKey, t as DinewaySecretsError } from "../secrets-ZV5rd8yV.mjs";
|
|
29
|
+
import { t as validateSeed } from "../validate-D1HL-oLR.mjs";
|
|
30
|
+
import { t as applySeed } from "../apply-DjTqq6Ae.mjs";
|
|
31
|
+
import { a as readSavedDeployTarget, c as listForgewayCredentials, i as readForgewayDeployMetadata, l as readForgewayCredentials, n as SeoProviderEvidenceService, o as writeDeployTarget, r as readDeployPackageJson, s as writeForgewayDeployMetadata, t as ForgewaySeoGatewayClient, u as writeForgewayCredentials } from "../forgeway-seo-client-G5ddhVHd.mjs";
|
|
32
|
+
import { n as output, t as configureOutputMode } from "../output-DhBe5_Pi.mjs";
|
|
33
|
+
import { createHeaderAwareFetch, isRedirectResponse, resolveCustomHeaders } from "../client/external-auth-headers.mjs";
|
|
34
|
+
import { t as convertDataForRead } from "../portable-text-BASHceim.mjs";
|
|
35
|
+
import { a as getMarketplaceCredential, c as resolveCredentialKey, i as getCredentials, l as saveCredentials, n as connectionArgs, o as removeCredentials, r as createClientFromArgs, s as removeMarketplaceCredential, u as saveMarketplaceCredential } from "../client-factory-BDD9azPJ.mjs";
|
|
36
|
+
import { a as resolveDinewayBaseUrl, n as ensureProjectGitignoreDinewayLocalState, o as resolveDinewayToken, r as ensureProjectGitignoreEntry, s as upsertProjectEnv, t as DEFAULT_DINEWAY_URL } from "../project-env-DKibmuP8.mjs";
|
|
31
37
|
import { LocalStorage } from "../storage/local.mjs";
|
|
32
|
-
import {
|
|
38
|
+
import { r as fileExists$11, t as bundleCommand } from "../bundle-CEcdUa1s.mjs";
|
|
39
|
+
import { createRequire } from "node:module";
|
|
40
|
+
import { createHash } from "node:crypto";
|
|
33
41
|
import { sql } from "kysely";
|
|
34
|
-
import { imageSize } from "image-size";
|
|
35
42
|
import { createGzipDecoder, unpackTar } from "modern-tar";
|
|
36
|
-
import { createReadStream,
|
|
37
|
-
import { basename, dirname,
|
|
43
|
+
import { createReadStream, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
44
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
45
|
+
import sax from "sax";
|
|
38
46
|
import { pathToFileURL } from "node:url";
|
|
39
47
|
import { defineCommand, runCommand, runMain } from "citty";
|
|
40
48
|
import { createInterface } from "node:readline/promises";
|
|
41
49
|
import consola, { consola as consola$1 } from "consola";
|
|
42
50
|
import pc from "picocolors";
|
|
43
51
|
import { ForgewayClient } from "@forgeway/sdk";
|
|
44
|
-
import { access,
|
|
45
|
-
import { homedir } from "node:os";
|
|
52
|
+
import { access, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
46
53
|
import { spawn } from "node:child_process";
|
|
47
|
-
import {
|
|
48
|
-
import { packTar } from "modern-tar/fs";
|
|
54
|
+
import { parse } from "parse5";
|
|
49
55
|
|
|
50
|
-
//#region src/cli/commands/deploy/utils/forgeway-secrets.ts
|
|
51
|
-
const DINEWAY_USER_CONFIG_DIR_NAME = "dineway";
|
|
52
|
-
const TRAILING_SLASH_PATTERN$3 = /\/$/;
|
|
53
|
-
function getDinewayUserConfigDir() {
|
|
54
|
-
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), DINEWAY_USER_CONFIG_DIR_NAME);
|
|
55
|
-
}
|
|
56
|
-
function getForgewayAccountSecretPath() {
|
|
57
|
-
return join(getDinewayUserConfigDir(), "forgeway.json");
|
|
58
|
-
}
|
|
59
|
-
function normalizePlatformApiUrl$1(platformApiUrl) {
|
|
60
|
-
return platformApiUrl.replace(TRAILING_SLASH_PATTERN$3, "");
|
|
61
|
-
}
|
|
62
|
-
function normalizeEmail$2(email) {
|
|
63
|
-
return email.trim().toLowerCase();
|
|
64
|
-
}
|
|
65
|
-
function accountKey(platformApiUrl, email) {
|
|
66
|
-
return `${normalizePlatformApiUrl$1(platformApiUrl)}#${normalizeEmail$2(email)}`;
|
|
67
|
-
}
|
|
68
|
-
function normalizeLookup(lookup) {
|
|
69
|
-
if (typeof lookup === "string") return { cwd: lookup };
|
|
70
|
-
return lookup ?? {};
|
|
71
|
-
}
|
|
72
|
-
async function readAccountStore() {
|
|
73
|
-
try {
|
|
74
|
-
const raw = await readFile(getForgewayAccountSecretPath(), "utf-8");
|
|
75
|
-
return JSON.parse(raw);
|
|
76
|
-
} catch {
|
|
77
|
-
return { version: 1 };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async function writeAccountStore(store) {
|
|
81
|
-
await mkdir(getDinewayUserConfigDir(), {
|
|
82
|
-
recursive: true,
|
|
83
|
-
mode: 448
|
|
84
|
-
});
|
|
85
|
-
const file = getForgewayAccountSecretPath();
|
|
86
|
-
await writeFile(file, JSON.stringify({
|
|
87
|
-
...store,
|
|
88
|
-
version: 1
|
|
89
|
-
}, null, " ") + "\n", {
|
|
90
|
-
encoding: "utf-8",
|
|
91
|
-
mode: 384
|
|
92
|
-
});
|
|
93
|
-
await chmod(file, 384);
|
|
94
|
-
}
|
|
95
|
-
function accountMatches(credentials, lookup) {
|
|
96
|
-
if (lookup.platformApiUrl && normalizePlatformApiUrl$1(credentials.platformApiUrl) !== normalizePlatformApiUrl$1(lookup.platformApiUrl)) return false;
|
|
97
|
-
if (lookup.email && normalizeEmail$2(credentials.user?.email ?? "") !== normalizeEmail$2(lookup.email)) return false;
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
async function readForgewayCredentials(lookup) {
|
|
101
|
-
const normalizedLookup = normalizeLookup(lookup);
|
|
102
|
-
const store = await readAccountStore();
|
|
103
|
-
const accounts = Object.values(store.accounts ?? {}).filter((credentials) => accountMatches(credentials, normalizedLookup));
|
|
104
|
-
if (normalizedLookup.platformApiUrl && normalizedLookup.email) return store.accounts?.[accountKey(normalizedLookup.platformApiUrl, normalizedLookup.email)] ?? null;
|
|
105
|
-
if (accounts.length === 1) return accounts[0] ?? null;
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
async function writeForgewayCredentials(credentials, lookup) {
|
|
109
|
-
const normalizedLookup = normalizeLookup(lookup);
|
|
110
|
-
const platformApiUrl = normalizePlatformApiUrl$1(normalizedLookup.platformApiUrl ?? credentials.platformApiUrl);
|
|
111
|
-
const email = normalizeEmail$2(normalizedLookup.email ?? credentials.user?.email ?? "");
|
|
112
|
-
if (!email) throw new Error("Forgeway credentials require a user email before they can be stored.");
|
|
113
|
-
const key = accountKey(platformApiUrl, email);
|
|
114
|
-
const store = await readAccountStore();
|
|
115
|
-
store.accounts = {
|
|
116
|
-
...store.accounts,
|
|
117
|
-
[key]: {
|
|
118
|
-
...credentials,
|
|
119
|
-
platformApiUrl
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
store.lastAccountKey = key;
|
|
123
|
-
await writeAccountStore(store);
|
|
124
|
-
}
|
|
125
|
-
async function listForgewayCredentials(platformApiUrl) {
|
|
126
|
-
const lookup = platformApiUrl ? { platformApiUrl } : {};
|
|
127
|
-
const store = await readAccountStore();
|
|
128
|
-
return Object.values(store.accounts ?? {}).filter((credentials) => accountMatches(credentials, lookup));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
//#endregion
|
|
132
56
|
//#region src/cli/forgeway-account.ts
|
|
133
|
-
const TRAILING_SLASH_PATTERN$
|
|
134
|
-
const SHADOW_EMAIL_PATTERN$
|
|
57
|
+
const TRAILING_SLASH_PATTERN$1 = /\/$/;
|
|
58
|
+
const SHADOW_EMAIL_PATTERN$1 = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
|
|
135
59
|
const DEFAULT_FORGEWAY_API_BASE_URL = "https://api.dineway.ai";
|
|
136
60
|
const DEFAULT_DINEWAY_WEB_URL = "https://dineway.ai";
|
|
137
61
|
function normalizeForgewayApiUrl(value) {
|
|
138
|
-
return (value || DEFAULT_FORGEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN$
|
|
62
|
+
return (value || DEFAULT_FORGEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN$1, "");
|
|
139
63
|
}
|
|
140
64
|
function normalizeDinewayWebUrl(value) {
|
|
141
|
-
return (value || DEFAULT_DINEWAY_WEB_URL).replace(TRAILING_SLASH_PATTERN$
|
|
65
|
+
return (value || DEFAULT_DINEWAY_WEB_URL).replace(TRAILING_SLASH_PATTERN$1, "");
|
|
142
66
|
}
|
|
143
67
|
function normalizeForgewayEmail(value) {
|
|
144
68
|
return value?.trim().toLowerCase() ?? "";
|
|
145
69
|
}
|
|
146
70
|
function isShadowForgewayEmail(email) {
|
|
147
|
-
return Boolean(email && SHADOW_EMAIL_PATTERN$
|
|
71
|
+
return Boolean(email && SHADOW_EMAIL_PATTERN$1.test(email));
|
|
148
72
|
}
|
|
149
73
|
function createForgewayAccountClient(platformApiUrl, accessToken) {
|
|
150
74
|
return new ForgewayClient({
|
|
@@ -154,7 +78,7 @@ function createForgewayAccountClient(platformApiUrl, accessToken) {
|
|
|
154
78
|
auth: { detectOAuthCallback: false }
|
|
155
79
|
});
|
|
156
80
|
}
|
|
157
|
-
function normalizeForgewayUser$
|
|
81
|
+
function normalizeForgewayUser$1(user) {
|
|
158
82
|
if (!user?.id || !user.email) throw new Error("Forgeway session response did not include a user email.");
|
|
159
83
|
return {
|
|
160
84
|
id: user.id,
|
|
@@ -192,7 +116,7 @@ async function refreshForgewayAccountCredentials(credentials) {
|
|
|
192
116
|
if (!credentials.refreshToken) throw new Error("Stored Forgeway account is missing a refresh token. Run `dineway account login`.");
|
|
193
117
|
const { data, error } = await createForgewayAccountClient(credentials.platformApiUrl).auth.refreshSession({ refreshToken: credentials.refreshToken });
|
|
194
118
|
if (error || !data?.accessToken) throw new Error(error?.message || "Failed to refresh Forgeway account. Run `dineway account login`.");
|
|
195
|
-
const refreshedUser = normalizeForgewayUser$
|
|
119
|
+
const refreshedUser = normalizeForgewayUser$1(data.user);
|
|
196
120
|
if (normalizeForgewayEmail(refreshedUser.email) !== normalizeForgewayEmail(user.email)) throw new Error("Forgeway refresh returned credentials for a different account.");
|
|
197
121
|
const updated = {
|
|
198
122
|
...credentials,
|
|
@@ -224,7 +148,7 @@ async function completeForgewayAccountLogin(input) {
|
|
|
224
148
|
const platformApiUrl = normalizeForgewayApiUrl(input.platformApiUrl);
|
|
225
149
|
const { data, error } = await createForgewayAccountClient(platformApiUrl).auth.signInWithPasswordlessToken({ token: input.token });
|
|
226
150
|
if (error || !data?.accessToken || !data.refreshToken) throw new Error(error?.message || "Forgeway sign-in token is invalid or expired.");
|
|
227
|
-
const user = normalizeForgewayUser$
|
|
151
|
+
const user = normalizeForgewayUser$1(data.user);
|
|
228
152
|
if (input.email && normalizeForgewayEmail(user.email) !== normalizeForgewayEmail(input.email)) throw new Error("Forgeway sign-in returned credentials for a different email.");
|
|
229
153
|
const credentials = {
|
|
230
154
|
platformApiUrl,
|
|
@@ -251,71 +175,6 @@ async function getForgewayDinewayAccount(credentials) {
|
|
|
251
175
|
};
|
|
252
176
|
}
|
|
253
177
|
|
|
254
|
-
//#endregion
|
|
255
|
-
//#region src/cli/output.ts
|
|
256
|
-
/**
|
|
257
|
-
* Redirect consola output to stderr so it doesn't pollute JSON on stdout.
|
|
258
|
-
*
|
|
259
|
-
* Call this early in any command that uses `output()` with `--json`.
|
|
260
|
-
* Safe to call multiple times — only applies the redirect once.
|
|
261
|
-
*/
|
|
262
|
-
function configureOutputMode(args) {
|
|
263
|
-
if (args.json || !process.stdout.isTTY) {
|
|
264
|
-
consola$1.options.stdout = process.stderr;
|
|
265
|
-
consola$1.options.stderr = process.stderr;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Output data as JSON or pretty-printed.
|
|
270
|
-
*
|
|
271
|
-
* If stdout is not a TTY or --json is set, outputs JSON.
|
|
272
|
-
* Otherwise, outputs a formatted representation.
|
|
273
|
-
*/
|
|
274
|
-
function output(data, args) {
|
|
275
|
-
if (args.json || !process.stdout.isTTY) process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
276
|
-
else prettyPrint(data);
|
|
277
|
-
}
|
|
278
|
-
function prettyPrint(data, indent = 0) {
|
|
279
|
-
if (data === null || data === void 0) {
|
|
280
|
-
consola$1.log("(empty)");
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (Array.isArray(data)) {
|
|
284
|
-
if (data.length === 0) {
|
|
285
|
-
consola$1.log("(no items)");
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
for (const item of data) {
|
|
289
|
-
prettyPrint(item, indent);
|
|
290
|
-
if (indent === 0) consola$1.log("---");
|
|
291
|
-
}
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
if (typeof data === "object") {
|
|
295
|
-
const obj = Object(data);
|
|
296
|
-
if ("items" in obj && Array.isArray(obj.items)) {
|
|
297
|
-
prettyPrint(obj.items, indent);
|
|
298
|
-
if (typeof obj.nextCursor === "string") consola$1.log(`\nNext cursor: ${obj.nextCursor}`);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const prefix = " ".repeat(indent);
|
|
302
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
303
|
-
if (value === null || value === void 0) continue;
|
|
304
|
-
if (typeof value === "object" && !Array.isArray(value)) {
|
|
305
|
-
consola$1.log(`${prefix}${key}:`);
|
|
306
|
-
prettyPrint(value, indent + 1);
|
|
307
|
-
} else if (Array.isArray(value)) consola$1.log(`${prefix}${key}: [${value.length} items]`);
|
|
308
|
-
else {
|
|
309
|
-
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
310
|
-
const display = str.length > 80 ? str.slice(0, 77) + "..." : str;
|
|
311
|
-
consola$1.log(`${prefix}${key}: ${display}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
consola$1.log(typeof data === "string" ? data : JSON.stringify(data));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
178
|
//#endregion
|
|
320
179
|
//#region src/cli/commands/account.ts
|
|
321
180
|
async function promptLine(question) {
|
|
@@ -504,320 +363,6 @@ const accountCommand = defineCommand({
|
|
|
504
363
|
}
|
|
505
364
|
});
|
|
506
365
|
|
|
507
|
-
//#endregion
|
|
508
|
-
//#region src/cli/credentials.ts
|
|
509
|
-
/**
|
|
510
|
-
* Credential storage for CLI auth tokens.
|
|
511
|
-
*
|
|
512
|
-
* Stores OAuth tokens in ~/.config/dineway/auth.json.
|
|
513
|
-
* Remote URLs are keyed by origin, local dev by project path.
|
|
514
|
-
*/
|
|
515
|
-
function getConfigDir() {
|
|
516
|
-
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
517
|
-
if (xdg) return join(xdg, "dineway");
|
|
518
|
-
return join(homedir(), ".config", "dineway");
|
|
519
|
-
}
|
|
520
|
-
function getCredentialPath() {
|
|
521
|
-
return join(getConfigDir(), "auth.json");
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Resolve the credential key for a given URL.
|
|
525
|
-
*
|
|
526
|
-
* Remote URLs are keyed by origin (e.g. "https://my-site.pages.dev").
|
|
527
|
-
* Local dev instances are keyed by project path (e.g. "path:/Users/matt/sites/blog").
|
|
528
|
-
*/
|
|
529
|
-
function resolveCredentialKey(baseUrl) {
|
|
530
|
-
try {
|
|
531
|
-
const url = new URL(baseUrl);
|
|
532
|
-
if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]") {
|
|
533
|
-
const projectPath = findProjectRoot(process.cwd());
|
|
534
|
-
if (projectPath) return `path:${projectPath}`;
|
|
535
|
-
return url.origin;
|
|
536
|
-
}
|
|
537
|
-
return url.origin;
|
|
538
|
-
} catch {
|
|
539
|
-
return baseUrl;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Walk up from cwd to find the project root (directory containing astro.config.*).
|
|
544
|
-
*/
|
|
545
|
-
function findProjectRoot(from) {
|
|
546
|
-
let dir = resolve(from);
|
|
547
|
-
const root = resolve("/");
|
|
548
|
-
while (dir !== root) {
|
|
549
|
-
for (const name of [
|
|
550
|
-
"astro.config.ts",
|
|
551
|
-
"astro.config.mts",
|
|
552
|
-
"astro.config.js",
|
|
553
|
-
"astro.config.mjs"
|
|
554
|
-
]) if (existsSync(join(dir, name))) return dir;
|
|
555
|
-
const parent = resolve(dir, "..");
|
|
556
|
-
if (parent === dir) break;
|
|
557
|
-
dir = parent;
|
|
558
|
-
}
|
|
559
|
-
return null;
|
|
560
|
-
}
|
|
561
|
-
function readStore() {
|
|
562
|
-
const path = getCredentialPath();
|
|
563
|
-
try {
|
|
564
|
-
if (existsSync(path)) {
|
|
565
|
-
const content = readFileSync(path, "utf-8");
|
|
566
|
-
return JSON.parse(content);
|
|
567
|
-
}
|
|
568
|
-
} catch {}
|
|
569
|
-
return {};
|
|
570
|
-
}
|
|
571
|
-
function writeStore(store) {
|
|
572
|
-
mkdirSync(getConfigDir(), {
|
|
573
|
-
recursive: true,
|
|
574
|
-
mode: 448
|
|
575
|
-
});
|
|
576
|
-
writeFileSync(getCredentialPath(), JSON.stringify(store, null, " "), {
|
|
577
|
-
encoding: "utf-8",
|
|
578
|
-
mode: 384
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Get stored credentials for a URL.
|
|
583
|
-
*/
|
|
584
|
-
function getCredentials(baseUrl) {
|
|
585
|
-
const key = resolveCredentialKey(baseUrl);
|
|
586
|
-
const cred = readStore()[key];
|
|
587
|
-
if (!cred || !("accessToken" in cred)) return null;
|
|
588
|
-
return cred;
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Save credentials for a URL.
|
|
592
|
-
*/
|
|
593
|
-
function saveCredentials(baseUrl, cred) {
|
|
594
|
-
const key = resolveCredentialKey(baseUrl);
|
|
595
|
-
const store = readStore();
|
|
596
|
-
store[key] = cred;
|
|
597
|
-
writeStore(store);
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Remove credentials for a URL.
|
|
601
|
-
*/
|
|
602
|
-
function removeCredentials(baseUrl) {
|
|
603
|
-
const key = resolveCredentialKey(baseUrl);
|
|
604
|
-
const store = readStore();
|
|
605
|
-
if (key in store) {
|
|
606
|
-
delete store[key];
|
|
607
|
-
writeStore(store);
|
|
608
|
-
return true;
|
|
609
|
-
}
|
|
610
|
-
return false;
|
|
611
|
-
}
|
|
612
|
-
function marketplaceKey(registryUrl) {
|
|
613
|
-
try {
|
|
614
|
-
return `marketplace:${new URL(registryUrl).origin}`;
|
|
615
|
-
} catch {
|
|
616
|
-
return `marketplace:${registryUrl}`;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Get stored marketplace credential for a registry URL.
|
|
621
|
-
*/
|
|
622
|
-
function getMarketplaceCredential(registryUrl) {
|
|
623
|
-
const key = marketplaceKey(registryUrl);
|
|
624
|
-
const cred = readStore()[key];
|
|
625
|
-
if (!cred || !("token" in cred)) return null;
|
|
626
|
-
if (new Date(cred.expiresAt) < /* @__PURE__ */ new Date()) return null;
|
|
627
|
-
return cred;
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Save marketplace credential for a registry URL.
|
|
631
|
-
*/
|
|
632
|
-
function saveMarketplaceCredential(registryUrl, cred) {
|
|
633
|
-
const key = marketplaceKey(registryUrl);
|
|
634
|
-
const store = readStore();
|
|
635
|
-
store[key] = cred;
|
|
636
|
-
writeStore(store);
|
|
637
|
-
}
|
|
638
|
-
/**
|
|
639
|
-
* Remove marketplace credential for a registry URL.
|
|
640
|
-
*/
|
|
641
|
-
function removeMarketplaceCredential(registryUrl) {
|
|
642
|
-
const key = marketplaceKey(registryUrl);
|
|
643
|
-
const store = readStore();
|
|
644
|
-
if (key in store) {
|
|
645
|
-
delete store[key];
|
|
646
|
-
writeStore(store);
|
|
647
|
-
return true;
|
|
648
|
-
}
|
|
649
|
-
return false;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
//#endregion
|
|
653
|
-
//#region src/cli/project-env.ts
|
|
654
|
-
const DEFAULT_DINEWAY_URL = "http://localhost:4321";
|
|
655
|
-
const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
656
|
-
const LINE_SPLIT_PATTERN = /\r?\n/;
|
|
657
|
-
const DINEWAY_LOCAL_STATE_GITIGNORE_COMMENT = "# Dineway local state";
|
|
658
|
-
const DINEWAY_LOCAL_STATE_GITIGNORE_ENTRIES = [
|
|
659
|
-
".dineway/deploy.json",
|
|
660
|
-
".dineway/forgeway.json",
|
|
661
|
-
".dineway/types.ts",
|
|
662
|
-
".dineway/schema.json",
|
|
663
|
-
".dineway/uploads/"
|
|
664
|
-
];
|
|
665
|
-
const NEWLINE_PATTERN = /\r?\n/g;
|
|
666
|
-
function parseDotenvValue(value) {
|
|
667
|
-
const trimmed = value.trim();
|
|
668
|
-
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
669
|
-
return trimmed;
|
|
670
|
-
}
|
|
671
|
-
function parseDotenv(text) {
|
|
672
|
-
const env = {};
|
|
673
|
-
for (const line of text.split(LINE_SPLIT_PATTERN)) {
|
|
674
|
-
const trimmed = line.trim();
|
|
675
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
676
|
-
const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
|
|
677
|
-
const equalsIndex = normalized.indexOf("=");
|
|
678
|
-
if (equalsIndex <= 0) continue;
|
|
679
|
-
const key = normalized.slice(0, equalsIndex).trim();
|
|
680
|
-
if (!ENV_KEY_PATTERN.test(key)) continue;
|
|
681
|
-
env[key] = parseDotenvValue(normalized.slice(equalsIndex + 1));
|
|
682
|
-
}
|
|
683
|
-
return env;
|
|
684
|
-
}
|
|
685
|
-
function readProjectEnv(cwd = process.cwd()) {
|
|
686
|
-
const envPath = join(cwd, ".env");
|
|
687
|
-
if (!existsSync(envPath)) return {};
|
|
688
|
-
return parseDotenv(readFileSync(envPath, "utf-8"));
|
|
689
|
-
}
|
|
690
|
-
function getProjectEnvValue(name, cwd = process.cwd()) {
|
|
691
|
-
return process.env[name] ?? readProjectEnv(cwd)[name];
|
|
692
|
-
}
|
|
693
|
-
function resolveDinewayBaseUrl(url, cwd = process.cwd()) {
|
|
694
|
-
if (url && url !== DEFAULT_DINEWAY_URL) return url;
|
|
695
|
-
const projectEnv = readProjectEnv(cwd);
|
|
696
|
-
return process.env["DINEWAY_URL"] ?? process.env["DINEWAY_SITE_URL"] ?? projectEnv["DINEWAY_SITE_URL"] ?? projectEnv["DINEWAY_URL"] ?? url ?? DEFAULT_DINEWAY_URL;
|
|
697
|
-
}
|
|
698
|
-
function resolveDinewayToken(token, cwd = process.cwd()) {
|
|
699
|
-
return token ?? getProjectEnvValue("DINEWAY_TOKEN", cwd);
|
|
700
|
-
}
|
|
701
|
-
function formatEnvValue(value) {
|
|
702
|
-
return value.replace(NEWLINE_PATTERN, "");
|
|
703
|
-
}
|
|
704
|
-
async function upsertProjectEnv(cwd, values) {
|
|
705
|
-
const envPath = join(cwd, ".env");
|
|
706
|
-
const existing = await readFile(envPath, "utf-8").catch(() => "");
|
|
707
|
-
const lines = existing ? existing.split(LINE_SPLIT_PATTERN) : [];
|
|
708
|
-
const seen = /* @__PURE__ */ new Set();
|
|
709
|
-
const nextLines = lines.map((line) => {
|
|
710
|
-
const trimmed = line.trim();
|
|
711
|
-
const normalized = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
|
|
712
|
-
const equalsIndex = normalized.indexOf("=");
|
|
713
|
-
if (equalsIndex <= 0) return line;
|
|
714
|
-
const key = normalized.slice(0, equalsIndex).trim();
|
|
715
|
-
const value = values[key];
|
|
716
|
-
if (!(key in values) || value === void 0) return line;
|
|
717
|
-
seen.add(key);
|
|
718
|
-
return `${key}=${formatEnvValue(value)}`;
|
|
719
|
-
});
|
|
720
|
-
for (const [key, value] of Object.entries(values)) {
|
|
721
|
-
if (value === void 0 || seen.has(key)) continue;
|
|
722
|
-
nextLines.push(`${key}=${formatEnvValue(value)}`);
|
|
723
|
-
}
|
|
724
|
-
await writeFile(envPath, `${nextLines.filter((line, index) => line || index < nextLines.length - 1).join("\n")}\n`, "utf-8");
|
|
725
|
-
}
|
|
726
|
-
async function ensureProjectGitignoreEntry(cwd, entry) {
|
|
727
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
728
|
-
const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
|
|
729
|
-
if (existing.split(LINE_SPLIT_PATTERN).map((line) => line.trim()).includes(entry)) return;
|
|
730
|
-
await writeFile(gitignorePath, `${existing}${existing && !existing.endsWith("\n") ? "\n" : ""}${entry}\n`, "utf-8");
|
|
731
|
-
}
|
|
732
|
-
async function ensureProjectGitignoreDinewayLocalState(cwd) {
|
|
733
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
734
|
-
const existing = await readFile(gitignorePath, "utf-8").catch(() => "");
|
|
735
|
-
const lines = new Set(existing.split(LINE_SPLIT_PATTERN).map((line) => line.trim()));
|
|
736
|
-
const missingEntries = DINEWAY_LOCAL_STATE_GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry));
|
|
737
|
-
if (missingEntries.length === 0) return;
|
|
738
|
-
const block = [DINEWAY_LOCAL_STATE_GITIGNORE_COMMENT, ...missingEntries].join("\n");
|
|
739
|
-
await writeFile(gitignorePath, `${existing}${existing.trim() ? existing.endsWith("\n") ? "\n" : "\n\n" : ""}${block}\n`, "utf-8");
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
//#endregion
|
|
743
|
-
//#region src/cli/client-factory.ts
|
|
744
|
-
var client_factory_exports = /* @__PURE__ */ __exportAll({
|
|
745
|
-
connectionArgs: () => connectionArgs,
|
|
746
|
-
createClientFromArgs: () => createClientFromArgs
|
|
747
|
-
});
|
|
748
|
-
/**
|
|
749
|
-
* Shared connection args for all CLI commands that talk to a Dineway instance.
|
|
750
|
-
* Spread into each command's `args` definition.
|
|
751
|
-
*/
|
|
752
|
-
const connectionArgs = {
|
|
753
|
-
url: {
|
|
754
|
-
type: "string",
|
|
755
|
-
alias: "u",
|
|
756
|
-
description: "Dineway instance URL",
|
|
757
|
-
default: DEFAULT_DINEWAY_URL
|
|
758
|
-
},
|
|
759
|
-
token: {
|
|
760
|
-
type: "string",
|
|
761
|
-
alias: "t",
|
|
762
|
-
description: "Auth token"
|
|
763
|
-
},
|
|
764
|
-
header: {
|
|
765
|
-
type: "string",
|
|
766
|
-
alias: "H",
|
|
767
|
-
description: "Custom header \"Name: Value\" (repeatable, or use DINEWAY_HEADERS env)"
|
|
768
|
-
},
|
|
769
|
-
json: {
|
|
770
|
-
type: "boolean",
|
|
771
|
-
description: "Output as JSON"
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
/**
|
|
775
|
-
* Create a DinewayClient from CLI args, env vars, and stored credentials.
|
|
776
|
-
*
|
|
777
|
-
* Auth resolution order:
|
|
778
|
-
* 1. --token flag
|
|
779
|
-
* 2. DINEWAY_TOKEN env var
|
|
780
|
-
* 3. Stored credentials (~/.config/dineway/auth.json)
|
|
781
|
-
* 4. Dev bypass (if URL is localhost)
|
|
782
|
-
*
|
|
783
|
-
* Custom headers are merged from (in priority order):
|
|
784
|
-
* 1. Stored credentials (persisted during `dineway login --header`)
|
|
785
|
-
* 2. DINEWAY_HEADERS env var
|
|
786
|
-
* 3. --header CLI flags
|
|
787
|
-
*/
|
|
788
|
-
function createClientFromArgs(args) {
|
|
789
|
-
const baseUrl = resolveDinewayBaseUrl(args.url);
|
|
790
|
-
let token = resolveDinewayToken(args.token);
|
|
791
|
-
const isLocal = baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1");
|
|
792
|
-
const cred = !token ? getCredentials(baseUrl) : null;
|
|
793
|
-
const customHeaders = {
|
|
794
|
-
...cred?.customHeaders,
|
|
795
|
-
...resolveCustomHeaders()
|
|
796
|
-
};
|
|
797
|
-
const extraInterceptors = [];
|
|
798
|
-
if (Object.keys(customHeaders).length > 0) extraInterceptors.push(customHeadersInterceptor(customHeaders));
|
|
799
|
-
if (!token && cred) if (new Date(cred.expiresAt) > /* @__PURE__ */ new Date()) token = cred.accessToken;
|
|
800
|
-
else return new DinewayClient({
|
|
801
|
-
baseUrl,
|
|
802
|
-
token: cred.accessToken,
|
|
803
|
-
refreshToken: cred.refreshToken,
|
|
804
|
-
onTokenRefresh: (newAccessToken, expiresIn) => {
|
|
805
|
-
saveCredentials(baseUrl, {
|
|
806
|
-
...cred,
|
|
807
|
-
accessToken: newAccessToken,
|
|
808
|
-
expiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString()
|
|
809
|
-
});
|
|
810
|
-
},
|
|
811
|
-
interceptors: extraInterceptors
|
|
812
|
-
});
|
|
813
|
-
return new DinewayClient({
|
|
814
|
-
baseUrl,
|
|
815
|
-
token,
|
|
816
|
-
devBypass: !token && isLocal,
|
|
817
|
-
interceptors: extraInterceptors
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
|
|
821
366
|
//#endregion
|
|
822
367
|
//#region src/cli/commands/auth.ts
|
|
823
368
|
/**
|
|
@@ -1064,7 +609,7 @@ const createCommand$1 = defineCommand({
|
|
|
1064
609
|
},
|
|
1065
610
|
draft: {
|
|
1066
611
|
type: "boolean",
|
|
1067
|
-
description: "
|
|
612
|
+
description: "Explicitly create as Draft (the only supported creation mode)"
|
|
1068
613
|
},
|
|
1069
614
|
...connectionArgs
|
|
1070
615
|
},
|
|
@@ -1079,7 +624,6 @@ const createCommand$1 = defineCommand({
|
|
|
1079
624
|
locale: args.locale,
|
|
1080
625
|
translationOf: args["translation-of"]
|
|
1081
626
|
});
|
|
1082
|
-
if (!args.draft) await client.publish(args.collection, item.id);
|
|
1083
627
|
output(await client.get(args.collection, item.id), args);
|
|
1084
628
|
} catch (error) {
|
|
1085
629
|
consola$1.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -1122,7 +666,7 @@ const updateCommand = defineCommand({
|
|
|
1122
666
|
},
|
|
1123
667
|
draft: {
|
|
1124
668
|
type: "boolean",
|
|
1125
|
-
description: "
|
|
669
|
+
description: "Explicitly update the Draft (the only supported update mode)"
|
|
1126
670
|
},
|
|
1127
671
|
...connectionArgs
|
|
1128
672
|
},
|
|
@@ -1136,7 +680,6 @@ const updateCommand = defineCommand({
|
|
|
1136
680
|
_rev: args.rev,
|
|
1137
681
|
locale: args.locale
|
|
1138
682
|
});
|
|
1139
|
-
if (!args.draft && updated.draftRevisionId) await client.publish(args.collection, updated.id);
|
|
1140
683
|
output(await client.get(args.collection, updated.id), args);
|
|
1141
684
|
} catch (error) {
|
|
1142
685
|
consola$1.error(error instanceof Error ? error.message : "Unknown error");
|
|
@@ -1190,12 +733,25 @@ const publishCommand$1 = defineCommand({
|
|
|
1190
733
|
description: "Content item ID or slug",
|
|
1191
734
|
required: true
|
|
1192
735
|
},
|
|
736
|
+
"review-request": {
|
|
737
|
+
type: "string",
|
|
738
|
+
description: "Approved editorial Review Request ID for the exact Draft",
|
|
739
|
+
required: true
|
|
740
|
+
},
|
|
741
|
+
"release-authorization": {
|
|
742
|
+
type: "string",
|
|
743
|
+
description: "Approved release authorization ID for this token action",
|
|
744
|
+
required: true
|
|
745
|
+
},
|
|
1193
746
|
...connectionArgs
|
|
1194
747
|
},
|
|
1195
748
|
async run({ args }) {
|
|
1196
749
|
configureOutputMode(args);
|
|
1197
750
|
try {
|
|
1198
|
-
await createClientFromArgs(args).publish(args.collection, args.id
|
|
751
|
+
await createClientFromArgs(args).publish(args.collection, args.id, {
|
|
752
|
+
reviewRequestId: args["review-request"],
|
|
753
|
+
releaseAuthorizationId: args["release-authorization"]
|
|
754
|
+
});
|
|
1199
755
|
output({ success: true }, args);
|
|
1200
756
|
consola$1.success(`Published ${args.collection}/${args.id}`);
|
|
1201
757
|
} catch (error) {
|
|
@@ -1255,12 +811,26 @@ const scheduleCommand = defineCommand({
|
|
|
1255
811
|
description: "ISO 8601 datetime to publish at",
|
|
1256
812
|
required: true
|
|
1257
813
|
},
|
|
814
|
+
"review-request": {
|
|
815
|
+
type: "string",
|
|
816
|
+
description: "Approved editorial Review Request ID for the exact Draft",
|
|
817
|
+
required: true
|
|
818
|
+
},
|
|
819
|
+
"release-authorization": {
|
|
820
|
+
type: "string",
|
|
821
|
+
description: "Approved release authorization ID for this token action",
|
|
822
|
+
required: true
|
|
823
|
+
},
|
|
1258
824
|
...connectionArgs
|
|
1259
825
|
},
|
|
1260
826
|
async run({ args }) {
|
|
1261
827
|
configureOutputMode(args);
|
|
1262
828
|
try {
|
|
1263
|
-
await createClientFromArgs(args).schedule(args.collection, args.id, {
|
|
829
|
+
await createClientFromArgs(args).schedule(args.collection, args.id, {
|
|
830
|
+
at: args.at,
|
|
831
|
+
reviewRequestId: args["review-request"],
|
|
832
|
+
releaseAuthorizationId: args["release-authorization"]
|
|
833
|
+
});
|
|
1264
834
|
output({ success: true }, args);
|
|
1265
835
|
consola$1.success(`Scheduled ${args.collection}/${args.id} for ${args.at}`);
|
|
1266
836
|
} catch (error) {
|
|
@@ -1349,7 +919,7 @@ const contentCommand = defineCommand({
|
|
|
1349
919
|
//#endregion
|
|
1350
920
|
//#region src/cli/paywall.ts
|
|
1351
921
|
const PAYWALL_MESSAGE_PATTERN = /\b(quota|credit|credits|upgrade|plan|billing|subscription|slot)\b/i;
|
|
1352
|
-
function isRecord$
|
|
922
|
+
function isRecord$2(value) {
|
|
1353
923
|
return typeof value === "object" && value !== null;
|
|
1354
924
|
}
|
|
1355
925
|
function stringField(record, key) {
|
|
@@ -1357,9 +927,9 @@ function stringField(record, key) {
|
|
|
1357
927
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1358
928
|
}
|
|
1359
929
|
function getNestedError(value) {
|
|
1360
|
-
if (!isRecord$
|
|
930
|
+
if (!isRecord$2(value)) return null;
|
|
1361
931
|
const nested = value.error;
|
|
1362
|
-
return isRecord$
|
|
932
|
+
return isRecord$2(nested) ? nested : value;
|
|
1363
933
|
}
|
|
1364
934
|
function isHostedSitesResource(resource) {
|
|
1365
935
|
const normalized = resource.toLowerCase();
|
|
@@ -1367,7 +937,11 @@ function isHostedSitesResource(resource) {
|
|
|
1367
937
|
}
|
|
1368
938
|
function isCreditResource(resource) {
|
|
1369
939
|
const normalized = resource.toLowerCase();
|
|
1370
|
-
return normalized.includes("credit") || normalized.includes("firecrawl") || normalized.includes("dataforseo")
|
|
940
|
+
return normalized.includes("credit") || normalized.includes("firecrawl") || normalized.includes("dataforseo");
|
|
941
|
+
}
|
|
942
|
+
function isQuotaResource(resource) {
|
|
943
|
+
const normalized = resource.toLowerCase();
|
|
944
|
+
return normalized.includes("quota") || normalized.includes("place_search") || normalized.includes("place_get") || normalized.includes("/api/places");
|
|
1371
945
|
}
|
|
1372
946
|
function getCliPaywallRefreshStatus(paywall, account) {
|
|
1373
947
|
if (isHostedSitesResource(paywall.resource)) {
|
|
@@ -1391,6 +965,10 @@ function getCliPaywallRefreshStatus(paywall, account) {
|
|
|
1391
965
|
message: "There are still no provider credits. Complete checkout or top up credits in Dineway Web."
|
|
1392
966
|
};
|
|
1393
967
|
}
|
|
968
|
+
if (isQuotaResource(paywall.resource)) return {
|
|
969
|
+
blocked: false,
|
|
970
|
+
message: "Account quota status refreshed. Re-run the blocked command when ready."
|
|
971
|
+
};
|
|
1394
972
|
return {
|
|
1395
973
|
blocked: false,
|
|
1396
974
|
message: "Account status refreshed. Re-run the blocked command when ready."
|
|
@@ -1533,7 +1111,7 @@ function collectUploadIncludeFiles(directory = dinewayUploadsJoin(dinewayForgewa
|
|
|
1533
1111
|
\t});
|
|
1534
1112
|
}
|
|
1535
1113
|
`;
|
|
1536
|
-
async function fileExists$
|
|
1114
|
+
async function fileExists$10(path) {
|
|
1537
1115
|
try {
|
|
1538
1116
|
await access(path);
|
|
1539
1117
|
return true;
|
|
@@ -1694,7 +1272,7 @@ function configurePackageJson(pkg, adapter) {
|
|
|
1694
1272
|
}
|
|
1695
1273
|
async function configureAstroConfig(cwd, adapter, dryRun) {
|
|
1696
1274
|
const configPath = join(cwd, "astro.config.mjs");
|
|
1697
|
-
if (!await fileExists$
|
|
1275
|
+
if (!await fileExists$10(configPath)) return [];
|
|
1698
1276
|
const current = await readFile(configPath, "utf-8");
|
|
1699
1277
|
const next = configureAstroConfigSource(current, adapter);
|
|
1700
1278
|
if (next === current) return [{
|
|
@@ -1713,7 +1291,7 @@ async function configureAstroConfig(cwd, adapter, dryRun) {
|
|
|
1713
1291
|
}
|
|
1714
1292
|
async function configurePackage(cwd, adapter, dryRun) {
|
|
1715
1293
|
const packagePath = join(cwd, "package.json");
|
|
1716
|
-
if (!await fileExists$
|
|
1294
|
+
if (!await fileExists$10(packagePath)) return [];
|
|
1717
1295
|
const current = await readFile(packagePath, "utf-8");
|
|
1718
1296
|
const nextPkg = configurePackageJson(JSON.parse(current), adapter);
|
|
1719
1297
|
const next = JSON.stringify(nextPkg, null, " ") + "\n";
|
|
@@ -1732,7 +1310,7 @@ async function configurePackage(cwd, adapter, dryRun) {
|
|
|
1732
1310
|
}];
|
|
1733
1311
|
}
|
|
1734
1312
|
async function configureAstroDeployAdapter(cwd, adapter, dryRun = false) {
|
|
1735
|
-
if (!await fileExists$
|
|
1313
|
+
if (!await fileExists$10(join(cwd, "astro.config.mjs"))) return [];
|
|
1736
1314
|
return [...await configureAstroConfig(cwd, adapter, dryRun), ...await configurePackage(cwd, adapter, dryRun)];
|
|
1737
1315
|
}
|
|
1738
1316
|
|
|
@@ -1822,7 +1400,7 @@ volumes:
|
|
|
1822
1400
|
|
|
1823
1401
|
//#endregion
|
|
1824
1402
|
//#region src/cli/commands/deploy/targets/docker.ts
|
|
1825
|
-
async function fileExists$
|
|
1403
|
+
async function fileExists$9(path) {
|
|
1826
1404
|
try {
|
|
1827
1405
|
await access(path);
|
|
1828
1406
|
return true;
|
|
@@ -1832,7 +1410,7 @@ async function fileExists$10(path) {
|
|
|
1832
1410
|
}
|
|
1833
1411
|
async function writeGeneratedFile$2(cwd, relativePath, content, dryRun) {
|
|
1834
1412
|
const path = join(cwd, relativePath);
|
|
1835
|
-
if (await fileExists$
|
|
1413
|
+
if (await fileExists$9(path)) return {
|
|
1836
1414
|
path: relativePath,
|
|
1837
1415
|
action: "exists"
|
|
1838
1416
|
};
|
|
@@ -1919,10 +1497,10 @@ const FLY_HOST_RE = /\b[a-z0-9][a-z0-9-]*\.fly\.dev\b/;
|
|
|
1919
1497
|
const INVALID_FLY_APP_CHARS_RE = /[^a-z0-9-]+/g;
|
|
1920
1498
|
const REPEATED_HYPHENS_RE$2 = /-+/g;
|
|
1921
1499
|
const EDGE_HYPHENS_RE$2 = /^-|-$/g;
|
|
1922
|
-
const LINE_SPLIT_RE = /\r?\n/;
|
|
1500
|
+
const LINE_SPLIT_RE$1 = /\r?\n/;
|
|
1923
1501
|
const DEFAULT_REGION$1 = "iad";
|
|
1924
1502
|
const FLY_VOLUME_NAME = "dineway_data";
|
|
1925
|
-
async function fileExists$
|
|
1503
|
+
async function fileExists$8(path) {
|
|
1926
1504
|
try {
|
|
1927
1505
|
await access(path);
|
|
1928
1506
|
return true;
|
|
@@ -1932,7 +1510,7 @@ async function fileExists$9(path) {
|
|
|
1932
1510
|
}
|
|
1933
1511
|
async function writeGeneratedFile$1(cwd, relativePath, content, dryRun) {
|
|
1934
1512
|
const path = join(cwd, relativePath);
|
|
1935
|
-
if (await fileExists$
|
|
1513
|
+
if (await fileExists$8(path)) return {
|
|
1936
1514
|
path: relativePath,
|
|
1937
1515
|
action: "exists"
|
|
1938
1516
|
};
|
|
@@ -2011,7 +1589,7 @@ primary_region = "${region}"
|
|
|
2011
1589
|
function parseFlyUrl(stdout) {
|
|
2012
1590
|
const direct = stdout.match(FLY_URL_RE);
|
|
2013
1591
|
if (direct) return direct[0];
|
|
2014
|
-
for (const line of stdout.split(LINE_SPLIT_RE).filter(Boolean)) try {
|
|
1592
|
+
for (const line of stdout.split(LINE_SPLIT_RE$1).filter(Boolean)) try {
|
|
2015
1593
|
const url = findFlyUrl(JSON.parse(line));
|
|
2016
1594
|
if (url) return url;
|
|
2017
1595
|
} catch {
|
|
@@ -2121,69 +1699,9 @@ const flyTarget = {
|
|
|
2121
1699
|
}
|
|
2122
1700
|
};
|
|
2123
1701
|
|
|
2124
|
-
//#endregion
|
|
2125
|
-
//#region src/cli/commands/deploy/utils/config.ts
|
|
2126
|
-
const DINEWAY_DIR_NAME = ".dineway";
|
|
2127
|
-
const DEPLOY_STATE_FILE_NAME = "deploy.json";
|
|
2128
|
-
function deployStatePath(cwd) {
|
|
2129
|
-
return join(cwd, DINEWAY_DIR_NAME, DEPLOY_STATE_FILE_NAME);
|
|
2130
|
-
}
|
|
2131
|
-
function emptyDeployState() {
|
|
2132
|
-
return { version: 1 };
|
|
2133
|
-
}
|
|
2134
|
-
async function readDeployPackageJson(cwd) {
|
|
2135
|
-
const content = await readFile(join(cwd, "package.json"), "utf-8");
|
|
2136
|
-
return JSON.parse(content);
|
|
2137
|
-
}
|
|
2138
|
-
async function readDeployState(cwd) {
|
|
2139
|
-
try {
|
|
2140
|
-
const content = await readFile(deployStatePath(cwd), "utf-8");
|
|
2141
|
-
return {
|
|
2142
|
-
...emptyDeployState(),
|
|
2143
|
-
...JSON.parse(content),
|
|
2144
|
-
version: 1
|
|
2145
|
-
};
|
|
2146
|
-
} catch (error) {
|
|
2147
|
-
if (error && typeof error === "object" && error.code === "ENOENT") return emptyDeployState();
|
|
2148
|
-
throw error;
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
async function writeDeployState(cwd, state) {
|
|
2152
|
-
await mkdir(join(cwd, DINEWAY_DIR_NAME), { recursive: true });
|
|
2153
|
-
await writeFile(deployStatePath(cwd), JSON.stringify({
|
|
2154
|
-
...state,
|
|
2155
|
-
version: 1
|
|
2156
|
-
}, null, " ") + "\n", "utf-8");
|
|
2157
|
-
}
|
|
2158
|
-
async function readSavedDeployTarget(cwd) {
|
|
2159
|
-
return (await readDeployState(cwd)).target;
|
|
2160
|
-
}
|
|
2161
|
-
async function writeDeployTarget(cwd, target) {
|
|
2162
|
-
await writeDeployState(cwd, {
|
|
2163
|
-
...await readDeployState(cwd),
|
|
2164
|
-
target
|
|
2165
|
-
});
|
|
2166
|
-
}
|
|
2167
|
-
async function readForgewayDeployMetadata(cwd) {
|
|
2168
|
-
return (await readDeployState(cwd)).targets?.forgeway ?? {};
|
|
2169
|
-
}
|
|
2170
|
-
async function writeForgewayDeployMetadata(cwd, metadata) {
|
|
2171
|
-
const state = await readDeployState(cwd);
|
|
2172
|
-
await writeDeployState(cwd, {
|
|
2173
|
-
...state,
|
|
2174
|
-
targets: {
|
|
2175
|
-
...state.targets,
|
|
2176
|
-
forgeway: {
|
|
2177
|
-
...state.targets?.forgeway,
|
|
2178
|
-
...metadata
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
1702
|
//#endregion
|
|
2185
1703
|
//#region src/cli/commands/deploy/targets/forgeway.ts
|
|
2186
|
-
const DEFAULT_DINEWAY_API_BASE_URL
|
|
1704
|
+
const DEFAULT_DINEWAY_API_BASE_URL = "https://api.dineway.ai";
|
|
2187
1705
|
const DATABASE_ENV_VAR_NAMES = ["DINEWAY_DATABASE_URL", "DINEWAY_DATABASE_AUTH_TOKEN"];
|
|
2188
1706
|
const POLL_INTERVAL_MS$1 = 5e3;
|
|
2189
1707
|
const POLL_TIMEOUT_MS$1 = 3e5;
|
|
@@ -2198,12 +1716,12 @@ const NON_COMPARE_PATTERN = /[^\p{Letter}\p{Number}]+/gu;
|
|
|
2198
1716
|
const LOCATION_PARTS_PATTERN = /[,;|/]+|\s+-\s+/u;
|
|
2199
1717
|
const JWT_PATTERN = /[A-Za-z0-9_-]{32,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g;
|
|
2200
1718
|
const TOKEN_ASSIGNMENT_PATTERN = /(DINEWAY_DATABASE_AUTH_TOKEN|DATABASE_AUTH_TOKEN|AUTH_TOKEN)=\S+/gi;
|
|
2201
|
-
const TRAILING_SLASH_PATTERN
|
|
1719
|
+
const TRAILING_SLASH_PATTERN = /\/$/;
|
|
2202
1720
|
const BACKSLASH_PATTERN = /\\/g;
|
|
2203
1721
|
const DINEWAY_PACKAGE_NAME = "dineway";
|
|
2204
1722
|
const SEMVER_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
|
|
2205
1723
|
const LOCAL_PACKAGE_SPEC_PATTERN = /^(?:workspace|file|link|portal):/;
|
|
2206
|
-
const SHADOW_EMAIL_PATTERN
|
|
1724
|
+
const SHADOW_EMAIL_PATTERN = /^shadow_[a-f0-9]{16}@dineway\.ai$/i;
|
|
2207
1725
|
const FOOD_TYPES = new Set([
|
|
2208
1726
|
"restaurant",
|
|
2209
1727
|
"cafe",
|
|
@@ -2251,7 +1769,7 @@ var ForgewayApiError = class extends Error {
|
|
|
2251
1769
|
this.name = "ForgewayApiError";
|
|
2252
1770
|
}
|
|
2253
1771
|
};
|
|
2254
|
-
function normalizeForgewayUser
|
|
1772
|
+
function normalizeForgewayUser(user) {
|
|
2255
1773
|
return {
|
|
2256
1774
|
id: user.id,
|
|
2257
1775
|
name: user.profile?.name ?? user.name ?? user.email,
|
|
@@ -2263,7 +1781,7 @@ function normalizeForgewayUser$1(user) {
|
|
|
2263
1781
|
function getFetch(deps) {
|
|
2264
1782
|
return deps.fetch ?? fetch;
|
|
2265
1783
|
}
|
|
2266
|
-
async function fileExists$
|
|
1784
|
+
async function fileExists$7(path) {
|
|
2267
1785
|
try {
|
|
2268
1786
|
await access(path);
|
|
2269
1787
|
return true;
|
|
@@ -2272,7 +1790,7 @@ async function fileExists$8(path) {
|
|
|
2272
1790
|
}
|
|
2273
1791
|
}
|
|
2274
1792
|
function normalizePlatformApiUrl(value) {
|
|
2275
|
-
return (value || DEFAULT_DINEWAY_API_BASE_URL
|
|
1793
|
+
return (value || DEFAULT_DINEWAY_API_BASE_URL).replace(TRAILING_SLASH_PATTERN, "");
|
|
2276
1794
|
}
|
|
2277
1795
|
function resolvePlatformApiUrl(savedPlatformApiUrl) {
|
|
2278
1796
|
return normalizePlatformApiUrl(getEnv("DINEWAY_API_BASE_URL") ?? (typeof savedPlatformApiUrl === "string" ? savedPlatformApiUrl : void 0));
|
|
@@ -2288,7 +1806,7 @@ function formatDuration(ms) {
|
|
|
2288
1806
|
if (ms < 1e3) return `${ms}ms`;
|
|
2289
1807
|
return `${Math.round(ms / 100) / 10}s`;
|
|
2290
1808
|
}
|
|
2291
|
-
function formatBytes
|
|
1809
|
+
function formatBytes(bytes) {
|
|
2292
1810
|
if (bytes < 1024) return `${bytes} B`;
|
|
2293
1811
|
const units = [
|
|
2294
1812
|
"KB",
|
|
@@ -2335,11 +1853,11 @@ async function parseErrorResponse(response) {
|
|
|
2335
1853
|
}
|
|
2336
1854
|
return `Forgeway request failed: ${response.status}`;
|
|
2337
1855
|
}
|
|
2338
|
-
function normalizeEmail
|
|
1856
|
+
function normalizeEmail(email) {
|
|
2339
1857
|
return email.trim().toLowerCase();
|
|
2340
1858
|
}
|
|
2341
|
-
function isShadowEmail
|
|
2342
|
-
return Boolean(email && SHADOW_EMAIL_PATTERN
|
|
1859
|
+
function isShadowEmail(email) {
|
|
1860
|
+
return Boolean(email && SHADOW_EMAIL_PATTERN.test(email));
|
|
2343
1861
|
}
|
|
2344
1862
|
async function refreshFormalAccessToken(context, deps) {
|
|
2345
1863
|
if (!context.refreshToken) throw new Error("Forgeway formal account refresh token is missing. Run dineway deploy again.");
|
|
@@ -2351,7 +1869,7 @@ async function refreshFormalAccessToken(context, deps) {
|
|
|
2351
1869
|
if (!response.ok) throw new Error("Failed to refresh Forgeway formal account credentials. Run dineway deploy again.");
|
|
2352
1870
|
const payload = await response.json();
|
|
2353
1871
|
if (!payload.accessToken) throw new Error("Forgeway refresh response was missing accessToken.");
|
|
2354
|
-
const user = assertVerifiedFormalAccountUser(payload.user ? normalizeForgewayUser
|
|
1872
|
+
const user = assertVerifiedFormalAccountUser(payload.user ? normalizeForgewayUser(payload.user) : context.user, context.user?.email);
|
|
2355
1873
|
const updated = {
|
|
2356
1874
|
platformApiUrl: context.platformApiUrl,
|
|
2357
1875
|
accessToken: payload.accessToken,
|
|
@@ -2398,7 +1916,7 @@ function getDependencySpec(pkg, name) {
|
|
|
2398
1916
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2399
1917
|
}
|
|
2400
1918
|
}
|
|
2401
|
-
async function readProjectPackage(cwd) {
|
|
1919
|
+
async function readProjectPackage$1(cwd) {
|
|
2402
1920
|
try {
|
|
2403
1921
|
return await readDeployPackageJson(cwd);
|
|
2404
1922
|
} catch {
|
|
@@ -2412,7 +1930,7 @@ function isDinewayDependencySpecCompatible(dependencySpec, cliVersion) {
|
|
|
2412
1930
|
}
|
|
2413
1931
|
async function warnProjectDinewayRuntimeMismatch(cwd, cliVersion = VERSION) {
|
|
2414
1932
|
if (!SEMVER_VERSION_PATTERN.test(cliVersion)) return;
|
|
2415
|
-
const pkg = await readProjectPackage(cwd);
|
|
1933
|
+
const pkg = await readProjectPackage$1(cwd);
|
|
2416
1934
|
if (!pkg) return;
|
|
2417
1935
|
const dependencySpec = getDependencySpec(pkg, DINEWAY_PACKAGE_NAME);
|
|
2418
1936
|
if (!dependencySpec) return;
|
|
@@ -2426,20 +1944,20 @@ function savedPublisherEmail(saved) {
|
|
|
2426
1944
|
return typeof saved.publisherEmail === "string" && saved.publisherEmail.trim() ? saved.publisherEmail : void 0;
|
|
2427
1945
|
}
|
|
2428
1946
|
function validatePublisherEmail(rawEmail) {
|
|
2429
|
-
const email = normalizeEmail
|
|
2430
|
-
if (!email || isShadowEmail
|
|
1947
|
+
const email = normalizeEmail(rawEmail);
|
|
1948
|
+
if (!email || isShadowEmail(email)) throw new Error("Dineway account email must be a real owner email, not a shadow user email.");
|
|
2431
1949
|
return email;
|
|
2432
1950
|
}
|
|
2433
1951
|
function isStoredFormalCredentialForEmail(stored, email) {
|
|
2434
1952
|
if (!stored?.accessToken || !stored.user) return false;
|
|
2435
|
-
if (email && normalizeEmail
|
|
1953
|
+
if (email && normalizeEmail(stored.user.email) !== email) return false;
|
|
2436
1954
|
if (stored.user.emailVerified !== true) return false;
|
|
2437
|
-
if (isShadowEmail
|
|
1955
|
+
if (isShadowEmail(stored.user.email)) return false;
|
|
2438
1956
|
return true;
|
|
2439
1957
|
}
|
|
2440
1958
|
function assertVerifiedFormalAccountUser(user, expectedEmail) {
|
|
2441
|
-
if (!user || user.emailVerified !== true || isShadowEmail
|
|
2442
|
-
if (expectedEmail && normalizeEmail
|
|
1959
|
+
if (!user || user.emailVerified !== true || isShadowEmail(user.email)) throw new Error("Forgeway formal account credentials require a verified formal account.");
|
|
1960
|
+
if (expectedEmail && normalizeEmail(user.email) !== normalizeEmail(expectedEmail)) throw new Error("Forgeway refresh returned credentials for a different email.");
|
|
2443
1961
|
return user;
|
|
2444
1962
|
}
|
|
2445
1963
|
async function readCachedPublisherCredentials(cwd, platformApiUrl, email, deps) {
|
|
@@ -2472,14 +1990,14 @@ async function resolveCachedPublisherAccount(cwd, options, saved, platformApiUrl
|
|
|
2472
1990
|
platformApiUrl
|
|
2473
1991
|
});
|
|
2474
1992
|
if (isStoredFormalCredentialForEmail(singleCredentials)) return {
|
|
2475
|
-
email: normalizeEmail
|
|
1993
|
+
email: normalizeEmail(singleCredentials.user.email),
|
|
2476
1994
|
credentials: singleCredentials
|
|
2477
1995
|
};
|
|
2478
1996
|
const accounts = (await (deps.listCredentials ?? listForgewayCredentials)(platformApiUrl)).filter((credentials) => isStoredFormalCredentialForEmail(credentials));
|
|
2479
1997
|
if (accounts.length === 1) {
|
|
2480
1998
|
const credentials = accounts[0];
|
|
2481
1999
|
return {
|
|
2482
|
-
email: normalizeEmail
|
|
2000
|
+
email: normalizeEmail(credentials.user.email),
|
|
2483
2001
|
credentials
|
|
2484
2002
|
};
|
|
2485
2003
|
}
|
|
@@ -2541,7 +2059,7 @@ async function resolveProjectContext(cwd, options, deps) {
|
|
|
2541
2059
|
if (formal.email) throw new Error(`No verified Dineway account credentials were found for ${formal.email}. Run: dineway account login --email ${formal.email}`);
|
|
2542
2060
|
throw new Error("No verified Dineway account credentials were found. Run: dineway account login");
|
|
2543
2061
|
}
|
|
2544
|
-
function isRecord(value) {
|
|
2062
|
+
function isRecord$1(value) {
|
|
2545
2063
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2546
2064
|
}
|
|
2547
2065
|
function scalarText(value) {
|
|
@@ -2554,7 +2072,7 @@ function uniqueStrings(values) {
|
|
|
2554
2072
|
function displayText(value) {
|
|
2555
2073
|
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
2556
2074
|
if (Array.isArray(value)) return value.map(displayText).filter(Boolean).join(" ");
|
|
2557
|
-
if (isRecord(value)) return displayText(value.text) || displayText(value.name) || displayText(value.displayName) || displayText(value.longText) || displayText(value.shortText) || displayText(value.long_name) || displayText(value.short_name) || displayText(value.value);
|
|
2075
|
+
if (isRecord$1(value)) return displayText(value.text) || displayText(value.name) || displayText(value.displayName) || displayText(value.longText) || displayText(value.shortText) || displayText(value.long_name) || displayText(value.short_name) || displayText(value.value);
|
|
2558
2076
|
return "";
|
|
2559
2077
|
}
|
|
2560
2078
|
function normalizeCompare(value) {
|
|
@@ -2572,7 +2090,7 @@ function candidateNameTexts(candidate) {
|
|
|
2572
2090
|
]);
|
|
2573
2091
|
}
|
|
2574
2092
|
function candidateAddressTexts(candidate) {
|
|
2575
|
-
const addressComponents = Array.isArray(candidate.addressComponents) ? candidate.addressComponents.flatMap((component) => isRecord(component) ? [
|
|
2093
|
+
const addressComponents = Array.isArray(candidate.addressComponents) ? candidate.addressComponents.flatMap((component) => isRecord$1(component) ? [
|
|
2576
2094
|
displayText(component.longText),
|
|
2577
2095
|
displayText(component.shortText),
|
|
2578
2096
|
displayText(component.long_name),
|
|
@@ -2714,11 +2232,11 @@ function selectPlaceCandidate(candidates, context) {
|
|
|
2714
2232
|
} : null;
|
|
2715
2233
|
}
|
|
2716
2234
|
function extractPlaceCandidates(payload) {
|
|
2717
|
-
const root = isRecord(payload) ? payload : {};
|
|
2718
|
-
const nestedData = isRecord(root.data) ? root.data : {};
|
|
2235
|
+
const root = isRecord$1(payload) ? payload : {};
|
|
2236
|
+
const nestedData = isRecord$1(root.data) ? root.data : {};
|
|
2719
2237
|
const places = root.places ?? root.results ?? nestedData.places ?? nestedData.results;
|
|
2720
2238
|
if (!Array.isArray(places)) throw new Error("Dineway place search response did not include places/results.");
|
|
2721
|
-
return places.filter(isRecord);
|
|
2239
|
+
return places.filter(isRecord$1);
|
|
2722
2240
|
}
|
|
2723
2241
|
async function resolvePlaceIdWithFormalSearch(options) {
|
|
2724
2242
|
const candidates = extractPlaceCandidates(await ossFetch(options.context, "/api/places/search", options.deps, {
|
|
@@ -2735,7 +2253,7 @@ async function resolvePlaceIdWithFormalSearch(options) {
|
|
|
2735
2253
|
return finalPlaceId;
|
|
2736
2254
|
}
|
|
2737
2255
|
async function ossFetch(context, path, deps, options = {}) {
|
|
2738
|
-
const request = async (token) => await forgewayRequest(`${context.ossHost.replace(TRAILING_SLASH_PATTERN
|
|
2256
|
+
const request = async (token) => await forgewayRequest(`${context.ossHost.replace(TRAILING_SLASH_PATTERN, "")}${path}`, {
|
|
2739
2257
|
...options,
|
|
2740
2258
|
headers: {
|
|
2741
2259
|
"Content-Type": "application/json",
|
|
@@ -2761,18 +2279,18 @@ function deriveForgewaySiteSlug(restaurantName, city) {
|
|
|
2761
2279
|
async function resolveSeedPath$1(cwd, explicitSeedPath) {
|
|
2762
2280
|
if (explicitSeedPath) {
|
|
2763
2281
|
const resolved = resolve(cwd, explicitSeedPath);
|
|
2764
|
-
if (!await fileExists$
|
|
2282
|
+
if (!await fileExists$7(resolved)) throw new Error(`Seed file not found: ${explicitSeedPath}`);
|
|
2765
2283
|
return resolved;
|
|
2766
2284
|
}
|
|
2767
2285
|
const convention = resolve(cwd, ".dineway", "seed.json");
|
|
2768
|
-
if (await fileExists$
|
|
2286
|
+
if (await fileExists$7(convention)) return convention;
|
|
2769
2287
|
const pkg = await readDeployPackageJson(cwd).catch(() => null);
|
|
2770
2288
|
if (typeof pkg?.dineway?.seed === "string") {
|
|
2771
2289
|
const configured = resolve(cwd, pkg.dineway.seed);
|
|
2772
|
-
if (await fileExists$
|
|
2290
|
+
if (await fileExists$7(configured)) return configured;
|
|
2773
2291
|
}
|
|
2774
2292
|
const legacyConvention = resolve(cwd, "seed", "seed.json");
|
|
2775
|
-
if (await fileExists$
|
|
2293
|
+
if (await fileExists$7(legacyConvention)) return legacyConvention;
|
|
2776
2294
|
return null;
|
|
2777
2295
|
}
|
|
2778
2296
|
async function readSeedTitle(seedPath) {
|
|
@@ -3140,7 +2658,7 @@ async function deploySiteProject(options) {
|
|
|
3140
2658
|
consola.start("Scanning deploy files");
|
|
3141
2659
|
const localFiles = await (options.deps.collectDeploymentFiles ?? collectDeploymentFiles)(options.sourceDir);
|
|
3142
2660
|
if (localFiles.length === 0) throw new Error("No deployable files found in the source directory.");
|
|
3143
|
-
consola.success(`Scanned ${localFiles.length} files (${formatBytes
|
|
2661
|
+
consola.success(`Scanned ${localFiles.length} files (${formatBytes(deploymentFileSize(localFiles))}) in ${formatDuration(Date.now() - scanStart)}`);
|
|
3144
2662
|
const createStart = Date.now();
|
|
3145
2663
|
consola.start("Creating Forgeway deployment");
|
|
3146
2664
|
const createResult = await ossFetch(options.context, `/api/deployments/sites/${encodeURIComponent(options.siteId)}/deploy`, options.deps, {
|
|
@@ -3159,7 +2677,7 @@ async function deploySiteProject(options) {
|
|
|
3159
2677
|
const pendingFiles = createResult.files.filter((file) => !file.uploadedAt);
|
|
3160
2678
|
const pendingSize = pendingFiles.reduce((total, file) => total + file.size, 0);
|
|
3161
2679
|
if (pendingFiles.length === 0) consola.success("No file uploads needed");
|
|
3162
|
-
else consola.start(`Uploading ${pendingFiles.length} changed files (${formatBytes
|
|
2680
|
+
else consola.start(`Uploading ${pendingFiles.length} changed files (${formatBytes(pendingSize)})`);
|
|
3163
2681
|
let uploadedCount = 0;
|
|
3164
2682
|
await runWithConcurrency(pendingFiles, DIRECT_UPLOAD_CONCURRENCY, async (manifestFile) => {
|
|
3165
2683
|
const localFile = localFileByPath.get(manifestFile.path);
|
|
@@ -3250,7 +2768,7 @@ async function deployForgeway(cwd, options, deps = {}) {
|
|
|
3250
2768
|
deps,
|
|
3251
2769
|
verbose
|
|
3252
2770
|
});
|
|
3253
|
-
if (context.user?.email) await writeForgewayDeployMetadata(cwd, { publisherEmail: normalizeEmail
|
|
2771
|
+
if (context.user?.email) await writeForgewayDeployMetadata(cwd, { publisherEmail: normalizeEmail(context.user.email) });
|
|
3254
2772
|
const url = deployment.liveUrl ?? `https://${site.domain}`;
|
|
3255
2773
|
await (deps.writeProjectEnv ?? persistForgewayCliEnv)(cwd, {
|
|
3256
2774
|
siteUrl: url,
|
|
@@ -3303,7 +2821,7 @@ pnpm-debug.log*
|
|
|
3303
2821
|
yarn-debug.log*
|
|
3304
2822
|
yarn-error.log*
|
|
3305
2823
|
`;
|
|
3306
|
-
async function fileExists$
|
|
2824
|
+
async function fileExists$6(path) {
|
|
3307
2825
|
try {
|
|
3308
2826
|
await access(path);
|
|
3309
2827
|
return true;
|
|
@@ -3313,7 +2831,7 @@ async function fileExists$7(path) {
|
|
|
3313
2831
|
}
|
|
3314
2832
|
async function writeGeneratedFile(cwd, relativePath, content, dryRun) {
|
|
3315
2833
|
const path = join(cwd, relativePath);
|
|
3316
|
-
if (await fileExists$
|
|
2834
|
+
if (await fileExists$6(path)) return {
|
|
3317
2835
|
path: relativePath,
|
|
3318
2836
|
action: "exists"
|
|
3319
2837
|
};
|
|
@@ -3446,7 +2964,7 @@ const gcpTarget = {
|
|
|
3446
2964
|
env: options.env,
|
|
3447
2965
|
stdio: "inherit"
|
|
3448
2966
|
});
|
|
3449
|
-
if (result.code !== 0 && !await fileExists$
|
|
2967
|
+
if (result.code !== 0 && !await fileExists$6(join(cwd, "Dockerfile"))) {
|
|
3450
2968
|
await writeFile(join(cwd, "Dockerfile"), generateDockerfile(), "utf-8");
|
|
3451
2969
|
result = await runCommand$1("gcloud", deployArgs, {
|
|
3452
2970
|
cwd,
|
|
@@ -3465,7 +2983,7 @@ const gcpTarget = {
|
|
|
3465
2983
|
|
|
3466
2984
|
//#endregion
|
|
3467
2985
|
//#region src/cli/commands/deploy/utils/build.ts
|
|
3468
|
-
async function fileExists$
|
|
2986
|
+
async function fileExists$5(path) {
|
|
3469
2987
|
try {
|
|
3470
2988
|
await access(path);
|
|
3471
2989
|
return true;
|
|
@@ -3474,19 +2992,19 @@ async function fileExists$6(path) {
|
|
|
3474
2992
|
}
|
|
3475
2993
|
}
|
|
3476
2994
|
async function detectBuildCommand(cwd) {
|
|
3477
|
-
if (await fileExists$
|
|
2995
|
+
if (await fileExists$5(join(cwd, "pnpm-lock.yaml"))) return {
|
|
3478
2996
|
packageManager: "pnpm",
|
|
3479
2997
|
command: "pnpm",
|
|
3480
2998
|
args: ["build"],
|
|
3481
2999
|
display: "pnpm build"
|
|
3482
3000
|
};
|
|
3483
|
-
if (await fileExists$
|
|
3001
|
+
if (await fileExists$5(join(cwd, "yarn.lock"))) return {
|
|
3484
3002
|
packageManager: "yarn",
|
|
3485
3003
|
command: "yarn",
|
|
3486
3004
|
args: ["build"],
|
|
3487
3005
|
display: "yarn build"
|
|
3488
3006
|
};
|
|
3489
|
-
if (await fileExists$
|
|
3007
|
+
if (await fileExists$5(join(cwd, "bun.lock")) || await fileExists$5(join(cwd, "bun.lockb"))) return {
|
|
3490
3008
|
packageManager: "bun",
|
|
3491
3009
|
command: "bun",
|
|
3492
3010
|
args: ["run", "build"],
|
|
@@ -3517,7 +3035,7 @@ const INVALID_RAILWAY_PROJECT_CHARS_RE = /[^a-z0-9-]+/g;
|
|
|
3517
3035
|
const REPEATED_HYPHENS_RE = /-+/g;
|
|
3518
3036
|
const EDGE_HYPHENS_RE = /^-|-$/g;
|
|
3519
3037
|
const RAILWAY_BUILDER = "RAILPACK";
|
|
3520
|
-
async function fileExists$
|
|
3038
|
+
async function fileExists$4(path) {
|
|
3521
3039
|
try {
|
|
3522
3040
|
await access(path);
|
|
3523
3041
|
return true;
|
|
@@ -3611,7 +3129,7 @@ const railwayTarget = {
|
|
|
3611
3129
|
async generateConfig(cwd, options) {
|
|
3612
3130
|
const generated = await configureAstroDeployAdapter(cwd, "node", options.dryRun);
|
|
3613
3131
|
const railwayPath = join(cwd, "railway.json");
|
|
3614
|
-
if (await fileExists$
|
|
3132
|
+
if (await fileExists$4(railwayPath)) return [...generated, {
|
|
3615
3133
|
path: "railway.json",
|
|
3616
3134
|
action: "exists"
|
|
3617
3135
|
}];
|
|
@@ -3968,7 +3486,7 @@ async function readPackageJson$2(cwd) {
|
|
|
3968
3486
|
return null;
|
|
3969
3487
|
}
|
|
3970
3488
|
}
|
|
3971
|
-
async function fileExists$
|
|
3489
|
+
async function fileExists$3(path) {
|
|
3972
3490
|
try {
|
|
3973
3491
|
await access(path);
|
|
3974
3492
|
return true;
|
|
@@ -4015,7 +3533,7 @@ const devCommand = defineCommand({
|
|
|
4015
3533
|
}
|
|
4016
3534
|
const database = resolveCliDatabaseTarget(cwd, args.database);
|
|
4017
3535
|
consola.info(`Database: ${database.display}`);
|
|
4018
|
-
if (database.localPath && !await fileExists$
|
|
3536
|
+
if (database.localPath && !await fileExists$3(database.localPath)) consola.start("Database not found, initializing...");
|
|
4019
3537
|
const db = createDatabase({ url: database.url });
|
|
4020
3538
|
try {
|
|
4021
3539
|
consola.start("Checking database migrations...");
|
|
@@ -4032,7 +3550,7 @@ const devCommand = defineCommand({
|
|
|
4032
3550
|
const remoteUrl = pkg.dineway?.url || process.env.DINEWAY_URL;
|
|
4033
3551
|
if (!remoteUrl) consola.warn("No remote URL configured. Set DINEWAY_URL or dineway.url in package.json");
|
|
4034
3552
|
else try {
|
|
4035
|
-
const { createClientFromArgs } = await
|
|
3553
|
+
const { createClientFromArgs } = await import("../client-factory-BDD9azPJ.mjs").then((n) => n.t);
|
|
4036
3554
|
const client = createClientFromArgs({ url: remoteUrl });
|
|
4037
3555
|
const schema = await client.schemaExport();
|
|
4038
3556
|
const types = await client.schemaTypes();
|
|
@@ -4054,8 +3572,8 @@ const devCommand = defineCommand({
|
|
|
4054
3572
|
"--port",
|
|
4055
3573
|
args.port
|
|
4056
3574
|
];
|
|
4057
|
-
const pnpmLockExists = await fileExists$
|
|
4058
|
-
const yarnLockExists = await fileExists$
|
|
3575
|
+
const pnpmLockExists = await fileExists$3(resolve(cwd, "pnpm-lock.yaml"));
|
|
3576
|
+
const yarnLockExists = await fileExists$3(resolve(cwd, "yarn.lock"));
|
|
4059
3577
|
let cmd;
|
|
4060
3578
|
let cmdArgs;
|
|
4061
3579
|
if (pnpmLockExists) {
|
|
@@ -4099,7 +3617,7 @@ const devCommand = defineCommand({
|
|
|
4099
3617
|
*
|
|
4100
3618
|
* Diagnose database health: connection, migrations, schema integrity.
|
|
4101
3619
|
*/
|
|
4102
|
-
async function fileExists$
|
|
3620
|
+
async function fileExists$2(path) {
|
|
4103
3621
|
try {
|
|
4104
3622
|
await access(path);
|
|
4105
3623
|
return true;
|
|
@@ -4112,7 +3630,7 @@ function printResult(result) {
|
|
|
4112
3630
|
}
|
|
4113
3631
|
async function checkDatabase(database) {
|
|
4114
3632
|
const results = [];
|
|
4115
|
-
if (database.localPath && !await fileExists$
|
|
3633
|
+
if (database.localPath && !await fileExists$2(database.localPath)) {
|
|
4116
3634
|
results.push({
|
|
4117
3635
|
name: "database",
|
|
4118
3636
|
status: "fail",
|
|
@@ -4166,7 +3684,7 @@ async function checkDatabase(database) {
|
|
|
4166
3684
|
});
|
|
4167
3685
|
} catch {}
|
|
4168
3686
|
try {
|
|
4169
|
-
const count = (await sql`SELECT COUNT(id) as count FROM
|
|
3687
|
+
const count = (await sql`SELECT COUNT(id) as count FROM users`.execute(db)).rows[0]?.count ?? 0;
|
|
4170
3688
|
results.push({
|
|
4171
3689
|
name: "users",
|
|
4172
3690
|
status: count > 0 ? "pass" : "warn",
|
|
@@ -4314,7 +3832,7 @@ async function exportSeed(db, withContent) {
|
|
|
4314
3832
|
const { bylines, idToSeedId: bylineIdToSeedId } = await exportBylines(db);
|
|
4315
3833
|
if (bylines.length > 0) seed.bylines = bylines;
|
|
4316
3834
|
if (withContent !== void 0) {
|
|
4317
|
-
const collections = withContent === "" || withContent === "true" ? null : withContent.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3835
|
+
const collections = withContent === "" || withContent === "true" || withContent === "all" ? null : withContent.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4318
3836
|
seed.content = await exportContent(db, seed.collections || [], collections, bylineIdToSeedId, i18nEnabled);
|
|
4319
3837
|
}
|
|
4320
3838
|
return seed;
|
|
@@ -4444,8 +3962,8 @@ async function exportTaxonomies(db, i18nEnabled) {
|
|
|
4444
3962
|
for (const def of defs) {
|
|
4445
3963
|
const defSeedId = i18nEnabled && def.locale ? `tax:${def.name}:${def.locale}` : `tax:${def.name}`;
|
|
4446
3964
|
const terms = await termRepo.findByName(def.name, { locale: def.locale });
|
|
4447
|
-
const
|
|
4448
|
-
for (const term of terms)
|
|
3965
|
+
const groupToSlug = /* @__PURE__ */ new Map();
|
|
3966
|
+
for (const term of terms) groupToSlug.set(term.translationGroup ?? term.id, term.slug);
|
|
4449
3967
|
const termGroupToSeedId = /* @__PURE__ */ new Map();
|
|
4450
3968
|
const seedTerms = [];
|
|
4451
3969
|
for (const term of terms) {
|
|
@@ -4456,7 +3974,7 @@ async function exportTaxonomies(db, i18nEnabled) {
|
|
|
4456
3974
|
label: term.label,
|
|
4457
3975
|
description: typeof term.data?.description === "string" ? term.data.description : void 0
|
|
4458
3976
|
};
|
|
4459
|
-
if (term.parentId) seedTerm.parent =
|
|
3977
|
+
if (term.parentId) seedTerm.parent = groupToSlug.get(term.parentId);
|
|
4460
3978
|
if (i18nEnabled && term.locale) {
|
|
4461
3979
|
seedTerm.locale = term.locale;
|
|
4462
3980
|
if (term.translationGroup) {
|
|
@@ -4745,7 +4263,7 @@ async function getTaxonomyAssignments(taxonomyRepo, collection, entryId) {
|
|
|
4745
4263
|
*
|
|
4746
4264
|
* Initialize database from template config in package.json
|
|
4747
4265
|
*/
|
|
4748
|
-
async function fileExists$
|
|
4266
|
+
async function fileExists$1(path) {
|
|
4749
4267
|
try {
|
|
4750
4268
|
await access(path);
|
|
4751
4269
|
return true;
|
|
@@ -4845,7 +4363,7 @@ const initCommand = defineCommand({
|
|
|
4845
4363
|
if (alreadyInitialized && args.force) consola.warn("Re-initializing (--force)...");
|
|
4846
4364
|
if (config?.schema) {
|
|
4847
4365
|
const schemaPath = resolve(cwd, config.schema);
|
|
4848
|
-
if (await fileExists$
|
|
4366
|
+
if (await fileExists$1(schemaPath)) {
|
|
4849
4367
|
consola.start(`Running schema: ${config.schema}`);
|
|
4850
4368
|
try {
|
|
4851
4369
|
await runSqlFile(db, schemaPath);
|
|
@@ -5389,606 +4907,19 @@ const menuCommand = defineCommand({
|
|
|
5389
4907
|
});
|
|
5390
4908
|
|
|
5391
4909
|
//#endregion
|
|
5392
|
-
//#region src/cli/commands/
|
|
4910
|
+
//#region src/cli/commands/plugin-init.ts
|
|
5393
4911
|
/**
|
|
5394
|
-
*
|
|
4912
|
+
* dineway plugin init
|
|
5395
4913
|
*
|
|
5396
|
-
*
|
|
5397
|
-
*
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
const ICON_SIZE = 256;
|
|
5406
|
-
function toFileImportSpecifier(filePath) {
|
|
5407
|
-
return pathToFileURL(filePath).href;
|
|
5408
|
-
}
|
|
5409
|
-
/** Matches Node.js built-in imports in bundled output. */
|
|
5410
|
-
const NODE_BUILTIN_IMPORT_RE = /(?:import|export|require)\s*(?:\(|[^(]*?\bfrom\s+)["'](?:node:)?([a-z_]+)(?:\/[^"']*)?\s*["']\)?/g;
|
|
5411
|
-
const LEADING_DOT_SLASH_RE = /^\.\//;
|
|
5412
|
-
const DIST_PREFIX_RE = /^dist\//;
|
|
5413
|
-
const MJS_EXT_RE = /\.m?js$/;
|
|
5414
|
-
const BUILD_OUTPUT_EXT_RE = /\.(?:mjs|js|cjs)$/;
|
|
5415
|
-
const TS_TO_TSX_RE = /\.ts$/;
|
|
5416
|
-
/** Node.js built-in modules that shouldn't appear in sandbox code */
|
|
5417
|
-
const NODE_BUILTINS = new Set([
|
|
5418
|
-
"assert",
|
|
5419
|
-
"buffer",
|
|
5420
|
-
"child_process",
|
|
5421
|
-
"cluster",
|
|
5422
|
-
"crypto",
|
|
5423
|
-
"dgram",
|
|
5424
|
-
"dns",
|
|
5425
|
-
"domain",
|
|
5426
|
-
"events",
|
|
5427
|
-
"fs",
|
|
5428
|
-
"http",
|
|
5429
|
-
"http2",
|
|
5430
|
-
"https",
|
|
5431
|
-
"inspector",
|
|
5432
|
-
"module",
|
|
5433
|
-
"net",
|
|
5434
|
-
"os",
|
|
5435
|
-
"path",
|
|
5436
|
-
"perf_hooks",
|
|
5437
|
-
"process",
|
|
5438
|
-
"punycode",
|
|
5439
|
-
"querystring",
|
|
5440
|
-
"readline",
|
|
5441
|
-
"repl",
|
|
5442
|
-
"stream",
|
|
5443
|
-
"string_decoder",
|
|
5444
|
-
"sys",
|
|
5445
|
-
"timers",
|
|
5446
|
-
"tls",
|
|
5447
|
-
"trace_events",
|
|
5448
|
-
"tty",
|
|
5449
|
-
"url",
|
|
5450
|
-
"util",
|
|
5451
|
-
"v8",
|
|
5452
|
-
"vm",
|
|
5453
|
-
"wasi",
|
|
5454
|
-
"worker_threads",
|
|
5455
|
-
"zlib"
|
|
5456
|
-
]);
|
|
5457
|
-
async function fileExists$1(path) {
|
|
5458
|
-
try {
|
|
5459
|
-
await access(path);
|
|
5460
|
-
return true;
|
|
5461
|
-
} catch {
|
|
5462
|
-
return false;
|
|
5463
|
-
}
|
|
5464
|
-
}
|
|
5465
|
-
/**
|
|
5466
|
-
* Read image dimensions from a buffer.
|
|
5467
|
-
* Returns [width, height] or null if the format is unrecognized.
|
|
5468
|
-
*/
|
|
5469
|
-
function readImageDimensions(buf) {
|
|
5470
|
-
try {
|
|
5471
|
-
const result = imageSize(buf);
|
|
5472
|
-
if (result.width != null && result.height != null) return [result.width, result.height];
|
|
5473
|
-
return null;
|
|
5474
|
-
} catch {
|
|
5475
|
-
return null;
|
|
5476
|
-
}
|
|
5477
|
-
}
|
|
5478
|
-
/**
|
|
5479
|
-
* Extract manifest metadata from a ResolvedPlugin.
|
|
5480
|
-
* Strips functions (hooks, route handlers) and keeps only serializable metadata.
|
|
5481
|
-
*/
|
|
5482
|
-
function extractManifest(plugin) {
|
|
5483
|
-
const hooks = [];
|
|
5484
|
-
for (const [name, resolved] of Object.entries(plugin.hooks)) {
|
|
5485
|
-
if (!resolved) continue;
|
|
5486
|
-
if (resolved.exclusive || resolved.priority !== 100 || resolved.timeout !== 5e3) {
|
|
5487
|
-
const entry = { name };
|
|
5488
|
-
if (resolved.exclusive) entry.exclusive = true;
|
|
5489
|
-
if (resolved.priority !== 100) entry.priority = resolved.priority;
|
|
5490
|
-
if (resolved.timeout !== 5e3) entry.timeout = resolved.timeout;
|
|
5491
|
-
hooks.push(entry);
|
|
5492
|
-
} else hooks.push(name);
|
|
5493
|
-
}
|
|
5494
|
-
return {
|
|
5495
|
-
id: plugin.id,
|
|
5496
|
-
version: plugin.version,
|
|
5497
|
-
capabilities: plugin.capabilities,
|
|
5498
|
-
allowedHosts: plugin.allowedHosts,
|
|
5499
|
-
storage: plugin.storage,
|
|
5500
|
-
hooks,
|
|
5501
|
-
routes: Object.keys(plugin.routes),
|
|
5502
|
-
admin: {
|
|
5503
|
-
settingsSchema: plugin.admin.settingsSchema,
|
|
5504
|
-
pages: plugin.admin.pages,
|
|
5505
|
-
widgets: plugin.admin.widgets
|
|
5506
|
-
}
|
|
5507
|
-
};
|
|
5508
|
-
}
|
|
5509
|
-
/**
|
|
5510
|
-
* Scan bundled code for Node.js built-in imports.
|
|
5511
|
-
* Matches require("node:xxx"), require("xxx"), import("node:xxx") — the patterns
|
|
5512
|
-
* that appear in bundled ESM/CJS output (not source-level named imports).
|
|
5513
|
-
* Returns deduplicated array of built-in module names found.
|
|
5514
|
-
*/
|
|
5515
|
-
function findNodeBuiltinImports(code) {
|
|
5516
|
-
const found = [];
|
|
5517
|
-
NODE_BUILTIN_IMPORT_RE.lastIndex = 0;
|
|
5518
|
-
let match;
|
|
5519
|
-
while ((match = NODE_BUILTIN_IMPORT_RE.exec(code)) !== null) {
|
|
5520
|
-
const mod = match[1];
|
|
5521
|
-
if (NODE_BUILTINS.has(mod)) found.push(mod);
|
|
5522
|
-
}
|
|
5523
|
-
return [...new Set(found)];
|
|
5524
|
-
}
|
|
5525
|
-
/**
|
|
5526
|
-
* Find a build output file by base name, checking common extensions.
|
|
5527
|
-
* tsdown may output .mjs, .js, or .cjs depending on format and config.
|
|
5528
|
-
*/
|
|
5529
|
-
async function findBuildOutput(dir, baseName) {
|
|
5530
|
-
const normalizedBaseName = baseName.replace(BUILD_OUTPUT_EXT_RE, "");
|
|
5531
|
-
for (const ext of [
|
|
5532
|
-
".mjs",
|
|
5533
|
-
".js",
|
|
5534
|
-
".cjs"
|
|
5535
|
-
]) {
|
|
5536
|
-
const candidate = join(dir, `${normalizedBaseName}${ext}`);
|
|
5537
|
-
if (await fileExists$1(candidate)) return candidate;
|
|
5538
|
-
}
|
|
5539
|
-
}
|
|
5540
|
-
/**
|
|
5541
|
-
* Resolve a dist/built path back to its source .ts/.tsx equivalent.
|
|
5542
|
-
* E.g., "./dist/index.mjs" → "src/index.ts"
|
|
5543
|
-
*/
|
|
5544
|
-
async function resolveSourceEntry(pluginDir, distPath) {
|
|
5545
|
-
const cleaned = distPath.replace(LEADING_DOT_SLASH_RE, "");
|
|
5546
|
-
const srcPath = cleaned.replace(DIST_PREFIX_RE, "src/").replace(MJS_EXT_RE, ".ts");
|
|
5547
|
-
const srcFull = resolve(pluginDir, srcPath);
|
|
5548
|
-
if (await fileExists$1(srcFull)) return srcFull;
|
|
5549
|
-
const tsxFull = resolve(pluginDir, srcPath.replace(TS_TO_TSX_RE, ".tsx"));
|
|
5550
|
-
if (await fileExists$1(tsxFull)) return tsxFull;
|
|
5551
|
-
const direct = resolve(pluginDir, cleaned);
|
|
5552
|
-
if (await fileExists$1(direct)) return direct;
|
|
5553
|
-
}
|
|
5554
|
-
const TS_SOURCE_EXPORT_RE = /\.(?:ts|tsx|mts|cts|jsx)$/;
|
|
5555
|
-
/**
|
|
5556
|
-
* Find package.json exports that point to source files instead of built output.
|
|
5557
|
-
* Returns an array of `{ exportPath, resolvedPath }` for each offending export.
|
|
5558
|
-
*/
|
|
5559
|
-
function findSourceExports(exports) {
|
|
5560
|
-
const issues = [];
|
|
5561
|
-
for (const [exportPath, exportValue] of Object.entries(exports)) {
|
|
5562
|
-
const resolved = typeof exportValue === "string" ? exportValue : exportValue && typeof exportValue === "object" && "import" in exportValue ? exportValue.import : null;
|
|
5563
|
-
if (resolved && TS_SOURCE_EXPORT_RE.test(resolved)) issues.push({
|
|
5564
|
-
exportPath,
|
|
5565
|
-
resolvedPath: resolved
|
|
5566
|
-
});
|
|
5567
|
-
}
|
|
5568
|
-
return issues;
|
|
5569
|
-
}
|
|
5570
|
-
/**
|
|
5571
|
-
* Recursively walk a staging directory and return all regular files with
|
|
5572
|
-
* tarball-relative names.
|
|
5573
|
-
*/
|
|
5574
|
-
async function collectBundleEntries(dir) {
|
|
5575
|
-
const entries = [];
|
|
5576
|
-
await walkBundle(dir, "", entries);
|
|
5577
|
-
return entries;
|
|
5578
|
-
}
|
|
5579
|
-
async function walkBundle(dir, prefix, into) {
|
|
5580
|
-
const items = await readdir(dir, { withFileTypes: true });
|
|
5581
|
-
for (const item of items) {
|
|
5582
|
-
const fullPath = join(dir, item.name);
|
|
5583
|
-
const relPath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
5584
|
-
if (item.isFile()) {
|
|
5585
|
-
const s = await stat(fullPath);
|
|
5586
|
-
into.push({
|
|
5587
|
-
name: relPath,
|
|
5588
|
-
bytes: s.size
|
|
5589
|
-
});
|
|
5590
|
-
} else if (item.isDirectory()) await walkBundle(fullPath, relPath, into);
|
|
5591
|
-
}
|
|
5592
|
-
}
|
|
5593
|
-
function totalBundleBytes(entries) {
|
|
5594
|
-
let total = 0;
|
|
5595
|
-
for (const entry of entries) total += entry.bytes;
|
|
5596
|
-
return total;
|
|
5597
|
-
}
|
|
5598
|
-
/**
|
|
5599
|
-
* Enforce sandboxed plugin caps against decompressed bundle files:
|
|
5600
|
-
* total size, per-file size, and file count.
|
|
5601
|
-
*/
|
|
5602
|
-
function validateBundleSize(entries) {
|
|
5603
|
-
const violations = [];
|
|
5604
|
-
const total = totalBundleBytes(entries);
|
|
5605
|
-
if (total > MAX_BUNDLE_SIZE) violations.push(`Bundle size ${formatBytes(total)} exceeds maximum of ${formatBytes(MAX_BUNDLE_SIZE)}.`);
|
|
5606
|
-
if (entries.length > MAX_FILE_COUNT) violations.push(`Bundle contains ${entries.length} files, exceeds maximum of ${MAX_FILE_COUNT}.`);
|
|
5607
|
-
const oversized = entries.filter((entry) => entry.bytes > MAX_FILE_SIZE).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
5608
|
-
for (const entry of oversized) violations.push(`File ${entry.name} is ${formatBytes(entry.bytes)}, exceeds per-file maximum of ${formatBytes(MAX_FILE_SIZE)}.`);
|
|
5609
|
-
return violations;
|
|
5610
|
-
}
|
|
5611
|
-
function formatBytes(bytes) {
|
|
5612
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
5613
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
5614
|
-
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
|
|
5615
|
-
}
|
|
5616
|
-
/**
|
|
5617
|
-
* Create a gzipped tarball from a directory.
|
|
5618
|
-
*/
|
|
5619
|
-
async function createTarball(sourceDir, outputPath) {
|
|
5620
|
-
const { createGzip } = await import("node:zlib");
|
|
5621
|
-
await pipeline(packTar(sourceDir), createGzip({ level: 9 }), createWriteStream(outputPath));
|
|
5622
|
-
}
|
|
5623
|
-
|
|
5624
|
-
//#endregion
|
|
5625
|
-
//#region src/cli/commands/bundle.ts
|
|
5626
|
-
/**
|
|
5627
|
-
* dineway plugin bundle
|
|
5628
|
-
*
|
|
5629
|
-
* Produces a publishable plugin tarball from a plugin source directory.
|
|
5630
|
-
*
|
|
5631
|
-
* Steps:
|
|
5632
|
-
* 1. Resolve plugin entrypoint (finds definePlugin() export)
|
|
5633
|
-
* 2. Bundle backend code with tsdown → backend.js (single ES module, tree-shaken)
|
|
5634
|
-
* 3. Bundle admin code if present → admin.js
|
|
5635
|
-
* 4. Extract manifest from definePlugin() → manifest.json
|
|
5636
|
-
* 5. Collect assets (README.md, icon.png, screenshots/)
|
|
5637
|
-
* 6. Validate bundle (manifest schema, size limits, no Node.js builtins)
|
|
5638
|
-
* 7. Create tarball ({id}-{version}.tar.gz)
|
|
5639
|
-
*/
|
|
5640
|
-
var bundle_exports = /* @__PURE__ */ __exportAll({ bundleCommand: () => bundleCommand });
|
|
5641
|
-
const TS_EXT_RE = /\.tsx?$/;
|
|
5642
|
-
const SLASH_RE = /\//g;
|
|
5643
|
-
const LEADING_AT_RE = /^@/;
|
|
5644
|
-
const DINEWAY_SCOPE_RE = /^@dineway-ai\//;
|
|
5645
|
-
const bundleCommand = defineCommand({
|
|
5646
|
-
meta: {
|
|
5647
|
-
name: "bundle",
|
|
5648
|
-
description: "Bundle a plugin for marketplace distribution"
|
|
5649
|
-
},
|
|
5650
|
-
args: {
|
|
5651
|
-
dir: {
|
|
5652
|
-
type: "string",
|
|
5653
|
-
description: "Plugin directory (default: current directory)",
|
|
5654
|
-
default: process.cwd()
|
|
5655
|
-
},
|
|
5656
|
-
outDir: {
|
|
5657
|
-
type: "string",
|
|
5658
|
-
alias: "o",
|
|
5659
|
-
description: "Output directory for the tarball (default: ./dist)",
|
|
5660
|
-
default: "dist"
|
|
5661
|
-
},
|
|
5662
|
-
validateOnly: {
|
|
5663
|
-
type: "boolean",
|
|
5664
|
-
description: "Run validation only, skip tarball creation",
|
|
5665
|
-
default: false
|
|
5666
|
-
}
|
|
5667
|
-
},
|
|
5668
|
-
async run({ args }) {
|
|
5669
|
-
const pluginDir = resolve(args.dir);
|
|
5670
|
-
const outDir = resolve(pluginDir, args.outDir);
|
|
5671
|
-
const validateOnly = args.validateOnly;
|
|
5672
|
-
consola.start(validateOnly ? "Validating plugin..." : "Bundling plugin...");
|
|
5673
|
-
const pkgPath = join(pluginDir, "package.json");
|
|
5674
|
-
if (!await fileExists$1(pkgPath)) {
|
|
5675
|
-
consola.error("No package.json found in", pluginDir);
|
|
5676
|
-
process.exit(1);
|
|
5677
|
-
}
|
|
5678
|
-
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
5679
|
-
let backendEntry;
|
|
5680
|
-
let adminEntry;
|
|
5681
|
-
if (pkg.exports) {
|
|
5682
|
-
const sandboxExport = pkg.exports["./sandbox"];
|
|
5683
|
-
if (typeof sandboxExport === "string") backendEntry = await resolveSourceEntry(pluginDir, sandboxExport);
|
|
5684
|
-
else if (sandboxExport && typeof sandboxExport === "object" && "import" in sandboxExport) backendEntry = await resolveSourceEntry(pluginDir, sandboxExport.import);
|
|
5685
|
-
const adminExport = pkg.exports["./admin"];
|
|
5686
|
-
if (typeof adminExport === "string") adminEntry = await resolveSourceEntry(pluginDir, adminExport);
|
|
5687
|
-
else if (adminExport && typeof adminExport === "object" && "import" in adminExport) adminEntry = await resolveSourceEntry(pluginDir, adminExport.import);
|
|
5688
|
-
}
|
|
5689
|
-
if (!backendEntry) {
|
|
5690
|
-
const defaultSandbox = join(pluginDir, "src/sandbox-entry.ts");
|
|
5691
|
-
if (await fileExists$1(defaultSandbox)) backendEntry = defaultSandbox;
|
|
5692
|
-
}
|
|
5693
|
-
let mainEntry;
|
|
5694
|
-
if (pkg.exports?.["."] !== void 0) {
|
|
5695
|
-
const mainExport = pkg.exports["."];
|
|
5696
|
-
if (typeof mainExport === "string") mainEntry = await resolveSourceEntry(pluginDir, mainExport);
|
|
5697
|
-
else if (mainExport && typeof mainExport === "object" && "import" in mainExport) mainEntry = await resolveSourceEntry(pluginDir, mainExport.import);
|
|
5698
|
-
}
|
|
5699
|
-
if (!mainEntry && pkg.main) mainEntry = await resolveSourceEntry(pluginDir, pkg.main);
|
|
5700
|
-
if (!mainEntry) {
|
|
5701
|
-
const defaultMain = join(pluginDir, "src/index.ts");
|
|
5702
|
-
if (await fileExists$1(defaultMain)) mainEntry = defaultMain;
|
|
5703
|
-
}
|
|
5704
|
-
if (!mainEntry) {
|
|
5705
|
-
consola.error("Cannot find plugin entrypoint. Expected src/index.ts or main/exports in package.json");
|
|
5706
|
-
process.exit(1);
|
|
5707
|
-
}
|
|
5708
|
-
consola.info(`Main entry: ${mainEntry}`);
|
|
5709
|
-
if (backendEntry) consola.info(`Backend entry: ${backendEntry}`);
|
|
5710
|
-
if (adminEntry) consola.info(`Admin entry: ${adminEntry}`);
|
|
5711
|
-
consola.start("Extracting plugin manifest...");
|
|
5712
|
-
const { build } = await import("tsdown");
|
|
5713
|
-
const tmpDir = join(pluginDir, ".dineway-bundle-tmp");
|
|
5714
|
-
try {
|
|
5715
|
-
await mkdir(tmpDir, { recursive: true });
|
|
5716
|
-
await copyFile(pkgPath, join(tmpDir, "package.json"));
|
|
5717
|
-
const mainOutDir = join(tmpDir, "main");
|
|
5718
|
-
await build({
|
|
5719
|
-
config: false,
|
|
5720
|
-
entry: [mainEntry],
|
|
5721
|
-
format: "esm",
|
|
5722
|
-
outDir: mainOutDir,
|
|
5723
|
-
dts: false,
|
|
5724
|
-
platform: "node",
|
|
5725
|
-
external: ["dineway", DINEWAY_SCOPE_RE]
|
|
5726
|
-
});
|
|
5727
|
-
const pluginNodeModules = join(pluginDir, "node_modules");
|
|
5728
|
-
const tmpNodeModules = join(mainOutDir, "node_modules");
|
|
5729
|
-
if (await fileExists$1(pluginNodeModules)) await symlink(pluginNodeModules, tmpNodeModules, "junction");
|
|
5730
|
-
const mainOutputPath = await findBuildOutput(mainOutDir, basename(mainEntry).replace(TS_EXT_RE, ""));
|
|
5731
|
-
if (!mainOutputPath) {
|
|
5732
|
-
consola.error("Failed to build main entry — no output found in", mainOutDir);
|
|
5733
|
-
process.exit(1);
|
|
5734
|
-
}
|
|
5735
|
-
const pluginModule = await import(toFileImportSpecifier(mainOutputPath));
|
|
5736
|
-
let resolvedPlugin;
|
|
5737
|
-
if (typeof pluginModule.createPlugin === "function") resolvedPlugin = pluginModule.createPlugin();
|
|
5738
|
-
else if (typeof pluginModule.default === "function") resolvedPlugin = pluginModule.default();
|
|
5739
|
-
else if (typeof pluginModule.default === "object" && pluginModule.default !== null) {
|
|
5740
|
-
const defaultExport = pluginModule.default;
|
|
5741
|
-
if ("id" in defaultExport && "version" in defaultExport) resolvedPlugin = defaultExport;
|
|
5742
|
-
}
|
|
5743
|
-
if (!resolvedPlugin) for (const [key, value] of Object.entries(pluginModule)) {
|
|
5744
|
-
if (key === "default" || typeof value !== "function") continue;
|
|
5745
|
-
try {
|
|
5746
|
-
const result = value();
|
|
5747
|
-
if (result && typeof result === "object" && "id" in result && "version" in result) {
|
|
5748
|
-
resolvedPlugin = {
|
|
5749
|
-
id: result.id,
|
|
5750
|
-
version: result.version,
|
|
5751
|
-
capabilities: result.capabilities ?? [],
|
|
5752
|
-
allowedHosts: result.allowedHosts ?? [],
|
|
5753
|
-
storage: result.storage ?? {},
|
|
5754
|
-
hooks: {},
|
|
5755
|
-
routes: {},
|
|
5756
|
-
admin: {
|
|
5757
|
-
pages: result.adminPages,
|
|
5758
|
-
widgets: result.adminWidgets
|
|
5759
|
-
}
|
|
5760
|
-
};
|
|
5761
|
-
if (backendEntry) {
|
|
5762
|
-
const backendProbeDir = join(tmpDir, "backend-probe");
|
|
5763
|
-
const probeShimDir = join(tmpDir, "probe-shims");
|
|
5764
|
-
await mkdir(probeShimDir, { recursive: true });
|
|
5765
|
-
await writeFile(join(probeShimDir, "dineway.mjs"), "export const definePlugin = (d) => d;\n");
|
|
5766
|
-
await build({
|
|
5767
|
-
config: false,
|
|
5768
|
-
entry: [backendEntry],
|
|
5769
|
-
format: "esm",
|
|
5770
|
-
outDir: backendProbeDir,
|
|
5771
|
-
dts: false,
|
|
5772
|
-
platform: "neutral",
|
|
5773
|
-
external: [],
|
|
5774
|
-
alias: { dineway: join(probeShimDir, "dineway.mjs") },
|
|
5775
|
-
treeshake: true
|
|
5776
|
-
});
|
|
5777
|
-
const backendProbePath = await findBuildOutput(backendProbeDir, basename(backendEntry).replace(TS_EXT_RE, ""));
|
|
5778
|
-
if (backendProbePath) {
|
|
5779
|
-
const standardDef = (await import(toFileImportSpecifier(backendProbePath))).default ?? {};
|
|
5780
|
-
const hooks = standardDef.hooks;
|
|
5781
|
-
const routes = standardDef.routes;
|
|
5782
|
-
if (hooks) for (const hookName of Object.keys(hooks)) {
|
|
5783
|
-
const hookEntry = hooks[hookName];
|
|
5784
|
-
const isConfig = typeof hookEntry === "object" && hookEntry !== null && "handler" in hookEntry;
|
|
5785
|
-
const config = isConfig ? hookEntry : {};
|
|
5786
|
-
resolvedPlugin.hooks[hookName] = {
|
|
5787
|
-
handler: isConfig ? hookEntry.handler : hookEntry,
|
|
5788
|
-
priority: config.priority ?? 100,
|
|
5789
|
-
timeout: config.timeout ?? 5e3,
|
|
5790
|
-
dependencies: config.dependencies ?? [],
|
|
5791
|
-
errorPolicy: config.errorPolicy ?? "abort",
|
|
5792
|
-
exclusive: config.exclusive ?? false,
|
|
5793
|
-
pluginId: result.id
|
|
5794
|
-
};
|
|
5795
|
-
}
|
|
5796
|
-
if (routes) for (const [name, route] of Object.entries(routes)) {
|
|
5797
|
-
const routeObj = route;
|
|
5798
|
-
resolvedPlugin.routes[name] = {
|
|
5799
|
-
handler: routeObj.handler,
|
|
5800
|
-
public: routeObj.public
|
|
5801
|
-
};
|
|
5802
|
-
}
|
|
5803
|
-
}
|
|
5804
|
-
}
|
|
5805
|
-
break;
|
|
5806
|
-
}
|
|
5807
|
-
} catch {}
|
|
5808
|
-
}
|
|
5809
|
-
if (!resolvedPlugin?.id || !resolvedPlugin?.version) {
|
|
5810
|
-
consola.error("Could not extract plugin definition. Expected one of:\n - createPlugin() export (native format)\n - Descriptor factory function returning { id, version, ... } (standard format)");
|
|
5811
|
-
process.exit(1);
|
|
5812
|
-
}
|
|
5813
|
-
const manifest = extractManifest(resolvedPlugin);
|
|
5814
|
-
if (resolvedPlugin.admin?.entry) {
|
|
5815
|
-
consola.error("Plugin declares adminEntry — React admin components require native/trusted mode. Use Block Kit for sandboxed admin pages, or remove adminEntry.");
|
|
5816
|
-
process.exit(1);
|
|
5817
|
-
}
|
|
5818
|
-
if (resolvedPlugin.admin?.portableTextBlocks && resolvedPlugin.admin.portableTextBlocks.length > 0) {
|
|
5819
|
-
consola.error("Plugin declares portableTextBlocks — these require native/trusted mode and cannot be bundled for the marketplace.");
|
|
5820
|
-
process.exit(1);
|
|
5821
|
-
}
|
|
5822
|
-
consola.success(`Plugin: ${manifest.id}@${manifest.version}`);
|
|
5823
|
-
consola.info(` Capabilities: ${manifest.capabilities.length > 0 ? manifest.capabilities.join(", ") : "(none)"}`);
|
|
5824
|
-
consola.info(` Hooks: ${manifest.hooks.length > 0 ? manifest.hooks.map((h) => typeof h === "string" ? h : h.name).join(", ") : "(none)"}`);
|
|
5825
|
-
consola.info(` Routes: ${manifest.routes.length > 0 ? manifest.routes.map((r) => typeof r === "string" ? r : r.name).join(", ") : "(none)"}`);
|
|
5826
|
-
const bundleDir = join(tmpDir, "bundle");
|
|
5827
|
-
await mkdir(bundleDir, { recursive: true });
|
|
5828
|
-
if (backendEntry) {
|
|
5829
|
-
consola.start("Bundling backend...");
|
|
5830
|
-
const shimDir = join(tmpDir, "shims");
|
|
5831
|
-
await mkdir(shimDir, { recursive: true });
|
|
5832
|
-
await writeFile(join(shimDir, "dineway.mjs"), "export const definePlugin = (d) => d;\n");
|
|
5833
|
-
await build({
|
|
5834
|
-
config: false,
|
|
5835
|
-
entry: [backendEntry],
|
|
5836
|
-
format: "esm",
|
|
5837
|
-
outDir: join(tmpDir, "backend"),
|
|
5838
|
-
dts: false,
|
|
5839
|
-
platform: "neutral",
|
|
5840
|
-
external: [],
|
|
5841
|
-
alias: { dineway: join(shimDir, "dineway.mjs") },
|
|
5842
|
-
minify: true,
|
|
5843
|
-
treeshake: true
|
|
5844
|
-
});
|
|
5845
|
-
const backendBaseName = basename(backendEntry).replace(TS_EXT_RE, "");
|
|
5846
|
-
const backendOutputPath = await findBuildOutput(join(tmpDir, "backend"), backendBaseName);
|
|
5847
|
-
if (backendOutputPath) {
|
|
5848
|
-
await copyFile(backendOutputPath, join(bundleDir, "backend.js"));
|
|
5849
|
-
consola.success("Built backend.js");
|
|
5850
|
-
} else {
|
|
5851
|
-
consola.error("Backend build produced no output");
|
|
5852
|
-
process.exit(1);
|
|
5853
|
-
}
|
|
5854
|
-
} else {
|
|
5855
|
-
consola.warn("No sandbox entry found — bundle will have no backend.js");
|
|
5856
|
-
consola.warn(" Add a \"sandbox-entry.ts\" in src/ or a \"./sandbox\" export in package.json");
|
|
5857
|
-
}
|
|
5858
|
-
if (adminEntry) {
|
|
5859
|
-
consola.start("Bundling admin...");
|
|
5860
|
-
await build({
|
|
5861
|
-
config: false,
|
|
5862
|
-
entry: [adminEntry],
|
|
5863
|
-
format: "esm",
|
|
5864
|
-
outDir: join(tmpDir, "admin"),
|
|
5865
|
-
dts: false,
|
|
5866
|
-
platform: "neutral",
|
|
5867
|
-
external: [],
|
|
5868
|
-
minify: true,
|
|
5869
|
-
treeshake: true
|
|
5870
|
-
});
|
|
5871
|
-
const adminBaseName = basename(adminEntry).replace(TS_EXT_RE, "");
|
|
5872
|
-
const adminOutputPath = await findBuildOutput(join(tmpDir, "admin"), adminBaseName);
|
|
5873
|
-
if (adminOutputPath) {
|
|
5874
|
-
await copyFile(adminOutputPath, join(bundleDir, "admin.js"));
|
|
5875
|
-
consola.success("Built admin.js");
|
|
5876
|
-
}
|
|
5877
|
-
}
|
|
5878
|
-
await writeFile(join(bundleDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
5879
|
-
consola.start("Collecting assets...");
|
|
5880
|
-
const readmePath = join(pluginDir, "README.md");
|
|
5881
|
-
if (await fileExists$1(readmePath)) {
|
|
5882
|
-
await copyFile(readmePath, join(bundleDir, "README.md"));
|
|
5883
|
-
consola.success("Included README.md");
|
|
5884
|
-
}
|
|
5885
|
-
const iconPath = join(pluginDir, "icon.png");
|
|
5886
|
-
if (await fileExists$1(iconPath)) {
|
|
5887
|
-
const dims = readImageDimensions(await readFile(iconPath));
|
|
5888
|
-
if (!dims) consola.warn("icon.png is not a valid PNG — skipping");
|
|
5889
|
-
else if (dims[0] !== ICON_SIZE || dims[1] !== ICON_SIZE) {
|
|
5890
|
-
consola.warn(`icon.png is ${dims[0]}x${dims[1]}, expected ${ICON_SIZE}x${ICON_SIZE} — including anyway`);
|
|
5891
|
-
await copyFile(iconPath, join(bundleDir, "icon.png"));
|
|
5892
|
-
} else {
|
|
5893
|
-
await copyFile(iconPath, join(bundleDir, "icon.png"));
|
|
5894
|
-
consola.success("Included icon.png");
|
|
5895
|
-
}
|
|
5896
|
-
}
|
|
5897
|
-
const screenshotsDir = join(pluginDir, "screenshots");
|
|
5898
|
-
if (await fileExists$1(screenshotsDir)) {
|
|
5899
|
-
const screenshotFiles = (await readdir(screenshotsDir)).filter((f) => {
|
|
5900
|
-
const ext = extname(f).toLowerCase();
|
|
5901
|
-
return ext === ".png" || ext === ".jpg" || ext === ".jpeg";
|
|
5902
|
-
}).toSorted().slice(0, MAX_SCREENSHOTS);
|
|
5903
|
-
if (screenshotFiles.length > 0) {
|
|
5904
|
-
await mkdir(join(bundleDir, "screenshots"), { recursive: true });
|
|
5905
|
-
for (const file of screenshotFiles) {
|
|
5906
|
-
const filePath = join(screenshotsDir, file);
|
|
5907
|
-
const dims = readImageDimensions(await readFile(filePath));
|
|
5908
|
-
if (!dims) {
|
|
5909
|
-
consola.warn(`screenshots/${file} — cannot read dimensions, skipping`);
|
|
5910
|
-
continue;
|
|
5911
|
-
}
|
|
5912
|
-
if (dims[0] > MAX_SCREENSHOT_WIDTH || dims[1] > MAX_SCREENSHOT_HEIGHT) consola.warn(`screenshots/${file} is ${dims[0]}x${dims[1]}, max ${MAX_SCREENSHOT_WIDTH}x${MAX_SCREENSHOT_HEIGHT} — including anyway`);
|
|
5913
|
-
await copyFile(filePath, join(bundleDir, "screenshots", file));
|
|
5914
|
-
}
|
|
5915
|
-
consola.success(`Included ${screenshotFiles.length} screenshot(s)`);
|
|
5916
|
-
}
|
|
5917
|
-
}
|
|
5918
|
-
consola.start("Validating bundle...");
|
|
5919
|
-
let hasErrors = false;
|
|
5920
|
-
if (pkg.exports) for (const issue of findSourceExports(pkg.exports)) {
|
|
5921
|
-
consola.error(`Export "${issue.exportPath}" points to source (${issue.resolvedPath}). Package exports must point to built files (e.g. dist/*.mjs). Add a build step and update the exports map.`);
|
|
5922
|
-
hasErrors = true;
|
|
5923
|
-
}
|
|
5924
|
-
const backendPath = join(bundleDir, "backend.js");
|
|
5925
|
-
if (await fileExists$1(backendPath)) {
|
|
5926
|
-
const builtins = findNodeBuiltinImports(await readFile(backendPath, "utf-8"));
|
|
5927
|
-
if (builtins.length > 0) {
|
|
5928
|
-
consola.error(`backend.js imports Node.js built-in modules: ${builtins.join(", ")}`);
|
|
5929
|
-
consola.error("Sandboxed plugins cannot use Node.js APIs");
|
|
5930
|
-
hasErrors = true;
|
|
5931
|
-
}
|
|
5932
|
-
}
|
|
5933
|
-
if (manifest.capabilities.includes("network:request:unrestricted")) consola.warn("Plugin declares unrestricted network access (network:request:unrestricted) — it can make requests to any host");
|
|
5934
|
-
else if (manifest.capabilities.includes("network:request") && manifest.allowedHosts.length === 0) consola.warn("Plugin declares network:request capability but no allowedHosts — all fetch requests will be blocked");
|
|
5935
|
-
if (resolvedPlugin.admin?.portableTextBlocks && resolvedPlugin.admin.portableTextBlocks.length > 0) consola.warn("Plugin declares portableTextBlocks — these require trusted mode and will be ignored in sandboxed plugins");
|
|
5936
|
-
if (resolvedPlugin.admin?.entry) consola.warn("Plugin declares admin.entry — custom React components require trusted mode. Use Block Kit for sandboxed admin pages");
|
|
5937
|
-
if (resolvedPlugin.hooks["page:fragments"]) consola.warn("Plugin declares page:fragments hook — this is trusted-only and will not work in sandboxed mode");
|
|
5938
|
-
const hasAdminPages = (manifest.admin?.pages?.length ?? 0) > 0;
|
|
5939
|
-
const hasAdminWidgets = (manifest.admin?.widgets?.length ?? 0) > 0;
|
|
5940
|
-
if (hasAdminPages || hasAdminWidgets) {
|
|
5941
|
-
if (!manifest.routes.map((r) => typeof r === "string" ? r : r.name).includes("admin")) {
|
|
5942
|
-
consola.error(`Plugin declares ${hasAdminPages ? "adminPages" : ""}${hasAdminPages && hasAdminWidgets ? " and " : ""}${hasAdminWidgets ? "adminWidgets" : ""} but the sandbox entry has no "admin" route. Add an admin route handler to serve Block Kit pages.`);
|
|
5943
|
-
hasErrors = true;
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
5946
|
-
const bundleEntries = await collectBundleEntries(bundleDir);
|
|
5947
|
-
const sizeViolations = validateBundleSize(bundleEntries);
|
|
5948
|
-
if (sizeViolations.length > 0) {
|
|
5949
|
-
for (const violation of sizeViolations) consola.error(violation);
|
|
5950
|
-
hasErrors = true;
|
|
5951
|
-
} else consola.info(`Bundle size: ${formatBytes(totalBundleBytes(bundleEntries))} across ${bundleEntries.length} file${bundleEntries.length === 1 ? "" : "s"}`);
|
|
5952
|
-
if (hasErrors) {
|
|
5953
|
-
consola.error("Bundle validation failed");
|
|
5954
|
-
process.exit(1);
|
|
5955
|
-
}
|
|
5956
|
-
consola.success("Validation passed");
|
|
5957
|
-
if (validateOnly) return;
|
|
5958
|
-
await mkdir(outDir, { recursive: true });
|
|
5959
|
-
const tarballName = `${manifest.id.replace(SLASH_RE, "-").replace(LEADING_AT_RE, "")}-${manifest.version}.tar.gz`;
|
|
5960
|
-
const tarballPath = join(outDir, tarballName);
|
|
5961
|
-
consola.start("Creating tarball...");
|
|
5962
|
-
await createTarball(bundleDir, tarballPath);
|
|
5963
|
-
const tarballSizeKB = ((await stat(tarballPath)).size / 1024).toFixed(1);
|
|
5964
|
-
const tarballBuf = await readFile(tarballPath);
|
|
5965
|
-
const checksum = createHash("sha256").update(tarballBuf).digest("hex");
|
|
5966
|
-
consola.success(`Created ${tarballName} (${tarballSizeKB}KB)`);
|
|
5967
|
-
consola.info(` SHA-256: ${checksum}`);
|
|
5968
|
-
consola.info(` Path: ${tarballPath}`);
|
|
5969
|
-
} finally {
|
|
5970
|
-
if (tmpDir.endsWith(".dineway-bundle-tmp")) await rm(tmpDir, {
|
|
5971
|
-
recursive: true,
|
|
5972
|
-
force: true
|
|
5973
|
-
});
|
|
5974
|
-
}
|
|
5975
|
-
}
|
|
5976
|
-
});
|
|
5977
|
-
|
|
5978
|
-
//#endregion
|
|
5979
|
-
//#region src/cli/commands/plugin-init.ts
|
|
5980
|
-
/**
|
|
5981
|
-
* dineway plugin init
|
|
5982
|
-
*
|
|
5983
|
-
* Scaffold a new Dineway plugin. Generates the sandboxed-format boilerplate:
|
|
5984
|
-
* src/index.ts -- descriptor factory
|
|
5985
|
-
* src/sandbox-entry.ts -- definePlugin({ hooks, routes })
|
|
5986
|
-
* package.json
|
|
5987
|
-
* tsconfig.json
|
|
5988
|
-
*
|
|
5989
|
-
* Use --format=native (or --native) to generate native-format boilerplate
|
|
5990
|
-
* instead (createPlugin + React admin). When neither is passed and stdout
|
|
5991
|
-
* is a TTY, the user is prompted to choose.
|
|
4914
|
+
* Scaffold a new Dineway plugin. Generates the sandboxed-format boilerplate:
|
|
4915
|
+
* src/index.ts -- descriptor factory
|
|
4916
|
+
* src/sandbox-entry.ts -- definePlugin({ hooks, routes })
|
|
4917
|
+
* package.json
|
|
4918
|
+
* tsconfig.json
|
|
4919
|
+
*
|
|
4920
|
+
* Use --format=native (or --native) to generate native-format boilerplate
|
|
4921
|
+
* instead (createPlugin + React admin). When neither is passed and stdout
|
|
4922
|
+
* is a TTY, the user is prompted to choose.
|
|
5992
4923
|
*/
|
|
5993
4924
|
const SLUG_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
5994
4925
|
const SCOPE_RE = /^@[^/]+\//;
|
|
@@ -6050,7 +4981,7 @@ const pluginInitCommand = defineCommand({
|
|
|
6050
4981
|
process.exit(1);
|
|
6051
4982
|
}
|
|
6052
4983
|
const srcDir = join(targetDir, "src");
|
|
6053
|
-
if (await fileExists$
|
|
4984
|
+
if (await fileExists$11(join(targetDir, "package.json"))) {
|
|
6054
4985
|
consola.error(`package.json already exists in ${targetDir}`);
|
|
6055
4986
|
process.exit(1);
|
|
6056
4987
|
}
|
|
@@ -6416,6 +5347,7 @@ async function pollGitHubDeviceFlow(tokenEndpoint, clientId, deviceCode, interva
|
|
|
6416
5347
|
const manifestSummarySchema = pluginManifestSchema.pick({
|
|
6417
5348
|
id: true,
|
|
6418
5349
|
version: true,
|
|
5350
|
+
declaredAccess: true,
|
|
6419
5351
|
capabilities: true,
|
|
6420
5352
|
allowedHosts: true
|
|
6421
5353
|
});
|
|
@@ -6433,7 +5365,20 @@ async function readManifestFromTarball(tarballPath) {
|
|
|
6433
5365
|
const parsed = JSON.parse(content);
|
|
6434
5366
|
const result = manifestSummarySchema.safeParse(parsed);
|
|
6435
5367
|
if (!result.success) throw new Error(`Invalid manifest.json: ${result.error.message}`);
|
|
6436
|
-
|
|
5368
|
+
const reconciled = reconcileManifestAccess({
|
|
5369
|
+
...result.data,
|
|
5370
|
+
storage: {},
|
|
5371
|
+
hooks: [],
|
|
5372
|
+
routes: [],
|
|
5373
|
+
admin: {}
|
|
5374
|
+
});
|
|
5375
|
+
return {
|
|
5376
|
+
id: reconciled.id,
|
|
5377
|
+
version: reconciled.version,
|
|
5378
|
+
declaredAccess: reconciled.declaredAccess,
|
|
5379
|
+
capabilities: reconciled.capabilities,
|
|
5380
|
+
allowedHosts: reconciled.allowedHosts
|
|
5381
|
+
};
|
|
6437
5382
|
}
|
|
6438
5383
|
const POLL_INTERVAL_MS = 3e3;
|
|
6439
5384
|
const POLL_TIMEOUT_MS = 12e4;
|
|
@@ -6518,7 +5463,7 @@ const publishCommand = defineCommand({
|
|
|
6518
5463
|
const pluginDir = resolve(args.dir);
|
|
6519
5464
|
try {
|
|
6520
5465
|
const { runCommand } = await import("citty");
|
|
6521
|
-
const { bundleCommand } = await
|
|
5466
|
+
const { bundleCommand } = await import("../bundle-CEcdUa1s.mjs").then((n) => n.n);
|
|
6522
5467
|
await runCommand(bundleCommand, { rawArgs: ["--dir", pluginDir] });
|
|
6523
5468
|
} catch {
|
|
6524
5469
|
consola.error("Build failed");
|
|
@@ -7182,7 +6127,7 @@ const seedCommand = defineCommand({
|
|
|
7182
6127
|
},
|
|
7183
6128
|
"no-content": {
|
|
7184
6129
|
type: "boolean",
|
|
7185
|
-
description: "Skip sample content",
|
|
6130
|
+
description: "Skip sample data (content entries, bylines, taxonomy terms)",
|
|
7186
6131
|
default: false
|
|
7187
6132
|
},
|
|
7188
6133
|
"on-conflict": {
|
|
@@ -7288,141 +6233,714 @@ const seedCommand = defineCommand({
|
|
|
7288
6233
|
});
|
|
7289
6234
|
|
|
7290
6235
|
//#endregion
|
|
7291
|
-
//#region src/
|
|
7292
|
-
const
|
|
7293
|
-
const
|
|
7294
|
-
const
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
|
|
6236
|
+
//#region src/seo/local-crawl.ts
|
|
6237
|
+
const CONTRACT_VERSION = 1;
|
|
6238
|
+
const USER_AGENT = "DinewayContentCrawler/1.0";
|
|
6239
|
+
const DEFAULT_PAGE_LIMIT = 100;
|
|
6240
|
+
const MAX_PAGE_LIMIT = 500;
|
|
6241
|
+
const DEFAULT_MAX_DEPTH = 3;
|
|
6242
|
+
const MAX_DEPTH = 10;
|
|
6243
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
6244
|
+
const MAX_CONCURRENCY = 10;
|
|
6245
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
6246
|
+
const MAX_TIMEOUT_MS = 6e4;
|
|
6247
|
+
const DEFAULT_MAX_BYTES = 1e6;
|
|
6248
|
+
const MAX_RESOURCE_BYTES = 1e7;
|
|
6249
|
+
const MAX_SITEMAPS = 20;
|
|
6250
|
+
const MAX_REDIRECTS = 5;
|
|
6251
|
+
const MAX_TEXT_EXCERPT = 2e4;
|
|
6252
|
+
const LINE_SPLIT_RE = /\r?\n/;
|
|
6253
|
+
const COMMENT_RE = /#.*$/;
|
|
6254
|
+
const WHITESPACE_RE = /\s+/g;
|
|
6255
|
+
const WORD_RE = /[\p{L}\p{N}]+(?:['’-][\p{L}\p{N}]+)*/gu;
|
|
6256
|
+
const REGEXP_SPECIAL_RE = /[.*+?^${}()|[\]\\]/g;
|
|
6257
|
+
async function crawlLocalSite(options) {
|
|
6258
|
+
const normalized = normalizeOptions(options);
|
|
6259
|
+
if (!normalized.allowPrivateNetwork) await resolveAndValidateExternalUrl(normalized.startUrl.href, { resolver: normalized.dnsResolver });
|
|
6260
|
+
const origin = normalized.startUrl.origin;
|
|
6261
|
+
const skipped = /* @__PURE__ */ new Map();
|
|
6262
|
+
const robots = await loadRobots(normalized);
|
|
6263
|
+
const sitemapResult = normalized.sitemap === "skip" ? {
|
|
6264
|
+
sources: [],
|
|
6265
|
+
urls: []
|
|
6266
|
+
} : await loadSitemaps(normalized, robots.sitemaps);
|
|
6267
|
+
const queue = [];
|
|
6268
|
+
const queued = /* @__PURE__ */ new Set();
|
|
6269
|
+
const enqueue = (candidate, depth, discoveredFrom) => {
|
|
6270
|
+
const url = normalizeUrl(candidate, normalized.startUrl, normalized.keepQuery);
|
|
6271
|
+
if (!url || url.origin !== origin || queued.has(url.href)) return;
|
|
6272
|
+
const pathDecision = pathEligibility(url, normalized);
|
|
6273
|
+
if (pathDecision) {
|
|
6274
|
+
skipped.set(url.href, {
|
|
6275
|
+
url: url.href,
|
|
6276
|
+
reason: pathDecision
|
|
6277
|
+
});
|
|
6278
|
+
return;
|
|
6279
|
+
}
|
|
6280
|
+
if (!robotsAllows(url, robots.rules)) {
|
|
6281
|
+
skipped.set(url.href, {
|
|
6282
|
+
url: url.href,
|
|
6283
|
+
reason: "robots_disallowed"
|
|
6284
|
+
});
|
|
6285
|
+
return;
|
|
6286
|
+
}
|
|
6287
|
+
queued.add(url.href);
|
|
6288
|
+
queue.push({
|
|
6289
|
+
url: url.href,
|
|
6290
|
+
depth,
|
|
6291
|
+
discoveredFrom
|
|
6292
|
+
});
|
|
6293
|
+
};
|
|
6294
|
+
if (normalized.sitemap !== "only") enqueue(normalized.startUrl.href, 0, "start");
|
|
6295
|
+
for (const url of sitemapResult.urls) enqueue(url, 0, "sitemap");
|
|
6296
|
+
const pages = [];
|
|
6297
|
+
let cursor = 0;
|
|
6298
|
+
while (cursor < queue.length && pages.length < normalized.pageLimit) {
|
|
6299
|
+
const remaining = normalized.pageLimit - pages.length;
|
|
6300
|
+
const batch = queue.slice(cursor, cursor + Math.min(normalized.concurrency, remaining));
|
|
6301
|
+
cursor += batch.length;
|
|
6302
|
+
const resources = await Promise.all(batch.map(async (item) => ({
|
|
6303
|
+
item,
|
|
6304
|
+
resource: await fetchResource(item.url, normalized)
|
|
6305
|
+
})));
|
|
6306
|
+
for (const { item, resource } of resources) {
|
|
6307
|
+
const page = pageFromResource(item, resource, normalized);
|
|
6308
|
+
pages.push(page);
|
|
6309
|
+
if (normalized.sitemap !== "only" && !page.error && page.contentType?.includes("text/html") && item.depth < normalized.maxDepth) for (const link of page.internalLinks) enqueue(link.url, item.depth + 1, item.url);
|
|
6310
|
+
}
|
|
6311
|
+
}
|
|
6312
|
+
const edges = pages.flatMap((page) => page.internalLinks.map((link) => ({
|
|
6313
|
+
from: page.url,
|
|
6314
|
+
...link
|
|
6315
|
+
})));
|
|
6316
|
+
const brokenInternalUrls = uniqueSorted(pages.filter((page) => page.httpStatus !== null && page.httpStatus >= 400).filter((page) => edges.some((edge) => edge.url === page.url)).map((page) => page.url));
|
|
6317
|
+
const incoming = new Set(edges.map((edge) => edge.url));
|
|
6318
|
+
const orphanUrls = uniqueSorted(pages.filter((page) => page.url !== normalized.startUrl.href && page.httpStatus !== null && page.httpStatus < 400 && page.contentType?.includes("text/html") && !incoming.has(page.url)).map((page) => page.url));
|
|
6319
|
+
const successfulHtml = pages.filter((page) => page.httpStatus !== null && page.httpStatus < 400 && page.contentType?.includes("text/html"));
|
|
6320
|
+
return {
|
|
6321
|
+
contractVersion: CONTRACT_VERSION,
|
|
6322
|
+
startUrl: normalized.startUrl.href,
|
|
6323
|
+
origin,
|
|
6324
|
+
crawledAt: normalized.now().toISOString(),
|
|
6325
|
+
options: {
|
|
6326
|
+
pageLimit: normalized.pageLimit,
|
|
6327
|
+
maxDepth: normalized.maxDepth,
|
|
6328
|
+
concurrency: normalized.concurrency,
|
|
6329
|
+
include: normalized.includeValues,
|
|
6330
|
+
exclude: normalized.excludeValues,
|
|
6331
|
+
timeoutMs: normalized.timeoutMs,
|
|
6332
|
+
maxBytesPerResource: normalized.maxBytesPerResource,
|
|
6333
|
+
sitemap: normalized.sitemap,
|
|
6334
|
+
keepQuery: normalized.keepQuery,
|
|
6335
|
+
allowPrivateNetwork: normalized.allowPrivateNetwork
|
|
6336
|
+
},
|
|
6337
|
+
sources: {
|
|
6338
|
+
robots: robots.source,
|
|
6339
|
+
sitemaps: sitemapResult.sources
|
|
6340
|
+
},
|
|
6341
|
+
pages,
|
|
6342
|
+
skipped: [...skipped.values()].toSorted((left, right) => left.url.localeCompare(right.url)),
|
|
6343
|
+
linkGraph: {
|
|
6344
|
+
edges,
|
|
6345
|
+
orphanUrls,
|
|
6346
|
+
brokenInternalUrls,
|
|
6347
|
+
duplicateTitles: duplicateGroups(successfulHtml, "title"),
|
|
6348
|
+
duplicateDescriptions: duplicateGroups(successfulHtml, "description"),
|
|
6349
|
+
duplicateContent: duplicateGroups(successfulHtml, "contentSha256")
|
|
6350
|
+
},
|
|
6351
|
+
summary: {
|
|
6352
|
+
fetchedPages: pages.length,
|
|
6353
|
+
htmlPages: pages.filter((page) => page.contentType?.includes("text/html")).length,
|
|
6354
|
+
brokenPages: pages.filter((page) => page.httpStatus !== null && page.httpStatus >= 400).length,
|
|
6355
|
+
orphanPages: orphanUrls.length,
|
|
6356
|
+
skippedUrls: skipped.size,
|
|
6357
|
+
errorPages: pages.filter((page) => page.error).length,
|
|
6358
|
+
pageLimitReached: pages.length === normalized.pageLimit && cursor < queue.length
|
|
6359
|
+
}
|
|
6360
|
+
};
|
|
6361
|
+
}
|
|
6362
|
+
function normalizeOptions(options) {
|
|
6363
|
+
const startUrl = normalizeUrl(options.startUrl, void 0, options.keepQuery === true);
|
|
6364
|
+
if (!startUrl) throw new Error("startUrl must be an absolute HTTP or HTTPS URL without credentials");
|
|
6365
|
+
const pageLimit = boundedInteger(options.pageLimit, DEFAULT_PAGE_LIMIT, 1, MAX_PAGE_LIMIT, "pageLimit");
|
|
6366
|
+
const maxDepth = boundedInteger(options.maxDepth, DEFAULT_MAX_DEPTH, 0, MAX_DEPTH, "maxDepth");
|
|
6367
|
+
const concurrency = boundedInteger(options.concurrency, DEFAULT_CONCURRENCY, 1, MAX_CONCURRENCY, "concurrency");
|
|
6368
|
+
const timeoutMs = boundedInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, 100, MAX_TIMEOUT_MS, "timeoutMs");
|
|
6369
|
+
const maxBytesPerResource = boundedInteger(options.maxBytesPerResource, DEFAULT_MAX_BYTES, 1024, MAX_RESOURCE_BYTES, "maxBytesPerResource");
|
|
6370
|
+
const sitemap = options.sitemap ?? "include";
|
|
6371
|
+
if (![
|
|
6372
|
+
"include",
|
|
6373
|
+
"skip",
|
|
6374
|
+
"only"
|
|
6375
|
+
].includes(sitemap)) throw new Error("sitemap must be include, skip, or only");
|
|
6376
|
+
const includeValues = normalizePatterns(options.include ?? []);
|
|
6377
|
+
const excludeValues = normalizePatterns(options.exclude ?? []);
|
|
6378
|
+
return {
|
|
6379
|
+
startUrl,
|
|
6380
|
+
pageLimit,
|
|
6381
|
+
maxDepth,
|
|
6382
|
+
concurrency,
|
|
6383
|
+
include: includeValues.map(pathMatcher),
|
|
6384
|
+
exclude: excludeValues.map(pathMatcher),
|
|
6385
|
+
includeValues,
|
|
6386
|
+
excludeValues,
|
|
6387
|
+
timeoutMs,
|
|
6388
|
+
maxBytesPerResource,
|
|
6389
|
+
sitemap,
|
|
6390
|
+
keepQuery: options.keepQuery === true,
|
|
6391
|
+
allowPrivateNetwork: options.allowPrivateNetwork === true,
|
|
6392
|
+
dnsResolver: options.dnsResolver,
|
|
6393
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
6394
|
+
now: options.now ?? (() => /* @__PURE__ */ new Date())
|
|
6395
|
+
};
|
|
6396
|
+
}
|
|
6397
|
+
function boundedInteger(value, fallback, minimum, maximum, name) {
|
|
6398
|
+
const normalized = value ?? fallback;
|
|
6399
|
+
if (!Number.isInteger(normalized) || normalized < minimum || normalized > maximum) throw new Error(`${name} must be an integer from ${minimum} to ${maximum}`);
|
|
6400
|
+
return normalized;
|
|
6401
|
+
}
|
|
6402
|
+
function normalizePatterns(values) {
|
|
6403
|
+
return uniqueSorted(values.map((value) => value.trim()).filter((value) => {
|
|
6404
|
+
if (!value.startsWith("/") || value.length > 255) throw new Error("include and exclude patterns must be path globs starting with /");
|
|
6405
|
+
return true;
|
|
6406
|
+
}));
|
|
6407
|
+
}
|
|
6408
|
+
function pathMatcher(value) {
|
|
6409
|
+
let source = "^";
|
|
6410
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
6411
|
+
const character = value[index];
|
|
6412
|
+
if (character === "*" && value[index + 1] === "*") {
|
|
6413
|
+
source += ".*";
|
|
6414
|
+
index += 1;
|
|
6415
|
+
} else if (character === "*") source += "[^/]*";
|
|
6416
|
+
else source += escapeRegExp(character ?? "");
|
|
7301
6417
|
}
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
6418
|
+
return {
|
|
6419
|
+
value,
|
|
6420
|
+
pattern: new RegExp(`${source}$`)
|
|
6421
|
+
};
|
|
7305
6422
|
}
|
|
7306
|
-
function
|
|
7307
|
-
|
|
6423
|
+
function pathEligibility(url, options) {
|
|
6424
|
+
const path = url.pathname;
|
|
6425
|
+
if (options.include.length > 0 && !options.include.some((item) => item.pattern.test(path))) return "not_included";
|
|
6426
|
+
if (options.exclude.some((item) => item.pattern.test(path))) return "excluded";
|
|
6427
|
+
return null;
|
|
7308
6428
|
}
|
|
7309
|
-
function
|
|
7310
|
-
|
|
6429
|
+
async function loadRobots(options) {
|
|
6430
|
+
const url = new URL("/robots.txt", options.startUrl.origin).href;
|
|
6431
|
+
const resource = await fetchResource(url, options);
|
|
6432
|
+
if (!resource.response) return {
|
|
6433
|
+
source: {
|
|
6434
|
+
url,
|
|
6435
|
+
status: "temporarily_unavailable",
|
|
6436
|
+
httpStatus: null,
|
|
6437
|
+
error: resource.error ?? "fetch_failed"
|
|
6438
|
+
},
|
|
6439
|
+
rules: [],
|
|
6440
|
+
sitemaps: []
|
|
6441
|
+
};
|
|
6442
|
+
if (resource.response.status === 404) return {
|
|
6443
|
+
source: {
|
|
6444
|
+
url,
|
|
6445
|
+
status: "not_found",
|
|
6446
|
+
httpStatus: 404
|
|
6447
|
+
},
|
|
6448
|
+
rules: [],
|
|
6449
|
+
sitemaps: []
|
|
6450
|
+
};
|
|
6451
|
+
if (!resource.response.ok || resource.error) return {
|
|
6452
|
+
source: {
|
|
6453
|
+
url,
|
|
6454
|
+
status: "temporarily_unavailable",
|
|
6455
|
+
httpStatus: resource.response.status,
|
|
6456
|
+
error: resource.error ?? `http_${resource.response.status}`
|
|
6457
|
+
},
|
|
6458
|
+
rules: [],
|
|
6459
|
+
sitemaps: []
|
|
6460
|
+
};
|
|
6461
|
+
const parsed = parseRobots(resource.body);
|
|
6462
|
+
return {
|
|
6463
|
+
source: {
|
|
6464
|
+
url,
|
|
6465
|
+
status: "available",
|
|
6466
|
+
httpStatus: resource.response.status
|
|
6467
|
+
},
|
|
6468
|
+
rules: parsed.rules,
|
|
6469
|
+
sitemaps: parsed.sitemaps
|
|
6470
|
+
};
|
|
7311
6471
|
}
|
|
7312
|
-
function
|
|
7313
|
-
|
|
6472
|
+
function parseRobots(body) {
|
|
6473
|
+
const groups = [];
|
|
6474
|
+
const sitemaps = [];
|
|
6475
|
+
let current = {
|
|
6476
|
+
agents: [],
|
|
6477
|
+
rules: []
|
|
6478
|
+
};
|
|
6479
|
+
for (const rawLine of body.split(LINE_SPLIT_RE)) {
|
|
6480
|
+
const line = rawLine.replace(COMMENT_RE, "").trim();
|
|
6481
|
+
if (!line) continue;
|
|
6482
|
+
const separator = line.indexOf(":");
|
|
6483
|
+
if (separator < 0) continue;
|
|
6484
|
+
const field = line.slice(0, separator).trim().toLowerCase();
|
|
6485
|
+
const value = line.slice(separator + 1).trim();
|
|
6486
|
+
if (field === "sitemap" && value) {
|
|
6487
|
+
sitemaps.push(value);
|
|
6488
|
+
continue;
|
|
6489
|
+
}
|
|
6490
|
+
if (field === "user-agent") {
|
|
6491
|
+
if (current.rules.length > 0) {
|
|
6492
|
+
groups.push(current);
|
|
6493
|
+
current = {
|
|
6494
|
+
agents: [],
|
|
6495
|
+
rules: []
|
|
6496
|
+
};
|
|
6497
|
+
}
|
|
6498
|
+
current.agents.push(value.toLowerCase());
|
|
6499
|
+
continue;
|
|
6500
|
+
}
|
|
6501
|
+
if ((field === "allow" || field === "disallow") && current.agents.length > 0 && value) current.rules.push({
|
|
6502
|
+
type: field,
|
|
6503
|
+
path: value
|
|
6504
|
+
});
|
|
6505
|
+
}
|
|
6506
|
+
if (current.agents.length > 0 || current.rules.length > 0) groups.push(current);
|
|
6507
|
+
const crawlerName = USER_AGENT.split("/")[0]?.toLowerCase() ?? "dinewaycontentcrawler";
|
|
6508
|
+
const exact = groups.filter((group) => group.agents.includes(crawlerName));
|
|
6509
|
+
return {
|
|
6510
|
+
rules: (exact.length > 0 ? exact : groups.filter((group) => group.agents.includes("*"))).flatMap((group) => group.rules),
|
|
6511
|
+
sitemaps: uniqueSorted(sitemaps)
|
|
6512
|
+
};
|
|
7314
6513
|
}
|
|
7315
|
-
function
|
|
7316
|
-
|
|
7317
|
-
|
|
6514
|
+
function robotsAllows(url, rules) {
|
|
6515
|
+
const path = `${url.pathname}${url.search}`;
|
|
6516
|
+
const matching = rules.filter((rule) => robotsRuleMatches(path, rule.path)).toSorted((left, right) => right.path.length - left.path.length);
|
|
6517
|
+
if (matching.length === 0) return true;
|
|
6518
|
+
const longest = matching[0]?.path.length ?? 0;
|
|
6519
|
+
return matching.some((rule) => rule.path.length === longest && rule.type === "allow");
|
|
7318
6520
|
}
|
|
7319
|
-
function
|
|
7320
|
-
|
|
6521
|
+
function robotsRuleMatches(path, rulePath) {
|
|
6522
|
+
const anchored = rulePath.endsWith("$");
|
|
6523
|
+
const pattern = escapeRegExp(anchored ? rulePath.slice(0, -1) : rulePath).replaceAll("\\*", ".*");
|
|
6524
|
+
return new RegExp(`^${pattern}${anchored ? "$" : ""}`).test(path);
|
|
6525
|
+
}
|
|
6526
|
+
async function loadSitemaps(options, robotsSitemaps) {
|
|
6527
|
+
const queue = (robotsSitemaps.length > 0 ? robotsSitemaps : [new URL("/sitemap.xml", options.startUrl.origin).href]).map((value) => normalizeUrl(value, options.startUrl, options.keepQuery)).filter((value) => value !== null && value.origin === options.startUrl.origin).map((value) => value.href);
|
|
6528
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6529
|
+
const sources = [];
|
|
6530
|
+
const urls = [];
|
|
6531
|
+
while (queue.length > 0 && seen.size < MAX_SITEMAPS) {
|
|
6532
|
+
const url = queue.shift();
|
|
6533
|
+
if (!url || seen.has(url)) continue;
|
|
6534
|
+
seen.add(url);
|
|
6535
|
+
const resource = await fetchResource(url, options);
|
|
6536
|
+
if (!resource.response) {
|
|
6537
|
+
sources.push({
|
|
6538
|
+
url,
|
|
6539
|
+
status: "temporarily_unavailable",
|
|
6540
|
+
httpStatus: null,
|
|
6541
|
+
error: resource.error ?? "fetch_failed"
|
|
6542
|
+
});
|
|
6543
|
+
continue;
|
|
6544
|
+
}
|
|
6545
|
+
if (resource.response.status === 404) {
|
|
6546
|
+
sources.push({
|
|
6547
|
+
url,
|
|
6548
|
+
status: "not_found",
|
|
6549
|
+
httpStatus: 404
|
|
6550
|
+
});
|
|
6551
|
+
continue;
|
|
6552
|
+
}
|
|
6553
|
+
if (!resource.response.ok || resource.error) {
|
|
6554
|
+
sources.push({
|
|
6555
|
+
url,
|
|
6556
|
+
status: "temporarily_unavailable",
|
|
6557
|
+
httpStatus: resource.response.status,
|
|
6558
|
+
error: resource.error ?? `http_${resource.response.status}`
|
|
6559
|
+
});
|
|
6560
|
+
continue;
|
|
6561
|
+
}
|
|
6562
|
+
try {
|
|
6563
|
+
const parsed = parseSitemap(resource.body);
|
|
6564
|
+
sources.push({
|
|
6565
|
+
url,
|
|
6566
|
+
status: "available",
|
|
6567
|
+
httpStatus: resource.response.status
|
|
6568
|
+
});
|
|
6569
|
+
for (const location of parsed.locations) {
|
|
6570
|
+
const normalized = normalizeUrl(location, options.startUrl, options.keepQuery);
|
|
6571
|
+
if (!normalized || normalized.origin !== options.startUrl.origin) continue;
|
|
6572
|
+
if (parsed.kind === "index") queue.push(normalized.href);
|
|
6573
|
+
else urls.push(normalized.href);
|
|
6574
|
+
}
|
|
6575
|
+
} catch {
|
|
6576
|
+
sources.push({
|
|
6577
|
+
url,
|
|
6578
|
+
status: "invalid",
|
|
6579
|
+
httpStatus: resource.response.status,
|
|
6580
|
+
error: "invalid_sitemap_xml"
|
|
6581
|
+
});
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
7321
6584
|
return {
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
6585
|
+
sources,
|
|
6586
|
+
urls: uniqueSorted(urls)
|
|
6587
|
+
};
|
|
6588
|
+
}
|
|
6589
|
+
function parseSitemap(body) {
|
|
6590
|
+
const parser = sax.parser(true, {
|
|
6591
|
+
trim: true,
|
|
6592
|
+
normalize: true
|
|
6593
|
+
});
|
|
6594
|
+
let root = null;
|
|
6595
|
+
let inLocation = false;
|
|
6596
|
+
let location = "";
|
|
6597
|
+
const locations = [];
|
|
6598
|
+
parser.onopentag = (node) => {
|
|
6599
|
+
const name = node.name.toLowerCase();
|
|
6600
|
+
root ??= name;
|
|
6601
|
+
if (name === "loc") {
|
|
6602
|
+
inLocation = true;
|
|
6603
|
+
location = "";
|
|
6604
|
+
}
|
|
6605
|
+
};
|
|
6606
|
+
parser.ontext = (text) => {
|
|
6607
|
+
if (inLocation) location += text;
|
|
6608
|
+
};
|
|
6609
|
+
parser.oncdata = (text) => {
|
|
6610
|
+
if (inLocation) location += text;
|
|
6611
|
+
};
|
|
6612
|
+
parser.onclosetag = (name) => {
|
|
6613
|
+
if (name.toLowerCase() !== "loc") return;
|
|
6614
|
+
inLocation = false;
|
|
6615
|
+
if (location.trim()) locations.push(location.trim());
|
|
6616
|
+
};
|
|
6617
|
+
parser.write(body).close();
|
|
6618
|
+
if (root !== "urlset" && root !== "sitemapindex") throw new Error("Unsupported sitemap root");
|
|
6619
|
+
return {
|
|
6620
|
+
kind: root === "sitemapindex" ? "index" : "urls",
|
|
6621
|
+
locations
|
|
7327
6622
|
};
|
|
7328
6623
|
}
|
|
7329
|
-
async function
|
|
7330
|
-
const
|
|
7331
|
-
|
|
6624
|
+
async function fetchResource(url, options) {
|
|
6625
|
+
const controller = new AbortController();
|
|
6626
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
7332
6627
|
try {
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
6628
|
+
let currentUrl = url;
|
|
6629
|
+
let response = null;
|
|
6630
|
+
for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects += 1) {
|
|
6631
|
+
if (!options.allowPrivateNetwork) await resolveAndValidateExternalUrl(currentUrl, { resolver: options.dnsResolver });
|
|
6632
|
+
response = await options.fetch(currentUrl, {
|
|
6633
|
+
headers: {
|
|
6634
|
+
accept: "text/html,application/xml,text/xml,text/plain;q=0.8",
|
|
6635
|
+
"user-agent": USER_AGENT
|
|
6636
|
+
},
|
|
6637
|
+
redirect: "manual",
|
|
6638
|
+
signal: controller.signal
|
|
6639
|
+
});
|
|
6640
|
+
const location = response.headers.get("location");
|
|
6641
|
+
if (response.status < 300 || response.status >= 400 || !location) break;
|
|
6642
|
+
const next = normalizeUrl(location, new URL(currentUrl), options.keepQuery);
|
|
6643
|
+
if (!next) return {
|
|
6644
|
+
response,
|
|
6645
|
+
finalUrl: currentUrl,
|
|
6646
|
+
contentType: response.headers.get("content-type"),
|
|
6647
|
+
body: "",
|
|
6648
|
+
bytes: 0,
|
|
6649
|
+
error: "invalid_redirect"
|
|
6650
|
+
};
|
|
6651
|
+
if (next.origin !== options.startUrl.origin) return {
|
|
6652
|
+
response,
|
|
6653
|
+
finalUrl: next.href,
|
|
6654
|
+
contentType: response.headers.get("content-type"),
|
|
6655
|
+
body: "",
|
|
6656
|
+
bytes: 0,
|
|
6657
|
+
error: "cross_origin_redirect"
|
|
6658
|
+
};
|
|
6659
|
+
if (redirects === MAX_REDIRECTS) return {
|
|
6660
|
+
response,
|
|
6661
|
+
finalUrl: next.href,
|
|
6662
|
+
contentType: response.headers.get("content-type"),
|
|
6663
|
+
body: "",
|
|
6664
|
+
bytes: 0,
|
|
6665
|
+
error: "too_many_redirects"
|
|
6666
|
+
};
|
|
6667
|
+
currentUrl = next.href;
|
|
6668
|
+
}
|
|
6669
|
+
if (!response) throw new Error("No response");
|
|
6670
|
+
const finalUrl = response.url || currentUrl;
|
|
6671
|
+
const contentType = response.headers.get("content-type");
|
|
6672
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
6673
|
+
if (Number.isFinite(declaredLength) && declaredLength > options.maxBytesPerResource) return {
|
|
6674
|
+
response,
|
|
6675
|
+
finalUrl,
|
|
6676
|
+
contentType,
|
|
6677
|
+
body: "",
|
|
6678
|
+
bytes: 0,
|
|
6679
|
+
error: "resource_too_large"
|
|
6680
|
+
};
|
|
6681
|
+
const body = await readBoundedBody(response, options.maxBytesPerResource);
|
|
6682
|
+
return {
|
|
6683
|
+
response,
|
|
6684
|
+
finalUrl,
|
|
6685
|
+
contentType,
|
|
6686
|
+
body: body.text,
|
|
6687
|
+
bytes: body.bytes
|
|
6688
|
+
};
|
|
6689
|
+
} catch (error) {
|
|
7372
6690
|
return {
|
|
7373
|
-
|
|
7374
|
-
|
|
6691
|
+
response: null,
|
|
6692
|
+
finalUrl: url,
|
|
6693
|
+
contentType: null,
|
|
6694
|
+
body: "",
|
|
6695
|
+
bytes: 0,
|
|
6696
|
+
error: error instanceof ResourceTooLargeError ? "resource_too_large" : fetchError(error)
|
|
7375
6697
|
};
|
|
6698
|
+
} finally {
|
|
6699
|
+
clearTimeout(timeout);
|
|
7376
6700
|
}
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
6701
|
+
}
|
|
6702
|
+
async function readBoundedBody(response, limit) {
|
|
6703
|
+
if (!response.body) return {
|
|
6704
|
+
text: "",
|
|
6705
|
+
bytes: 0
|
|
6706
|
+
};
|
|
6707
|
+
const reader = response.body.getReader();
|
|
6708
|
+
const chunks = [];
|
|
6709
|
+
let bytes = 0;
|
|
6710
|
+
while (true) {
|
|
6711
|
+
const result = await reader.read();
|
|
6712
|
+
if (result.done) break;
|
|
6713
|
+
bytes += result.value.byteLength;
|
|
6714
|
+
if (bytes > limit) {
|
|
6715
|
+
await reader.cancel();
|
|
6716
|
+
throw new ResourceTooLargeError();
|
|
6717
|
+
}
|
|
6718
|
+
chunks.push(result.value);
|
|
6719
|
+
}
|
|
6720
|
+
const combined = new Uint8Array(bytes);
|
|
6721
|
+
let offset = 0;
|
|
6722
|
+
for (const chunk of chunks) {
|
|
6723
|
+
combined.set(chunk, offset);
|
|
6724
|
+
offset += chunk.byteLength;
|
|
6725
|
+
}
|
|
6726
|
+
return {
|
|
6727
|
+
text: new TextDecoder().decode(combined),
|
|
6728
|
+
bytes
|
|
6729
|
+
};
|
|
6730
|
+
}
|
|
6731
|
+
var ResourceTooLargeError = class extends Error {};
|
|
6732
|
+
function fetchError(error) {
|
|
6733
|
+
if (error instanceof SsrfError) return "private_network_blocked";
|
|
6734
|
+
if (error instanceof Error && error.name === "AbortError") return "timeout";
|
|
6735
|
+
return "fetch_failed";
|
|
6736
|
+
}
|
|
6737
|
+
function pageFromResource(item, resource, options) {
|
|
6738
|
+
const base = {
|
|
6739
|
+
url: item.url,
|
|
6740
|
+
finalUrl: resource.finalUrl,
|
|
6741
|
+
depth: item.depth,
|
|
6742
|
+
discoveredFrom: item.discoveredFrom,
|
|
6743
|
+
httpStatus: resource.response?.status ?? null,
|
|
6744
|
+
contentType: resource.contentType,
|
|
6745
|
+
bytes: resource.bytes
|
|
6746
|
+
};
|
|
6747
|
+
const empty = {
|
|
6748
|
+
title: null,
|
|
6749
|
+
description: null,
|
|
6750
|
+
canonicalUrl: null,
|
|
6751
|
+
robots: null,
|
|
6752
|
+
language: null,
|
|
6753
|
+
headings: {
|
|
6754
|
+
h1: [],
|
|
6755
|
+
h2: [],
|
|
6756
|
+
h3: []
|
|
6757
|
+
},
|
|
6758
|
+
wordCount: 0,
|
|
6759
|
+
contentSha256: null,
|
|
6760
|
+
textExcerpt: "",
|
|
6761
|
+
textTruncated: false,
|
|
6762
|
+
internalLinks: [],
|
|
6763
|
+
externalLinks: [],
|
|
6764
|
+
structuredDataBlocks: 0,
|
|
6765
|
+
renderingRecommended: false
|
|
6766
|
+
};
|
|
6767
|
+
if (resource.error) return {
|
|
6768
|
+
...base,
|
|
6769
|
+
...empty,
|
|
6770
|
+
error: resource.error
|
|
6771
|
+
};
|
|
6772
|
+
const final = normalizeUrl(resource.finalUrl, void 0, options.keepQuery);
|
|
6773
|
+
if (!final || final.origin !== options.startUrl.origin) return {
|
|
6774
|
+
...base,
|
|
6775
|
+
...empty,
|
|
6776
|
+
error: "cross_origin_redirect"
|
|
6777
|
+
};
|
|
6778
|
+
if (!resource.contentType?.includes("text/html")) return {
|
|
6779
|
+
...base,
|
|
6780
|
+
...empty
|
|
6781
|
+
};
|
|
6782
|
+
try {
|
|
6783
|
+
return {
|
|
6784
|
+
...base,
|
|
6785
|
+
...parseHtml(resource.body, final, options.keepQuery)
|
|
6786
|
+
};
|
|
6787
|
+
} catch {
|
|
6788
|
+
return {
|
|
6789
|
+
...base,
|
|
6790
|
+
...empty,
|
|
6791
|
+
error: "invalid_html"
|
|
7393
6792
|
};
|
|
7394
|
-
await this.writeCredentials(updated, {
|
|
7395
|
-
cwd: this.cwd,
|
|
7396
|
-
platformApiUrl: updated.platformApiUrl,
|
|
7397
|
-
email: user.email
|
|
7398
|
-
});
|
|
7399
|
-
return updated;
|
|
7400
6793
|
}
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
6794
|
+
}
|
|
6795
|
+
function parseHtml(body, pageUrl, keepQuery) {
|
|
6796
|
+
const document = parse(body);
|
|
6797
|
+
let title = null;
|
|
6798
|
+
let description = null;
|
|
6799
|
+
let canonicalUrl = null;
|
|
6800
|
+
let robots = null;
|
|
6801
|
+
let language = null;
|
|
6802
|
+
let structuredDataBlocks = 0;
|
|
6803
|
+
let scriptBlocks = 0;
|
|
6804
|
+
const headings = {
|
|
6805
|
+
h1: [],
|
|
6806
|
+
h2: [],
|
|
6807
|
+
h3: []
|
|
6808
|
+
};
|
|
6809
|
+
const internalLinks = [];
|
|
6810
|
+
const externalLinks = [];
|
|
6811
|
+
walk(document, (element) => {
|
|
6812
|
+
const attributes = new Map(element.attrs.map((attribute) => [attribute.name, attribute.value]));
|
|
6813
|
+
if (element.tagName === "html") language = cleanText(attributes.get("lang")) || language;
|
|
6814
|
+
if (element.tagName === "title" && title === null) title = cleanText(textContent(element));
|
|
6815
|
+
if (element.tagName === "meta") {
|
|
6816
|
+
const name = attributes.get("name")?.toLowerCase();
|
|
6817
|
+
if (name === "description" && description === null) description = cleanText(attributes.get("content"));
|
|
6818
|
+
if ((name === "robots" || name === "googlebot") && robots === null) robots = cleanText(attributes.get("content"));
|
|
6819
|
+
}
|
|
6820
|
+
if (element.tagName === "link") {
|
|
6821
|
+
if ((attributes.get("rel")?.toLowerCase().split(WHITESPACE_RE) ?? []).includes("canonical") && canonicalUrl === null) canonicalUrl = normalizeUrl(attributes.get("href") ?? "", pageUrl, keepQuery)?.href ?? null;
|
|
6822
|
+
}
|
|
6823
|
+
if (element.tagName === "h1" || element.tagName === "h2" || element.tagName === "h3") {
|
|
6824
|
+
const value = cleanText(textContent(element));
|
|
6825
|
+
if (value) headings[element.tagName].push(value);
|
|
6826
|
+
}
|
|
6827
|
+
if (element.tagName === "script") {
|
|
6828
|
+
scriptBlocks += 1;
|
|
6829
|
+
if (attributes.get("type")?.toLowerCase() === "application/ld+json") structuredDataBlocks += 1;
|
|
6830
|
+
}
|
|
6831
|
+
if (element.tagName === "a") {
|
|
6832
|
+
const link = normalizeUrl(attributes.get("href") ?? "", pageUrl, keepQuery);
|
|
6833
|
+
if (!link) return;
|
|
6834
|
+
const item = {
|
|
6835
|
+
url: link.href,
|
|
6836
|
+
anchor: cleanText(textContent(element)) ?? ""
|
|
6837
|
+
};
|
|
6838
|
+
if (link.origin === pageUrl.origin) internalLinks.push(item);
|
|
6839
|
+
else externalLinks.push(item);
|
|
6840
|
+
}
|
|
6841
|
+
});
|
|
6842
|
+
const visibleText = cleanText(textContent(document)) ?? "";
|
|
6843
|
+
const wordCount = countWords(visibleText);
|
|
6844
|
+
const textExcerpt = visibleText.slice(0, MAX_TEXT_EXCERPT);
|
|
6845
|
+
return {
|
|
6846
|
+
title,
|
|
6847
|
+
description,
|
|
6848
|
+
canonicalUrl,
|
|
6849
|
+
robots,
|
|
6850
|
+
language,
|
|
6851
|
+
headings,
|
|
6852
|
+
wordCount,
|
|
6853
|
+
contentSha256: createHash("sha256").update(visibleText).digest("hex"),
|
|
6854
|
+
textExcerpt,
|
|
6855
|
+
textTruncated: textExcerpt.length < visibleText.length,
|
|
6856
|
+
internalLinks: uniqueLinks(internalLinks),
|
|
6857
|
+
externalLinks: uniqueLinks(externalLinks),
|
|
6858
|
+
structuredDataBlocks,
|
|
6859
|
+
renderingRecommended: wordCount < 20 && scriptBlocks > 0
|
|
6860
|
+
};
|
|
6861
|
+
}
|
|
6862
|
+
function walk(node, visit) {
|
|
6863
|
+
if (isElement(node)) visit(node);
|
|
6864
|
+
if (!("childNodes" in node)) return;
|
|
6865
|
+
for (const child of node.childNodes) walk(child, visit);
|
|
6866
|
+
}
|
|
6867
|
+
function textContent(node, hidden = false) {
|
|
6868
|
+
if ("value" in node) return hidden ? "" : node.value;
|
|
6869
|
+
const nextHidden = hidden || isElement(node) && [
|
|
6870
|
+
"script",
|
|
6871
|
+
"style",
|
|
6872
|
+
"noscript",
|
|
6873
|
+
"template",
|
|
6874
|
+
"svg",
|
|
6875
|
+
"head"
|
|
6876
|
+
].includes(node.tagName);
|
|
6877
|
+
if (!("childNodes" in node)) return "";
|
|
6878
|
+
return node.childNodes.map((child) => textContent(child, nextHidden)).join(" ");
|
|
6879
|
+
}
|
|
6880
|
+
function isElement(node) {
|
|
6881
|
+
return "tagName" in node;
|
|
6882
|
+
}
|
|
6883
|
+
function normalizeUrl(value, base, keepQuery = false) {
|
|
6884
|
+
try {
|
|
6885
|
+
const url = base ? new URL(value, base) : new URL(value);
|
|
6886
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) return null;
|
|
6887
|
+
url.hash = "";
|
|
6888
|
+
if (!keepQuery) url.search = "";
|
|
6889
|
+
if (!url.pathname) url.pathname = "/";
|
|
6890
|
+
return url;
|
|
6891
|
+
} catch {
|
|
6892
|
+
return null;
|
|
7416
6893
|
}
|
|
7417
|
-
}
|
|
6894
|
+
}
|
|
6895
|
+
function cleanText(value) {
|
|
6896
|
+
if (!value) return null;
|
|
6897
|
+
return value.replaceAll(WHITESPACE_RE, " ").trim() || null;
|
|
6898
|
+
}
|
|
6899
|
+
function countWords(value) {
|
|
6900
|
+
return value.match(WORD_RE)?.length ?? 0;
|
|
6901
|
+
}
|
|
6902
|
+
function uniqueLinks(links) {
|
|
6903
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6904
|
+
return links.filter((link) => {
|
|
6905
|
+
const key = `${link.url}\u0000${link.anchor}`;
|
|
6906
|
+
if (seen.has(key)) return false;
|
|
6907
|
+
seen.add(key);
|
|
6908
|
+
return true;
|
|
6909
|
+
});
|
|
6910
|
+
}
|
|
6911
|
+
function duplicateGroups(pages, field) {
|
|
6912
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6913
|
+
for (const page of pages) {
|
|
6914
|
+
const value = page[field];
|
|
6915
|
+
if (!value) continue;
|
|
6916
|
+
const key = field === "contentSha256" ? value : value.toLocaleLowerCase();
|
|
6917
|
+
const group = groups.get(key) ?? {
|
|
6918
|
+
value,
|
|
6919
|
+
urls: []
|
|
6920
|
+
};
|
|
6921
|
+
group.urls.push(page.url);
|
|
6922
|
+
groups.set(key, group);
|
|
6923
|
+
}
|
|
6924
|
+
return [...groups.values()].filter((group) => group.urls.length > 1).map((group) => ({
|
|
6925
|
+
value: group.value,
|
|
6926
|
+
urls: uniqueSorted(group.urls)
|
|
6927
|
+
})).toSorted((left, right) => left.value.localeCompare(right.value));
|
|
6928
|
+
}
|
|
6929
|
+
function uniqueSorted(values) {
|
|
6930
|
+
return [...new Set(values)].toSorted((left, right) => left.localeCompare(right));
|
|
6931
|
+
}
|
|
6932
|
+
function escapeRegExp(value) {
|
|
6933
|
+
return value.replace(REGEXP_SPECIAL_RE, "\\$&");
|
|
6934
|
+
}
|
|
7418
6935
|
|
|
7419
6936
|
//#endregion
|
|
7420
6937
|
//#region src/cli/commands/seo.ts
|
|
6938
|
+
const jsonArgs = { json: {
|
|
6939
|
+
type: "boolean",
|
|
6940
|
+
description: "Output as JSON"
|
|
6941
|
+
} };
|
|
7421
6942
|
const hostedProviderArgs = {
|
|
7422
|
-
|
|
7423
|
-
type: "boolean",
|
|
7424
|
-
description: "Output as JSON"
|
|
7425
|
-
},
|
|
6943
|
+
...jsonArgs,
|
|
7426
6944
|
"force-refresh": {
|
|
7427
6945
|
type: "boolean",
|
|
7428
6946
|
description: "Bypass Forgeway SEO provider cache"
|
|
@@ -7468,416 +6986,515 @@ function requireArg(value, name) {
|
|
|
7468
6986
|
if (!normalized) throw new Error(`${name} is required.`);
|
|
7469
6987
|
return normalized;
|
|
7470
6988
|
}
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
} catch (error) {
|
|
7476
|
-
if (await handleCliPaywall({
|
|
7477
|
-
error,
|
|
7478
|
-
resource: path,
|
|
7479
|
-
context: { providerPath: path }
|
|
7480
|
-
})) process.exit(2);
|
|
7481
|
-
consola$1.error(error instanceof Error ? error.message : "Unknown error");
|
|
7482
|
-
process.exit(1);
|
|
7483
|
-
}
|
|
6989
|
+
function sitemapMode(value) {
|
|
6990
|
+
const mode = toStringValue(value) ?? "include";
|
|
6991
|
+
if (mode === "include" || mode === "skip" || mode === "only") return mode;
|
|
6992
|
+
throw new Error("sitemap must be include, skip, or only.");
|
|
7484
6993
|
}
|
|
7485
|
-
const
|
|
6994
|
+
const crawlCommand = defineCommand({
|
|
7486
6995
|
meta: {
|
|
7487
|
-
name: "
|
|
7488
|
-
description: "
|
|
6996
|
+
name: "crawl",
|
|
6997
|
+
description: "Deterministic site crawl controls"
|
|
7489
6998
|
},
|
|
7490
|
-
subCommands: {
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
6999
|
+
subCommands: { local: defineCommand({
|
|
7000
|
+
meta: {
|
|
7001
|
+
name: "local",
|
|
7002
|
+
description: "Crawl one site locally and return bounded structural evidence"
|
|
7003
|
+
},
|
|
7004
|
+
args: {
|
|
7005
|
+
url: {
|
|
7006
|
+
type: "positional",
|
|
7007
|
+
description: "First-party site URL",
|
|
7008
|
+
required: true
|
|
7495
7009
|
},
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
},
|
|
7502
|
-
...hostedProviderArgs,
|
|
7503
|
-
formats: {
|
|
7504
|
-
type: "string",
|
|
7505
|
-
description: "Comma-separated Firecrawl formats",
|
|
7506
|
-
default: "markdown"
|
|
7507
|
-
},
|
|
7508
|
-
"only-main-content": {
|
|
7509
|
-
type: "boolean",
|
|
7510
|
-
description: "Return main content only",
|
|
7511
|
-
default: true
|
|
7512
|
-
},
|
|
7513
|
-
"timeout-ms": {
|
|
7514
|
-
type: "string",
|
|
7515
|
-
description: "Provider timeout in milliseconds",
|
|
7516
|
-
default: "60000"
|
|
7517
|
-
}
|
|
7010
|
+
...jsonArgs,
|
|
7011
|
+
limit: {
|
|
7012
|
+
type: "string",
|
|
7013
|
+
description: "Maximum pages",
|
|
7014
|
+
default: "100"
|
|
7518
7015
|
},
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
onlyMainContent: args["only-main-content"] !== false,
|
|
7524
|
-
timeoutMs: toNumberValue(args["timeout-ms"], 6e4),
|
|
7525
|
-
forceRefresh: forceRefresh(args)
|
|
7526
|
-
});
|
|
7527
|
-
}
|
|
7528
|
-
}),
|
|
7529
|
-
map: defineCommand({
|
|
7530
|
-
meta: {
|
|
7531
|
-
name: "map",
|
|
7532
|
-
description: "Map URLs through Forgeway-hosted Firecrawl"
|
|
7016
|
+
"max-depth": {
|
|
7017
|
+
type: "string",
|
|
7018
|
+
description: "Maximum link discovery depth",
|
|
7019
|
+
default: "3"
|
|
7533
7020
|
},
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
required: true
|
|
7539
|
-
},
|
|
7540
|
-
...hostedProviderArgs,
|
|
7541
|
-
search: {
|
|
7542
|
-
type: "string",
|
|
7543
|
-
description: "Optional relevance search term"
|
|
7544
|
-
},
|
|
7545
|
-
sitemap: {
|
|
7546
|
-
type: "string",
|
|
7547
|
-
description: "Sitemap mode: skip, include, or only",
|
|
7548
|
-
default: "include"
|
|
7549
|
-
},
|
|
7550
|
-
limit: {
|
|
7551
|
-
type: "string",
|
|
7552
|
-
description: "Maximum URLs",
|
|
7553
|
-
default: "100"
|
|
7554
|
-
},
|
|
7555
|
-
"include-subdomains": {
|
|
7556
|
-
type: "boolean",
|
|
7557
|
-
description: "Include subdomains"
|
|
7558
|
-
},
|
|
7559
|
-
"ignore-query-parameters": {
|
|
7560
|
-
type: "boolean",
|
|
7561
|
-
description: "Ignore query parameters",
|
|
7562
|
-
default: true
|
|
7563
|
-
}
|
|
7021
|
+
concurrency: {
|
|
7022
|
+
type: "string",
|
|
7023
|
+
description: "Maximum concurrent page requests",
|
|
7024
|
+
default: "4"
|
|
7564
7025
|
},
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
search: toStringValue(args.search),
|
|
7569
|
-
sitemap: toStringValue(args.sitemap) ?? "include",
|
|
7570
|
-
limit: toNumberValue(args.limit, 100),
|
|
7571
|
-
includeSubdomains: args["include-subdomains"] === true,
|
|
7572
|
-
ignoreQueryParameters: args["ignore-query-parameters"] !== false,
|
|
7573
|
-
forceRefresh: forceRefresh(args)
|
|
7574
|
-
});
|
|
7575
|
-
}
|
|
7576
|
-
}),
|
|
7577
|
-
crawl: defineCommand({
|
|
7578
|
-
meta: {
|
|
7579
|
-
name: "crawl",
|
|
7580
|
-
description: "Start a bounded Firecrawl crawl, or check status with --id"
|
|
7026
|
+
include: {
|
|
7027
|
+
type: "string",
|
|
7028
|
+
description: "Comma-separated path globs to include"
|
|
7581
7029
|
},
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
|
|
7585
|
-
description: "Base URL to crawl"
|
|
7586
|
-
},
|
|
7587
|
-
...hostedProviderArgs,
|
|
7588
|
-
id: {
|
|
7589
|
-
type: "string",
|
|
7590
|
-
description: "Existing Firecrawl crawl id to check"
|
|
7591
|
-
},
|
|
7592
|
-
limit: {
|
|
7593
|
-
type: "string",
|
|
7594
|
-
description: "Maximum pages",
|
|
7595
|
-
default: "25"
|
|
7596
|
-
},
|
|
7597
|
-
"max-depth": {
|
|
7598
|
-
type: "string",
|
|
7599
|
-
description: "Maximum discovery depth",
|
|
7600
|
-
default: "3"
|
|
7601
|
-
},
|
|
7602
|
-
"allow-subdomains": {
|
|
7603
|
-
type: "boolean",
|
|
7604
|
-
description: "Allow subdomains"
|
|
7605
|
-
},
|
|
7606
|
-
"allow-external-links": {
|
|
7607
|
-
type: "boolean",
|
|
7608
|
-
description: "Allow external links"
|
|
7609
|
-
},
|
|
7610
|
-
formats: {
|
|
7611
|
-
type: "string",
|
|
7612
|
-
description: "Comma-separated scrape formats",
|
|
7613
|
-
default: "markdown"
|
|
7614
|
-
}
|
|
7030
|
+
exclude: {
|
|
7031
|
+
type: "string",
|
|
7032
|
+
description: "Comma-separated path globs to exclude"
|
|
7615
7033
|
},
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
try {
|
|
7621
|
-
output(await new ForgewaySeoGatewayClient().request(`/api/firecrawl/crawl/${encodeURIComponent(crawlId)}`, void 0, { method: "GET" }), args);
|
|
7622
|
-
} catch (error) {
|
|
7623
|
-
if (await handleCliPaywall({
|
|
7624
|
-
error,
|
|
7625
|
-
resource: `/api/firecrawl/crawl/${crawlId}`,
|
|
7626
|
-
context: { providerPath: `/api/firecrawl/crawl/${crawlId}` }
|
|
7627
|
-
})) process.exit(2);
|
|
7628
|
-
consola$1.error(error instanceof Error ? error.message : "Unknown error");
|
|
7629
|
-
process.exit(1);
|
|
7630
|
-
}
|
|
7631
|
-
return;
|
|
7632
|
-
}
|
|
7633
|
-
await runSeoRequest(args, "/api/firecrawl/crawl", {
|
|
7634
|
-
url: requireArg(args.url, "url"),
|
|
7635
|
-
limit: toNumberValue(args.limit, 25),
|
|
7636
|
-
maxDiscoveryDepth: toNumberValue(args["max-depth"], 3),
|
|
7637
|
-
allowExternalLinks: args["allow-external-links"] === true,
|
|
7638
|
-
allowSubdomains: args["allow-subdomains"] === true,
|
|
7639
|
-
scrapeFormats: formats(args.formats),
|
|
7640
|
-
forceRefresh: forceRefresh(args)
|
|
7641
|
-
});
|
|
7642
|
-
}
|
|
7643
|
-
}),
|
|
7644
|
-
search: defineCommand({
|
|
7645
|
-
meta: {
|
|
7646
|
-
name: "search",
|
|
7647
|
-
description: "Search through Forgeway-hosted Firecrawl"
|
|
7034
|
+
"timeout-ms": {
|
|
7035
|
+
type: "string",
|
|
7036
|
+
description: "Per-resource timeout in milliseconds",
|
|
7037
|
+
default: "10000"
|
|
7648
7038
|
},
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
required: true
|
|
7654
|
-
},
|
|
7655
|
-
...hostedProviderArgs,
|
|
7656
|
-
limit: {
|
|
7657
|
-
type: "string",
|
|
7658
|
-
description: "Maximum results",
|
|
7659
|
-
default: "10"
|
|
7660
|
-
},
|
|
7661
|
-
scrape: {
|
|
7662
|
-
type: "boolean",
|
|
7663
|
-
description: "Scrape result markdown"
|
|
7664
|
-
}
|
|
7039
|
+
"max-bytes": {
|
|
7040
|
+
type: "string",
|
|
7041
|
+
description: "Maximum bytes per fetched resource",
|
|
7042
|
+
default: "1000000"
|
|
7665
7043
|
},
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7044
|
+
sitemap: {
|
|
7045
|
+
type: "string",
|
|
7046
|
+
description: "Sitemap mode: include, skip, or only",
|
|
7047
|
+
default: "include"
|
|
7048
|
+
},
|
|
7049
|
+
"keep-query": {
|
|
7050
|
+
type: "boolean",
|
|
7051
|
+
description: "Keep query parameters in page identity"
|
|
7052
|
+
},
|
|
7053
|
+
"allow-private-network": {
|
|
7054
|
+
type: "boolean",
|
|
7055
|
+
description: "Allow an explicitly selected localhost or private origin"
|
|
7673
7056
|
}
|
|
7674
|
-
}
|
|
7675
|
-
|
|
7057
|
+
},
|
|
7058
|
+
async run({ args }) {
|
|
7059
|
+
configureOutputMode(args);
|
|
7060
|
+
try {
|
|
7061
|
+
output(await crawlLocalSite({
|
|
7062
|
+
startUrl: requireArg(args.url, "url"),
|
|
7063
|
+
pageLimit: toNumberValue(args.limit, 100),
|
|
7064
|
+
maxDepth: toNumberValue(args["max-depth"], 3),
|
|
7065
|
+
concurrency: toNumberValue(args.concurrency, 4),
|
|
7066
|
+
include: formats(args.include) ?? [],
|
|
7067
|
+
exclude: formats(args.exclude) ?? [],
|
|
7068
|
+
timeoutMs: toNumberValue(args["timeout-ms"], 1e4),
|
|
7069
|
+
maxBytesPerResource: toNumberValue(args["max-bytes"], 1e6),
|
|
7070
|
+
sitemap: sitemapMode(args.sitemap),
|
|
7071
|
+
keepQuery: args["keep-query"] === true,
|
|
7072
|
+
allowPrivateNetwork: args["allow-private-network"] === true
|
|
7073
|
+
}), args);
|
|
7074
|
+
} catch (error) {
|
|
7075
|
+
consola$1.error(error instanceof Error ? error.message : "Local site crawl failed");
|
|
7076
|
+
process.exit(1);
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
}) }
|
|
7676
7080
|
});
|
|
7081
|
+
function dataForSeoDevice(value) {
|
|
7082
|
+
const normalized = toStringValue(value) ?? "desktop";
|
|
7083
|
+
if (normalized === "desktop" || normalized === "mobile") return normalized;
|
|
7084
|
+
throw new Error("device must be desktop or mobile.");
|
|
7085
|
+
}
|
|
7086
|
+
function aiScrapePlatform(value) {
|
|
7087
|
+
const normalized = toStringValue(value) ?? "chat_gpt";
|
|
7088
|
+
if (normalized === "chat_gpt" || normalized === "gemini" || normalized === "perplexity") return normalized;
|
|
7089
|
+
throw new Error("platform must be chat_gpt, gemini, or perplexity.");
|
|
7090
|
+
}
|
|
7091
|
+
function aiMentionPlatform(value) {
|
|
7092
|
+
const normalized = toStringValue(value) ?? "google";
|
|
7093
|
+
if (normalized === "google" || normalized === "chat_gpt") return normalized;
|
|
7094
|
+
throw new Error("platform must be google or chat_gpt.");
|
|
7095
|
+
}
|
|
7677
7096
|
function dataForSeoBase(args) {
|
|
7678
7097
|
return {
|
|
7679
7098
|
locationCode: toNumberValue(args["location-code"], 2840),
|
|
7680
7099
|
languageCode: toStringValue(args["language-code"]) ?? "en",
|
|
7681
|
-
device:
|
|
7100
|
+
device: dataForSeoDevice(args.device),
|
|
7682
7101
|
forceRefresh: forceRefresh(args)
|
|
7683
7102
|
};
|
|
7684
7103
|
}
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
description: "SERP depth",
|
|
7700
|
-
default: "10"
|
|
7104
|
+
function createSeoCommand(options = {}) {
|
|
7105
|
+
const providerService = options.providerService ?? new SeoProviderEvidenceService(new ForgewaySeoGatewayClient());
|
|
7106
|
+
async function runSeoRequest(args, resource, request) {
|
|
7107
|
+
configureOutputMode(args);
|
|
7108
|
+
try {
|
|
7109
|
+
output(await request(), args);
|
|
7110
|
+
} catch (error) {
|
|
7111
|
+
if (await handleCliPaywall({
|
|
7112
|
+
error,
|
|
7113
|
+
resource,
|
|
7114
|
+
context: { providerPath: resource }
|
|
7115
|
+
})) process.exit(2);
|
|
7116
|
+
consola$1.error(error instanceof Error ? error.message : "Unknown error");
|
|
7117
|
+
process.exit(1);
|
|
7701
7118
|
}
|
|
7702
|
-
},
|
|
7703
|
-
async run({ args }) {
|
|
7704
|
-
await runSeoRequest(args, "/api/dataforseo/serp", {
|
|
7705
|
-
keyword: requireArg(args.keyword, "keyword"),
|
|
7706
|
-
depth: toNumberValue(args.depth, 10),
|
|
7707
|
-
...dataForSeoBase(args)
|
|
7708
|
-
});
|
|
7709
7119
|
}
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7120
|
+
const firecrawlCommand = defineCommand({
|
|
7121
|
+
meta: {
|
|
7122
|
+
name: "firecrawl",
|
|
7123
|
+
description: "Forgeway-hosted Firecrawl workflows"
|
|
7124
|
+
},
|
|
7125
|
+
subCommands: {
|
|
7126
|
+
scrape: defineCommand({
|
|
7127
|
+
meta: {
|
|
7128
|
+
name: "scrape",
|
|
7129
|
+
description: "Scrape one URL through Forgeway-hosted Firecrawl"
|
|
7130
|
+
},
|
|
7131
|
+
args: {
|
|
7132
|
+
url: {
|
|
7133
|
+
type: "positional",
|
|
7134
|
+
description: "URL to scrape",
|
|
7135
|
+
required: true
|
|
7136
|
+
},
|
|
7137
|
+
...hostedProviderArgs,
|
|
7138
|
+
formats: {
|
|
7139
|
+
type: "string",
|
|
7140
|
+
description: "Comma-separated Firecrawl formats",
|
|
7141
|
+
default: "markdown"
|
|
7142
|
+
},
|
|
7143
|
+
"only-main-content": {
|
|
7144
|
+
type: "boolean",
|
|
7145
|
+
description: "Return main content only",
|
|
7146
|
+
default: true
|
|
7147
|
+
},
|
|
7148
|
+
"timeout-ms": {
|
|
7149
|
+
type: "string",
|
|
7150
|
+
description: "Provider timeout in milliseconds",
|
|
7151
|
+
default: "60000"
|
|
7152
|
+
}
|
|
7153
|
+
},
|
|
7154
|
+
async run({ args }) {
|
|
7155
|
+
await runSeoRequest(args, "/api/firecrawl/scrape", () => providerService.firecrawlScrape({
|
|
7156
|
+
url: requireArg(args.url, "url"),
|
|
7157
|
+
formats: formats(args.formats) ?? ["markdown"],
|
|
7158
|
+
onlyMainContent: args["only-main-content"] !== false,
|
|
7159
|
+
timeoutMs: toNumberValue(args["timeout-ms"], 6e4),
|
|
7160
|
+
forceRefresh: forceRefresh(args)
|
|
7161
|
+
}));
|
|
7162
|
+
}
|
|
7163
|
+
}),
|
|
7164
|
+
map: defineCommand({
|
|
7165
|
+
meta: {
|
|
7166
|
+
name: "map",
|
|
7167
|
+
description: "Map URLs through Forgeway-hosted Firecrawl"
|
|
7168
|
+
},
|
|
7169
|
+
args: {
|
|
7170
|
+
url: {
|
|
7171
|
+
type: "positional",
|
|
7172
|
+
description: "Base URL to map",
|
|
7173
|
+
required: true
|
|
7174
|
+
},
|
|
7175
|
+
...hostedProviderArgs,
|
|
7176
|
+
search: {
|
|
7177
|
+
type: "string",
|
|
7178
|
+
description: "Optional relevance search term"
|
|
7179
|
+
},
|
|
7180
|
+
sitemap: {
|
|
7181
|
+
type: "string",
|
|
7182
|
+
description: "Sitemap mode: skip, include, or only",
|
|
7183
|
+
default: "include"
|
|
7184
|
+
},
|
|
7185
|
+
limit: {
|
|
7186
|
+
type: "string",
|
|
7187
|
+
description: "Maximum URLs",
|
|
7188
|
+
default: "100"
|
|
7189
|
+
},
|
|
7190
|
+
"include-subdomains": {
|
|
7191
|
+
type: "boolean",
|
|
7192
|
+
description: "Include subdomains"
|
|
7193
|
+
},
|
|
7194
|
+
"ignore-query-parameters": {
|
|
7195
|
+
type: "boolean",
|
|
7196
|
+
description: "Ignore query parameters",
|
|
7197
|
+
default: true
|
|
7198
|
+
}
|
|
7199
|
+
},
|
|
7200
|
+
async run({ args }) {
|
|
7201
|
+
await runSeoRequest(args, "/api/firecrawl/map", () => providerService.firecrawlMap({
|
|
7202
|
+
url: requireArg(args.url, "url"),
|
|
7203
|
+
search: toStringValue(args.search),
|
|
7204
|
+
sitemap: sitemapMode(args.sitemap),
|
|
7205
|
+
limit: toNumberValue(args.limit, 100),
|
|
7206
|
+
includeSubdomains: args["include-subdomains"] === true,
|
|
7207
|
+
ignoreQueryParameters: args["ignore-query-parameters"] !== false,
|
|
7208
|
+
forceRefresh: forceRefresh(args)
|
|
7209
|
+
}));
|
|
7210
|
+
}
|
|
7211
|
+
}),
|
|
7212
|
+
crawl: defineCommand({
|
|
7213
|
+
meta: {
|
|
7214
|
+
name: "crawl",
|
|
7215
|
+
description: "Start a bounded Firecrawl crawl, or check status with --id"
|
|
7216
|
+
},
|
|
7217
|
+
args: {
|
|
7218
|
+
url: {
|
|
7219
|
+
type: "positional",
|
|
7220
|
+
description: "Base URL to crawl",
|
|
7221
|
+
required: false
|
|
7222
|
+
},
|
|
7223
|
+
...hostedProviderArgs,
|
|
7224
|
+
id: {
|
|
7225
|
+
type: "string",
|
|
7226
|
+
description: "Existing Firecrawl crawl id to check"
|
|
7227
|
+
},
|
|
7228
|
+
limit: {
|
|
7229
|
+
type: "string",
|
|
7230
|
+
description: "Maximum pages",
|
|
7231
|
+
default: "25"
|
|
7232
|
+
},
|
|
7233
|
+
"max-depth": {
|
|
7234
|
+
type: "string",
|
|
7235
|
+
description: "Maximum discovery depth",
|
|
7236
|
+
default: "3"
|
|
7237
|
+
},
|
|
7238
|
+
"allow-subdomains": {
|
|
7239
|
+
type: "boolean",
|
|
7240
|
+
description: "Allow subdomains"
|
|
7241
|
+
},
|
|
7242
|
+
"allow-external-links": {
|
|
7243
|
+
type: "boolean",
|
|
7244
|
+
description: "Allow external links"
|
|
7245
|
+
},
|
|
7246
|
+
formats: {
|
|
7247
|
+
type: "string",
|
|
7248
|
+
description: "Comma-separated scrape formats",
|
|
7249
|
+
default: "markdown"
|
|
7250
|
+
}
|
|
7251
|
+
},
|
|
7252
|
+
async run({ args }) {
|
|
7253
|
+
const crawlId = toStringValue(args.id);
|
|
7254
|
+
if (crawlId) {
|
|
7255
|
+
await runSeoRequest(args, `/api/firecrawl/crawl/${crawlId}`, () => providerService.firecrawlCrawlStatus({ id: crawlId }));
|
|
7256
|
+
return;
|
|
7257
|
+
}
|
|
7258
|
+
await runSeoRequest(args, "/api/firecrawl/crawl", () => providerService.firecrawlCrawlStart({
|
|
7259
|
+
url: requireArg(args.url, "url"),
|
|
7260
|
+
limit: toNumberValue(args.limit, 25),
|
|
7261
|
+
maxDiscoveryDepth: toNumberValue(args["max-depth"], 3),
|
|
7262
|
+
allowExternalLinks: args["allow-external-links"] === true,
|
|
7263
|
+
allowSubdomains: args["allow-subdomains"] === true,
|
|
7264
|
+
scrapeFormats: formats(args.formats) ?? ["markdown"],
|
|
7265
|
+
forceRefresh: forceRefresh(args)
|
|
7266
|
+
}));
|
|
7267
|
+
}
|
|
7268
|
+
}),
|
|
7269
|
+
search: defineCommand({
|
|
7270
|
+
meta: {
|
|
7271
|
+
name: "search",
|
|
7272
|
+
description: "Search through Forgeway-hosted Firecrawl"
|
|
7273
|
+
},
|
|
7274
|
+
args: {
|
|
7275
|
+
query: {
|
|
7276
|
+
type: "positional",
|
|
7277
|
+
description: "Search query",
|
|
7278
|
+
required: true
|
|
7279
|
+
},
|
|
7280
|
+
...hostedProviderArgs,
|
|
7281
|
+
limit: {
|
|
7282
|
+
type: "string",
|
|
7283
|
+
description: "Maximum results",
|
|
7284
|
+
default: "10"
|
|
7285
|
+
},
|
|
7286
|
+
scrape: {
|
|
7287
|
+
type: "boolean",
|
|
7288
|
+
description: "Scrape result markdown"
|
|
7289
|
+
}
|
|
7290
|
+
},
|
|
7291
|
+
async run({ args }) {
|
|
7292
|
+
await runSeoRequest(args, "/api/firecrawl/search", () => providerService.firecrawlSearch({
|
|
7293
|
+
query: requireArg(args.query, "query"),
|
|
7294
|
+
limit: toNumberValue(args.limit, 10),
|
|
7295
|
+
scrape: args.scrape === true,
|
|
7296
|
+
forceRefresh: forceRefresh(args)
|
|
7297
|
+
}));
|
|
7298
|
+
}
|
|
7299
|
+
})
|
|
7727
7300
|
}
|
|
7728
|
-
}
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
required: true
|
|
7301
|
+
});
|
|
7302
|
+
const dataForSeoSerpCommand = defineCommand({
|
|
7303
|
+
meta: {
|
|
7304
|
+
name: "serp",
|
|
7305
|
+
description: "Fetch Google organic SERP data"
|
|
7306
|
+
},
|
|
7307
|
+
args: {
|
|
7308
|
+
keyword: {
|
|
7309
|
+
type: "positional",
|
|
7310
|
+
description: "Keyword",
|
|
7311
|
+
required: true
|
|
7312
|
+
},
|
|
7313
|
+
...dataForSeoCommonArgs,
|
|
7314
|
+
depth: {
|
|
7315
|
+
type: "string",
|
|
7316
|
+
description: "SERP depth",
|
|
7317
|
+
default: "10"
|
|
7318
|
+
}
|
|
7747
7319
|
},
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7320
|
+
async run({ args }) {
|
|
7321
|
+
await runSeoRequest(args, "/api/dataforseo/serp", () => providerService.dataForSeoSerp({
|
|
7322
|
+
keyword: requireArg(args.keyword, "keyword"),
|
|
7323
|
+
depth: toNumberValue(args.depth, 10),
|
|
7324
|
+
...dataForSeoBase(args)
|
|
7325
|
+
}));
|
|
7753
7326
|
}
|
|
7754
|
-
}
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
type: "positional",
|
|
7773
|
-
description: "Target domain",
|
|
7774
|
-
required: true
|
|
7327
|
+
});
|
|
7328
|
+
const dataForSeoMapsCommand = defineCommand({
|
|
7329
|
+
meta: {
|
|
7330
|
+
name: "maps",
|
|
7331
|
+
description: "Fetch Google Maps SERP data"
|
|
7332
|
+
},
|
|
7333
|
+
args: {
|
|
7334
|
+
keyword: {
|
|
7335
|
+
type: "positional",
|
|
7336
|
+
description: "Keyword",
|
|
7337
|
+
required: true
|
|
7338
|
+
},
|
|
7339
|
+
...dataForSeoCommonArgs,
|
|
7340
|
+
depth: {
|
|
7341
|
+
type: "string",
|
|
7342
|
+
description: "Maps depth",
|
|
7343
|
+
default: "10"
|
|
7344
|
+
}
|
|
7775
7345
|
},
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7346
|
+
async run({ args }) {
|
|
7347
|
+
await runSeoRequest(args, "/api/dataforseo/maps", () => providerService.dataForSeoMaps({
|
|
7348
|
+
keyword: requireArg(args.keyword, "keyword"),
|
|
7349
|
+
depth: toNumberValue(args.depth, 10),
|
|
7350
|
+
...dataForSeoBase(args)
|
|
7351
|
+
}));
|
|
7781
7352
|
}
|
|
7782
|
-
}
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
required: true
|
|
7353
|
+
});
|
|
7354
|
+
const dataForSeoKeywordsCommand = defineCommand({
|
|
7355
|
+
meta: {
|
|
7356
|
+
name: "keywords",
|
|
7357
|
+
description: "Fetch keyword suggestions"
|
|
7358
|
+
},
|
|
7359
|
+
args: {
|
|
7360
|
+
keyword: {
|
|
7361
|
+
type: "positional",
|
|
7362
|
+
description: "Seed keyword",
|
|
7363
|
+
required: true
|
|
7364
|
+
},
|
|
7365
|
+
...dataForSeoCommonArgs,
|
|
7366
|
+
limit: {
|
|
7367
|
+
type: "string",
|
|
7368
|
+
description: "Maximum suggestions",
|
|
7369
|
+
default: "20"
|
|
7370
|
+
}
|
|
7801
7371
|
},
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7372
|
+
async run({ args }) {
|
|
7373
|
+
await runSeoRequest(args, "/api/dataforseo/keywords", () => providerService.dataForSeoKeywords({
|
|
7374
|
+
keyword: requireArg(args.keyword, "keyword"),
|
|
7375
|
+
limit: toNumberValue(args.limit, 20),
|
|
7376
|
+
...dataForSeoBase(args)
|
|
7377
|
+
}));
|
|
7807
7378
|
}
|
|
7808
|
-
}
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
description: "Target domain"
|
|
7379
|
+
});
|
|
7380
|
+
const dataForSeoCompetitorsCommand = defineCommand({
|
|
7381
|
+
meta: {
|
|
7382
|
+
name: "competitors",
|
|
7383
|
+
description: "Fetch competitor-domain data"
|
|
7384
|
+
},
|
|
7385
|
+
args: {
|
|
7386
|
+
domain: {
|
|
7387
|
+
type: "positional",
|
|
7388
|
+
description: "Target domain",
|
|
7389
|
+
required: true
|
|
7390
|
+
},
|
|
7391
|
+
...dataForSeoCommonArgs,
|
|
7392
|
+
limit: {
|
|
7393
|
+
type: "string",
|
|
7394
|
+
description: "Maximum competitors",
|
|
7395
|
+
default: "20"
|
|
7396
|
+
}
|
|
7827
7397
|
},
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7398
|
+
async run({ args }) {
|
|
7399
|
+
await runSeoRequest(args, "/api/dataforseo/competitors", () => providerService.dataForSeoCompetitors({
|
|
7400
|
+
domain: requireArg(args.domain, "domain"),
|
|
7401
|
+
limit: toNumberValue(args.limit, 20),
|
|
7402
|
+
...dataForSeoBase(args)
|
|
7403
|
+
}));
|
|
7404
|
+
}
|
|
7405
|
+
});
|
|
7406
|
+
const dataForSeoAiScrapeCommand = defineCommand({
|
|
7407
|
+
meta: {
|
|
7408
|
+
name: "ai-scrape",
|
|
7409
|
+
description: "Fetch DataForSEO AI scraper response data"
|
|
7410
|
+
},
|
|
7411
|
+
args: {
|
|
7412
|
+
keyword: {
|
|
7413
|
+
type: "positional",
|
|
7414
|
+
description: "AI search keyword",
|
|
7415
|
+
required: true
|
|
7416
|
+
},
|
|
7417
|
+
...dataForSeoCommonArgs,
|
|
7418
|
+
platform: {
|
|
7419
|
+
type: "string",
|
|
7420
|
+
description: "AI platform: chat_gpt, gemini, or perplexity",
|
|
7421
|
+
default: "chat_gpt"
|
|
7422
|
+
}
|
|
7831
7423
|
},
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7424
|
+
async run({ args }) {
|
|
7425
|
+
await runSeoRequest(args, "/api/dataforseo/ai-scrape", () => providerService.dataForSeoAiScrape({
|
|
7426
|
+
keyword: requireArg(args.keyword, "keyword"),
|
|
7427
|
+
platform: aiScrapePlatform(args.platform),
|
|
7428
|
+
...dataForSeoBase(args)
|
|
7429
|
+
}));
|
|
7430
|
+
}
|
|
7431
|
+
});
|
|
7432
|
+
const dataForSeoAiMentionsCommand = defineCommand({
|
|
7433
|
+
meta: {
|
|
7434
|
+
name: "ai-mentions",
|
|
7435
|
+
description: "Fetch DataForSEO LLM mention data"
|
|
7436
|
+
},
|
|
7437
|
+
args: {
|
|
7438
|
+
...dataForSeoCommonArgs,
|
|
7439
|
+
domain: {
|
|
7440
|
+
type: "string",
|
|
7441
|
+
description: "Target domain"
|
|
7442
|
+
},
|
|
7443
|
+
keyword: {
|
|
7444
|
+
type: "string",
|
|
7445
|
+
description: "Target keyword"
|
|
7446
|
+
},
|
|
7447
|
+
platform: {
|
|
7448
|
+
type: "string",
|
|
7449
|
+
description: "Mention platform: google or chat_gpt",
|
|
7450
|
+
default: "google"
|
|
7451
|
+
},
|
|
7452
|
+
limit: {
|
|
7453
|
+
type: "string",
|
|
7454
|
+
description: "Maximum mentions",
|
|
7455
|
+
default: "20"
|
|
7456
|
+
}
|
|
7836
7457
|
},
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7458
|
+
async run({ args }) {
|
|
7459
|
+
if (!toStringValue(args.domain) && !toStringValue(args.keyword)) {
|
|
7460
|
+
consola$1.error("domain or keyword is required.");
|
|
7461
|
+
process.exit(1);
|
|
7462
|
+
}
|
|
7463
|
+
await runSeoRequest(args, "/api/dataforseo/ai-mentions", () => providerService.dataForSeoAiMentions({
|
|
7464
|
+
domain: toStringValue(args.domain),
|
|
7465
|
+
keyword: toStringValue(args.keyword),
|
|
7466
|
+
platform: aiMentionPlatform(args.platform),
|
|
7467
|
+
limit: toNumberValue(args.limit, 20),
|
|
7468
|
+
...dataForSeoBase(args)
|
|
7469
|
+
}));
|
|
7841
7470
|
}
|
|
7842
|
-
}
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7471
|
+
});
|
|
7472
|
+
return defineCommand({
|
|
7473
|
+
meta: {
|
|
7474
|
+
name: "seo",
|
|
7475
|
+
description: "Local site evidence and hosted SEO provider workflows"
|
|
7476
|
+
},
|
|
7477
|
+
subCommands: {
|
|
7478
|
+
crawl: crawlCommand,
|
|
7479
|
+
firecrawl: firecrawlCommand,
|
|
7480
|
+
dataforseo: defineCommand({
|
|
7481
|
+
meta: {
|
|
7482
|
+
name: "dataforseo",
|
|
7483
|
+
description: "Forgeway-hosted DataForSEO workflows"
|
|
7484
|
+
},
|
|
7485
|
+
subCommands: {
|
|
7486
|
+
serp: dataForSeoSerpCommand,
|
|
7487
|
+
maps: dataForSeoMapsCommand,
|
|
7488
|
+
keywords: dataForSeoKeywordsCommand,
|
|
7489
|
+
competitors: dataForSeoCompetitorsCommand,
|
|
7490
|
+
"ai-scrape": dataForSeoAiScrapeCommand,
|
|
7491
|
+
"ai-mentions": dataForSeoAiMentionsCommand
|
|
7492
|
+
}
|
|
7493
|
+
})
|
|
7847
7494
|
}
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
platform: toStringValue(args.platform) ?? "google",
|
|
7852
|
-
limit: toNumberValue(args.limit, 20),
|
|
7853
|
-
...dataForSeoBase(args)
|
|
7854
|
-
});
|
|
7855
|
-
}
|
|
7856
|
-
});
|
|
7857
|
-
const dataForSeoCommand = defineCommand({
|
|
7858
|
-
meta: {
|
|
7859
|
-
name: "dataforseo",
|
|
7860
|
-
description: "Forgeway-hosted DataForSEO workflows"
|
|
7861
|
-
},
|
|
7862
|
-
subCommands: {
|
|
7863
|
-
serp: dataForSeoSerpCommand,
|
|
7864
|
-
maps: dataForSeoMapsCommand,
|
|
7865
|
-
keywords: dataForSeoKeywordsCommand,
|
|
7866
|
-
competitors: dataForSeoCompetitorsCommand,
|
|
7867
|
-
"ai-scrape": dataForSeoAiScrapeCommand,
|
|
7868
|
-
"ai-mentions": dataForSeoAiMentionsCommand
|
|
7869
|
-
}
|
|
7870
|
-
});
|
|
7871
|
-
const seoCommand = defineCommand({
|
|
7872
|
-
meta: {
|
|
7873
|
-
name: "seo",
|
|
7874
|
-
description: "Hosted SEO provider workflows through Forgeway"
|
|
7875
|
-
},
|
|
7876
|
-
subCommands: {
|
|
7877
|
-
firecrawl: firecrawlCommand,
|
|
7878
|
-
dataforseo: dataForSeoCommand
|
|
7879
|
-
}
|
|
7880
|
-
});
|
|
7495
|
+
});
|
|
7496
|
+
}
|
|
7497
|
+
const seoCommand = createSeoCommand();
|
|
7881
7498
|
|
|
7882
7499
|
//#endregion
|
|
7883
7500
|
//#region src/cli/commands/taxonomy.ts
|
|
@@ -8047,6 +7664,88 @@ const typesCommand = defineCommand({
|
|
|
8047
7664
|
}
|
|
8048
7665
|
});
|
|
8049
7666
|
|
|
7667
|
+
//#endregion
|
|
7668
|
+
//#region src/cli/contributions.ts
|
|
7669
|
+
const DINEWAY_PLUGIN_PACKAGE = /^(?:@dineway-ai\/plugin-|dineway-plugin-)/;
|
|
7670
|
+
const CLI_COMMAND_NAME = /^[a-z][a-z0-9-]*$/;
|
|
7671
|
+
async function discoverDinewayCliContributions(cwd = process.cwd()) {
|
|
7672
|
+
const projectPackage = await findProjectPackage(cwd);
|
|
7673
|
+
if (!projectPackage) return [];
|
|
7674
|
+
const { packagePath, project } = projectPackage;
|
|
7675
|
+
const requireFromProject = createRequire(packagePath);
|
|
7676
|
+
const packageNames = new Set([
|
|
7677
|
+
...Object.keys(project.dependencies ?? {}),
|
|
7678
|
+
...Object.keys(project.devDependencies ?? {}),
|
|
7679
|
+
...Object.keys(project.optionalDependencies ?? {})
|
|
7680
|
+
]);
|
|
7681
|
+
const contributions = [];
|
|
7682
|
+
for (const packageName of [...packageNames].filter(isDinewayPluginPackage).toSorted()) {
|
|
7683
|
+
const specifier = `${packageName}/cli`;
|
|
7684
|
+
let modulePath;
|
|
7685
|
+
try {
|
|
7686
|
+
modulePath = requireFromProject.resolve(specifier);
|
|
7687
|
+
} catch (error) {
|
|
7688
|
+
if (isMissingContribution(error)) continue;
|
|
7689
|
+
throw error;
|
|
7690
|
+
}
|
|
7691
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
7692
|
+
const value = loaded.dinewayCliContribution ?? loaded.default;
|
|
7693
|
+
if (!isDinewayCliContribution(value)) throw new Error(`${specifier} does not export a valid Dineway CLI contribution`);
|
|
7694
|
+
contributions.push({
|
|
7695
|
+
packageName,
|
|
7696
|
+
commands: value.commands
|
|
7697
|
+
});
|
|
7698
|
+
}
|
|
7699
|
+
return contributions;
|
|
7700
|
+
}
|
|
7701
|
+
async function findProjectPackage(from) {
|
|
7702
|
+
let directory = resolve(from);
|
|
7703
|
+
while (true) {
|
|
7704
|
+
const packagePath = join(directory, "package.json");
|
|
7705
|
+
const project = await readProjectPackage(packagePath);
|
|
7706
|
+
if (project) return {
|
|
7707
|
+
packagePath,
|
|
7708
|
+
project
|
|
7709
|
+
};
|
|
7710
|
+
const parent = dirname(directory);
|
|
7711
|
+
if (parent === directory) return null;
|
|
7712
|
+
directory = parent;
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
function mergeDinewayCliContributions(builtIns, contributions) {
|
|
7716
|
+
const commands = { ...builtIns };
|
|
7717
|
+
for (const contribution of contributions) for (const [name, command] of Object.entries(contribution.commands).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
7718
|
+
if (commands[name]) throw new Error(`Dineway CLI command "${name}" from ${contribution.packageName} conflicts with an existing command`);
|
|
7719
|
+
commands[name] = command;
|
|
7720
|
+
}
|
|
7721
|
+
return commands;
|
|
7722
|
+
}
|
|
7723
|
+
async function readProjectPackage(path) {
|
|
7724
|
+
try {
|
|
7725
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
7726
|
+
return isRecord(parsed) ? parsed : null;
|
|
7727
|
+
} catch (error) {
|
|
7728
|
+
if (isFileNotFound(error)) return null;
|
|
7729
|
+
throw error;
|
|
7730
|
+
}
|
|
7731
|
+
}
|
|
7732
|
+
function isDinewayPluginPackage(packageName) {
|
|
7733
|
+
return DINEWAY_PLUGIN_PACKAGE.test(packageName);
|
|
7734
|
+
}
|
|
7735
|
+
function isDinewayCliContribution(value) {
|
|
7736
|
+
if (!isRecord(value) || !isRecord(value.commands)) return false;
|
|
7737
|
+
return Object.entries(value.commands).every(([name, command]) => CLI_COMMAND_NAME.test(name) && isRecord(command));
|
|
7738
|
+
}
|
|
7739
|
+
function isRecord(value) {
|
|
7740
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7741
|
+
}
|
|
7742
|
+
function isMissingContribution(error) {
|
|
7743
|
+
return error instanceof Error && "code" in error && (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED");
|
|
7744
|
+
}
|
|
7745
|
+
function isFileNotFound(error) {
|
|
7746
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
7747
|
+
}
|
|
7748
|
+
|
|
8050
7749
|
//#endregion
|
|
8051
7750
|
//#region src/cli/index.ts
|
|
8052
7751
|
/**
|
|
@@ -8072,36 +7771,40 @@ const typesCommand = defineCommand({
|
|
|
8072
7771
|
* - deploy: Deploy a standard Dineway Node server through a provider CLI
|
|
8073
7772
|
* - plugin: Plugin management (init, bundle, validate, publish, login, logout)
|
|
8074
7773
|
*/
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
}
|
|
7774
|
+
const builtInCommands = {
|
|
7775
|
+
init: initCommand,
|
|
7776
|
+
account: accountCommand,
|
|
7777
|
+
types: typesCommand,
|
|
7778
|
+
dev: devCommand,
|
|
7779
|
+
deploy: deployCommand,
|
|
7780
|
+
doctor: doctorCommand,
|
|
7781
|
+
seed: seedCommand,
|
|
7782
|
+
"export-seed": exportSeedCommand,
|
|
7783
|
+
secrets: secretsCommand,
|
|
7784
|
+
auth: authCommand,
|
|
7785
|
+
login: loginCommand,
|
|
7786
|
+
logout: logoutCommand,
|
|
7787
|
+
whoami: whoamiCommand,
|
|
7788
|
+
content: contentCommand,
|
|
7789
|
+
schema: schemaCommand,
|
|
7790
|
+
media: mediaCommand,
|
|
7791
|
+
search: searchCommand,
|
|
7792
|
+
seo: seoCommand,
|
|
7793
|
+
taxonomy: taxonomyCommand,
|
|
7794
|
+
menu: menuCommand,
|
|
7795
|
+
plugin: pluginCommand
|
|
7796
|
+
};
|
|
7797
|
+
async function createDinewayCliCommand(cwd = process.cwd()) {
|
|
7798
|
+
return defineCommand({
|
|
7799
|
+
meta: {
|
|
7800
|
+
name: "dineway",
|
|
7801
|
+
version: "0.0.0",
|
|
7802
|
+
description: "CLI for Dineway Agentic Web builder"
|
|
7803
|
+
},
|
|
7804
|
+
subCommands: mergeDinewayCliContributions(builtInCommands, await discoverDinewayCliContributions(cwd))
|
|
7805
|
+
});
|
|
7806
|
+
}
|
|
7807
|
+
createDinewayCliCommand().then((main) => runMain(main));
|
|
8105
7808
|
|
|
8106
7809
|
//#endregion
|
|
8107
|
-
export {
|
|
7810
|
+
export { createDinewayCliCommand };
|