hexo-theme-stellar 1.44.0 → 2.0.0-rc.2
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/CHANGELOG.md +63 -5
- package/CONTRIBUTING.md +29 -120
- package/README.md +66 -73
- package/README_EN.md +66 -73
- package/_config.yml +890 -743
- package/_data/icons.yml +43 -171
- package/_data/widgets.yml +1 -1
- package/assets/icon-v2-x512.webp +0 -0
- package/languages/en.yml +59 -2
- package/languages/zh-CN.yml +59 -2
- package/languages/zh-TW.yml +59 -2
- package/layout/404.ejs +7 -4
- package/layout/_partial/comments/artalk/layout.ejs +3 -2
- package/layout/_partial/comments/beaudar/layout.ejs +2 -2
- package/layout/_partial/comments/giscus/layout.ejs +2 -5
- package/layout/_partial/comments/layout.ejs +11 -40
- package/layout/_partial/comments/twikoo/layout.ejs +3 -2
- package/layout/_partial/comments/utterances/layout.ejs +2 -2
- package/layout/_partial/comments/waline/layout.ejs +3 -2
- package/layout/_partial/components/collection-item.ejs +30 -13
- package/layout/_partial/components/collection.ejs +2 -2
- package/layout/_partial/cover/index.ejs +5 -2
- package/layout/_partial/cover/wiki_cover.ejs +39 -28
- package/layout/_partial/dropdown.ejs +26 -14
- package/layout/_partial/head.ejs +96 -69
- package/layout/_partial/main/article/contributors.ejs +12 -9
- package/layout/_partial/main/article/post_footer.ejs +47 -0
- package/layout/_partial/main/article/post_read_next.ejs +22 -0
- package/layout/_partial/main/article/post_related.ejs +14 -0
- package/layout/_partial/main/article/post_tags.ejs +12 -0
- package/layout/_partial/main/article/share.ejs +55 -0
- package/layout/_partial/main/footer.ejs +11 -10
- package/layout/_partial/main/{navbar/nav_tabs_blog.ejs → listing_nav/blog.ejs} +21 -18
- package/layout/_partial/main/listing_nav/wiki.ejs +56 -0
- package/layout/_partial/main/navbar/article_banner.ejs +39 -35
- package/layout/_partial/main/navbar/breadcrumb/blog.ejs +15 -28
- package/layout/_partial/main/navbar/breadcrumb/note.ejs +12 -5
- package/layout/_partial/main/navbar/breadcrumb/wiki.ejs +15 -11
- package/layout/_partial/main/navbar/dateinfo.ejs +28 -12
- package/layout/_partial/main/navbar/ghinfo.ejs +5 -3
- package/layout/_partial/main/notebook/note_card.ejs +12 -17
- package/layout/_partial/main/notebook/note_tags.ejs +3 -4
- package/layout/_partial/main/notebook/notebook_card.ejs +11 -4
- package/layout/_partial/main/pin_slider.ejs +48 -43
- package/layout/_partial/main/post_list/archive_item.ejs +8 -4
- package/layout/_partial/main/post_list/latest_post_card.ejs +10 -4
- package/layout/_partial/main/post_list/post_card.ejs +39 -90
- package/layout/_partial/main/post_list/topic_card.ejs +7 -5
- package/layout/_partial/main/post_list/wiki_card.ejs +16 -13
- package/layout/_partial/menubtn.ejs +3 -3
- package/layout/_partial/primitives/item.ejs +8 -0
- package/layout/_partial/primitives/navigation.ejs +17 -0
- package/layout/_partial/primitives/region.ejs +16 -0
- package/layout/_partial/primitives/section.ejs +8 -0
- package/layout/_partial/primitives/shell.ejs +35 -0
- package/layout/_partial/regions/widgets.ejs +84 -0
- package/layout/_partial/scripts/defines.ejs +46 -76
- package/layout/_partial/scripts/runtime.ejs +55 -0
- package/layout/_partial/scripts.ejs +18 -42
- package/layout/_partial/search/dialog.ejs +33 -0
- package/layout/_partial/search/shortcut.ejs +1 -0
- package/layout/_partial/sidebar/brand.ejs +127 -0
- package/layout/_partial/sidebar/menu.ejs +59 -14
- package/layout/_partial/sidebar/search.ejs +58 -41
- package/layout/_partial/widgets/actions.ejs +25 -0
- package/layout/_partial/widgets/author.ejs +9 -5
- package/layout/_partial/widgets/components/linklist.ejs +1 -2
- package/layout/_partial/widgets/ghissues.ejs +7 -10
- package/layout/_partial/widgets/ghrepo.ejs +23 -16
- package/layout/_partial/widgets/ghuser.ejs +3 -3
- package/layout/_partial/widgets/recent.ejs +25 -21
- package/layout/_partial/widgets/related.ejs +25 -34
- package/layout/_partial/widgets/settings.ejs +17 -0
- package/layout/_partial/widgets/tagtree.ejs +18 -10
- package/layout/_partial/widgets/toc.ejs +17 -9
- package/layout/_partial/widgets/tree.ejs +12 -13
- package/layout/archive.ejs +20 -13
- package/layout/categories.ejs +4 -3
- package/layout/collection-preview.ejs +8 -9
- package/layout/index.ejs +32 -43
- package/layout/index_topic.ejs +6 -13
- package/layout/index_wiki.ejs +17 -34
- package/layout/layout.ejs +195 -65
- package/layout/notebooks.ejs +3 -3
- package/layout/notes.ejs +4 -4
- package/layout/page.ejs +95 -50
- package/layout/settings.ejs +109 -0
- package/layout/tags.ejs +4 -3
- package/{THIRD-PARTY-NOTICES.md → legal/THIRD-PARTY-NOTICES.md} +8 -4
- package/package.json +28 -6
- package/scripts/commands/stellar.js +55 -0
- package/scripts/events/index.js +15 -9
- package/scripts/events/lib/authors.js +9 -7
- package/scripts/events/lib/config-hot-reload.js +65 -0
- package/scripts/events/lib/config-schema.js +20 -0
- package/scripts/events/lib/config.js +22 -63
- package/scripts/events/lib/content-config.js +5 -0
- package/scripts/events/lib/doc_tree.js +99 -9
- package/scripts/events/lib/links.js +6 -3
- package/scripts/events/lib/notebooks.js +200 -8
- package/scripts/events/lib/utils.js +4 -4
- package/scripts/events/lib/version-check.js +57 -3
- package/scripts/filters/index.js +2 -27
- package/scripts/filters/lib/img_lazyload.js +4 -126
- package/scripts/filters/lib/img_onerror.js +14 -17
- package/scripts/filters/lib/page-view-model.js +152 -0
- package/scripts/generators/404.js +13 -8
- package/scripts/generators/author.js +16 -11
- package/scripts/generators/notebooks.js +68 -39
- package/scripts/generators/search.js +62 -67
- package/scripts/generators/settings.js +43 -0
- package/scripts/generators/stellar-icons.js +13 -5
- package/scripts/generators/topic.js +24 -13
- package/scripts/generators/wiki.js +49 -21
- package/scripts/helpers/ai_label.js +6 -12
- package/scripts/helpers/brand.js +17 -0
- package/scripts/helpers/browser_runtime_manifest.js +12 -0
- package/scripts/helpers/caption.js +8 -0
- package/scripts/helpers/category_color.js +13 -13
- package/scripts/helpers/collection.js +18 -0
- package/scripts/helpers/comments_model.js +14 -0
- package/scripts/helpers/extension_assets.js +8 -0
- package/scripts/helpers/json_ld.js +16 -7
- package/scripts/helpers/mdrender.js +3 -9
- package/scripts/helpers/menu.js +9 -0
- package/scripts/helpers/post_view_model.js +23 -0
- package/scripts/helpers/regions.js +37 -0
- package/scripts/helpers/scrollreveal.js +7 -7
- package/scripts/helpers/search_domain.js +34 -0
- package/scripts/helpers/service_provider.js +9 -0
- package/scripts/helpers/settings.js +16 -0
- package/scripts/helpers/share_action.js +8 -0
- package/scripts/helpers/stellar_config.js +12 -0
- package/scripts/helpers/stellar_data.js +8 -0
- package/scripts/helpers/stellar_info.js +21 -16
- package/scripts/helpers/stellar_inject.js +9 -0
- package/scripts/helpers/ui_classes.js +12 -0
- package/scripts/lib/ai_label.js +14 -11
- package/scripts/lib/brand.js +41 -0
- package/scripts/lib/browser-runtime.js +115 -0
- package/scripts/lib/caption.js +21 -0
- package/scripts/lib/collection-pipeline/adapters/notebook.js +62 -0
- package/scripts/lib/collection-pipeline/adapters/post.js +20 -0
- package/scripts/lib/collection-pipeline/adapters/topic.js +82 -0
- package/scripts/lib/collection-pipeline/adapters/wiki.js +20 -0
- package/scripts/lib/collection-pipeline/index.js +169 -0
- package/scripts/lib/collection-pipeline/registry.js +27 -0
- package/scripts/lib/collection-pipeline/shared.js +164 -0
- package/scripts/lib/comments.js +60 -0
- package/scripts/lib/config-inject.js +20 -0
- package/scripts/lib/config-schema.js +1095 -0
- package/scripts/lib/content-config.js +157 -0
- package/scripts/lib/content-defaults.js +56 -0
- package/scripts/lib/content-membership.js +233 -0
- package/scripts/lib/contribution-contract.js +165 -0
- package/scripts/lib/contribution-registry.js +429 -0
- package/scripts/lib/doc_tree.js +46 -44
- package/scripts/lib/doctor.js +296 -0
- package/scripts/lib/front-matter.js +29 -0
- package/scripts/lib/hero-effect-registry.js +262 -0
- package/scripts/lib/html-images.js +128 -0
- package/scripts/lib/internal-constants.js +152 -0
- package/scripts/lib/layout-config.js +62 -0
- package/scripts/lib/mdrender_html.js +36 -23
- package/scripts/lib/menu.js +36 -0
- package/scripts/lib/model-schema.js +90 -0
- package/scripts/lib/models/index.js +1542 -0
- package/scripts/lib/new-note.js +172 -0
- package/scripts/lib/page-view-model-registry.js +103 -0
- package/scripts/lib/regions.js +132 -0
- package/scripts/lib/runtime-data.js +23 -0
- package/scripts/lib/safe-path.js +44 -0
- package/scripts/lib/search-domain.js +59 -0
- package/scripts/lib/seo.js +10 -10
- package/scripts/lib/service-provider.js +12 -0
- package/scripts/lib/settings.js +16 -0
- package/scripts/lib/share-services.js +96 -0
- package/scripts/lib/source-config.js +27 -0
- package/scripts/lib/theme-metadata.js +22 -0
- package/scripts/lib/ui-capabilities.js +32 -0
- package/scripts/lib/widget-registry.js +150 -0
- package/scripts/lib/wiki_readme.js +15 -11
- package/scripts/schema/config-rules.js +290 -0
- package/scripts/schema/config-schema.js +163 -0
- package/scripts/schema/content-config-rules.js +264 -0
- package/scripts/schema/content-config-schema.js +321 -0
- package/scripts/schema/model-schema.js +971 -0
- package/scripts/schema/schema-utils.js +48 -0
- package/scripts/tags/index.js +2 -3
- package/scripts/tags/lib/albums.js +6 -6
- package/scripts/tags/lib/banner.js +10 -4
- package/scripts/tags/lib/box.js +1 -1
- package/scripts/tags/lib/button.js +1 -1
- package/scripts/tags/lib/chat.js +9 -31
- package/scripts/tags/lib/checkbox.js +6 -1
- package/scripts/tags/lib/copy.js +4 -1
- package/scripts/tags/lib/dropdown.js +6 -4
- package/scripts/tags/lib/emoji.js +5 -10
- package/scripts/tags/lib/friends.js +4 -4
- package/scripts/tags/lib/gallery.js +7 -7
- package/scripts/tags/lib/ghcard.js +5 -3
- package/scripts/tags/lib/gist.js +16 -0
- package/scripts/tags/lib/grid.js +4 -2
- package/scripts/tags/lib/hashtag.js +1 -1
- package/scripts/tags/lib/icon.js +1 -1
- package/scripts/tags/lib/image.js +1 -1
- package/scripts/tags/lib/link.js +6 -3
- package/scripts/tags/lib/mark.js +8 -3
- package/scripts/tags/lib/md.js +1 -1
- package/scripts/tags/lib/note.js +1 -1
- package/scripts/tags/lib/okr.js +18 -1
- package/scripts/tags/lib/posters.js +6 -6
- package/scripts/tags/lib/quot.js +15 -5
- package/scripts/tags/lib/rating.js +7 -4
- package/scripts/tags/lib/sites.js +8 -6
- package/scripts/tags/lib/tip.js +6 -6
- package/scripts/tags/lib/toc.js +2 -2
- package/scripts/tags/lib/vote.js +8 -5
- package/source/css/_appearances/_mixins.styl +258 -0
- package/source/css/_appearances/card.styl +30 -0
- package/source/css/_appearances/flat.styl +44 -0
- package/source/css/_appearances/glass.styl +138 -0
- package/source/css/_appearances/minimal.styl +325 -0
- package/source/css/_common/base.styl +5 -9
- package/source/css/_common/blockquote.styl +0 -1
- package/source/css/_common/button.styl +1 -1
- package/source/css/_common/device.styl +24 -42
- package/source/css/_common/dropdown.styl +20 -14
- package/source/css/_common/highlight.styl +5 -12
- package/source/css/_common/icon.styl +38 -0
- package/source/css/_common/input.styl +33 -0
- package/source/css/_common/loading.styl +2 -2
- package/source/css/_common/svg.styl +0 -8
- package/source/css/_components/collection.styl +21 -64
- package/source/css/_components/layout.styl +524 -84
- package/source/css/_components/list.styl +36 -22
- package/source/css/_components/main.styl +16 -19
- package/source/css/_components/md.styl +8 -13
- package/source/css/_components/pages/article-indent.styl +1 -1
- package/source/css/_components/pages/article-story.styl +7 -6
- package/source/css/_components/pages/article-tech.styl +5 -5
- package/source/css/_components/pages/collection-preview.styl +1 -5
- package/source/css/_components/pages/settings.styl +184 -0
- package/source/css/_components/partial/article-banner.styl +2 -2
- package/source/css/_components/partial/article-footer.styl +0 -32
- package/source/css/_components/partial/article-share.styl +48 -0
- package/source/css/_components/partial/article-tags.styl +1 -1
- package/source/css/_components/partial/cover.styl +13 -13
- package/source/css/_components/partial/listing-nav.styl +68 -0
- package/source/css/_components/partial/paginator.styl +1 -1
- package/source/css/_components/pin-slider.styl +3 -3
- package/source/css/_components/sidebar/brand.styl +135 -0
- package/source/css/_components/sidebar/footer.styl +14 -31
- package/source/css/_components/sidebar/nav-area.styl +4 -0
- package/source/css/_components/sidebar/search.styl +176 -26
- package/source/css/_components/sidebar/sidebar.styl +76 -110
- package/source/css/_components/tag-plugins/banner.styl +16 -28
- package/source/css/_components/tag-plugins/checkbox.styl +7 -22
- package/source/css/_components/tag-plugins/copy.styl +3 -5
- package/source/css/_components/tag-plugins/gallery.styl +2 -2
- package/source/css/_components/tag-plugins/grid.styl +3 -0
- package/source/css/_components/tag-plugins/image.styl +2 -1
- package/source/css/_components/tag-plugins/link.styl +0 -1
- package/source/css/_components/tag-plugins/mark.styl +19 -4
- package/source/css/_components/tag-plugins/mdrender.styl +2 -2
- package/source/css/_components/tag-plugins/navbar.styl +23 -17
- package/source/css/_components/tag-plugins/note.styl +2 -2
- package/source/css/_components/tag-plugins/okr.styl +1 -2
- package/source/css/_components/tag-plugins/quot.styl +5 -0
- package/source/css/_components/tag-plugins/rating.styl +3 -2
- package/source/css/_components/tag-plugins/timeline.styl +1 -1
- package/source/css/_components/tag-plugins/tip.styl +1 -1
- package/source/css/_components/tag-plugins/vote.styl +3 -2
- package/source/css/_components/widgets/ghrepo.styl +2 -1
- package/source/css/_components/widgets/ghuser.styl +4 -6
- package/source/css/_components/widgets/settings.styl +49 -0
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +12 -33
- package/source/css/_components/widgets/widgets.styl +14 -19
- package/source/css/_components/wiki-card.styl +2 -2
- package/source/css/_custom.styl +26 -32
- package/source/css/_defines/func.styl +47 -37
- package/source/css/_defines/theme_base.styl +8 -10
- package/source/css/_plugins/card-hover.styl +1 -1
- package/source/css/_plugins/copycode.styl +16 -19
- package/source/css/_plugins/index.styl +3 -8
- package/source/css/_plugins/lazyload.styl +1 -1
- package/source/css/main.styl +12 -0
- package/source/js/main.js +302 -59
- package/source/js/plugins/adaptive-text.js +33 -16
- package/source/js/plugins/card-hover.js +2 -24
- package/source/js/plugins/copycode.js +10 -16
- package/source/js/plugins/voice.js +51 -48
- package/source/js/profile.js +316 -0
- package/source/js/runtime/asset-loader.js +61 -0
- package/source/js/runtime/extension-registry.js +149 -0
- package/source/js/runtime/extensions/card-hover.js +5 -0
- package/source/js/runtime/extensions/color-scheme-switch.js +101 -0
- package/source/js/runtime/extensions/comments.js +200 -0
- package/source/js/runtime/extensions/deferred-icons.js +53 -0
- package/source/js/{plugins → runtime/extensions}/dropdown.js +69 -30
- package/source/js/runtime/extensions/feature.js +200 -0
- package/source/js/runtime/extensions/hero-effect.js +179 -0
- package/source/js/runtime/extensions/reveal.js +80 -0
- package/source/js/runtime/extensions/search.js +23 -0
- package/source/js/{services.js → runtime/extensions/services.js} +67 -54
- package/source/js/runtime/extensions/settings.js +235 -0
- package/source/js/runtime/hero-effects/ferrofluid.js +385 -0
- package/source/js/{plugins → runtime/hero-effects}/galaxy.js +53 -177
- package/source/js/runtime/hero-effects/light-rays.js +293 -0
- package/source/js/runtime/index.js +75 -0
- package/source/js/runtime/legacy-request-adapter.js +47 -0
- package/source/js/runtime/request-cache.js +219 -0
- package/source/js/search/algolia-search.js +76 -51
- package/source/js/search/highlight.js +1 -1
- package/source/js/search/local-search.js +96 -57
- package/source/js/search/shortcut.js +31 -20
- package/source/js/services/friends_and_posts.js +6 -1
- package/source/js/services/rating.js +14 -6
- package/source/js/services/siteinfo.js +3 -7
- package/source/js/services/timeline.js +1 -1
- package/source/js/services/vote.js +7 -5
- package/source/js/services/weibo.js +1 -1
- package/source/js/utils.js +19 -444
- package/.claude/skills/stellar-theme-dev/SKILL.md +0 -51
- package/_data/chat_users.yml +0 -0
- package/layout/_partial/comments/artalk/script.ejs +0 -76
- package/layout/_partial/comments/beaudar/script.ejs +0 -33
- package/layout/_partial/comments/giscus/script.ejs +0 -31
- package/layout/_partial/comments/script.ejs +0 -3
- package/layout/_partial/comments/twikoo/script.ejs +0 -25
- package/layout/_partial/comments/utterances/script.ejs +0 -33
- package/layout/_partial/comments/waline/script.ejs +0 -43
- package/layout/_partial/main/article/article_footer.ejs +0 -164
- package/layout/_partial/main/article/article_tags.ejs +0 -16
- package/layout/_partial/main/article/read_next.ejs +0 -63
- package/layout/_partial/main/article/related_posts.ejs +0 -16
- package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +0 -52
- package/layout/_partial/scripts/bootstrap.ejs +0 -37
- package/layout/_partial/scripts/lazyload.ejs +0 -89
- package/layout/_partial/sidebar/grad-def.ejs +0 -26
- package/layout/_partial/sidebar/index_leftbar.ejs +0 -191
- package/layout/_partial/sidebar/index_rightbar.ejs +0 -94
- package/layout/_partial/sidebar/logo.ejs +0 -99
- package/layout/_plugins/adaptive_text.ejs +0 -11
- package/layout/_plugins/card_hover.ejs +0 -32
- package/layout/_plugins/copycode.ejs +0 -17
- package/layout/_plugins/fancybox.ejs +0 -53
- package/layout/_plugins/heti.ejs +0 -21
- package/layout/_plugins/index.ejs +0 -50
- package/layout/_plugins/mathjax.ejs +0 -49
- package/layout/_plugins/mermaid.ejs +0 -33
- package/layout/_plugins/preload.ejs +0 -9
- package/layout/_plugins/scrollreveal.ejs +0 -102
- package/layout/_plugins/search/algolia_search.ejs +0 -12
- package/layout/_plugins/search/local_search.ejs +0 -9
- package/layout/_plugins/swiper.ejs +0 -27
- package/layout/_plugins/tianli_gpt.ejs +0 -14
- package/scripts/commands/new-note.js +0 -49
- package/scripts/events/lib/merge_posts.js +0 -48
- package/scripts/events/lib/topic_tree.js +0 -48
- package/scripts/helpers/related_posts.js +0 -51
- package/scripts/helpers/subtitle.js +0 -8
- package/scripts/lib/notebooks.js +0 -181
- package/scripts/lib/subtitle.js +0 -28
- package/scripts/tags/lib/about.js +0 -48
- package/source/css/_components/page-transition.styl +0 -22
- package/source/css/_components/partial/navbar.styl +0 -51
- package/source/css/_components/sidebar/logo.styl +0 -111
- package/source/css/_components/tag-plugins/about.styl +0 -56
- package/source/css/_components/widgets/tree.styl +0 -0
- package/source/css/_plugins/scrollreveal.styl +0 -6
- package/source/css/_plugins/tianli_gpt.styl +0 -213
- package/source/css/plugins/mermaid.styl +0 -137
- package/source/js/icons.js +0 -60
- package/source/js/services/fcircle.js +0 -35
- package/source/js/theme.js +0 -61
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* global hexo */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
ConfigSchemaError,
|
|
6
|
+
formatIssue,
|
|
7
|
+
isPlainObject,
|
|
8
|
+
parseConfigSchema
|
|
9
|
+
} = require("./config-schema");
|
|
10
|
+
const {
|
|
11
|
+
COLLECTION_CONFIG_SCHEMA,
|
|
12
|
+
FRONT_MATTER_CONFIG_SCHEMA
|
|
13
|
+
} = require("../schema/content-config-schema");
|
|
14
|
+
|
|
15
|
+
const CONTENT_MODEL_FIELDS = Object.freeze({
|
|
16
|
+
article: Object.freeze(["style", "paragraphIndent", "author", "aiLabel"]),
|
|
17
|
+
banner: Object.freeze(["enabled", "image", "avatar", "headline", "tagline"]),
|
|
18
|
+
brand: Object.freeze(["image", "name", "tagline", "href"]),
|
|
19
|
+
brandImage: Object.freeze(["src", "variant"]),
|
|
20
|
+
comments: Object.freeze(["enabled", "title", "id", "provider", "options"]),
|
|
21
|
+
footer: Object.freeze(["references", "license", "share", "showTags"]),
|
|
22
|
+
navigation: Object.freeze(["menu", "breadcrumb"]),
|
|
23
|
+
regionIds: Object.freeze(["topbar", "leftbar", "rightbar"]),
|
|
24
|
+
region: Object.freeze(["enabled", "brand", "menu", "footer", "widgets"]),
|
|
25
|
+
source: Object.freeze(["repository", "branch"]),
|
|
26
|
+
visibility: Object.freeze(["listed", "searchable"])
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
class ContentConfigError extends Error {
|
|
30
|
+
constructor(issues) {
|
|
31
|
+
super(`Stellar v2 内容配置校验失败:\n${issues.map(issue => `- ${typeof issue === "string" ? issue : formatIssue(issue)}`).join("\n")}`);
|
|
32
|
+
this.name = "ContentConfigError";
|
|
33
|
+
this.issues = issues;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function contentError(error) {
|
|
38
|
+
if (!(error instanceof ConfigSchemaError)) throw error;
|
|
39
|
+
throw new ContentConfigError(error.issues);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseCollectionConfig(config, source = "<collection>", options = {}) {
|
|
43
|
+
try {
|
|
44
|
+
return parseConfigSchema(COLLECTION_CONFIG_SCHEMA, config, {
|
|
45
|
+
...options,
|
|
46
|
+
source,
|
|
47
|
+
isFatalIssue(currentIssue) {
|
|
48
|
+
return currentIssue.path === "name" || options.isFatalIssue?.(currentIssue);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return contentError(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parsePageConfig(config, source = "<page>", options = {}) {
|
|
57
|
+
try {
|
|
58
|
+
return parseConfigSchema(FRONT_MATTER_CONFIG_SCHEMA, config, {
|
|
59
|
+
...options,
|
|
60
|
+
source,
|
|
61
|
+
isFatalIssue(currentIssue) {
|
|
62
|
+
return /^collection\.(?:profile|id)$/.test(currentIssue.path) || options.isFatalIssue?.(currentIssue);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return contentError(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateObjectInput(config, source) {
|
|
71
|
+
if (isPlainObject(config)) return config;
|
|
72
|
+
const issue = Object.freeze({
|
|
73
|
+
code: "invalid_type",
|
|
74
|
+
source,
|
|
75
|
+
path: "root",
|
|
76
|
+
actualType: Array.isArray(config) ? "array" : config === null ? "null" : typeof config,
|
|
77
|
+
expected: "object",
|
|
78
|
+
migration: "configuration/v2"
|
|
79
|
+
});
|
|
80
|
+
throw new ContentConfigError([issue]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateThemeConfig(config, source = "<theme>") {
|
|
84
|
+
return validateObjectInput(config, source);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hasOwn(value, key) {
|
|
88
|
+
return value != null && Object.prototype.hasOwnProperty.call(value, key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unsupportedProfileField(source, path, profile) {
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
code: "invalid_value",
|
|
94
|
+
source,
|
|
95
|
+
path,
|
|
96
|
+
actualType: "declared field",
|
|
97
|
+
expected: `field supported by the ${profile} profile`,
|
|
98
|
+
migration: "content-schema/profile-capabilities"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validateCollectionProfileConfig(config, source, profile, capabilities) {
|
|
103
|
+
const policy = capabilities?.collection;
|
|
104
|
+
if (!policy) throw new ContentConfigError([unsupportedProfileField(source, "root", profile)]);
|
|
105
|
+
const issues = [];
|
|
106
|
+
if (hasOwn(config, "hero") && policy.hero !== true) {
|
|
107
|
+
issues.push(unsupportedProfileField(source, "hero", profile));
|
|
108
|
+
}
|
|
109
|
+
if (hasOwn(config?.route, "start") && policy.routeStart !== true) {
|
|
110
|
+
issues.push(unsupportedProfileField(source, "route.start", profile));
|
|
111
|
+
}
|
|
112
|
+
if (hasOwn(config?.navigation, "tree") && policy.navigationTree !== true) {
|
|
113
|
+
issues.push(unsupportedProfileField(source, "navigation.tree", profile));
|
|
114
|
+
}
|
|
115
|
+
const listingFields = new Set(policy.listing || []);
|
|
116
|
+
for (const field of Object.keys(config?.listing || {})) {
|
|
117
|
+
if (!listingFields.has(field)) {
|
|
118
|
+
issues.push(unsupportedProfileField(source, `listing.${field.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)}`, profile));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (issues.length > 0) throw new ContentConfigError(issues);
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validatePageProfileConfig(config, source, profile, capabilities) {
|
|
126
|
+
if (hasOwn(config?.listing, "priority") && capabilities?.page?.listingPriority !== true) {
|
|
127
|
+
throw new ContentConfigError([unsupportedProfileField(source, "listing.priority", profile)]);
|
|
128
|
+
}
|
|
129
|
+
return config;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getCollectionId(page, profile) {
|
|
133
|
+
if (page?.collection?.profile !== profile) return null;
|
|
134
|
+
return page.collection.id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isListed(content) {
|
|
138
|
+
return content?.visibility?.listed !== false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isSearchable(content) {
|
|
142
|
+
return content?.visibility?.searchable !== false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
CONTENT_MODEL_FIELDS,
|
|
147
|
+
ContentConfigError,
|
|
148
|
+
getCollectionId,
|
|
149
|
+
isPlainObject,
|
|
150
|
+
isListed,
|
|
151
|
+
isSearchable,
|
|
152
|
+
parseCollectionConfig,
|
|
153
|
+
parsePageConfig,
|
|
154
|
+
validateCollectionProfileConfig,
|
|
155
|
+
validatePageProfileConfig,
|
|
156
|
+
validateThemeConfig
|
|
157
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* global hexo */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
ConfigSchemaError,
|
|
6
|
+
isPlainObject,
|
|
7
|
+
valueType
|
|
8
|
+
} = require("./config-schema");
|
|
9
|
+
|
|
10
|
+
function requireContentConfig(stellarConfig, source = "_config.stellar.yml") {
|
|
11
|
+
const article = stellarConfig?.article;
|
|
12
|
+
const notebook = stellarConfig?.profiles?.notebook;
|
|
13
|
+
if (isPlainObject(article) && isPlainObject(notebook)) {
|
|
14
|
+
return { article, notebook };
|
|
15
|
+
}
|
|
16
|
+
const path = isPlainObject(article) ? "stellarConfig.profiles.notebook" : "stellarConfig.article";
|
|
17
|
+
const actual = isPlainObject(article) ? notebook : article;
|
|
18
|
+
throw new ConfigSchemaError([Object.freeze({
|
|
19
|
+
code: "invalid_type",
|
|
20
|
+
source,
|
|
21
|
+
path,
|
|
22
|
+
actualType: valueType(actual),
|
|
23
|
+
expected: "normalized content defaults object",
|
|
24
|
+
migration: null
|
|
25
|
+
})]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function articlePresentationDefaults(content) {
|
|
29
|
+
return {
|
|
30
|
+
style: content.article.style,
|
|
31
|
+
paragraphIndent: content.article.paragraphIndent
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function articleFooterDefaults(content) {
|
|
36
|
+
return {
|
|
37
|
+
references: [],
|
|
38
|
+
license: content.article.footer.license,
|
|
39
|
+
share: content.article.footer.share,
|
|
40
|
+
showTags: content.article.footer.showTags
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function collectionFooterDefaults(content) {
|
|
45
|
+
return {
|
|
46
|
+
...articleFooterDefaults(content),
|
|
47
|
+
share: []
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
articleFooterDefaults,
|
|
53
|
+
articlePresentationDefaults,
|
|
54
|
+
collectionFooterDefaults,
|
|
55
|
+
requireContentConfig
|
|
56
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { deepFreeze } = require("../schema/schema-utils");
|
|
4
|
+
const { normalize_path: normalizePathKey } = require("./path_utils");
|
|
5
|
+
|
|
6
|
+
const PROFILE_DIRECTORY = Object.freeze({
|
|
7
|
+
wiki: "wiki",
|
|
8
|
+
topic: "topic",
|
|
9
|
+
notebook: "notebooks"
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function normalizePath(value) {
|
|
13
|
+
if (typeof value !== "string") return "";
|
|
14
|
+
let result = value.replace(/\\/g, "/").replace(/^source\//, "").replace(/^\/+/, "");
|
|
15
|
+
result = result.replace(/\.md$/i, "");
|
|
16
|
+
result = normalizePathKey(result).replace(/^\/+|\/+$/g, "");
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sourcePagePath(source) {
|
|
21
|
+
const normalized = normalizePath(source);
|
|
22
|
+
return normalizePath(normalized.replace(/^_posts\//, ""));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function flattenTree(tree) {
|
|
26
|
+
if (Array.isArray(tree)) return tree.filter(item => typeof item === "string");
|
|
27
|
+
if (tree == null || typeof tree !== "object") return [];
|
|
28
|
+
return Object.values(tree).flatMap(items => (
|
|
29
|
+
Array.isArray(items) ? items.filter(item => typeof item === "string") : []
|
|
30
|
+
));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function addIndex(index, signal, key) {
|
|
34
|
+
const normalized = normalizePath(signal);
|
|
35
|
+
if (!normalized) return;
|
|
36
|
+
if (!index.has(normalized)) index.set(normalized, new Set());
|
|
37
|
+
index.get(normalized).add(key);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseCollectionKey(key) {
|
|
41
|
+
const matched = String(key).match(/^(wiki|topic|notebooks)\/(.+)$/);
|
|
42
|
+
if (!matched) return null;
|
|
43
|
+
return {
|
|
44
|
+
profile: matched[1] === "notebooks" ? "notebook" : matched[1],
|
|
45
|
+
id: matched[2]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createCollectionRegistry(collectionConfigs) {
|
|
50
|
+
const records = [];
|
|
51
|
+
const byKey = new Map();
|
|
52
|
+
const wikiMembers = new Map();
|
|
53
|
+
const topicStarts = new Map();
|
|
54
|
+
for (const [sourceKey, config] of collectionConfigs || []) {
|
|
55
|
+
const identity = parseCollectionKey(sourceKey);
|
|
56
|
+
if (!identity) continue;
|
|
57
|
+
const key = `${identity.profile}:${identity.id}`;
|
|
58
|
+
const record = deepFreeze({ ...identity, key, sourceKey, config });
|
|
59
|
+
records.push(record);
|
|
60
|
+
byKey.set(key, record);
|
|
61
|
+
if (identity.profile === "wiki") {
|
|
62
|
+
const base = normalizePath(config.route?.path || `wiki/${identity.id}`);
|
|
63
|
+
for (const member of flattenTree(config.navigation?.tree)) {
|
|
64
|
+
addIndex(wikiMembers, `${base}/${member}`, key);
|
|
65
|
+
}
|
|
66
|
+
} else if (identity.profile === "topic" && typeof config.route?.start === "string") {
|
|
67
|
+
addIndex(topicStarts, config.route.start, key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
records: Object.freeze(records),
|
|
72
|
+
byKey,
|
|
73
|
+
wikiMembers,
|
|
74
|
+
topicStarts
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function namespaceFor(kind, source) {
|
|
79
|
+
const normalized = normalizePath(source);
|
|
80
|
+
if (kind === "pages") {
|
|
81
|
+
const wiki = normalized.match(/^wiki\/([^/]+)(?:\/|$)/);
|
|
82
|
+
if (wiki) return { profile: "wiki", id: wiki[1], key: `wiki:${wiki[1]}` };
|
|
83
|
+
const notebook = normalized.match(/^notebooks\/([^/]+)(?:\/|$)/);
|
|
84
|
+
if (notebook) return { profile: "notebook", id: notebook[1], key: `notebook:${notebook[1]}` };
|
|
85
|
+
}
|
|
86
|
+
if (kind === "posts") {
|
|
87
|
+
const topic = normalized.match(/^_posts\/topics?\/([^/]+)(?:\/|$)/);
|
|
88
|
+
if (topic) return { profile: "topic", id: topic[1], key: `topic:${topic[1]}` };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function pageSignals(source, pagePath, config) {
|
|
94
|
+
const normalizedSource = normalizePath(source);
|
|
95
|
+
const withoutPosts = normalizedSource.replace(/^_posts\//, "");
|
|
96
|
+
const signals = new Set([
|
|
97
|
+
normalizedSource,
|
|
98
|
+
withoutPosts,
|
|
99
|
+
sourcePagePath(source),
|
|
100
|
+
normalizePath(pagePath),
|
|
101
|
+
normalizePath(config?.permalink),
|
|
102
|
+
normalizePath(withoutPosts.split("/").at(-1))
|
|
103
|
+
].filter(Boolean));
|
|
104
|
+
return signals;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function collectCandidateKeys({ kind, source, pagePath, config, registry }, includeNamespace) {
|
|
108
|
+
const keys = new Set();
|
|
109
|
+
const namespace = namespaceFor(kind, source);
|
|
110
|
+
if (includeNamespace && namespace && registry.byKey.has(namespace.key)) keys.add(namespace.key);
|
|
111
|
+
const index = kind === "posts" ? registry.topicStarts : registry.wikiMembers;
|
|
112
|
+
for (const signal of pageSignals(source, pagePath, config)) {
|
|
113
|
+
for (const key of index.get(signal) || []) keys.add(key);
|
|
114
|
+
}
|
|
115
|
+
return Object.freeze([...keys].sort());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function candidateKeys(input) {
|
|
119
|
+
return collectCandidateKeys(input, true);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function membershipIssue(code, source, actual, candidates, fix) {
|
|
123
|
+
const list = candidates.length > 0 ? candidates.join(", ") : "<none>";
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
code,
|
|
126
|
+
source,
|
|
127
|
+
path: "collection",
|
|
128
|
+
actualType: actual,
|
|
129
|
+
expected: `one registered Collection; candidates=${list}; fix=${fix}`,
|
|
130
|
+
migration: "content-schema/membership"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveContentMembership(input) {
|
|
135
|
+
const kind = input.kind;
|
|
136
|
+
const source = input.source || "<page>";
|
|
137
|
+
const config = input.config || {};
|
|
138
|
+
const registry = input.registry;
|
|
139
|
+
const candidates = candidateKeys({ ...input, registry });
|
|
140
|
+
const relationshipCandidates = collectCandidateKeys({ ...input, registry }, false);
|
|
141
|
+
const namespace = namespaceFor(kind, source);
|
|
142
|
+
const hardCandidates = new Set(relationshipCandidates);
|
|
143
|
+
if (namespace?.profile !== "wiki" && registry.byKey.has(namespace?.key)) hardCandidates.add(namespace.key);
|
|
144
|
+
const conflictingCandidates = Object.freeze([...hardCandidates].sort());
|
|
145
|
+
const explicit = config.collection == null
|
|
146
|
+
? null
|
|
147
|
+
: `${config.collection.profile}:${config.collection.id}`;
|
|
148
|
+
const allowedProfiles = kind === "posts" ? new Set(["topic"]) : new Set(["wiki", "notebook"]);
|
|
149
|
+
|
|
150
|
+
if (explicit) {
|
|
151
|
+
const profile = config.collection.profile;
|
|
152
|
+
if (!allowedProfiles.has(profile) || !registry.byKey.has(explicit)) {
|
|
153
|
+
return deepFreeze({
|
|
154
|
+
config: null,
|
|
155
|
+
candidates,
|
|
156
|
+
inferred: false,
|
|
157
|
+
issues: [membershipIssue(
|
|
158
|
+
"collection_not_found",
|
|
159
|
+
source,
|
|
160
|
+
`collection:${explicit}`,
|
|
161
|
+
candidates,
|
|
162
|
+
"use a profile/id registered in source/_data and valid for this content source"
|
|
163
|
+
)]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (conflictingCandidates.length > 0 && !conflictingCandidates.includes(explicit)) {
|
|
167
|
+
return deepFreeze({
|
|
168
|
+
config: null,
|
|
169
|
+
candidates,
|
|
170
|
+
inferred: false,
|
|
171
|
+
issues: [membershipIssue(
|
|
172
|
+
"collection_conflict",
|
|
173
|
+
source,
|
|
174
|
+
`collection:${explicit}`,
|
|
175
|
+
conflictingCandidates,
|
|
176
|
+
"change collection.profile/id to the inferred member or move the source file"
|
|
177
|
+
)]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return deepFreeze({ config, candidates, inferred: false, issues: [] });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (candidates.length === 1) {
|
|
184
|
+
const record = registry.byKey.get(candidates[0]);
|
|
185
|
+
return deepFreeze({
|
|
186
|
+
config: {
|
|
187
|
+
...config,
|
|
188
|
+
collection: { profile: record.profile, id: record.id }
|
|
189
|
+
},
|
|
190
|
+
candidates,
|
|
191
|
+
inferred: true,
|
|
192
|
+
issues: []
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (candidates.length > 1) {
|
|
196
|
+
return deepFreeze({
|
|
197
|
+
config: null,
|
|
198
|
+
candidates,
|
|
199
|
+
inferred: false,
|
|
200
|
+
issues: [membershipIssue(
|
|
201
|
+
"collection_ambiguous",
|
|
202
|
+
source,
|
|
203
|
+
"collection:missing",
|
|
204
|
+
candidates,
|
|
205
|
+
"add collection.profile and collection.id to this page"
|
|
206
|
+
)]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (namespace) {
|
|
211
|
+
return deepFreeze({
|
|
212
|
+
config: null,
|
|
213
|
+
candidates,
|
|
214
|
+
inferred: false,
|
|
215
|
+
issues: [membershipIssue(
|
|
216
|
+
"collection_not_found",
|
|
217
|
+
source,
|
|
218
|
+
"collection:missing",
|
|
219
|
+
candidates,
|
|
220
|
+
`create source/_data/${PROFILE_DIRECTORY[namespace.profile]}/${namespace.id}.yml or add a valid collection.profile/id`
|
|
221
|
+
)]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return deepFreeze({ config, candidates, inferred: false, issues: [] });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
candidateKeys,
|
|
229
|
+
createCollectionRegistry,
|
|
230
|
+
normalizePath,
|
|
231
|
+
resolveContentMembership,
|
|
232
|
+
sourcePagePath
|
|
233
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* global hexo */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const CONTRIBUTION_KINDS = new Set(["extension", "feature", "component"]);
|
|
6
|
+
const ENTRY_TYPES = new Set(["browser-module", "template"]);
|
|
7
|
+
const ACTIVATION_TYPES = new Set(["always", "selector", "server"]);
|
|
8
|
+
const DEFINITION_FIELDS = new Set([
|
|
9
|
+
"id",
|
|
10
|
+
"kind",
|
|
11
|
+
"entry",
|
|
12
|
+
"resources",
|
|
13
|
+
"activation",
|
|
14
|
+
"schema",
|
|
15
|
+
"i18n",
|
|
16
|
+
"docs",
|
|
17
|
+
"tests",
|
|
18
|
+
"defaultsOwner",
|
|
19
|
+
"project"
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function deepFreeze(value) {
|
|
23
|
+
if (value == null || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
24
|
+
Object.values(value).forEach(deepFreeze);
|
|
25
|
+
return Object.freeze(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function plainObject(value, label) {
|
|
29
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
30
|
+
throw new TypeError(`[stellar contributions] ${label} must be an object`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nonEmptyString(value, label) {
|
|
36
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
37
|
+
throw new TypeError(`[stellar contributions] ${label} must be a non-empty string`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function stringList(value, label, options = {}) {
|
|
42
|
+
if (!Array.isArray(value) || (!options.allowEmpty && value.length === 0)) {
|
|
43
|
+
throw new TypeError(`[stellar contributions] ${label} must be ${options.allowEmpty ? "an" : "a non-empty"} array`);
|
|
44
|
+
}
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
value.forEach((item, index) => {
|
|
47
|
+
nonEmptyString(item, `${label}[${index}]`);
|
|
48
|
+
if (seen.has(item)) throw new TypeError(`[stellar contributions] duplicate ${label} value ${item}`);
|
|
49
|
+
seen.add(item);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateEntry(entry, label) {
|
|
54
|
+
plainObject(entry, label);
|
|
55
|
+
const allowed = new Set(["type", "path", "adapter"]);
|
|
56
|
+
const unknown = Object.keys(entry).find(key => !allowed.has(key));
|
|
57
|
+
if (unknown) throw new TypeError(`[stellar contributions] ${label} has unknown field ${unknown}`);
|
|
58
|
+
if (!ENTRY_TYPES.has(entry.type)) throw new TypeError(`[stellar contributions] ${label}.type is invalid`);
|
|
59
|
+
nonEmptyString(entry.path, `${label}.path`);
|
|
60
|
+
if (entry.type === "browser-module" && (!entry.path.startsWith("/js/runtime/") || !entry.path.endsWith(".js"))) {
|
|
61
|
+
throw new TypeError(`[stellar contributions] ${label}.path must be a local runtime .js path`);
|
|
62
|
+
}
|
|
63
|
+
if (entry.type === "template" && !entry.path.startsWith("layout/")) {
|
|
64
|
+
throw new TypeError(`[stellar contributions] ${label}.path must be a layout path`);
|
|
65
|
+
}
|
|
66
|
+
if (entry.adapter !== undefined && entry.adapter !== "feature") {
|
|
67
|
+
throw new TypeError(`[stellar contributions] ${label}.adapter is invalid`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validateActivation(activation, label) {
|
|
72
|
+
plainObject(activation, label);
|
|
73
|
+
const allowed = new Set(["type", "value"]);
|
|
74
|
+
const unknown = Object.keys(activation).find(key => !allowed.has(key));
|
|
75
|
+
if (unknown) throw new TypeError(`[stellar contributions] ${label} has unknown field ${unknown}`);
|
|
76
|
+
if (!ACTIVATION_TYPES.has(activation.type)) throw new TypeError(`[stellar contributions] ${label}.type is invalid`);
|
|
77
|
+
if (activation.type === "always") {
|
|
78
|
+
if (activation.value !== undefined) throw new TypeError(`[stellar contributions] ${label}.value is not allowed for always`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
nonEmptyString(activation.value, `${label}.value`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function validateI18n(i18n, label) {
|
|
85
|
+
if (i18n === null) return;
|
|
86
|
+
plainObject(i18n, label);
|
|
87
|
+
const unknown = Object.keys(i18n).find(key => !["namespace", "keys"].includes(key));
|
|
88
|
+
if (unknown) throw new TypeError(`[stellar contributions] ${label} has unknown field ${unknown}`);
|
|
89
|
+
nonEmptyString(i18n.namespace, `${label}.namespace`);
|
|
90
|
+
stringList(i18n.keys, `${label}.keys`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateDocs(docs, label) {
|
|
94
|
+
plainObject(docs, label);
|
|
95
|
+
const unknown = Object.keys(docs).find(key => !["category", "path"].includes(key));
|
|
96
|
+
if (unknown) throw new TypeError(`[stellar contributions] ${label} has unknown field ${unknown}`);
|
|
97
|
+
nonEmptyString(docs.category, `${label}.category`);
|
|
98
|
+
nonEmptyString(docs.path, `${label}.path`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function validateContributionDefinitions(definitions) {
|
|
102
|
+
if (!Array.isArray(definitions) || definitions.length === 0) {
|
|
103
|
+
throw new TypeError("[stellar contributions] registry must be a non-empty array");
|
|
104
|
+
}
|
|
105
|
+
const ids = new Set();
|
|
106
|
+
const resourceOwners = new Map();
|
|
107
|
+
const schemaOwners = new Map();
|
|
108
|
+
|
|
109
|
+
definitions.forEach((definition, index) => {
|
|
110
|
+
const label = `definitions[${index}]`;
|
|
111
|
+
plainObject(definition, label);
|
|
112
|
+
const unknown = Object.keys(definition).find(key => !DEFINITION_FIELDS.has(key));
|
|
113
|
+
if (unknown) throw new TypeError(`[stellar contributions] ${label} has unknown field ${unknown}`);
|
|
114
|
+
const missing = [...DEFINITION_FIELDS].find(key => !Object.prototype.hasOwnProperty.call(definition, key));
|
|
115
|
+
if (missing) throw new TypeError(`[stellar contributions] ${label} is missing field ${missing}`);
|
|
116
|
+
if (typeof definition.id !== "string" || !/^[a-z][a-z0-9-]*$/.test(definition.id)) {
|
|
117
|
+
throw new TypeError(`[stellar contributions] ${label}.id is invalid`);
|
|
118
|
+
}
|
|
119
|
+
if (ids.has(definition.id)) throw new TypeError(`[stellar contributions] duplicate contribution id ${definition.id}`);
|
|
120
|
+
ids.add(definition.id);
|
|
121
|
+
if (!CONTRIBUTION_KINDS.has(definition.kind)) throw new TypeError(`[stellar contributions] ${label}.kind is invalid`);
|
|
122
|
+
validateEntry(definition.entry, `${label}.entry`);
|
|
123
|
+
stringList(definition.resources, `${label}.resources`);
|
|
124
|
+
validateActivation(definition.activation, `${label}.activation`);
|
|
125
|
+
if (definition.schema !== null) nonEmptyString(definition.schema, `${label}.schema`);
|
|
126
|
+
validateI18n(definition.i18n, `${label}.i18n`);
|
|
127
|
+
validateDocs(definition.docs, `${label}.docs`);
|
|
128
|
+
stringList(definition.tests, `${label}.tests`);
|
|
129
|
+
if (definition.defaultsOwner !== null) nonEmptyString(definition.defaultsOwner, `${label}.defaultsOwner`);
|
|
130
|
+
if ((definition.schema === null) !== (definition.defaultsOwner === null)) {
|
|
131
|
+
throw new TypeError(`[stellar contributions] ${definition.id} schema and defaultsOwner must both be null or strings`);
|
|
132
|
+
}
|
|
133
|
+
if (definition.project !== null && typeof definition.project !== "function") {
|
|
134
|
+
throw new TypeError(`[stellar contributions] ${label}.project must be a function or null`);
|
|
135
|
+
}
|
|
136
|
+
if (definition.project !== null && definition.entry.type !== "browser-module") {
|
|
137
|
+
throw new TypeError(`[stellar contributions] ${definition.id} projected contribution must use a browser-module entry`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
definition.resources.forEach(resource => {
|
|
141
|
+
if (resourceOwners.has(resource)) {
|
|
142
|
+
throw new TypeError(`[stellar contributions] resource ${resource} is owned by both ${resourceOwners.get(resource)} and ${definition.id}`);
|
|
143
|
+
}
|
|
144
|
+
resourceOwners.set(resource, definition.id);
|
|
145
|
+
});
|
|
146
|
+
if (definition.schema !== null) {
|
|
147
|
+
const owner = schemaOwners.get(definition.schema);
|
|
148
|
+
if (owner && owner !== definition.defaultsOwner) {
|
|
149
|
+
throw new TypeError(`[stellar contributions] schema ${definition.schema} has conflicting defaults owners ${owner} and ${definition.defaultsOwner}`);
|
|
150
|
+
}
|
|
151
|
+
schemaOwners.set(definition.schema, definition.defaultsOwner);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return definitions;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function defineContributions(definitions) {
|
|
158
|
+
validateContributionDefinitions(definitions);
|
|
159
|
+
return deepFreeze(definitions.slice());
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
defineContributions,
|
|
164
|
+
validateContributionDefinitions
|
|
165
|
+
};
|