ghost 5.115.0 → 5.116.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/{tryghost-api-framework-5.115.0.tgz → tryghost-api-framework-5.116.0.tgz} +0 -0
- package/components/tryghost-constants-5.116.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.116.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.115.0.tgz → tryghost-custom-theme-settings-service-5.116.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.116.0.tgz +0 -0
- package/components/{tryghost-donations-5.115.0.tgz → tryghost-donations-5.116.0.tgz} +0 -0
- package/components/tryghost-email-addresses-5.116.0.tgz +0 -0
- package/components/tryghost-email-service-5.116.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.116.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.116.0.tgz +0 -0
- package/components/tryghost-i18n-5.116.0.tgz +0 -0
- package/components/tryghost-job-manager-5.116.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.116.0.tgz +0 -0
- package/components/tryghost-magic-link-5.116.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.116.0.tgz +0 -0
- package/components/tryghost-member-events-5.116.0.tgz +0 -0
- package/components/tryghost-members-api-5.116.0.tgz +0 -0
- package/components/tryghost-members-csv-5.116.0.tgz +0 -0
- package/components/{tryghost-members-offers-5.115.0.tgz → tryghost-members-offers-5.116.0.tgz} +0 -0
- package/components/{tryghost-milestones-5.115.0.tgz → tryghost-milestones-5.116.0.tgz} +0 -0
- package/components/{tryghost-mw-error-handler-5.115.0.tgz → tryghost-mw-error-handler-5.116.0.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.116.0.tgz +0 -0
- package/components/{tryghost-post-events-5.115.0.tgz → tryghost-post-events-5.116.0.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.115.0.tgz → tryghost-post-revisions-5.116.0.tgz} +0 -0
- package/components/tryghost-posts-service-5.116.0.tgz +0 -0
- package/components/{tryghost-prometheus-metrics-5.115.0.tgz → tryghost-prometheus-metrics-5.116.0.tgz} +0 -0
- package/components/tryghost-security-5.116.0.tgz +0 -0
- package/components/{tryghost-tiers-5.115.0.tgz → tryghost-tiers-5.116.0.tgz} +0 -0
- package/components/tryghost-webmentions-5.116.0.tgz +0 -0
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +1 -1
- package/content/themes/source/LICENSE +1 -1
- package/content/themes/source/README.md +1 -1
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/screen.css.map +1 -1
- package/content/themes/source/assets/css/screen.css +11 -6
- package/content/themes/source/partials/feature-image.hbs +2 -2
- package/core/boot.js +3 -43
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +30494 -29403
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-a9601514.mjs} +5 -4
- package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-c1789d04.mjs} +67 -65
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-e9c9deb8.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-84580c3a.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f744cab7.mjs} +3147 -3123
- package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-d9ca60c5.mjs} +1198 -1192
- package/core/built/admin/assets/chunk.524.8371443ef8f60db429d0.js +35 -0
- package/core/built/admin/assets/chunk.582.f90151775f2e53dd21d9.js +37 -0
- package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js → chunk.713.e9027c0cc3c56110f5da.js} +125 -98
- package/core/built/admin/assets/{ghost-938b3d9c29e3564a53a22f8c8f82d351.js → ghost-03b64c086f3c60cabc85fe7a7e2b640a.js} +272 -251
- package/core/built/admin/assets/ghost-ba58e9822f7384461e926c7e23f04a75.css +1 -0
- package/core/built/admin/assets/ghost-dark-f1f29683b14ffa11615b3bba8b6ab92c.css +1 -0
- package/core/built/admin/assets/koenig-lexical/index.css +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +20563 -20891
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +139 -139
- package/core/built/admin/assets/posts/posts.js +5732 -5667
- package/core/built/admin/assets/stats/stats.js +75373 -0
- package/core/built/admin/assets/{vendor-68a4aa424a179a90f5bbc2b750def576.js → vendor-72026232b36d97babc6320917c16c321.js} +36 -34
- package/core/built/admin/index.html +6 -6
- package/core/cli/generate-data.js +1 -1
- package/core/frontend/helpers/ghost_head.js +8 -1
- package/core/frontend/public/ghost-stats.js +55 -2
- package/core/frontend/services/assets-minification/AdminAuthAssets.js +2 -1
- package/core/frontend/services/assets-minification/CardAssets.js +1 -1
- package/core/frontend/services/assets-minification/CommentCountsAssets.js +1 -1
- package/core/frontend/services/assets-minification/MemberAttributionAssets.js +1 -1
- package/core/frontend/services/assets-minification/Minifier.js +191 -0
- package/core/frontend/services/routing/controllers/previews.js +2 -1
- package/core/frontend/src/cards/css/cta.css +1 -1
- package/core/server/adapters/cache/Redis.js +1 -1
- package/core/server/adapters/lib/redis/AdapterCacheRedis.js +287 -0
- package/core/server/adapters/lib/redis/redis-store-factory.js +22 -0
- package/core/server/api/endpoints/posts.js +9 -3
- package/core/server/api/endpoints/previews.js +35 -1
- package/core/server/api/endpoints/slugs.js +6 -2
- package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -9
- package/core/server/api/endpoints/utils/validators/input/settings.js +1 -1
- package/core/server/data/db/connection.js +2 -0
- package/core/server/data/db/index.js +1 -0
- package/core/server/data/importer/handlers/ImporterContentFileHandler.js +90 -0
- package/core/server/data/importer/import-manager.js +3 -3
- package/core/server/data/importer/importers/importer-revue.js +128 -0
- package/core/server/data/importer/importers/json-to-html.js +107 -0
- package/core/server/data/migrations/utils/tables.js +2 -4
- package/core/server/data/seeders/DataGenerator.js +288 -0
- package/core/server/data/seeders/importers/BenefitsImporter.js +28 -0
- package/core/server/data/seeders/importers/CommentsImporter.js +73 -0
- package/core/server/data/seeders/importers/EmailBatchesImporter.js +38 -0
- package/core/server/data/seeders/importers/EmailRecipientFailuresImporter.js +67 -0
- package/core/server/data/seeders/importers/EmailRecipientsImporter.js +212 -0
- package/core/server/data/seeders/importers/EmailsImporter.js +99 -0
- package/core/server/data/seeders/importers/LabelsImporter.js +41 -0
- package/core/server/data/seeders/importers/MembersClickEventsImporter.js +69 -0
- package/core/server/data/seeders/importers/MembersCreatedEventsImporter.js +103 -0
- package/core/server/data/seeders/importers/MembersFeedbackImporter.js +45 -0
- package/core/server/data/seeders/importers/MembersImporter.js +111 -0
- package/core/server/data/seeders/importers/MembersLabelsImporter.js +39 -0
- package/core/server/data/seeders/importers/MembersLoginEventsImporter.js +69 -0
- package/core/server/data/seeders/importers/MembersNewslettersImporter.js +38 -0
- package/core/server/data/seeders/importers/MembersPaidSubscriptionEventsImporter.js +99 -0
- package/core/server/data/seeders/importers/MembersProductsImporter.js +42 -0
- package/core/server/data/seeders/importers/MembersStatusEventsImporter.js +58 -0
- package/core/server/data/seeders/importers/MembersStripeCustomersImporter.js +60 -0
- package/core/server/data/seeders/importers/MembersStripeCustomersSubscriptionsImporter.js +259 -0
- package/core/server/data/seeders/importers/MembersSubscribeEventsImporter.js +69 -0
- package/core/server/data/seeders/importers/MembersSubscriptionCreatedEventsImporter.js +95 -0
- package/core/server/data/seeders/importers/NewslettersImporter.js +40 -0
- package/core/server/data/seeders/importers/OffersImporter.js +70 -0
- package/core/server/data/seeders/importers/PostsAuthorsImporter.js +32 -0
- package/core/server/data/seeders/importers/PostsImporter.js +102 -0
- package/core/server/data/seeders/importers/PostsProductsImporter.js +35 -0
- package/core/server/data/seeders/importers/PostsTagsImporter.js +46 -0
- package/core/server/data/seeders/importers/ProductsBenefitsImporter.js +54 -0
- package/core/server/data/seeders/importers/ProductsImporter.js +90 -0
- package/core/server/data/seeders/importers/RecommendationClickEventsImporter.js +32 -0
- package/core/server/data/seeders/importers/RecommendationSubscribeEventsImporter.js +32 -0
- package/core/server/data/seeders/importers/RecommendationsImporter.js +34 -0
- package/core/server/data/seeders/importers/RedirectsImporter.js +49 -0
- package/core/server/data/seeders/importers/RolesUsersImporter.js +42 -0
- package/core/server/data/seeders/importers/StripePricesImporter.js +69 -0
- package/core/server/data/seeders/importers/StripeProductsImporter.js +34 -0
- package/core/server/data/seeders/importers/TableImporter.js +187 -0
- package/core/server/data/seeders/importers/TagsImporter.js +41 -0
- package/core/server/data/seeders/importers/UsersImporter.js +31 -0
- package/core/server/data/seeders/importers/WebMentionsImporter.js +42 -0
- package/core/server/data/seeders/importers/index.js +41 -0
- package/core/server/data/seeders/utils/JsonImporter.js +39 -0
- package/core/server/data/seeders/utils/blog-info.js +3 -0
- package/core/server/data/seeders/utils/database-date.js +7 -0
- package/core/server/data/seeders/utils/event-generator.js +48 -0
- package/core/server/data/seeders/utils/random.js +13 -0
- package/core/server/data/seeders/utils/topological-sort.js +33 -0
- package/core/server/lib/bootstrap-socket.js +87 -0
- package/core/server/lib/package-json/index.js +1 -0
- package/core/server/lib/package-json/package-json.js +160 -0
- package/core/server/lib/package-json/parse.js +57 -0
- package/core/server/models/base/plugins/actions.js +44 -31
- package/core/server/models/base/plugins/generate-slug.js +6 -0
- package/core/server/notify.js +1 -1
- package/core/server/services/activitypub/ActivityPubService.ts +1 -1
- package/core/server/services/adapter-manager/AdapterManager.js +161 -0
- package/core/server/services/adapter-manager/index.js +1 -1
- package/core/server/services/announcement-bar-service/AnnouncementBarSettings.js +54 -0
- package/core/server/services/announcement-bar-service/AnnouncementVisibilityValues.js +11 -0
- package/core/server/services/announcement-bar-service/index.js +1 -1
- package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +99 -0
- package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
- package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
- package/core/server/services/api-version-compatibility/index.js +2 -2
- package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
- package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
- package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
- package/core/server/services/audience-feedback/Feedback.js +35 -0
- package/core/server/services/audience-feedback/index.js +4 -2
- package/core/server/services/auth/session/emails/signin.js +168 -0
- package/core/server/services/auth/session/index.js +2 -2
- package/core/server/services/auth/session/session-from-token.js +69 -0
- package/core/server/services/auth/session/session-service.js +374 -0
- package/core/server/services/custom-redirects/index.js +1 -1
- package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
- package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
- package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
- package/core/server/services/email-service/EmailServiceWrapper.js +4 -4
- package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +1 -1
- package/core/server/services/email-suppression-list/service.js +1 -1
- package/core/server/services/explore-ping/ExplorePingService.js +106 -0
- package/core/server/services/explore-ping/index.js +31 -0
- package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
- package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
- package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
- package/core/server/services/invitations/accept.js +5 -2
- package/core/server/services/lib/DynamicRedirectManager.js +156 -0
- package/core/server/services/lib/EmailContentGenerator.js +54 -0
- package/core/server/services/lib/InMemoryRepository.js +62 -0
- package/core/server/services/lib/InMemoryRepository.ts +80 -0
- package/core/server/services/lib/MailgunClient.js +364 -0
- package/core/server/services/link-redirection/LinkRedirect.js +26 -0
- package/core/server/services/link-redirection/LinkRedirectRepository.js +7 -7
- package/core/server/services/link-redirection/LinkRedirectsService.js +123 -0
- package/core/server/services/link-redirection/README.md +151 -0
- package/core/server/services/link-redirection/RedirectEvent.js +24 -0
- package/core/server/services/link-redirection/index.js +1 -1
- package/core/server/services/link-tracking/LinkClickTrackingService.js +1 -1
- package/core/server/services/mail/index.js +1 -1
- package/core/server/services/mail-events/BookshelfMailEventRepository.js +2 -2
- package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
- package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
- package/core/server/services/mail-events/MailEvent.js +20 -0
- package/core/server/services/mail-events/MailEvent.ts +10 -0
- package/core/server/services/mail-events/MailEventRepository.js +2 -0
- package/core/server/services/mail-events/MailEventRepository.ts +5 -0
- package/core/server/services/mail-events/MailEventService.js +124 -0
- package/core/server/services/mail-events/MailEventService.ts +169 -0
- package/core/server/services/mail-events/index.js +1 -1
- package/core/server/services/mail-events/libraries.d.ts +2 -0
- package/core/server/services/members/CaptchaService.js +80 -0
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
- package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
- package/core/server/services/members/importer/email-template.js +182 -0
- package/core/server/services/members/importer/index.js +30 -0
- package/core/server/services/members/members-ssr.js +333 -0
- package/core/server/services/members/service.js +2 -2
- package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
- package/core/server/services/offers/service.js +1 -1
- package/core/server/services/posts/stats/PostStats.js +13 -0
- package/core/server/services/recommendations/RecommendationServiceWrapper.js +8 -8
- package/core/server/services/recommendations/service/BookshelfClickEventRepository.js +48 -0
- package/core/server/services/recommendations/service/BookshelfClickEventRepository.ts +49 -0
- package/core/server/services/recommendations/service/BookshelfRecommendationRepository.js +98 -0
- package/core/server/services/recommendations/service/BookshelfRecommendationRepository.ts +117 -0
- package/core/server/services/recommendations/service/BookshelfRepository.js +134 -0
- package/core/server/services/recommendations/service/BookshelfRepository.ts +196 -0
- package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.js +48 -0
- package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.ts +49 -0
- package/core/server/services/recommendations/service/ClickEvent.js +33 -0
- package/core/server/services/recommendations/service/ClickEvent.ts +32 -0
- package/core/server/services/recommendations/service/InMemoryRecommendationRepository.js +19 -0
- package/core/server/services/recommendations/service/InMemoryRecommendationRepository.ts +20 -0
- package/core/server/services/recommendations/service/IncomingRecommendationController.js +34 -0
- package/core/server/services/recommendations/service/IncomingRecommendationController.ts +51 -0
- package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.js +25 -0
- package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.ts +37 -0
- package/core/server/services/recommendations/service/IncomingRecommendationService.js +93 -0
- package/core/server/services/recommendations/service/IncomingRecommendationService.ts +160 -0
- package/core/server/services/recommendations/service/Recommendation.js +140 -0
- package/core/server/services/recommendations/service/Recommendation.ts +201 -0
- package/core/server/services/recommendations/service/RecommendationController.js +208 -0
- package/core/server/services/recommendations/service/RecommendationController.ts +258 -0
- package/core/server/services/recommendations/service/RecommendationMetadataService.js +86 -0
- package/core/server/services/recommendations/service/RecommendationMetadataService.ts +128 -0
- package/core/server/services/recommendations/service/RecommendationRepository.js +2 -0
- package/core/server/services/recommendations/service/RecommendationRepository.ts +13 -0
- package/core/server/services/recommendations/service/RecommendationService.js +228 -0
- package/core/server/services/recommendations/service/RecommendationService.ts +281 -0
- package/core/server/services/recommendations/service/SubscribeEvent.js +33 -0
- package/core/server/services/recommendations/service/SubscribeEvent.ts +32 -0
- package/core/server/services/recommendations/service/UnsafeData.js +183 -0
- package/core/server/services/recommendations/service/UnsafeData.ts +217 -0
- package/core/server/services/recommendations/service/WellknownService.js +36 -0
- package/core/server/services/recommendations/service/WellknownService.ts +47 -0
- package/core/server/services/recommendations/service/index.js +31 -0
- package/core/server/services/recommendations/service/index.ts +15 -0
- package/core/server/services/recommendations/service/libraries.d.ts +5 -0
- package/core/server/services/route-settings/SettingsPathManager.js +47 -0
- package/core/server/services/route-settings/index.js +1 -1
- package/core/server/services/slack-notifications/SlackNotifications.js +211 -0
- package/core/server/services/slack-notifications/SlackNotificationsService.js +90 -0
- package/core/server/services/slack-notifications/service.js +4 -6
- package/core/server/services/stripe/README.md +63 -0
- package/core/server/services/stripe/StripeAPI.js +931 -0
- package/core/server/services/stripe/StripeMigrations.js +613 -0
- package/core/server/services/stripe/StripeService.js +175 -0
- package/core/server/services/stripe/WebhookController.js +100 -0
- package/core/server/services/stripe/WebhookManager.js +175 -0
- package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
- package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
- package/core/server/services/stripe/events/index.js +4 -0
- package/core/server/services/stripe/service.js +1 -1
- package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
- package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
- package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
- package/core/server/services/themes/loader.js +1 -1
- package/core/server/services/themes/to-json.js +1 -1
- package/core/server/web/api/endpoints/admin/app.js +1 -21
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/api/middleware/version-match.js +41 -0
- package/core/server/web/shared/middleware/cache-control.js +51 -0
- package/core/server/web/shared/middleware/index.js +1 -1
- package/core/server/web/well-known.js +1 -1
- package/core/shared/labs.js +5 -3
- package/core/shared/settings-cache/CacheManager.js +64 -6
- package/package.json +98 -146
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +1478 -1634
- package/components/tryghost-adapter-cache-redis-5.115.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.115.0.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.115.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.115.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.115.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.115.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.115.0.tgz +0 -0
- package/components/tryghost-captcha-service-5.115.0.tgz +0 -0
- package/components/tryghost-constants-5.115.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.0.tgz +0 -0
- package/components/tryghost-data-generator-5.115.0.tgz +0 -0
- package/components/tryghost-domain-events-5.115.0.tgz +0 -0
- package/components/tryghost-email-addresses-5.115.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.115.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.115.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.115.0.tgz +0 -0
- package/components/tryghost-email-events-5.115.0.tgz +0 -0
- package/components/tryghost-email-service-5.115.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.115.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.115.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.115.0.tgz +0 -0
- package/components/tryghost-ghost-5.115.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.115.0.tgz +0 -0
- package/components/tryghost-i18n-5.115.0.tgz +0 -0
- package/components/tryghost-identity-token-service-5.115.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.115.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.115.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.0.tgz +0 -0
- package/components/tryghost-job-manager-5.115.0.tgz +0 -0
- package/components/tryghost-link-redirects-5.115.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.115.0.tgz +0 -0
- package/components/tryghost-magic-link-5.115.0.tgz +0 -0
- package/components/tryghost-mail-events-5.115.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.115.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.115.0.tgz +0 -0
- package/components/tryghost-member-events-5.115.0.tgz +0 -0
- package/components/tryghost-members-api-5.115.0.tgz +0 -0
- package/components/tryghost-members-csv-5.115.0.tgz +0 -0
- package/components/tryghost-members-importer-5.115.0.tgz +0 -0
- package/components/tryghost-members-payments-5.115.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.115.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.115.0.tgz +0 -0
- package/components/tryghost-minifier-5.115.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.115.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.115.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.115.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.115.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.115.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.115.0.tgz +0 -0
- package/components/tryghost-package-json-5.115.0.tgz +0 -0
- package/components/tryghost-posts-service-5.115.0.tgz +0 -0
- package/components/tryghost-recommendations-5.115.0.tgz +0 -0
- package/components/tryghost-referrers-5.115.0.tgz +0 -0
- package/components/tryghost-security-5.115.0.tgz +0 -0
- package/components/tryghost-session-service-5.115.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.115.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.115.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.115.0.tgz +0 -0
- package/components/tryghost-webmentions-5.115.0.tgz +0 -0
- package/core/built/admin/assets/chunk.524.31419fdf6fb3859ecc1e.js +0 -35
- package/core/built/admin/assets/chunk.582.08c816d5e4ab766486a7.js +0 -37
- package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
- package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.css +0 -1
- /package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js.LICENSE.txt → chunk.713.e9027c0cc3c56110f5da.js.LICENSE.txt} +0 -0
|
@@ -2,7 +2,7 @@ const _ = require('lodash');
|
|
|
2
2
|
const {ValidationError} = require('@tryghost/errors');
|
|
3
3
|
const validator = require('@tryghost/validator');
|
|
4
4
|
const tpl = require('@tryghost/tpl');
|
|
5
|
-
const AnnouncementBarSettings = require('
|
|
5
|
+
const AnnouncementBarSettings = require('../../../../../services/announcement-bar-service/AnnouncementBarSettings');
|
|
6
6
|
|
|
7
7
|
const messages = {
|
|
8
8
|
invalidEmailReceived: 'Please send a valid email',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class ImporterContentFileHandler {
|
|
5
|
+
/** @property {'media' | 'files' | 'images'} */
|
|
6
|
+
type;
|
|
7
|
+
|
|
8
|
+
/** @property {string[]} */
|
|
9
|
+
directories;
|
|
10
|
+
|
|
11
|
+
/** @property {string[]} */
|
|
12
|
+
extensions;
|
|
13
|
+
|
|
14
|
+
/** @property {string[]} */
|
|
15
|
+
contentTypes;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Holds path to the destination content directory
|
|
19
|
+
* @property {string} */
|
|
20
|
+
#contentPath;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} deps dependencies
|
|
25
|
+
* @param {'media' | 'files' | 'images'} deps.type type of content file
|
|
26
|
+
* @param {string[]} deps.extensions file extensions to search for
|
|
27
|
+
* @param {boolean} [deps.ignoreRootFolderFiles] whether to ignore files in the root folder
|
|
28
|
+
* @param {string[]} deps.contentTypes content types to search for
|
|
29
|
+
* @param {string[]} deps.directories directories to search for content files
|
|
30
|
+
* @param {string} deps.contentPath path to the destination content directory
|
|
31
|
+
* @param {Object} deps.storage storage adapter instance
|
|
32
|
+
* @param {object} deps.urlUtils urlUtils instance
|
|
33
|
+
*/
|
|
34
|
+
constructor(deps) {
|
|
35
|
+
this.type = deps.type;
|
|
36
|
+
this.directories = deps.directories;
|
|
37
|
+
this.extensions = deps.extensions;
|
|
38
|
+
this.contentTypes = deps.contentTypes;
|
|
39
|
+
this.ignoreRootFolderFiles = deps.ignoreRootFolderFiles;
|
|
40
|
+
this.storage = deps.storage;
|
|
41
|
+
this.#contentPath = deps.contentPath;
|
|
42
|
+
this.urlUtils = deps.urlUtils;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async loadFile(files, baseDir) {
|
|
46
|
+
const baseDirRegex = baseDir ? new RegExp('^' + baseDir + '/') : new RegExp('');
|
|
47
|
+
|
|
48
|
+
const contentFilesFolderRegexes = _.map(this.storage.staticFileURLPrefix.split('/'), function (dir) {
|
|
49
|
+
return new RegExp('^' + dir + '/');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (this.ignoreRootFolderFiles) {
|
|
53
|
+
files = _.filter(files, function (file) {
|
|
54
|
+
return file.name.indexOf('/') !== -1;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// normalize the directory structure
|
|
59
|
+
const filesContentPath = this.#contentPath;
|
|
60
|
+
files = _.map(files, function (file) {
|
|
61
|
+
const noBaseDir = file.name.replace(baseDirRegex, '');
|
|
62
|
+
let noGhostDirs = noBaseDir;
|
|
63
|
+
|
|
64
|
+
_.each(contentFilesFolderRegexes, function (regex) {
|
|
65
|
+
noGhostDirs = noGhostDirs.replace(regex, '');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
file.originalPath = noBaseDir;
|
|
69
|
+
file.name = noGhostDirs;
|
|
70
|
+
file.targetDir = path.join(filesContentPath, path.dirname(noGhostDirs));
|
|
71
|
+
return file;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const self = this;
|
|
75
|
+
return Promise.all(files.map(function (contentFile) {
|
|
76
|
+
return self.storage.getUniqueFileName(contentFile, contentFile.targetDir).then(function (targetFilename) {
|
|
77
|
+
contentFile.newPath = self.urlUtils.urlJoin(
|
|
78
|
+
'/',
|
|
79
|
+
self.urlUtils.getSubdir(),
|
|
80
|
+
self.storage.staticFileURLPrefix,
|
|
81
|
+
path.relative(filesContentPath, targetFilename)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return contentFile;
|
|
85
|
+
});
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = ImporterContentFileHandler;
|
|
@@ -11,12 +11,12 @@ const debug = require('@tryghost/debug')('import-manager');
|
|
|
11
11
|
const logging = require('@tryghost/logging');
|
|
12
12
|
const errors = require('@tryghost/errors');
|
|
13
13
|
const ImageHandler = require('./handlers/image');
|
|
14
|
-
const ImporterContentFileHandler = require('
|
|
14
|
+
const ImporterContentFileHandler = require('./handlers/ImporterContentFileHandler');
|
|
15
15
|
const RevueHandler = require('./handlers/revue');
|
|
16
16
|
const JSONHandler = require('./handlers/json');
|
|
17
17
|
const MarkdownHandler = require('./handlers/markdown');
|
|
18
18
|
const ContentFileImporter = require('./importers/ContentFileImporter');
|
|
19
|
-
const RevueImporter = require('
|
|
19
|
+
const RevueImporter = require('./importers/importer-revue');
|
|
20
20
|
const DataImporter = require('./importers/data');
|
|
21
21
|
const urlUtils = require('../../../shared/url-utils');
|
|
22
22
|
const {GhostMailer} = require('../../services/mail');
|
|
@@ -225,7 +225,7 @@ class ImportManager {
|
|
|
225
225
|
|
|
226
226
|
try {
|
|
227
227
|
await extract(filePath, tmpDir);
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
// Set permissions for all extracted files
|
|
230
230
|
const files = glob.sync('**/*', {cwd: tmpDir, nodir: true});
|
|
231
231
|
await Promise.all(files.map(file => fs.chmod(path.join(tmpDir, file), 0o644)));
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const debug = require('@tryghost/debug')('importer:revue');
|
|
2
|
+
const {slugify} = require('@tryghost/string');
|
|
3
|
+
const papaparse = require('papaparse');
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
|
|
6
|
+
const JSONToHTML = require('./json-to-html');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build posts out of the issue and item data
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} revueData
|
|
12
|
+
* @return {Array}
|
|
13
|
+
*/
|
|
14
|
+
const fetchPostsFromData = (revueData) => {
|
|
15
|
+
const itemData = JSON.parse(revueData.items);
|
|
16
|
+
const issueData = papaparse.parse(revueData.issues, {
|
|
17
|
+
header: true,
|
|
18
|
+
skipEmptyLines: true,
|
|
19
|
+
transform(value, header) {
|
|
20
|
+
if (header === 'id') {
|
|
21
|
+
return parseInt(value);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const posts = [];
|
|
28
|
+
|
|
29
|
+
issueData.data.forEach((postMeta) => {
|
|
30
|
+
// Convert issues to posts
|
|
31
|
+
if (!postMeta.subject) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const revuePostID = postMeta.id;
|
|
36
|
+
let postHTML = JSONToHTML.cleanCsvHTML(postMeta.description);
|
|
37
|
+
|
|
38
|
+
const postItems = _.filter(itemData, {issue_id: revuePostID});
|
|
39
|
+
const sortedPostItems = _.sortBy(postItems, o => o.order);
|
|
40
|
+
|
|
41
|
+
if (postItems) {
|
|
42
|
+
const convertedItems = JSONToHTML.itemsToHtml(sortedPostItems);
|
|
43
|
+
postHTML = `${postHTML}${convertedItems}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const postDate = JSONToHTML.getPostDate(postMeta);
|
|
47
|
+
const postSlug = slugify(postMeta.subject).slice(0, 190);
|
|
48
|
+
|
|
49
|
+
posts.push({
|
|
50
|
+
comment_id: revuePostID,
|
|
51
|
+
title: postMeta.subject,
|
|
52
|
+
slug: postSlug,
|
|
53
|
+
status: JSONToHTML.getPostStatus(postMeta),
|
|
54
|
+
visibility: 'public',
|
|
55
|
+
created_at: postDate,
|
|
56
|
+
published_at: postDate,
|
|
57
|
+
updated_at: postDate,
|
|
58
|
+
html: postHTML
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return posts;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param {*} revueData
|
|
68
|
+
*/
|
|
69
|
+
const buildSubscriberList = (revueData) => {
|
|
70
|
+
const subscribers = [];
|
|
71
|
+
|
|
72
|
+
const subscriberData = papaparse.parse(revueData.subscribers, {
|
|
73
|
+
header: true,
|
|
74
|
+
skipEmptyLines: true
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
subscriberData.data.forEach((subscriber) => {
|
|
78
|
+
subscribers.push({
|
|
79
|
+
email: subscriber.email,
|
|
80
|
+
name: `${subscriber.first_name} ${subscriber.last_name}`.trim(),
|
|
81
|
+
created_at: subscriber.created_at,
|
|
82
|
+
subscribed: true
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return subscribers;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const RevueImporter = {
|
|
90
|
+
type: 'revue',
|
|
91
|
+
preProcess: function (importData) {
|
|
92
|
+
debug('preProcess');
|
|
93
|
+
importData.preProcessedByRevue = true;
|
|
94
|
+
|
|
95
|
+
// TODO: this should really be in doImport
|
|
96
|
+
// No posts to prePprocess, return early
|
|
97
|
+
if (!importData?.revue?.revue?.issues) {
|
|
98
|
+
return importData;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// This processed data goes to the data importer
|
|
102
|
+
importData.data = {
|
|
103
|
+
meta: {version: '5.0.0'},
|
|
104
|
+
data: {}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
importData.data.data.posts = this.importPosts(importData.revue.revue);
|
|
108
|
+
|
|
109
|
+
// No subscribers to import, we're done
|
|
110
|
+
if (!importData?.revue?.revue?.subscribers) {
|
|
111
|
+
return importData;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
importData.data.data.revue_subscribers = this.importSubscribers(importData.revue.revue);
|
|
115
|
+
|
|
116
|
+
return importData;
|
|
117
|
+
},
|
|
118
|
+
doImport: function (importData) {
|
|
119
|
+
debug('doImport');
|
|
120
|
+
|
|
121
|
+
return importData;
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
importPosts: fetchPostsFromData,
|
|
125
|
+
importSubscribers: buildSubscriberList
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
module.exports = RevueImporter;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const SimpleDom = require('simple-dom');
|
|
2
|
+
const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);
|
|
3
|
+
const imageCard = require('@tryghost/kg-default-cards/lib/cards/image.js');
|
|
4
|
+
const embedCard = require('@tryghost/kg-default-cards/lib/cards/embed.js');
|
|
5
|
+
|
|
6
|
+
// Take the array of items for a specific post and return the converted HTML
|
|
7
|
+
const itemsToHtml = (items) => {
|
|
8
|
+
let itemHTMLChunks = [];
|
|
9
|
+
items.forEach((item) => {
|
|
10
|
+
let type = item.item_type;
|
|
11
|
+
|
|
12
|
+
if (type === 'header') {
|
|
13
|
+
itemHTMLChunks.push(`<h3>${item.title}</h3>`);
|
|
14
|
+
} else if (type === 'text') {
|
|
15
|
+
itemHTMLChunks.push(item.description); // THis is basic text HTML with <p>, <b>, <a>, etc (no media)
|
|
16
|
+
} else if (type === 'image') {
|
|
17
|
+
// We have 2 values to work with here. `image` is smaller and most suitable, and `original_image_url` is the full-res that would need to be resized
|
|
18
|
+
// - item.image (https://s3.amazonaws.com/revue/items/images/019/005/542/web/anita-austvika-C-JUrfmYqcw-unsplash.jpg?1667924147)
|
|
19
|
+
// - item.original_image_url (https://s3.amazonaws.com/revue/items/images/019/005/542/original/anita-austvika-C-JUrfmYqcw-unsplash.jpg?1667924147)
|
|
20
|
+
let cardOpts = {
|
|
21
|
+
env: {dom: new SimpleDom.Document()},
|
|
22
|
+
payload: {
|
|
23
|
+
src: item.image,
|
|
24
|
+
caption: item.description
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
itemHTMLChunks.push(serializer.serialize(imageCard.render(cardOpts)));
|
|
29
|
+
} else if (type === 'link') {
|
|
30
|
+
// This could be a bookmark, or it could be a paragraph of text with a linked header, there's no way to tell
|
|
31
|
+
// The safest option here is to output an image with text under it
|
|
32
|
+
let cardOpts = {
|
|
33
|
+
env: {dom: new SimpleDom.Document()},
|
|
34
|
+
payload: {
|
|
35
|
+
src: item.image,
|
|
36
|
+
caption: item.title,
|
|
37
|
+
href: item.url
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
itemHTMLChunks.push(serializer.serialize(imageCard.render(cardOpts)));
|
|
41
|
+
|
|
42
|
+
let linkHTML = `<h4><a href="${item.url}">${item.title}</a></h4>${item.description}`;
|
|
43
|
+
itemHTMLChunks.push(linkHTML);
|
|
44
|
+
} else if (type === 'tweet') {
|
|
45
|
+
// Should this be an oEmbed call? Probably.
|
|
46
|
+
itemHTMLChunks.push(`<figure class="kg-card kg-embed-card">
|
|
47
|
+
<blockquote class="twitter-tweet"><a href="${item.url}"></a></blockquote>
|
|
48
|
+
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
|
49
|
+
</figure>`);
|
|
50
|
+
} else if (type === 'video') {
|
|
51
|
+
const isLongYouTube = /youtube.com/.test(item.url);
|
|
52
|
+
const isShortYouTube = /youtu.be/.test(item.url);
|
|
53
|
+
const isVimeo = /vimeo.com/.test(item.url);
|
|
54
|
+
let videoHTML = '';
|
|
55
|
+
if (isLongYouTube) {
|
|
56
|
+
let videoID = item.url.replace(/https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]*)/gi, '$1');
|
|
57
|
+
videoHTML = `<iframe width="200" height="113" src="https://www.youtube.com/embed/${videoID}?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
|
|
58
|
+
} else if (isShortYouTube) {
|
|
59
|
+
let videoID = item.url.replace(/https?:\/\/(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]*)/gi, '$1');
|
|
60
|
+
videoHTML = `<iframe width="200" height="113" src="https://www.youtube.com/embed/${videoID}?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`;
|
|
61
|
+
} else if (isVimeo) {
|
|
62
|
+
let videoID = item.url.replace(/https?:\/\/(?:www\.)?vimeo\.com\/([0-9]+)/gi, '$1');
|
|
63
|
+
videoHTML = `<iframe src="https://player.vimeo.com/video/${videoID}" width="200" height="113" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>`;
|
|
64
|
+
}
|
|
65
|
+
let cardOpts = {
|
|
66
|
+
env: {dom: new SimpleDom.Document()},
|
|
67
|
+
payload: {
|
|
68
|
+
html: videoHTML,
|
|
69
|
+
caption: item.description
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
itemHTMLChunks.push(serializer.serialize(embedCard.render(cardOpts)));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return itemHTMLChunks.join('\n');
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getPostDate = (data) => {
|
|
80
|
+
const isPublished = (data.sent_at) ? true : false; // This is how we determine is a post is published or not
|
|
81
|
+
const postDate = (isPublished) ? new Date(data.sent_at) : new Date();
|
|
82
|
+
|
|
83
|
+
return postDate.toISOString();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getPostStatus = (data) => {
|
|
87
|
+
const isPublished = (data.sent_at) ? true : false; // This is how we determine is a post is published or not
|
|
88
|
+
return (isPublished) ? 'published' : 'draft';
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const cleanCsvHTML = (data) => {
|
|
92
|
+
// Blockquotes need to have some sort of wrapping elements around all contents
|
|
93
|
+
// Wrap all content in <p> tags. The HTML to Mobiledoc parse can handle duplicate <p> tags.
|
|
94
|
+
data = data.replace(/<blockquote.*?>(.*?)<\/blockquote>/gm, '<blockquote><p>$1</p></blockquote>');
|
|
95
|
+
|
|
96
|
+
// These exports have a lot of <p><br></p> that we don't want
|
|
97
|
+
data = data.replace(/<p><br><\/p>/gm, '');
|
|
98
|
+
|
|
99
|
+
return data;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
itemsToHtml,
|
|
104
|
+
getPostDate,
|
|
105
|
+
getPostStatus,
|
|
106
|
+
cleanCsvHTML
|
|
107
|
+
};
|
|
@@ -5,8 +5,7 @@ const {createIrreversibleMigration, createNonTransactionalMigration} = require('
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a migrations which will add a new table from schema.js to the database
|
|
7
7
|
* @param {string} name - table name
|
|
8
|
-
* @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration,
|
|
9
|
-
* this parameter MUST be present, otherwise @daniellockyer will hunt you down
|
|
8
|
+
* @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration, this parameter MUST be present
|
|
10
9
|
*
|
|
11
10
|
* @returns {Object} migration object returning config/up/down properties
|
|
12
11
|
*/
|
|
@@ -60,8 +59,7 @@ function dropTables(names) {
|
|
|
60
59
|
/**
|
|
61
60
|
* Creates a migration which will drop an existing table and then re-add a new table based on provided spec
|
|
62
61
|
* @param {string} name - table name
|
|
63
|
-
* @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration,
|
|
64
|
-
* this parameter MUST be present, otherwise @daniellockyer will hunt you down
|
|
62
|
+
* @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration, this parameter MUST be present
|
|
65
63
|
*
|
|
66
64
|
* @returns {Object} migration object returning config/up/down properties
|
|
67
65
|
*/
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const JsonImporter = require('./utils/JsonImporter');
|
|
4
|
+
const {getProcessRoot} = require('@tryghost/root-utils');
|
|
5
|
+
const topologicalSort = require('./utils/topological-sort');
|
|
6
|
+
const {faker} = require('@faker-js/faker');
|
|
7
|
+
const {faker: americanFaker} = require('@faker-js/faker/locale/en_US');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const {Buffer} = require('node:buffer');
|
|
10
|
+
const DatabaseInfo = require('@tryghost/database-info');
|
|
11
|
+
const errors = require('@tryghost/errors');
|
|
12
|
+
const importers = require('./importers').reduce((acc, val) => {
|
|
13
|
+
acc[val.table] = val;
|
|
14
|
+
return acc;
|
|
15
|
+
}, {});
|
|
16
|
+
|
|
17
|
+
class DataGenerator {
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @param {object} options
|
|
21
|
+
* @param {Record<string,number>} [options.quantities] Pass in custom amounts for specific tables
|
|
22
|
+
* @param {number} [options.seed] If you pass the same seed, the same data will be generated if you used the same options too and if the data generation code remained the same.
|
|
23
|
+
*/
|
|
24
|
+
constructor({
|
|
25
|
+
knex,
|
|
26
|
+
tables,
|
|
27
|
+
schemaTables,
|
|
28
|
+
clearDatabase = false,
|
|
29
|
+
baseDataPack = '',
|
|
30
|
+
baseUrl,
|
|
31
|
+
logger,
|
|
32
|
+
printDependencies,
|
|
33
|
+
withDefault,
|
|
34
|
+
seed,
|
|
35
|
+
quantities = {},
|
|
36
|
+
useTransaction = true
|
|
37
|
+
}) {
|
|
38
|
+
this.knex = knex;
|
|
39
|
+
this.tableList = tables || [];
|
|
40
|
+
this.schemaTables = schemaTables;
|
|
41
|
+
this.willClearData = clearDatabase;
|
|
42
|
+
this.useBaseDataPack = baseDataPack !== '';
|
|
43
|
+
this.baseDataPack = baseDataPack;
|
|
44
|
+
this.baseUrl = baseUrl;
|
|
45
|
+
this.logger = logger;
|
|
46
|
+
this.withDefault = withDefault;
|
|
47
|
+
this.printDependencies = printDependencies;
|
|
48
|
+
this.seed = seed;
|
|
49
|
+
this.quantities = quantities;
|
|
50
|
+
this.useTransaction = useTransaction;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
sortTableList() {
|
|
54
|
+
// Add missing dependencies
|
|
55
|
+
for (const table of this.tableList) {
|
|
56
|
+
table.importer = importers[table.name];
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line no-unused-vars
|
|
59
|
+
table.dependencies = Object.entries(this.schemaTables[table.name]).reduce((acc, [_col, data]) => {
|
|
60
|
+
if (data.references) {
|
|
61
|
+
const referencedTable = data.references.split('.')[0];
|
|
62
|
+
// The ghost_subscriptions_id property has a foreign key to the subscriptions table, but we don't use that table yet atm, so don't add it as a dependency
|
|
63
|
+
if (!acc.includes(referencedTable) && referencedTable !== 'subscriptions') {
|
|
64
|
+
acc.push(referencedTable);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return acc;
|
|
68
|
+
}, table.importer.dependencies);
|
|
69
|
+
|
|
70
|
+
for (const dependency of table.dependencies) {
|
|
71
|
+
if (!this.tableList.find(t => t.name === dependency)) {
|
|
72
|
+
this.tableList.push({
|
|
73
|
+
name: dependency,
|
|
74
|
+
importer: importers[dependency]
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Order to ensure dependencies are created before dependants
|
|
81
|
+
this.tableList = topologicalSort(this.tableList);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* TODO: This needs to reverse through all dependency chains to clear data from all tables
|
|
86
|
+
* @param {import('knex/types').Knex.Transaction} transaction
|
|
87
|
+
*/
|
|
88
|
+
async clearData(transaction) {
|
|
89
|
+
const tables = this.tableList.map(t => t.name).reverse();
|
|
90
|
+
|
|
91
|
+
// TODO: Remove this once we import posts_meta
|
|
92
|
+
tables.unshift('posts_meta');
|
|
93
|
+
|
|
94
|
+
// Clear data from any tables that are being imported
|
|
95
|
+
for (const table of tables) {
|
|
96
|
+
this.logger.debug(`Clearing table ${table}`);
|
|
97
|
+
|
|
98
|
+
if (table === 'roles_users') {
|
|
99
|
+
await transaction(table).del().whereNot('user_id', '1');
|
|
100
|
+
} else if (table === 'users') {
|
|
101
|
+
// Avoid deleting the admin user
|
|
102
|
+
await transaction(table).del().whereNot('id', '1');
|
|
103
|
+
} else {
|
|
104
|
+
await transaction(table).truncate();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async importBasePack(transaction) {
|
|
110
|
+
let baseDataPack = this.baseDataPack;
|
|
111
|
+
if (!path.isAbsolute(this.baseDataPack)) {
|
|
112
|
+
baseDataPack = path.join(getProcessRoot(), baseDataPack);
|
|
113
|
+
}
|
|
114
|
+
let baseData = {};
|
|
115
|
+
try {
|
|
116
|
+
baseData = JSON.parse((await fs.readFile(baseDataPack)).toString());
|
|
117
|
+
this.logger.info('Read base data pack');
|
|
118
|
+
} catch (error) {
|
|
119
|
+
this.logger.error('Failed to read data pack: ', error);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.logger.info('Starting base data import');
|
|
124
|
+
const jsonImporter = new JsonImporter(transaction);
|
|
125
|
+
|
|
126
|
+
// Clear settings table
|
|
127
|
+
await transaction('settings').del();
|
|
128
|
+
|
|
129
|
+
// Hard-coded for order
|
|
130
|
+
const tablesToImport = [
|
|
131
|
+
'newsletters',
|
|
132
|
+
'posts',
|
|
133
|
+
'tags',
|
|
134
|
+
'products',
|
|
135
|
+
'benefits',
|
|
136
|
+
'products_benefits',
|
|
137
|
+
'stripe_products',
|
|
138
|
+
'stripe_prices',
|
|
139
|
+
'settings',
|
|
140
|
+
'custom_theme_settings'
|
|
141
|
+
];
|
|
142
|
+
for (const table of tablesToImport) {
|
|
143
|
+
this.logger.info(`Importing content for table ${table} from base data pack`);
|
|
144
|
+
await jsonImporter.import({
|
|
145
|
+
name: table,
|
|
146
|
+
data: baseData[table]
|
|
147
|
+
});
|
|
148
|
+
const tableIndex = this.tableList.findIndex(t => t.name === table);
|
|
149
|
+
if (tableIndex !== -1) {
|
|
150
|
+
this.tableList.splice(tableIndex, 1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.logger.info('Completed base data import');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async importData() {
|
|
158
|
+
const start = Date.now();
|
|
159
|
+
|
|
160
|
+
// Add default tables if none are specified
|
|
161
|
+
if (this.tableList.length === 0) {
|
|
162
|
+
this.tableList = Object.keys(importers).map(name => ({name}));
|
|
163
|
+
} else if (this.withDefault) {
|
|
164
|
+
// Add default tables to the end of the list
|
|
165
|
+
const defaultTables = Object.keys(importers).map(name => ({name}));
|
|
166
|
+
for (const table of defaultTables) {
|
|
167
|
+
if (!this.tableList.find(t => t.name === table.name)) {
|
|
168
|
+
this.tableList.push(table);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Error if we have an unknown table
|
|
174
|
+
for (const table of this.tableList) {
|
|
175
|
+
if (importers[table.name] === undefined) {
|
|
176
|
+
throw new errors.IncorrectUsageError({message: `Unknown table: ${table.name}`});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.sortTableList();
|
|
181
|
+
|
|
182
|
+
if (this.printDependencies) {
|
|
183
|
+
this.logger.info('Table dependencies:');
|
|
184
|
+
for (const table of this.tableList) {
|
|
185
|
+
this.logger.info(`\t${table.name}: ${table.dependencies.join(', ')}`);
|
|
186
|
+
}
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.useTransaction) {
|
|
191
|
+
await this.knex.transaction(async (transaction) => {
|
|
192
|
+
if (!DatabaseInfo.isSQLite(this.knex)) {
|
|
193
|
+
await transaction.raw('SET autocommit=0;');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await this.#run(transaction);
|
|
197
|
+
}, {isolationLevel: 'read committed'});
|
|
198
|
+
} else {
|
|
199
|
+
await this.#run(this.knex);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.logger.info(`Completed data import in ${((Date.now() - start) / 1000).toFixed(1)}s`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async #run(transaction) {
|
|
206
|
+
if (!DatabaseInfo.isSQLite(this.knex)) {
|
|
207
|
+
if (process.env.DISABLE_FAST_IMPORT) {
|
|
208
|
+
await transaction.raw('SET FOREIGN_KEY_CHECKS=0;');
|
|
209
|
+
await transaction.raw('SET unique_checks=0;');
|
|
210
|
+
} else {
|
|
211
|
+
await transaction.raw('ALTER INSTANCE DISABLE INNODB REDO_LOG;');
|
|
212
|
+
await transaction.raw('SET FOREIGN_KEY_CHECKS=0;');
|
|
213
|
+
await transaction.raw('SET unique_checks=0;');
|
|
214
|
+
await transaction.raw('SET GLOBAL local_infile=1;');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.willClearData) {
|
|
219
|
+
await this.clearData(transaction);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.useBaseDataPack) {
|
|
223
|
+
await this.importBasePack(transaction);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Set quantities for tables
|
|
227
|
+
for (const table of this.tableList) {
|
|
228
|
+
if (this.quantities[table.name] !== undefined) {
|
|
229
|
+
table.quantity = this.quantities[table.name];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cryptoRandomBytes = crypto.randomBytes;
|
|
234
|
+
|
|
235
|
+
if (this.seed) {
|
|
236
|
+
// The probality distributions library uses crypto.randomBytes, which we can't seed, so we need to override it
|
|
237
|
+
crypto.randomBytes = (size) => {
|
|
238
|
+
const buffer = Buffer.alloc(size);
|
|
239
|
+
for (let i = 0; i < size; i++) {
|
|
240
|
+
buffer[i] = Math.floor(faker.datatype.number({min: 0, max: 255}));
|
|
241
|
+
}
|
|
242
|
+
return buffer;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
for (const table of this.tableList) {
|
|
248
|
+
if (this.seed) {
|
|
249
|
+
// We reset the seed for every table, so the chosen tables don't affect the data and changes in one importer don't affect the others
|
|
250
|
+
faker.seed(this.seed);
|
|
251
|
+
americanFaker.seed(this.seed);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Add all common options to every importer, whether they use them or not
|
|
255
|
+
const tableImporter = new table.importer(this.knex, transaction, {
|
|
256
|
+
baseUrl: this.baseUrl
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const amount = table.quantity ?? tableImporter.defaultQuantity;
|
|
260
|
+
this.logger.info('Importing content for table', table.name, amount ? `(${amount} records)` : '');
|
|
261
|
+
|
|
262
|
+
await tableImporter.import(table.quantity ?? undefined);
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
if (this.seed) {
|
|
266
|
+
// Revert crypto.randomBytes to the original function
|
|
267
|
+
crypto.randomBytes = cryptoRandomBytes;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Finalise all tables - uses new table importer objects to avoid keeping all data in memory
|
|
272
|
+
for (const table of this.tableList) {
|
|
273
|
+
const tableImporter = new table.importer(this.knex, transaction, {
|
|
274
|
+
baseUrl: this.baseUrl
|
|
275
|
+
});
|
|
276
|
+
await tableImporter.finalise();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Re-enable the redo log because it's a persisted global
|
|
280
|
+
// Leaving it disabled can break the database in the event of an unexpected shutdown
|
|
281
|
+
// See https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html#innodb-disable-redo-logging
|
|
282
|
+
if (!DatabaseInfo.isSQLite(this.knex) && !process.env.DISABLE_FAST_IMPORT) {
|
|
283
|
+
await transaction.raw('ALTER INSTANCE ENABLE INNODB REDO_LOG;');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = DataGenerator;
|