@takazudo/zudo-doc 0.2.21 → 1.0.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/bin/gen-z-index.mjs +164 -0
- package/bin/tags-audit-runner.ts +126 -0
- package/bin/tags-audit.mjs +64 -0
- package/bin/zudo-doc.mjs +83 -0
- package/dist/ai-chat-modal/index.d.ts +8 -0
- package/dist/ai-chat-modal/index.js +243 -0
- package/dist/body-foot-util/body-foot-util-area.d.ts +4 -8
- package/dist/body-foot-util/edit-link.d.ts +4 -8
- package/dist/body-foot-util/index.d.ts +4 -3
- package/dist/breadcrumb/breadcrumb.d.ts +5 -9
- package/dist/breadcrumb/find-path.d.ts +2 -5
- package/dist/breadcrumb/index.d.ts +4 -4
- package/dist/breadcrumb/types.d.ts +3 -5
- package/dist/category-nav/index.d.ts +86 -0
- package/dist/category-nav/index.js +44 -0
- package/dist/category-tree-nav/index.d.ts +65 -0
- package/dist/category-tree-nav/index.js +24 -0
- package/dist/code-group/index.d.ts +10 -0
- package/dist/code-group/index.js +37 -0
- package/dist/code-syntax/code-block-enhancer-script.d.ts +1 -3
- package/dist/code-syntax/code-block-enhancer.d.ts +3 -6
- package/dist/code-syntax/index.d.ts +9 -8
- package/dist/code-syntax/mermaid-init-script.d.ts +3 -5
- package/dist/code-syntax/mermaid-init.d.ts +4 -7
- package/dist/code-syntax/tabs-init-script.d.ts +1 -3
- package/dist/code-syntax/tabs-init.d.ts +3 -6
- package/dist/code-syntax/tabs.d.ts +4 -7
- package/dist/color-scheme-utils.d.ts +125 -0
- package/dist/color-scheme-utils.js +151 -0
- package/dist/compose-meta-title/index.d.ts +7 -0
- package/dist/compose-meta-title/index.js +10 -0
- package/dist/content/component-map.d.ts +13 -17
- package/dist/content/content-blockquote.d.ts +3 -6
- package/dist/content/content-code.d.ts +3 -6
- package/dist/content/content-code.js +1 -1
- package/dist/content/content-link.d.ts +3 -6
- package/dist/content/content-link.js +1 -1
- package/dist/content/content-ol.d.ts +3 -6
- package/dist/content/content-paragraph.d.ts +3 -6
- package/dist/content/content-strong.d.ts +3 -6
- package/dist/content/content-table.d.ts +3 -6
- package/dist/content/content-ul.d.ts +3 -6
- package/dist/content/heading-h2.d.ts +3 -6
- package/dist/content/heading-h3.d.ts +3 -6
- package/dist/content/heading-h4.d.ts +3 -6
- package/dist/content/index.d.ts +29 -13
- package/dist/content-admonition/index.d.ts +15 -0
- package/dist/content-admonition/index.js +14 -0
- package/dist/design-token-panel-bootstrap.d.ts +38 -0
- package/dist/design-token-panel-bootstrap.js +29 -0
- package/dist/desktop-sidebar-toggle-island/index.d.ts +5 -0
- package/dist/desktop-sidebar-toggle-island/index.js +62 -0
- package/dist/details/details.d.ts +3 -7
- package/dist/details/index.d.ts +2 -2
- package/dist/doc-body-end/index.d.ts +21 -0
- package/dist/doc-body-end/index.js +15 -0
- package/dist/doc-content-header/index.d.ts +69 -0
- package/dist/doc-content-header/index.js +49 -0
- package/dist/doc-history/index.d.ts +10 -0
- package/dist/doc-history/index.js +407 -0
- package/dist/doc-history-area/index.d.ts +84 -0
- package/dist/doc-history-area/index.js +76 -0
- package/dist/doc-metainfo-area/index.d.ts +46 -0
- package/dist/doc-metainfo-area/index.js +41 -0
- package/dist/doc-page-props/index.d.ts +93 -0
- package/dist/doc-page-props/index.js +0 -0
- package/dist/doc-page-renderer/index.d.ts +128 -0
- package/dist/doc-page-renderer/index.js +103 -0
- package/dist/doc-page-shell/index.d.ts +136 -0
- package/dist/doc-page-shell/index.js +127 -0
- package/dist/doc-pager/index.d.ts +25 -0
- package/dist/doc-pager/index.js +41 -0
- package/dist/doc-route-entries/index.d.ts +125 -0
- package/dist/doc-route-entries/index.js +76 -0
- package/dist/doc-route-paths/index.d.ts +54 -0
- package/dist/doc-route-paths/index.js +61 -0
- package/dist/doc-tags-area/index.d.ts +38 -0
- package/dist/doc-tags-area/index.js +30 -0
- package/dist/doclayout/anchors.d.ts +7 -9
- package/dist/doclayout/doc-layout-with-defaults.d.ts +16 -12
- package/dist/doclayout/doc-layout.d.ts +5 -9
- package/dist/doclayout/index.d.ts +6 -6
- package/dist/eject/index.d.ts +33 -0
- package/dist/eject/index.js +229 -0
- package/dist/extract-headings/index.d.ts +43 -0
- package/dist/extract-headings/index.js +111 -0
- package/dist/factory-context/index.d.ts +80 -0
- package/dist/factory-context/index.js +0 -0
- package/dist/features.css +882 -0
- package/dist/footer/footer.d.ts +4 -8
- package/dist/footer/index.d.ts +13 -3
- package/dist/footer/types.d.ts +4 -6
- package/dist/footer-with-defaults/index.d.ts +63 -0
- package/dist/footer-with-defaults/index.js +128 -0
- package/dist/frontmatter-preview-data/index.d.ts +26 -0
- package/dist/frontmatter-preview-data/index.js +23 -0
- package/dist/github-helpers/index.d.ts +17 -0
- package/dist/github-helpers/index.js +16 -0
- package/dist/head/doc-head.d.ts +3 -6
- package/dist/head/index.d.ts +9 -5
- package/dist/head/og-tags.d.ts +4 -7
- package/dist/head/twitter-card.d.ts +3 -6
- package/dist/head/types.d.ts +1 -3
- package/dist/head-with-defaults/index.d.ts +49 -0
- package/dist/head-with-defaults/index.js +62 -0
- package/dist/header/header.d.ts +6 -10
- package/dist/header/index.d.ts +16 -6
- package/dist/header/nav-active.d.ts +5 -7
- package/dist/header/nav-overflow-script.d.ts +1 -3
- package/dist/header/right-items.d.ts +3 -6
- package/dist/header/types.d.ts +10 -12
- package/dist/header-with-defaults/index.d.ts +77 -0
- package/dist/header-with-defaults/index.js +163 -0
- package/dist/html-preview-wrapper/dedent.d.ts +1 -3
- package/dist/html-preview-wrapper/highlighted-code.d.ts +3 -7
- package/dist/html-preview-wrapper/html-preview-wrapper.d.ts +6 -10
- package/dist/html-preview-wrapper/html-preview.d.ts +5 -9
- package/dist/html-preview-wrapper/index.d.ts +8 -5
- package/dist/html-preview-wrapper/preflight.d.ts +1 -3
- package/dist/html-preview-wrapper/preview-base.d.ts +4 -8
- package/dist/i18n-version/index.d.ts +7 -5
- package/dist/i18n-version/language-switcher.d.ts +5 -8
- package/dist/i18n-version/types.d.ts +3 -5
- package/dist/i18n-version/version-banner.d.ts +4 -8
- package/dist/i18n-version/version-switcher.d.ts +7 -10
- package/dist/icons/index.d.ts +9 -13
- package/dist/image-enlarge/index.d.ts +13 -0
- package/dist/image-enlarge/index.js +176 -0
- package/dist/index.d.ts +26 -2
- package/dist/inline-version-switcher/index.d.ts +31 -0
- package/dist/inline-version-switcher/index.js +44 -0
- package/dist/integrations/claude-resources/escape-for-mdx.d.ts +1 -3
- package/dist/integrations/claude-resources/generate.d.ts +2 -4
- package/dist/integrations/claude-resources/index.d.ts +7 -10
- package/dist/integrations/doc-history/index.d.ts +21 -24
- package/dist/integrations/doc-history/pre-build.d.ts +7 -9
- package/dist/integrations/llms-txt/dev-middleware.d.ts +7 -11
- package/dist/integrations/llms-txt/emit.d.ts +2 -7
- package/dist/integrations/llms-txt/generate.d.ts +3 -8
- package/dist/integrations/llms-txt/index.d.ts +33 -8
- package/dist/integrations/llms-txt/load.d.ts +3 -7
- package/dist/integrations/llms-txt/strip.d.ts +2 -6
- package/dist/integrations/llms-txt/types.d.ts +9 -13
- package/dist/integrations/search-index/build-emitter.d.ts +4 -7
- package/dist/integrations/search-index/collect.d.ts +2 -5
- package/dist/integrations/search-index/dev-middleware.d.ts +5 -8
- package/dist/integrations/search-index/index.d.ts +7 -5
- package/dist/integrations/search-index/types.d.ts +5 -7
- package/dist/island-types/index.d.ts +44 -0
- package/dist/island-types/index.js +12 -0
- package/dist/locale-merge/index.d.ts +114 -0
- package/dist/locale-merge/index.js +27 -0
- package/dist/math-block/index.d.ts +18 -0
- package/dist/math-block/index.js +29 -0
- package/dist/md-utils/index.d.ts +6 -8
- package/dist/md-utils/index.js +2 -1
- package/dist/mdx-components/index.d.ts +46 -0
- package/dist/mdx-components/index.js +161 -0
- package/dist/mermaid-enlarge/index.d.ts +11 -0
- package/dist/mermaid-enlarge/index.js +290 -0
- package/dist/metainfo/doc-metainfo.d.ts +5 -9
- package/dist/metainfo/doc-tags.d.ts +7 -11
- package/dist/metainfo/frontmatter-preview.d.ts +8 -12
- package/dist/metainfo/index.d.ts +6 -4
- package/dist/nav-data-prep/index.d.ts +72 -0
- package/dist/nav-data-prep/index.js +34 -0
- package/dist/nav-indexing/category-nav.d.ts +4 -8
- package/dist/nav-indexing/category-tree-nav.d.ts +4 -8
- package/dist/nav-indexing/doc-card-grid.d.ts +4 -8
- package/dist/nav-indexing/docs-sitemap.d.ts +4 -8
- package/dist/nav-indexing/index.d.ts +37 -10
- package/dist/nav-indexing/nav-card-grid.d.ts +4 -8
- package/dist/nav-indexing/site-tree-nav-demo.d.ts +4 -8
- package/dist/nav-indexing/tag-nav.d.ts +6 -10
- package/dist/nav-indexing/types.d.ts +6 -8
- package/dist/nav-indexing/versions-page-content.d.ts +4 -8
- package/dist/nav-scope/index.d.ts +41 -0
- package/dist/nav-scope/index.js +41 -0
- package/dist/nav-source-cache/index.d.ts +13 -0
- package/dist/nav-source-cache/index.js +28 -0
- package/dist/nav-source-docs/index.d.ts +169 -0
- package/dist/nav-source-docs/index.js +123 -0
- package/dist/page-loading/index.d.ts +1 -2
- package/dist/page-loading/page-loading-overlay.d.ts +4 -8
- package/dist/plugins/claude-resources.d.ts +3 -0
- package/dist/plugins/claude-resources.js +27 -0
- package/dist/plugins/connect-adapter.d.ts +31 -0
- package/dist/plugins/connect-adapter.js +78 -0
- package/dist/plugins/doc-history.d.ts +3 -0
- package/dist/plugins/doc-history.js +54 -0
- package/dist/plugins/llms-txt.d.ts +3 -0
- package/dist/plugins/llms-txt.js +48 -0
- package/dist/plugins/plugin-utils.d.ts +17 -0
- package/dist/plugins/plugin-utils.js +11 -0
- package/dist/plugins/routes.d.ts +2 -0
- package/dist/plugins/routes.js +100 -0
- package/dist/plugins/search-index.d.ts +3 -0
- package/dist/plugins/search-index.js +24 -0
- package/dist/preset.d.ts +198 -0
- package/dist/preset.js +185 -0
- package/dist/render-markdown/index.d.ts +5 -0
- package/dist/render-markdown/index.js +115 -0
- package/dist/robots.d.ts +36 -0
- package/dist/robots.js +17 -0
- package/dist/route-enumerators/index.d.ts +204 -0
- package/dist/route-enumerators/index.js +156 -0
- package/dist/routes/404.d.ts +7 -0
- package/dist/routes/404.js +47 -0
- package/dist/routes/_chrome.d.ts +32 -0
- package/dist/routes/_chrome.js +365 -0
- package/dist/routes/_context.d.ts +56 -0
- package/dist/routes/_context.js +222 -0
- package/dist/routes/_docs-helpers.d.ts +43 -0
- package/dist/routes/_docs-helpers.js +173 -0
- package/dist/routes/_virtual.d.js +0 -0
- package/dist/routes/api-ai-chat.d.ts +5 -0
- package/dist/routes/api-ai-chat.js +24 -0
- package/dist/routes/docs-slug.d.ts +21 -0
- package/dist/routes/docs-slug.js +26 -0
- package/dist/routes/docs-tags-index.d.ts +7 -0
- package/dist/routes/docs-tags-index.js +11 -0
- package/dist/routes/docs-tags-tag.d.ts +23 -0
- package/dist/routes/docs-tags-tag.js +19 -0
- package/dist/routes/docs-versions.d.ts +7 -0
- package/dist/routes/docs-versions.js +11 -0
- package/dist/routes/index.d.ts +7 -0
- package/dist/routes/index.js +117 -0
- package/dist/routes/locale-docs-slug.d.ts +27 -0
- package/dist/routes/locale-docs-slug.js +40 -0
- package/dist/routes/locale-docs-tags-index.d.ts +18 -0
- package/dist/routes/locale-docs-tags-index.js +15 -0
- package/dist/routes/locale-docs-tags-tag.d.ts +25 -0
- package/dist/routes/locale-docs-tags-tag.js +22 -0
- package/dist/routes/locale-docs-versions.d.ts +18 -0
- package/dist/routes/locale-docs-versions.js +15 -0
- package/dist/routes/locale-index.d.ts +24 -0
- package/dist/routes/locale-index.js +132 -0
- package/dist/routes/robots.txt.d.ts +3 -0
- package/dist/routes/robots.txt.js +8 -0
- package/dist/routes/sitemap.xml.d.ts +5 -0
- package/dist/routes/sitemap.xml.js +31 -0
- package/dist/routes/v-docs-slug.d.ts +27 -0
- package/dist/routes/v-docs-slug.js +34 -0
- package/dist/routes/v-locale-docs-slug.d.ts +31 -0
- package/dist/routes/v-locale-docs-slug.js +50 -0
- package/dist/safelist.css +1 -1
- package/dist/search-widget/index.d.ts +38 -0
- package/dist/search-widget/index.js +169 -0
- package/dist/search-widget-script/index.d.ts +18 -0
- package/dist/search-widget-script/index.js +465 -0
- package/dist/settings.d.ts +270 -0
- package/dist/settings.js +0 -0
- package/dist/sidebar/index.d.ts +21 -3
- package/dist/sidebar/sidebar.d.ts +5 -9
- package/dist/sidebar/types.d.ts +4 -6
- package/dist/sidebar-prepaint/index.d.ts +21 -0
- package/dist/sidebar-prepaint/index.js +23 -0
- package/dist/sidebar-resizer/index.d.ts +2 -6
- package/dist/sidebar-resizer/sidebar-resizer-init.d.ts +6 -10
- package/dist/sidebar-toggle-island/index.d.ts +13 -0
- package/dist/sidebar-toggle-island/index.js +122 -0
- package/dist/sidebar-tree/build-tree.d.ts +4 -8
- package/dist/sidebar-tree/category-meta.d.ts +3 -6
- package/dist/sidebar-tree/index.d.ts +18 -3
- package/dist/sidebar-tree/types.d.ts +6 -8
- package/dist/sidebar-tree-island/index.d.ts +13 -0
- package/dist/sidebar-tree-island/index.js +454 -0
- package/dist/sidebar-utils/index.d.ts +64 -0
- package/dist/sidebar-utils/index.js +88 -0
- package/dist/sidebar-with-defaults/index.d.ts +38 -0
- package/dist/sidebar-with-defaults/index.js +45 -0
- package/dist/site-tree-nav/index.d.ts +71 -0
- package/dist/site-tree-nav/index.js +36 -0
- package/dist/site-tree-nav-island/index.d.ts +11 -0
- package/dist/site-tree-nav-island/index.js +176 -0
- package/dist/slug/index.d.ts +4 -0
- package/dist/slug/index.js +19 -0
- package/dist/smart-break/index.d.ts +55 -0
- package/dist/{toc/smart-break.js → smart-break/index.js} +30 -3
- package/dist/tab-item/index.d.ts +2 -2
- package/dist/tab-item/tab-item.d.ts +6 -7
- package/dist/tag-helpers/index.d.ts +40 -0
- package/dist/tag-helpers/index.js +54 -0
- package/dist/tag-pages/index.d.ts +114 -0
- package/dist/tag-pages/index.js +146 -0
- package/dist/tags-audit.d.ts +107 -0
- package/dist/tags-audit.js +321 -0
- package/dist/theme/color-scheme-provider.d.ts +4 -8
- package/dist/theme/color-tweak-export-modal.d.ts +4 -7
- package/dist/theme/design-token-serde.d.ts +14 -17
- package/dist/theme/design-token-types.d.ts +4 -6
- package/dist/theme/iframe-bridge.d.ts +12 -14
- package/dist/theme/index.d.ts +7 -9
- package/dist/theme/theme-toggle.d.ts +3 -8
- package/dist/theme-toggle/color-scheme-sync.d.ts +5 -7
- package/dist/theme-toggle/index.d.ts +4 -8
- package/dist/toc/cx.d.ts +2 -3
- package/dist/toc/index.d.ts +8 -6
- package/dist/toc/mobile-toc.d.ts +5 -9
- package/dist/toc/mobile-toc.js +1 -1
- package/dist/toc/toc-title.d.ts +1 -3
- package/dist/toc/toc.d.ts +5 -9
- package/dist/toc/toc.js +1 -1
- package/dist/toc/types.d.ts +1 -3
- package/dist/toc/use-active-heading.d.ts +4 -7
- package/dist/transitions/index.d.ts +1 -1
- package/dist/transitions/page-events.d.ts +4 -6
- package/dist/tree-nav-shared/index.d.ts +22 -0
- package/dist/tree-nav-shared/index.js +66 -0
- package/dist/url-helpers/index.d.ts +37 -0
- package/dist/url-helpers/index.js +106 -0
- package/dist/url-normalizer/index.d.ts +39 -1
- package/dist/url-normalizer/normalize.d.ts +3 -5
- package/dist/use-modal-dialog/index.d.ts +70 -0
- package/dist/use-modal-dialog/index.js +73 -0
- package/dist/versions-page/index.d.ts +55 -0
- package/dist/versions-page/index.js +73 -0
- package/eject/breadcrumb/breadcrumb.tsx +194 -0
- package/eject/breadcrumb/find-path.ts +26 -0
- package/eject/breadcrumb/index.ts +4 -0
- package/eject/breadcrumb/types.ts +32 -0
- package/eject/code-group/index.tsx +78 -0
- package/eject/content-admonition/index.tsx +50 -0
- package/eject/details/details.tsx +34 -0
- package/eject/details/index.ts +4 -0
- package/eject/doc-pager/index.tsx +91 -0
- package/eject/footer/footer.tsx +167 -0
- package/eject/footer/index.ts +19 -0
- package/eject/footer/types.ts +60 -0
- package/eject/header/header.tsx +725 -0
- package/eject/header/index.ts +36 -0
- package/eject/header/nav-active.ts +122 -0
- package/eject/header/nav-overflow-script.ts +302 -0
- package/eject/header/right-items.ts +78 -0
- package/eject/header/types.ts +69 -0
- package/eject/page-loading/index.ts +10 -0
- package/eject/page-loading/page-loading-overlay.tsx +103 -0
- package/eject/sidebar/index.ts +27 -0
- package/eject/sidebar/sidebar.tsx +137 -0
- package/eject/sidebar/types.ts +73 -0
- package/eject/tab-item/index.ts +7 -0
- package/eject/tab-item/tab-item.tsx +70 -0
- package/eject/theme-toggle/color-scheme-sync.ts +73 -0
- package/eject/theme-toggle/index.tsx +120 -0
- package/eject/toc/cx.ts +38 -0
- package/eject/toc/index.ts +11 -0
- package/eject/toc/mobile-toc.tsx +133 -0
- package/eject/toc/toc-title.ts +33 -0
- package/eject/toc/toc.tsx +119 -0
- package/eject/toc/types.ts +14 -0
- package/eject/toc/use-active-heading.ts +162 -0
- package/package.json +371 -11
- package/routes-src/404.tsx +49 -0
- package/routes-src/_chrome.tsx +470 -0
- package/routes-src/_context.ts +314 -0
- package/routes-src/_docs-helpers.ts +284 -0
- package/routes-src/_virtual.d.ts +15 -0
- package/routes-src/api-ai-chat.tsx +46 -0
- package/routes-src/docs-slug.tsx +43 -0
- package/routes-src/docs-tags-index.tsx +14 -0
- package/routes-src/docs-tags-tag.tsx +29 -0
- package/routes-src/docs-versions.tsx +15 -0
- package/routes-src/index.tsx +134 -0
- package/routes-src/locale-docs-slug.tsx +69 -0
- package/routes-src/locale-docs-tags-index.tsx +23 -0
- package/routes-src/locale-docs-tags-tag.tsx +39 -0
- package/routes-src/locale-docs-versions.tsx +23 -0
- package/routes-src/locale-index.tsx +150 -0
- package/routes-src/robots.txt.tsx +13 -0
- package/routes-src/sitemap.xml.tsx +48 -0
- package/routes-src/v-docs-slug.tsx +64 -0
- package/routes-src/v-locale-docs-slug.tsx +85 -0
- package/dist/toc/smart-break.d.ts +0 -27
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
import type { ComponentChildren, JSX } from "preact";
|
|
4
|
+
/** Minimal docs entry shape needed by the tag-pages factory. */
|
|
5
|
+
export interface TagPagesDocsEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
data: {
|
|
8
|
+
slug?: string;
|
|
9
|
+
draft?: boolean;
|
|
10
|
+
unlisted?: boolean;
|
|
11
|
+
category_no_page?: boolean;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Tag info produced and consumed by the tag-pages factory. */
|
|
19
|
+
export interface TagInfo {
|
|
20
|
+
tag: string;
|
|
21
|
+
count: number;
|
|
22
|
+
docs: {
|
|
23
|
+
slug: string;
|
|
24
|
+
title: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
/** Settings subset read by the tag-pages factory. */
|
|
29
|
+
export interface TagPagesSettings {
|
|
30
|
+
noindex?: boolean;
|
|
31
|
+
dynamicPageTransition?: boolean;
|
|
32
|
+
docTags?: boolean;
|
|
33
|
+
base: string;
|
|
34
|
+
}
|
|
35
|
+
/** Host-supplied component bindings injected into the tag-pages factory. */
|
|
36
|
+
export interface TagPagesComponents {
|
|
37
|
+
HeadWithDefaults: (props: {
|
|
38
|
+
title: string;
|
|
39
|
+
}) => JSX.Element;
|
|
40
|
+
HeaderWithDefaults: (props: {
|
|
41
|
+
lang?: string;
|
|
42
|
+
currentPath?: string;
|
|
43
|
+
}) => JSX.Element;
|
|
44
|
+
FooterWithDefaults: (props: {
|
|
45
|
+
lang?: string;
|
|
46
|
+
}) => JSX.Element;
|
|
47
|
+
BodyEndIslands: (props: {
|
|
48
|
+
basePath: string;
|
|
49
|
+
}) => JSX.Element;
|
|
50
|
+
DocHistoryArea: (props: {
|
|
51
|
+
slug: string;
|
|
52
|
+
locale: string;
|
|
53
|
+
}) => JSX.Element | null;
|
|
54
|
+
}
|
|
55
|
+
/** Injected dependencies for {@link createTagPages}. */
|
|
56
|
+
export interface TagPagesDeps {
|
|
57
|
+
settings: TagPagesSettings;
|
|
58
|
+
/** Default locale code (e.g. `"en"`). */
|
|
59
|
+
defaultLocale: string;
|
|
60
|
+
/** Translate a UI string key for a locale. */
|
|
61
|
+
t: (key: string, locale: string) => string;
|
|
62
|
+
/** Prefix a path with the configured base directory. */
|
|
63
|
+
withBase: (path: string) => string;
|
|
64
|
+
/** Build a docs URL for the given slug and lang. */
|
|
65
|
+
docsUrl: (slug: string, lang?: string) => string;
|
|
66
|
+
/** Compose the meta `<title>` value. */
|
|
67
|
+
composeMetaTitle: (title: string) => string;
|
|
68
|
+
/**
|
|
69
|
+
* Collect tags from a docs array, producing a Map<tag, TagInfo>.
|
|
70
|
+
* Host passes `collectTags` from `@/utils/tags`.
|
|
71
|
+
*/
|
|
72
|
+
collectTags: (entries: TagPagesDocsEntry[], slugFn: (id: string, data: {
|
|
73
|
+
slug?: string;
|
|
74
|
+
}) => string) => Map<string, TagInfo>;
|
|
75
|
+
/**
|
|
76
|
+
* Identity-stable, draft-filtered docs array for a collection.
|
|
77
|
+
* Host passes `stableDocs` from `pages/lib/_nav-source-cache.ts`.
|
|
78
|
+
*/
|
|
79
|
+
stableDocs: (collectionName: string) => TagPagesDocsEntry[];
|
|
80
|
+
/**
|
|
81
|
+
* Returns true for paths that are only shown in the default locale.
|
|
82
|
+
* Host passes `isDefaultLocaleOnlyPath` from `@/utils/base`.
|
|
83
|
+
*/
|
|
84
|
+
isDefaultLocaleOnlyPath?: (path: string) => boolean;
|
|
85
|
+
components: TagPagesComponents;
|
|
86
|
+
}
|
|
87
|
+
/** Result of {@link createTagPages}. */
|
|
88
|
+
export interface TagPagesAPI {
|
|
89
|
+
/**
|
|
90
|
+
* Build the { tag → TagInfo } map for one locale, preserving each original
|
|
91
|
+
* page's exact data path (see the module-header branch notes).
|
|
92
|
+
*
|
|
93
|
+
* Memoized on the snapshot-anchored stableDocs identity (same pattern as
|
|
94
|
+
* _nav-source-cache) so the collection bridging and tag aggregation run once
|
|
95
|
+
* per locale per build rather than once per built tag page.
|
|
96
|
+
*/
|
|
97
|
+
collectTagMapForLocale: (locale: string) => Map<string, TagInfo>;
|
|
98
|
+
/** Per-tag detail page (chip target). */
|
|
99
|
+
TagDetailPageView: (props: {
|
|
100
|
+
locale: string;
|
|
101
|
+
tag: string;
|
|
102
|
+
tagInfo: TagInfo;
|
|
103
|
+
}) => JSX.Element;
|
|
104
|
+
/** "All Tags" index page — computes the tag map at render time. */
|
|
105
|
+
TagsIndexPageView: (props: {
|
|
106
|
+
locale: string;
|
|
107
|
+
children?: ComponentChildren;
|
|
108
|
+
}) => JSX.Element;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create the tag-pages functions bound to the host's settings and
|
|
112
|
+
* component dependencies.
|
|
113
|
+
*/
|
|
114
|
+
export declare function createTagPages(deps: TagPagesDeps): TagPagesAPI;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
|
|
2
|
+
import { DocLayoutWithDefaults } from "../doclayout/index.js";
|
|
3
|
+
import { Breadcrumb } from "../breadcrumb/index.js";
|
|
4
|
+
import { DocCardGrid, TagNav } from "../nav-indexing/index.js";
|
|
5
|
+
import { memoizeDerived } from "../nav-source-cache/index.js";
|
|
6
|
+
import { mergeLocaleDocs } from "../locale-merge/index.js";
|
|
7
|
+
import { toRouteSlug } from "../slug/index.js";
|
|
8
|
+
function createTagPages(deps) {
|
|
9
|
+
const {
|
|
10
|
+
settings,
|
|
11
|
+
defaultLocale,
|
|
12
|
+
t,
|
|
13
|
+
withBase,
|
|
14
|
+
docsUrl,
|
|
15
|
+
composeMetaTitle,
|
|
16
|
+
collectTags,
|
|
17
|
+
stableDocs,
|
|
18
|
+
isDefaultLocaleOnlyPath,
|
|
19
|
+
components: {
|
|
20
|
+
HeadWithDefaults,
|
|
21
|
+
HeaderWithDefaults,
|
|
22
|
+
FooterWithDefaults,
|
|
23
|
+
BodyEndIslands,
|
|
24
|
+
DocHistoryArea
|
|
25
|
+
}
|
|
26
|
+
} = deps;
|
|
27
|
+
function localePrefix(locale) {
|
|
28
|
+
return locale === defaultLocale ? "" : `/${locale}`;
|
|
29
|
+
}
|
|
30
|
+
function collectTagMapForLocale(locale) {
|
|
31
|
+
if (locale === defaultLocale) {
|
|
32
|
+
const baseDocs2 = stableDocs("docs");
|
|
33
|
+
return memoizeDerived([baseDocs2], "tagMap;default", () => {
|
|
34
|
+
const docs = baseDocs2.filter(
|
|
35
|
+
(doc) => !doc.data.unlisted && !doc.data.draft && !doc.data.category_no_page
|
|
36
|
+
);
|
|
37
|
+
return collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const baseDocs = stableDocs("docs");
|
|
41
|
+
const localeDocs = stableDocs(`docs-${locale}`);
|
|
42
|
+
return memoizeDerived([baseDocs, localeDocs], `tagMap;${locale}`, () => {
|
|
43
|
+
const { docs: mergedDocs } = mergeLocaleDocs({
|
|
44
|
+
baseDocs: baseDocs.filter((d) => !d.data.draft),
|
|
45
|
+
localeDocs: localeDocs.filter((d) => !d.data.draft),
|
|
46
|
+
applyDefaultLocaleOnlyFilter: true,
|
|
47
|
+
isDefaultLocaleOnlyPath
|
|
48
|
+
});
|
|
49
|
+
const docs = mergedDocs.filter((d) => !d.data.category_no_page);
|
|
50
|
+
return collectTags(docs, (id, data) => data.slug ?? toRouteSlug(id));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function TagDetailPageView({
|
|
54
|
+
locale,
|
|
55
|
+
tag,
|
|
56
|
+
tagInfo
|
|
57
|
+
}) {
|
|
58
|
+
const isDefault = locale === defaultLocale;
|
|
59
|
+
const prefix = localePrefix(locale);
|
|
60
|
+
const countText = tagInfo.count === 1 ? t("doc.pageCountSingle", locale).replace("{count}", String(tagInfo.count)) : t("doc.pageCount", locale).replace("{count}", String(tagInfo.count));
|
|
61
|
+
const pageTitle = `${t("doc.taggedWith", locale)}: ${tag}`;
|
|
62
|
+
const breadcrumbItems = [
|
|
63
|
+
{ label: "Docs" },
|
|
64
|
+
{ label: t("doc.allTags", locale), href: withBase(`${prefix}/docs/tags`) },
|
|
65
|
+
{ label: tag }
|
|
66
|
+
];
|
|
67
|
+
const cardItems = tagInfo.docs.map((doc) => ({
|
|
68
|
+
href: docsUrl(doc.slug, locale),
|
|
69
|
+
title: doc.title,
|
|
70
|
+
description: doc.description
|
|
71
|
+
}));
|
|
72
|
+
return /* @__PURE__ */ jsxs(
|
|
73
|
+
DocLayoutWithDefaults,
|
|
74
|
+
{
|
|
75
|
+
title: composeMetaTitle(pageTitle),
|
|
76
|
+
head: /* @__PURE__ */ jsx(HeadWithDefaults, { title: pageTitle }),
|
|
77
|
+
lang: isDefault ? void 0 : locale,
|
|
78
|
+
noindex: settings.noindex,
|
|
79
|
+
hideSidebar: true,
|
|
80
|
+
hideToc: true,
|
|
81
|
+
sidebarOverride: /* @__PURE__ */ jsx(Fragment, {}),
|
|
82
|
+
headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: withBase(`${prefix}/docs/tags/${encodeURIComponent(tag)}`) }),
|
|
83
|
+
breadcrumbOverride: /* @__PURE__ */ jsx(Breadcrumb, { items: breadcrumbItems }),
|
|
84
|
+
footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
|
|
85
|
+
bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }),
|
|
86
|
+
enableClientRouter: settings.dynamicPageTransition,
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-xs", children: pageTitle }),
|
|
89
|
+
/* @__PURE__ */ jsx("p", { class: "text-muted mb-vsp-lg", children: countText }),
|
|
90
|
+
/* @__PURE__ */ jsx(DocCardGrid, { ariaLabel: pageTitle, items: cardItems }),
|
|
91
|
+
/* @__PURE__ */ jsx(DocHistoryArea, { slug: `tags/${tag}`, locale })
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function TagsIndexPageView({ locale }) {
|
|
97
|
+
const isDefault = locale === defaultLocale;
|
|
98
|
+
const prefix = localePrefix(locale);
|
|
99
|
+
const pageTitle = t("doc.allTags", locale);
|
|
100
|
+
const tagMap = collectTagMapForLocale(locale);
|
|
101
|
+
const labels = {
|
|
102
|
+
tags: t("doc.tags", locale),
|
|
103
|
+
taggedWith: t("doc.taggedWith", locale)
|
|
104
|
+
};
|
|
105
|
+
const tags = [...tagMap.values()].sort((a, b) => a.tag.localeCompare(b.tag, locale)).map((info) => ({
|
|
106
|
+
tag: info.tag,
|
|
107
|
+
count: info.count,
|
|
108
|
+
// Tag segment URL-encoded — href sites only; route params stay raw.
|
|
109
|
+
href: withBase(`${prefix}/docs/tags/${encodeURIComponent(info.tag)}`)
|
|
110
|
+
}));
|
|
111
|
+
const breadcrumbItems = [
|
|
112
|
+
{ label: "Docs" },
|
|
113
|
+
{ label: pageTitle }
|
|
114
|
+
];
|
|
115
|
+
return /* @__PURE__ */ jsxs(
|
|
116
|
+
DocLayoutWithDefaults,
|
|
117
|
+
{
|
|
118
|
+
title: composeMetaTitle(pageTitle),
|
|
119
|
+
head: /* @__PURE__ */ jsx(HeadWithDefaults, { title: pageTitle }),
|
|
120
|
+
lang: isDefault ? void 0 : locale,
|
|
121
|
+
noindex: settings.noindex,
|
|
122
|
+
hideSidebar: true,
|
|
123
|
+
hideToc: true,
|
|
124
|
+
sidebarOverride: /* @__PURE__ */ jsx(Fragment, {}),
|
|
125
|
+
headerOverride: /* @__PURE__ */ jsx(HeaderWithDefaults, { lang: locale, currentPath: withBase(`${prefix}/docs/tags`) }),
|
|
126
|
+
breadcrumbOverride: /* @__PURE__ */ jsx(Breadcrumb, { items: breadcrumbItems }),
|
|
127
|
+
footerOverride: /* @__PURE__ */ jsx(FooterWithDefaults, { lang: locale }),
|
|
128
|
+
bodyEndComponents: /* @__PURE__ */ jsx(BodyEndIslands, { basePath: settings.base ?? "/" }),
|
|
129
|
+
enableClientRouter: settings.dynamicPageTransition,
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-lg", children: pageTitle }),
|
|
132
|
+
!settings.docTags || tags.length === 0 ? /* @__PURE__ */ jsx("p", { class: "text-muted", children: t("doc.noTags", locale) }) : /* @__PURE__ */ jsx(TagNav, { variant: "all", tags, labels }),
|
|
133
|
+
/* @__PURE__ */ jsx(DocHistoryArea, { slug: "tags", locale })
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
collectTagMapForLocale,
|
|
140
|
+
TagDetailPageView,
|
|
141
|
+
TagsIndexPageView
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
createTagPages
|
|
146
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @takazudo/zudo-doc/tags-audit
|
|
3
|
+
*
|
|
4
|
+
* Core library for vocabulary-aware frontmatter tag auditing.
|
|
5
|
+
*
|
|
6
|
+
* Exports pure functions and types consumed by:
|
|
7
|
+
* - the package bin (packages/zudo-doc/bin/tags-audit.mjs)
|
|
8
|
+
* - the project-side tags-suggest script (scripts/tags-suggest.ts)
|
|
9
|
+
* - custom integrations
|
|
10
|
+
*
|
|
11
|
+
* Does NOT import from project-specific config (settings, tag-vocabulary).
|
|
12
|
+
* The caller passes those in via AuditOptions.
|
|
13
|
+
*/
|
|
14
|
+
/** Minimal shape required by this library. Matches src/config/tag-vocabulary-types. */
|
|
15
|
+
export interface TagVocabularyEntry {
|
|
16
|
+
id: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
group?: string;
|
|
20
|
+
aliases?: readonly string[];
|
|
21
|
+
deprecated?: boolean | {
|
|
22
|
+
redirect?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
interface VocabularyIndex {
|
|
26
|
+
byId: Map<string, TagVocabularyEntry>;
|
|
27
|
+
byAlias: Map<string, TagVocabularyEntry>;
|
|
28
|
+
aliasToId: Map<string, string>;
|
|
29
|
+
}
|
|
30
|
+
export interface UnknownIssue {
|
|
31
|
+
file: string;
|
|
32
|
+
raw: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DeprecatedIssue {
|
|
35
|
+
file: string;
|
|
36
|
+
raw: string;
|
|
37
|
+
canonical: string;
|
|
38
|
+
redirect?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface AliasIssue {
|
|
41
|
+
file: string;
|
|
42
|
+
raw: string;
|
|
43
|
+
canonical: string;
|
|
44
|
+
}
|
|
45
|
+
export interface NearDuplicatePair {
|
|
46
|
+
a: string;
|
|
47
|
+
b: string;
|
|
48
|
+
reason: "similarity" | "plural";
|
|
49
|
+
score?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface AuditReport {
|
|
52
|
+
unknowns: UnknownIssue[];
|
|
53
|
+
deprecated: DeprecatedIssue[];
|
|
54
|
+
aliases: AliasIssue[];
|
|
55
|
+
nearDuplicates: NearDuplicatePair[];
|
|
56
|
+
orphans: string[];
|
|
57
|
+
filesScanned: number;
|
|
58
|
+
/** Totals keyed by canonical id after alias/deprecation resolution. */
|
|
59
|
+
frequency: Record<string, number>;
|
|
60
|
+
}
|
|
61
|
+
export interface AuditOptions {
|
|
62
|
+
rootDir: string;
|
|
63
|
+
contentDirs: string[];
|
|
64
|
+
vocabulary: readonly TagVocabularyEntry[];
|
|
65
|
+
governance: "off" | "warn" | "strict";
|
|
66
|
+
vocabularyActive: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Near-duplicate detection callbacks. Optional — if omitted the relevant
|
|
69
|
+
* checks are silently skipped so callers without pluralize / string-similarity
|
|
70
|
+
* can still use the core functions.
|
|
71
|
+
*/
|
|
72
|
+
nearDupHelpers?: NearDupHelpers;
|
|
73
|
+
}
|
|
74
|
+
export interface NearDupHelpers {
|
|
75
|
+
singular: (word: string) => string;
|
|
76
|
+
compareTwoStrings: (a: string, b: string) => number;
|
|
77
|
+
}
|
|
78
|
+
export declare function buildIndex(vocab: readonly TagVocabularyEntry[]): VocabularyIndex;
|
|
79
|
+
export declare function collectMdxFiles(dir: string): Promise<string[]>;
|
|
80
|
+
export declare function normalizeTags(value: unknown): string[];
|
|
81
|
+
export declare function findNearDuplicates(tags: string[], helpers: NearDupHelpers): NearDuplicatePair[];
|
|
82
|
+
export declare function audit(opts: AuditOptions): Promise<AuditReport>;
|
|
83
|
+
export declare function hasHardIssues(report: AuditReport): boolean;
|
|
84
|
+
export declare function computeRewrites(vocabulary: readonly TagVocabularyEntry[]): Map<string, string>;
|
|
85
|
+
export declare function applyFixes(contentDirs: string[], rewrites: Map<string, string>, rootDir: string): Promise<string[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Rewrite alias tags to canonical ids within a single MDX file, preserving
|
|
88
|
+
* every other byte of the file verbatim. Returns `{ content, changed }`.
|
|
89
|
+
*
|
|
90
|
+
* Supports block-style (`tags:\n - foo\n`) and flow-style
|
|
91
|
+
* (`tags: [foo, bar]`) YAML sequences. Quoted string values are handled.
|
|
92
|
+
*/
|
|
93
|
+
export declare function rewriteAliasesByteStable(content: string, rewrites: Map<string, string>): {
|
|
94
|
+
content: string;
|
|
95
|
+
changed: boolean;
|
|
96
|
+
};
|
|
97
|
+
/** Format a human-readable text report. Requires picocolors for coloring. */
|
|
98
|
+
export declare function formatTextReport(report: AuditReport, governance: AuditOptions["governance"], vocabularyActive: boolean, pc: {
|
|
99
|
+
bold: (s: string) => string;
|
|
100
|
+
cyan: (s: string) => string;
|
|
101
|
+
dim: (s: string) => string;
|
|
102
|
+
red: (s: string) => string;
|
|
103
|
+
yellow: (s: string) => string;
|
|
104
|
+
blue: (s: string) => string;
|
|
105
|
+
green: (s: string) => string;
|
|
106
|
+
}): string;
|
|
107
|
+
export {};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
function buildIndex(vocab) {
|
|
6
|
+
const byId = /* @__PURE__ */ new Map();
|
|
7
|
+
const byAlias = /* @__PURE__ */ new Map();
|
|
8
|
+
const aliasToId = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const entry of vocab) {
|
|
10
|
+
byId.set(entry.id, entry);
|
|
11
|
+
for (const alias of entry.aliases ?? []) {
|
|
12
|
+
byAlias.set(alias, entry);
|
|
13
|
+
aliasToId.set(alias, entry.id);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return { byId, byAlias, aliasToId };
|
|
17
|
+
}
|
|
18
|
+
async function collectMdxFiles(dir) {
|
|
19
|
+
const out = [];
|
|
20
|
+
async function walk(current) {
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const full = join(current, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
await walk(full);
|
|
31
|
+
} else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
|
|
32
|
+
out.push(full);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await walk(dir);
|
|
37
|
+
return out.sort();
|
|
38
|
+
}
|
|
39
|
+
function normalizeTags(value) {
|
|
40
|
+
if (!Array.isArray(value)) return [];
|
|
41
|
+
return value.filter((v) => typeof v === "string");
|
|
42
|
+
}
|
|
43
|
+
const NEAR_DUP_THRESHOLD = 0.82;
|
|
44
|
+
function findNearDuplicates(tags, helpers) {
|
|
45
|
+
const pairs = [];
|
|
46
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47
|
+
const sorted = [...tags].sort();
|
|
48
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
49
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
50
|
+
const a = sorted[i];
|
|
51
|
+
const b = sorted[j];
|
|
52
|
+
const key = `${a}||${b}`;
|
|
53
|
+
if (seen.has(key)) continue;
|
|
54
|
+
seen.add(key);
|
|
55
|
+
const singA = helpers.singular(a);
|
|
56
|
+
const singB = helpers.singular(b);
|
|
57
|
+
if (singA === singB && a !== b) {
|
|
58
|
+
pairs.push({ a, b, reason: "plural" });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const score = helpers.compareTwoStrings(a, b);
|
|
62
|
+
if (score >= NEAR_DUP_THRESHOLD) {
|
|
63
|
+
pairs.push({ a, b, reason: "similarity", score });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return pairs;
|
|
68
|
+
}
|
|
69
|
+
async function audit(opts) {
|
|
70
|
+
const index = buildIndex(opts.vocabulary);
|
|
71
|
+
const unknowns = [];
|
|
72
|
+
const deprecated = [];
|
|
73
|
+
const aliases = [];
|
|
74
|
+
const frequency = {};
|
|
75
|
+
const canonicalsUsed = /* @__PURE__ */ new Set();
|
|
76
|
+
const rawTagsUsed = /* @__PURE__ */ new Set();
|
|
77
|
+
let filesScanned = 0;
|
|
78
|
+
for (const dir of opts.contentDirs) {
|
|
79
|
+
if (!existsSync(dir)) continue;
|
|
80
|
+
const files = await collectMdxFiles(dir);
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
filesScanned++;
|
|
83
|
+
const raw = await readFile(file, "utf-8");
|
|
84
|
+
const parsed = matter(raw);
|
|
85
|
+
const tags = normalizeTags(parsed.data.tags);
|
|
86
|
+
const rel = relative(opts.rootDir, file);
|
|
87
|
+
for (const tag of tags) {
|
|
88
|
+
rawTagsUsed.add(tag);
|
|
89
|
+
const idEntry = index.byId.get(tag);
|
|
90
|
+
const aliasEntry = index.byAlias.get(tag);
|
|
91
|
+
if (!opts.vocabularyActive) {
|
|
92
|
+
frequency[tag] = (frequency[tag] ?? 0) + 1;
|
|
93
|
+
canonicalsUsed.add(tag);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!idEntry && !aliasEntry) {
|
|
97
|
+
unknowns.push({ file: rel, raw: tag });
|
|
98
|
+
frequency[tag] = (frequency[tag] ?? 0) + 1;
|
|
99
|
+
canonicalsUsed.add(tag);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const entry = idEntry ?? aliasEntry;
|
|
103
|
+
let canonical = entry.id;
|
|
104
|
+
const dep = entry.deprecated;
|
|
105
|
+
if (dep) {
|
|
106
|
+
if (typeof dep === "object" && dep.redirect) {
|
|
107
|
+
const target = index.byId.get(dep.redirect);
|
|
108
|
+
if (target) {
|
|
109
|
+
deprecated.push({ file: rel, raw: tag, canonical: entry.id, redirect: target.id });
|
|
110
|
+
canonical = target.id;
|
|
111
|
+
} else {
|
|
112
|
+
deprecated.push({ file: rel, raw: tag, canonical: entry.id });
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
deprecated.push({ file: rel, raw: tag, canonical: entry.id });
|
|
116
|
+
}
|
|
117
|
+
} else if (aliasEntry && tag !== entry.id) {
|
|
118
|
+
aliases.push({ file: rel, raw: tag, canonical: entry.id });
|
|
119
|
+
}
|
|
120
|
+
frequency[canonical] = (frequency[canonical] ?? 0) + 1;
|
|
121
|
+
canonicalsUsed.add(canonical);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const nearDuplicates = [];
|
|
126
|
+
if (opts.nearDupHelpers) {
|
|
127
|
+
const nearDupSource = opts.vocabularyActive ? Array.from(canonicalsUsed) : Array.from(rawTagsUsed);
|
|
128
|
+
nearDuplicates.push(...findNearDuplicates(nearDupSource, opts.nearDupHelpers));
|
|
129
|
+
}
|
|
130
|
+
const orphans = opts.vocabularyActive ? opts.vocabulary.filter((entry) => {
|
|
131
|
+
if (entry.deprecated) return false;
|
|
132
|
+
if (canonicalsUsed.has(entry.id)) return false;
|
|
133
|
+
for (const alias of entry.aliases ?? []) {
|
|
134
|
+
if (rawTagsUsed.has(alias)) return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}).map((entry) => entry.id) : [];
|
|
138
|
+
return {
|
|
139
|
+
unknowns,
|
|
140
|
+
deprecated,
|
|
141
|
+
aliases,
|
|
142
|
+
nearDuplicates,
|
|
143
|
+
orphans,
|
|
144
|
+
filesScanned,
|
|
145
|
+
frequency
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function hasHardIssues(report) {
|
|
149
|
+
return report.unknowns.length > 0 || report.deprecated.length > 0;
|
|
150
|
+
}
|
|
151
|
+
function computeRewrites(vocabulary) {
|
|
152
|
+
const rewrites = /* @__PURE__ */ new Map();
|
|
153
|
+
const { byId } = buildIndex(vocabulary);
|
|
154
|
+
for (const entry of vocabulary) {
|
|
155
|
+
for (const alias of entry.aliases ?? []) {
|
|
156
|
+
if (alias === entry.id) continue;
|
|
157
|
+
const dep2 = entry.deprecated;
|
|
158
|
+
if (dep2 && typeof dep2 === "object" && dep2.redirect) {
|
|
159
|
+
const target = byId.get(dep2.redirect);
|
|
160
|
+
rewrites.set(alias, target ? target.id : entry.id);
|
|
161
|
+
} else {
|
|
162
|
+
rewrites.set(alias, entry.id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const dep = entry.deprecated;
|
|
166
|
+
if (dep && typeof dep === "object" && dep.redirect) {
|
|
167
|
+
const target = byId.get(dep.redirect);
|
|
168
|
+
if (target) rewrites.set(entry.id, target.id);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return rewrites;
|
|
172
|
+
}
|
|
173
|
+
async function applyFixes(contentDirs, rewrites, rootDir) {
|
|
174
|
+
const touched = [];
|
|
175
|
+
for (const dir of contentDirs) {
|
|
176
|
+
if (!existsSync(dir)) continue;
|
|
177
|
+
const files = await collectMdxFiles(dir);
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
const original = await readFile(file, "utf-8");
|
|
180
|
+
const { content, changed } = rewriteAliasesByteStable(original, rewrites);
|
|
181
|
+
if (changed) {
|
|
182
|
+
await writeFile(file, content, "utf-8");
|
|
183
|
+
touched.push(relative(rootDir, file));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return touched;
|
|
188
|
+
}
|
|
189
|
+
function rewriteAliasesByteStable(content, rewrites) {
|
|
190
|
+
if (rewrites.size === 0) return { content, changed: false };
|
|
191
|
+
const fmMatch = content.match(/^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/);
|
|
192
|
+
if (!fmMatch) return { content, changed: false };
|
|
193
|
+
const whole = fmMatch[0] ?? "";
|
|
194
|
+
const open = fmMatch[1] ?? "";
|
|
195
|
+
const fmBody = fmMatch[2] ?? "";
|
|
196
|
+
const close = fmMatch[3] ?? "";
|
|
197
|
+
let changed = false;
|
|
198
|
+
let newBody = fmBody;
|
|
199
|
+
newBody = newBody.replace(
|
|
200
|
+
/^(tags[ \t]*:[ \t]*\[)([^\]\n]*)(\])/m,
|
|
201
|
+
(_full, pre, inner, post) => {
|
|
202
|
+
const rewritten = inner.replace(
|
|
203
|
+
/(^|,)([ \t]*)(?:(")([^"]*)(")|(')([^']*)(')|([^,\s"'][^,]*?))([ \t]*)(?=,|$)/g,
|
|
204
|
+
(_m, lead, leadWs, dq1, dqVal, dq2, sq1, sqVal, sq2, bareVal, trailWs) => {
|
|
205
|
+
const val = dqVal ?? sqVal ?? bareVal ?? "";
|
|
206
|
+
const target = rewrites.get(val.trim());
|
|
207
|
+
if (!target || target === val) return _m;
|
|
208
|
+
changed = true;
|
|
209
|
+
if (dq1) return `${lead}${leadWs}"${target}"${trailWs}`;
|
|
210
|
+
if (sq1) return `${lead}${leadWs}'${target}'${trailWs}`;
|
|
211
|
+
return `${lead}${leadWs}${target}${trailWs}`;
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
return pre + rewritten + post;
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
newBody = newBody.replace(
|
|
218
|
+
/^(tags[ \t]*:[ \t]*\r?\n)((?:[ \t]+-[^\n]*\r?\n?)+)/m,
|
|
219
|
+
(_full, pre, items) => {
|
|
220
|
+
const rewritten = items.replace(
|
|
221
|
+
/^([ \t]+-[ \t]+)(?:(")([^"]*)(")|(')([^']*)('))?([^\n]*)$/gm,
|
|
222
|
+
(line, dash, dq1, dqVal, dq2, sq1, sqVal, sq2, bare) => {
|
|
223
|
+
if (dq1) {
|
|
224
|
+
const target2 = rewrites.get(dqVal);
|
|
225
|
+
if (target2 && target2 !== dqVal) {
|
|
226
|
+
changed = true;
|
|
227
|
+
return `${dash}"${target2}"${bare}`;
|
|
228
|
+
}
|
|
229
|
+
return line;
|
|
230
|
+
}
|
|
231
|
+
if (sq1) {
|
|
232
|
+
const target2 = rewrites.get(sqVal);
|
|
233
|
+
if (target2 && target2 !== sqVal) {
|
|
234
|
+
changed = true;
|
|
235
|
+
return `${dash}'${target2}'${bare}`;
|
|
236
|
+
}
|
|
237
|
+
return line;
|
|
238
|
+
}
|
|
239
|
+
const trimmed = bare.replace(/[ \t\r]+$/, "");
|
|
240
|
+
const trailing = bare.slice(trimmed.length);
|
|
241
|
+
const target = rewrites.get(trimmed);
|
|
242
|
+
if (target && target !== trimmed) {
|
|
243
|
+
changed = true;
|
|
244
|
+
return `${dash}${target}${trailing}`;
|
|
245
|
+
}
|
|
246
|
+
return line;
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
return pre + rewritten;
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
if (!changed) return { content, changed: false };
|
|
253
|
+
return {
|
|
254
|
+
content: open + newBody + close + content.slice(whole.length),
|
|
255
|
+
changed: true
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function formatTextReport(report, governance, vocabularyActive, pc) {
|
|
259
|
+
const lines = [];
|
|
260
|
+
lines.push(pc.bold(pc.cyan(`tags:audit \u2014 scanned ${report.filesScanned} file(s)`)));
|
|
261
|
+
lines.push(
|
|
262
|
+
pc.dim(
|
|
263
|
+
`vocabulary: ${vocabularyActive ? "active" : "inactive"}, governance: ${governance}`
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
lines.push("");
|
|
267
|
+
if (report.unknowns.length > 0) {
|
|
268
|
+
lines.push(pc.bold(pc.red(`\u2717 Unknown tags (${report.unknowns.length})`)));
|
|
269
|
+
for (const { file, raw } of report.unknowns) {
|
|
270
|
+
lines.push(` ${pc.red("\u2022")} ${file} ${pc.yellow(raw)}`);
|
|
271
|
+
}
|
|
272
|
+
lines.push("");
|
|
273
|
+
}
|
|
274
|
+
if (report.deprecated.length > 0) {
|
|
275
|
+
lines.push(pc.bold(pc.yellow(`\u26A0 Deprecated tags (${report.deprecated.length})`)));
|
|
276
|
+
for (const { file, raw, redirect } of report.deprecated) {
|
|
277
|
+
const suffix = redirect ? pc.dim(` \u2192 ${redirect}`) : pc.dim(" (dropped)");
|
|
278
|
+
lines.push(` ${pc.yellow("\u2022")} ${file} ${raw}${suffix}`);
|
|
279
|
+
}
|
|
280
|
+
lines.push("");
|
|
281
|
+
}
|
|
282
|
+
if (report.aliases.length > 0) {
|
|
283
|
+
lines.push(pc.bold(pc.blue(`\u2139 Alias usage (${report.aliases.length})`)));
|
|
284
|
+
for (const { file, raw, canonical } of report.aliases) {
|
|
285
|
+
lines.push(` ${pc.blue("\u2022")} ${file} ${raw} ${pc.dim(`\u2192 ${canonical}`)}`);
|
|
286
|
+
}
|
|
287
|
+
lines.push(pc.dim(` (run with --fix to rewrite these in place)`));
|
|
288
|
+
lines.push("");
|
|
289
|
+
}
|
|
290
|
+
if (report.nearDuplicates.length > 0) {
|
|
291
|
+
lines.push(pc.bold(pc.yellow(`\u26A0 Near-duplicate tags (${report.nearDuplicates.length})`)));
|
|
292
|
+
for (const pair of report.nearDuplicates) {
|
|
293
|
+
const hint = pair.reason === "plural" ? "same singular" : `similarity ${(pair.score ?? 0).toFixed(2)}`;
|
|
294
|
+
lines.push(` ${pc.yellow("\u2022")} ${pair.a} \u2194 ${pair.b} ${pc.dim(`(${hint})`)}`);
|
|
295
|
+
}
|
|
296
|
+
lines.push("");
|
|
297
|
+
}
|
|
298
|
+
if (report.orphans.length > 0) {
|
|
299
|
+
lines.push(pc.bold(pc.dim(`\u2026 Orphan vocabulary entries (${report.orphans.length})`)));
|
|
300
|
+
for (const id of report.orphans) {
|
|
301
|
+
lines.push(` ${pc.dim("\u2022")} ${id}`);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
}
|
|
305
|
+
if (report.unknowns.length === 0 && report.deprecated.length === 0 && report.aliases.length === 0 && report.nearDuplicates.length === 0 && report.orphans.length === 0) {
|
|
306
|
+
lines.push(pc.green("\u2713 No tag issues found"));
|
|
307
|
+
}
|
|
308
|
+
return lines.join("\n");
|
|
309
|
+
}
|
|
310
|
+
export {
|
|
311
|
+
applyFixes,
|
|
312
|
+
audit,
|
|
313
|
+
buildIndex,
|
|
314
|
+
collectMdxFiles,
|
|
315
|
+
computeRewrites,
|
|
316
|
+
findNearDuplicates,
|
|
317
|
+
formatTextReport,
|
|
318
|
+
hasHardIssues,
|
|
319
|
+
normalizeTags,
|
|
320
|
+
rewriteAliasesByteStable
|
|
321
|
+
};
|