ghost 5.114.1 → 5.115.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/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
- package/components/{tryghost-adapter-manager-5.114.1.tgz → tryghost-adapter-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-announcement-bar-settings-5.114.1.tgz → tryghost-announcement-bar-settings-5.115.1.tgz} +0 -0
- package/components/{tryghost-api-framework-5.114.1.tgz → tryghost-api-framework-5.115.1.tgz} +0 -0
- package/components/tryghost-constants-5.115.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.114.1.tgz → tryghost-custom-theme-settings-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-data-generator-5.114.1.tgz → tryghost-data-generator-5.115.1.tgz} +0 -0
- package/components/{tryghost-domain-events-5.114.1.tgz → tryghost-domain-events-5.115.1.tgz} +0 -0
- package/components/tryghost-donations-5.115.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.115.1.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.114.1.tgz → tryghost-email-content-generator-5.115.1.tgz} +0 -0
- package/components/tryghost-email-events-5.115.1.tgz +0 -0
- package/components/tryghost-email-service-5.115.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.115.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.115.1.tgz +0 -0
- package/components/tryghost-ghost-5.115.1.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.114.1.tgz → tryghost-html-to-plaintext-5.115.1.tgz} +0 -0
- package/components/tryghost-i18n-5.115.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.115.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
- package/components/{tryghost-job-manager-5.114.1.tgz → tryghost-job-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.114.1.tgz → tryghost-link-redirects-5.115.1.tgz} +0 -0
- package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
- package/components/{tryghost-magic-link-5.114.1.tgz → tryghost-magic-link-5.115.1.tgz} +0 -0
- package/components/{tryghost-mailgun-client-5.114.1.tgz → tryghost-mailgun-client-5.115.1.tgz} +0 -0
- package/components/tryghost-member-attribution-5.115.1.tgz +0 -0
- package/components/{tryghost-member-events-5.114.1.tgz → tryghost-member-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-api-5.114.1.tgz → tryghost-members-api-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-csv-5.114.1.tgz → tryghost-members-csv-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-offers-5.114.1.tgz → tryghost-members-offers-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-payments-5.114.1.tgz → tryghost-members-payments-5.115.1.tgz} +0 -0
- package/components/{tryghost-milestones-5.114.1.tgz → tryghost-milestones-5.115.1.tgz} +0 -0
- package/components/tryghost-minifier-5.115.1.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.114.1.tgz → tryghost-mw-error-handler-5.115.1.tgz} +0 -0
- package/components/{tryghost-mw-version-match-5.114.1.tgz → tryghost-mw-version-match-5.115.1.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
- package/components/{tryghost-post-events-5.114.1.tgz → tryghost-post-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.114.1.tgz → tryghost-post-revisions-5.115.1.tgz} +0 -0
- package/components/{tryghost-posts-service-5.114.1.tgz → tryghost-posts-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-prometheus-metrics-5.114.1.tgz → tryghost-prometheus-metrics-5.115.1.tgz} +0 -0
- package/components/tryghost-recommendations-5.115.1.tgz +0 -0
- package/components/{tryghost-security-5.114.1.tgz → tryghost-security-5.115.1.tgz} +0 -0
- package/components/tryghost-slack-notifications-5.115.1.tgz +0 -0
- package/components/{tryghost-tiers-5.114.1.tgz → tryghost-tiers-5.115.1.tgz} +0 -0
- package/components/{tryghost-webmentions-5.114.1.tgz → tryghost-webmentions-5.115.1.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 -1
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +23497 -23041
- 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-15df2af5.mjs} +4 -3
- package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-8ca61d78.mjs} +67 -65
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-d2e6872f.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-8e8821e5.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f5cb3db3.mjs} +3104 -3094
- package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-e8ae4d46.mjs} +3 -3
- package/core/built/admin/assets/{chunk.524.85c5b32bd46b91c147b9.js → chunk.524.2439684964c164c598ab.js} +7 -7
- package/core/built/admin/assets/{chunk.582.449a129a8005f03574bd.js → chunk.582.bf5a2bbb2c4eb69ef1e7.js} +10 -10
- package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +1 -0
- package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +1 -0
- package/core/built/admin/assets/{ghost-c563138cc2c0767bf6eefc9a2587eaa4.js → ghost-df7b9558260aa27d18b195ee895b487d.js} +182 -160
- package/core/built/admin/assets/stats/stats.js +11824 -0
- package/core/built/admin/index.html +4 -4
- package/core/frontend/helpers/ghost_head.js +3 -1
- package/core/frontend/src/cards/css/cta.css +1 -1
- package/core/server/api/endpoints/slugs.js +6 -2
- package/core/server/data/importer/import-manager.js +2 -2
- 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/migrations/versions/5.115/2025-03-24-07-19-27-add-identity-read-permission-to-administrators.js +6 -0
- package/core/server/data/schema/fixtures/fixtures.json +2 -1
- 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/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 +364 -0
- 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/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/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/posts/stats/PostStats.js +13 -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/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/routes.js +1 -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 +3 -1
- package/core/shared/settings-cache/CacheManager.js +64 -6
- package/package.json +103 -134
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +7 -93
- package/components/tryghost-adapter-cache-redis-5.114.1.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.114.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.114.1.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.114.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.114.1.tgz +0 -0
- package/components/tryghost-captcha-service-5.114.1.tgz +0 -0
- package/components/tryghost-constants-5.114.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.114.1.tgz +0 -0
- package/components/tryghost-donations-5.114.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.114.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.114.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.114.1.tgz +0 -0
- package/components/tryghost-email-events-5.114.1.tgz +0 -0
- package/components/tryghost-email-service-5.114.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.114.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.114.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.114.1.tgz +0 -0
- package/components/tryghost-ghost-5.114.1.tgz +0 -0
- package/components/tryghost-i18n-5.114.1.tgz +0 -0
- package/components/tryghost-identity-token-service-5.114.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.114.1.tgz +0 -0
- package/components/tryghost-importer-revue-5.114.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.114.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.114.1.tgz +0 -0
- package/components/tryghost-mail-events-5.114.1.tgz +0 -0
- package/components/tryghost-member-attribution-5.114.1.tgz +0 -0
- package/components/tryghost-members-importer-5.114.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.114.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.114.1.tgz +0 -0
- package/components/tryghost-minifier-5.114.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.114.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.114.1.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.114.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.114.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.114.1.tgz +0 -0
- package/components/tryghost-package-json-5.114.1.tgz +0 -0
- package/components/tryghost-recommendations-5.114.1.tgz +0 -0
- package/components/tryghost-referrers-5.114.1.tgz +0 -0
- package/components/tryghost-session-service-5.114.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.114.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.114.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.114.1.tgz +0 -0
- package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
- package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.css +0 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.115%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22685db18f10%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2253d9d1eb76%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%223812b50e35%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%220667582c6d%22%2C%22postsFilename%22%3A%22posts.js%22%2C%22postsHash%22%3A%22cc9549f09d%22%2C%22statsFilename%22%3A%22stats.js%22%2C%22statsHash%22%3A%2256566f9e00%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</style>
|
|
38
38
|
|
|
39
39
|
<link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<script src="assets/vendor-68a4aa424a179a90f5bbc2b750def576.js"></script>
|
|
60
60
|
<script src="assets/chunk.874.461cb3cf5b6b36915f8c.js"></script>
|
|
61
|
-
<script src="assets/chunk.524.
|
|
62
|
-
<script src="assets/ghost-
|
|
61
|
+
<script src="assets/chunk.524.2439684964c164c598ab.js"></script>
|
|
62
|
+
<script src="assets/ghost-df7b9558260aa27d18b195ee895b487d.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -151,6 +151,8 @@ function getWebmentionDiscoveryLink() {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
function getTinybirdTrackerScript(dataRoot) {
|
|
154
|
+
const src = urlUtils.getSubdir() + '/public/ghost-stats.js';
|
|
155
|
+
|
|
154
156
|
const endpoint = config.get('tinybird:tracker:endpoint');
|
|
155
157
|
const token = config.get('tinybird:tracker:token');
|
|
156
158
|
const datasource = config.get('tinybird:tracker:datasource');
|
|
@@ -162,7 +164,7 @@ function getTinybirdTrackerScript(dataRoot) {
|
|
|
162
164
|
member_status: dataRoot.member?.status
|
|
163
165
|
}, (value, key) => `tb_${key}="${value}"`).join(' ');
|
|
164
166
|
|
|
165
|
-
return `<script defer src="
|
|
167
|
+
return `<script defer src="${src}" data-stringify-payload="false" ${datasource ? `data-datasource="${datasource}"` : ''} data-storage="localStorage" data-host="${endpoint}" data-token="${token}" ${tbParams}></script>`;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
function getHCaptchaScript() {
|
|
@@ -21,7 +21,8 @@ const controller = {
|
|
|
21
21
|
},
|
|
22
22
|
options: [
|
|
23
23
|
'include',
|
|
24
|
-
'type'
|
|
24
|
+
'type',
|
|
25
|
+
'id'
|
|
25
26
|
],
|
|
26
27
|
data: [
|
|
27
28
|
'name'
|
|
@@ -32,6 +33,9 @@ const controller = {
|
|
|
32
33
|
type: {
|
|
33
34
|
required: true,
|
|
34
35
|
values: Object.keys(allowedTypes)
|
|
36
|
+
},
|
|
37
|
+
id: {
|
|
38
|
+
required: false
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
data: {
|
|
@@ -41,7 +45,7 @@ const controller = {
|
|
|
41
45
|
}
|
|
42
46
|
},
|
|
43
47
|
query(frame) {
|
|
44
|
-
return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all'})
|
|
48
|
+
return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all', modelId: frame.options.id})
|
|
45
49
|
.then((slug) => {
|
|
46
50
|
if (!slug) {
|
|
47
51
|
return Promise.reject(new errors.InternalServerError({
|
|
@@ -16,7 +16,7 @@ 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,87 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
|
|
3
|
+
module.exports.connectAndSend = (socketAddress, message) => {
|
|
4
|
+
// Very basic guard against bad calls
|
|
5
|
+
if (!socketAddress || !socketAddress.host || !socketAddress.port || !logging || !logging.info || !logging.warn || !message) {
|
|
6
|
+
return Promise.resolve();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const net = require('net');
|
|
10
|
+
const client = new net.Socket();
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const connect = (options = {}) => {
|
|
14
|
+
let wasResolved = false;
|
|
15
|
+
|
|
16
|
+
const waitTimeout = setTimeout(() => {
|
|
17
|
+
logging.info('Bootstrap socket timed out.');
|
|
18
|
+
|
|
19
|
+
if (!client.destroyed) {
|
|
20
|
+
client.destroy();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (wasResolved) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
wasResolved = true;
|
|
28
|
+
resolve();
|
|
29
|
+
}, 1000 * 5);
|
|
30
|
+
|
|
31
|
+
client.connect(socketAddress.port, socketAddress.host, () => {
|
|
32
|
+
if (waitTimeout) {
|
|
33
|
+
clearTimeout(waitTimeout);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
client.write(JSON.stringify(message));
|
|
37
|
+
|
|
38
|
+
if (wasResolved) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
wasResolved = true;
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
client.on('close', () => {
|
|
47
|
+
logging.info('Bootstrap client was closed.');
|
|
48
|
+
|
|
49
|
+
if (waitTimeout) {
|
|
50
|
+
clearTimeout(waitTimeout);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
client.on('error', (err) => {
|
|
55
|
+
logging.warn(`Can't connect to the bootstrap socket (${socketAddress.host} ${socketAddress.port}) ${err.code}.`);
|
|
56
|
+
|
|
57
|
+
client.removeAllListeners();
|
|
58
|
+
|
|
59
|
+
if (waitTimeout) {
|
|
60
|
+
clearTimeout(waitTimeout);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (options.tries < 3) {
|
|
64
|
+
logging.warn(`Tries: ${options.tries}`);
|
|
65
|
+
|
|
66
|
+
// retry
|
|
67
|
+
logging.warn('Retrying...');
|
|
68
|
+
|
|
69
|
+
options.tries = options.tries + 1;
|
|
70
|
+
const retryTimeout = setTimeout(() => {
|
|
71
|
+
clearTimeout(retryTimeout);
|
|
72
|
+
connect(options);
|
|
73
|
+
}, 150);
|
|
74
|
+
} else {
|
|
75
|
+
if (wasResolved) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
wasResolved = true;
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
connect({tries: 0});
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./package-json');
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* # Package Utils
|
|
3
|
+
*
|
|
4
|
+
* Ghost has support for several different types of sub-packages:
|
|
5
|
+
* - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future
|
|
6
|
+
* - Adapters: replace fundamental pieces like storage, will become npm modules
|
|
7
|
+
*
|
|
8
|
+
* These utils facilitate loading, reading, managing etc, packages from the file system.
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
const _ = require('lodash');
|
|
12
|
+
const fs = require('fs-extra');
|
|
13
|
+
const join = require('path').join;
|
|
14
|
+
const errors = require('@tryghost/errors');
|
|
15
|
+
const parse = require('./parse');
|
|
16
|
+
|
|
17
|
+
const notAPackageRegex = /^\.|_messages|README.md|node_modules|bower_components/i;
|
|
18
|
+
const packageJSONPath = 'package.json';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} PackageList
|
|
22
|
+
* @typedef {object} Package
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Recursively read directory and find the packages in it
|
|
27
|
+
*
|
|
28
|
+
* @param {string} absolutePath
|
|
29
|
+
* @param {string} packageName
|
|
30
|
+
* @returns {Promise<Package>}
|
|
31
|
+
*/
|
|
32
|
+
async function processPackage(absolutePath, packageName) {
|
|
33
|
+
const pkg = {
|
|
34
|
+
name: packageName,
|
|
35
|
+
path: absolutePath
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const packageJSON = await parse(join(absolutePath, packageJSONPath));
|
|
40
|
+
pkg['package.json'] = packageJSON;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
// ignore invalid package.json for now,
|
|
43
|
+
// because Ghost does not rely/use them at the moment
|
|
44
|
+
// in the future, this .catch() will need to be removed,
|
|
45
|
+
// so that error is thrown on invalid json syntax
|
|
46
|
+
pkg['package.json'] = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return pkg;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ### Filter Packages
|
|
54
|
+
* Normalizes packages read by readPackages so that the themes module can use them.
|
|
55
|
+
* Iterates over each package and return an array of objects which are simplified representations of the package
|
|
56
|
+
* with 3 properties:
|
|
57
|
+
*
|
|
58
|
+
* @typedef {object} SimplePackage
|
|
59
|
+
* @prop {string} name - the package name
|
|
60
|
+
* @prop {object|boolean} package - contents of the package.json or false if there isn't one
|
|
61
|
+
* @prop {boolean} active - set to true if this package is active
|
|
62
|
+
*
|
|
63
|
+
* This data structure is used for listings of packages provided over the API and as such
|
|
64
|
+
* deliberately combines multiple sources of information in order to be efficient.
|
|
65
|
+
*
|
|
66
|
+
* TODO: simplify the package.json representation to contain only fields we use
|
|
67
|
+
*
|
|
68
|
+
* @param {PackageList} packages object made up of packages keyed by name as returned by readPackages
|
|
69
|
+
* @param {array|string} [active] optional set of names of packages that are active
|
|
70
|
+
* @returns {Array<SimplePackage>} array of objects with useful info about themes
|
|
71
|
+
*/
|
|
72
|
+
function filter(packages, active) {
|
|
73
|
+
// turn active into an array if it isn't one, so this function can deal with lists and one-offs
|
|
74
|
+
if (!Array.isArray(active)) {
|
|
75
|
+
active = [active];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return _.reduce(packages, function (result, pkg, key) {
|
|
79
|
+
let item = {};
|
|
80
|
+
if (!key.match(notAPackageRegex)) {
|
|
81
|
+
item = {
|
|
82
|
+
name: key,
|
|
83
|
+
package: pkg['package.json'] || false,
|
|
84
|
+
active: _.indexOf(active, key) !== -1
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
result.push(item);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}, []);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} packagePath
|
|
96
|
+
* @param {string} packageName
|
|
97
|
+
* @returns {Promise<Package>}
|
|
98
|
+
*/
|
|
99
|
+
async function readPackage(packagePath, packageName) {
|
|
100
|
+
const absolutePath = join(packagePath, packageName);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const stat = await fs.stat(absolutePath);
|
|
104
|
+
if (!stat.isDirectory()) {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const pkg = await processPackage(absolutePath, packageName);
|
|
109
|
+
const res = {};
|
|
110
|
+
res[packageName] = pkg;
|
|
111
|
+
return res;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
return Promise.reject(new errors.NotFoundError({
|
|
114
|
+
message: 'Package not found',
|
|
115
|
+
err: err,
|
|
116
|
+
help: 'path: ' + packagePath,
|
|
117
|
+
context: 'name: ' + packageName
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} packagePath
|
|
124
|
+
* @returns {Promise<PackageList>}
|
|
125
|
+
*/
|
|
126
|
+
async function readPackages(packagePath) {
|
|
127
|
+
const files = await fs.promises.readdir(packagePath, {withFileTypes: true});
|
|
128
|
+
const packages = await Promise.all(files.map(async (file) => {
|
|
129
|
+
// Filter out things which are not packages by regex
|
|
130
|
+
if (file.name.match(notAPackageRegex)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (file.isSymbolicLink()) {
|
|
135
|
+
try {
|
|
136
|
+
const packageFileOrig = await fs.stat(join(packagePath, file.name));
|
|
137
|
+
return packageFileOrig.isDirectory();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// if there's an error reading the symlink, we should just return false
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check the remaining items to ensure they are a directory
|
|
145
|
+
return file.isDirectory();
|
|
146
|
+
}))
|
|
147
|
+
.then(results => files.filter((_v, index) => results[index]))
|
|
148
|
+
.then(packageFiles => Promise.all(packageFiles.map((packageFile) => {
|
|
149
|
+
const absolutePath = join(packagePath, packageFile.name);
|
|
150
|
+
return processPackage(absolutePath, packageFile.name);
|
|
151
|
+
})));
|
|
152
|
+
|
|
153
|
+
return _.keyBy(packages, 'name');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
filter,
|
|
158
|
+
readPackage,
|
|
159
|
+
readPackages
|
|
160
|
+
};
|