blume 1.4.2 → 1.5.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/CHANGELOG.md +71 -0
- package/dist/cli/index.js +2260 -1100
- package/dist/cli/index.js.map +123 -117
- package/dist/types/ai/component-markdown.d.ts +14 -4
- package/dist/types/core/base-path.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +87 -27
- package/dist/types/core/config.d.ts +2 -1
- package/dist/types/core/data.d.ts +16 -1
- package/dist/types/core/diagnostics.d.ts +5 -1
- package/dist/types/core/i18n-ui.d.ts +12 -0
- package/dist/types/core/schema.d.ts +116 -15
- package/dist/types/core/sources/types.d.ts +11 -1
- package/dist/types/core/standard-schema.d.ts +7 -3
- package/dist/types/core/types.d.ts +43 -2
- package/dist/types/core/ui-packs/index.d.ts +9 -1
- package/dist/types/openapi/references.d.ts +14 -7
- package/dist/types/seo/x-handle.d.ts +3 -2
- package/docs/advanced/api-reference.mdx +8 -6
- package/docs/configuration/ai.mdx +26 -8
- package/docs/configuration/search.mdx +2 -0
- package/docs/configuration/seo.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/meta.mdx +2 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +35 -1
- package/docs/content/sources.mdx +1 -1
- package/docs/content/versioning.mdx +106 -0
- package/docs/reference/cli.mdx +1 -0
- package/docs/reference/frontmatter.mdx +3 -0
- package/package.json +13 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/references/docusaurus.md +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +1 -1
- package/src/ai/agent-readability.ts +40 -12
- package/src/ai/api-catalog.ts +2 -2
- package/src/ai/ask-context.ts +49 -12
- package/src/ai/ask.ts +10 -1
- package/src/ai/component-markdown.ts +80 -43
- package/src/ai/llms.ts +42 -17
- package/src/ai/mcp/data.ts +48 -12
- package/src/ai/mcp/discovery.ts +52 -16
- package/src/ai/mcp/server.ts +280 -125
- package/src/ai/mcp/tools.ts +3 -3
- package/src/ai/skills.ts +32 -9
- package/src/ai/tar.ts +29 -70
- package/src/ai/visibility.ts +2 -2
- package/src/astro/component-slots.ts +2 -0
- package/src/astro/examples.ts +13 -5
- package/src/astro/generate.ts +113 -63
- package/src/astro/integration.ts +13 -2
- package/src/astro/islands.ts +23 -12
- package/src/astro/templates.ts +185 -41
- package/src/audit/agent.ts +16 -31
- package/src/audit/checks/content.ts +26 -11
- package/src/audit/checks/dns-aid.ts +3 -0
- package/src/audit/checks/indexability.ts +24 -6
- package/src/audit/checks/llms.ts +9 -4
- package/src/audit/checks/network.ts +2 -0
- package/src/audit/checks/social.ts +18 -10
- package/src/audit/crawl.ts +78 -25
- package/src/audit/report.ts +20 -19
- package/src/audit/run.ts +15 -5
- package/src/audit/snapshot.ts +29 -6
- package/src/audit/types.ts +25 -3
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/commands/audit.ts +21 -21
- package/src/cli/commands/build.ts +30 -16
- package/src/cli/commands/dev.ts +15 -15
- package/src/cli/commands/doctor.ts +2 -0
- package/src/cli/commands/eject.ts +4 -4
- package/src/cli/commands/eval.ts +24 -30
- package/src/cli/commands/init.ts +9 -9
- package/src/cli/commands/mcp-stdio.ts +3 -0
- package/src/cli/commands/translate.ts +14 -3
- package/src/cli/commands/version.ts +85 -0
- package/src/cli/dev-lock.ts +31 -10
- package/src/cli/eject-scripts.ts +17 -2
- package/src/cli/env.ts +13 -30
- package/src/cli/index.ts +2 -0
- package/src/cli/init/questions.ts +1 -1
- package/src/cli/init/scaffold.ts +41 -13
- package/src/cli/internal-error.ts +1 -0
- package/src/cli/report-format.ts +22 -0
- package/src/components/content/AccordionItem.astro +2 -9
- package/src/components/content/ColorItem.astro +5 -13
- package/src/components/content/Component.astro +12 -8
- package/src/components/content/Frame.astro +2 -12
- package/src/components/content/Prompt.astro +12 -31
- package/src/components/content/Tab.astro +2 -9
- package/src/components/content/Tooltip.astro +1 -9
- package/src/components/content/Update.astro +2 -9
- package/src/components/content/auto-type-table.ts +3 -0
- package/src/components/content/diff.ts +9 -5
- package/src/components/content/github-info.ts +2 -0
- package/src/components/content/inline-markdown.ts +28 -0
- package/src/components/copy-feedback.ts +96 -0
- package/src/components/islands/ask-ai.tsx +111 -34
- package/src/components/islands/hooks.ts +5 -1
- package/src/components/islands/webmcp.ts +49 -12
- package/src/components/layout/Header.astro +25 -1
- package/src/components/layout/NavSelector.astro +11 -2
- package/src/components/layout/NavTree.astro +4 -2
- package/src/components/layout/PageActions.astro +20 -32
- package/src/components/layout/PageLayout.astro +8 -28
- package/src/components/layout/RootLayout.astro +24 -48
- package/src/components/layout/Search.astro +133 -22
- package/src/components/layout/VersionBanner.astro +39 -0
- package/src/components/layout/analytics-client.ts +8 -5
- package/src/components/layout/drawer-inert.ts +31 -0
- package/src/components/layout/hydration-hint.ts +1 -1
- package/src/components/layout/nav-utils.ts +1 -4
- package/src/components/layout/overrides.ts +25 -12
- package/src/components/layout/search/algolia.ts +18 -5
- package/src/components/layout/search/endpoint.ts +3 -0
- package/src/components/layout/search/flexsearch.ts +23 -7
- package/src/components/layout/search/orama-cloud.ts +1 -1
- package/src/components/layout/search/orama.ts +4 -1
- package/src/components/layout/search/pagefind.ts +8 -5
- package/src/components/layout/search/types.ts +45 -1
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/ApiOverview.astro +32 -6
- package/src/components/openapi/AsyncApiOperation.astro +237 -0
- package/src/components/openapi/Bindings.astro +89 -0
- package/src/components/openapi/MethodBadge.astro +3 -0
- package/src/components/openapi/Operation.astro +7 -2
- package/src/components/openapi/PanelTabs.astro +131 -0
- package/src/components/openapi/ParametersTable.astro +2 -0
- package/src/components/openapi/RequestPanel.astro +12 -119
- package/src/components/openapi/async-snippets.ts +174 -0
- package/src/components/openapi/async.ts +348 -0
- package/src/components/openapi/helpers.ts +52 -20
- package/src/components/openapi/panel.ts +11 -8
- package/src/components/openapi/security.ts +102 -29
- package/src/components/openapi/snippets.ts +11 -11
- package/src/components/raf-throttle.ts +21 -0
- package/src/components/slug.ts +14 -0
- package/src/core/base-path.ts +18 -1
- package/src/core/component-overrides.ts +28 -23
- package/src/core/config-input.ts +96 -27
- package/src/core/config.ts +20 -7
- package/src/core/content.ts +3 -1
- package/src/core/data.ts +16 -1
- package/src/core/define-components.ts +5 -0
- package/src/core/diagnostics.ts +46 -38
- package/src/core/frontmatter.ts +74 -4
- package/src/core/graph.ts +137 -53
- package/src/core/i18n-ui.ts +15 -0
- package/src/core/i18n.ts +16 -8
- package/src/core/load-module.ts +1 -0
- package/src/core/manifest.ts +92 -3
- package/src/core/meta.ts +44 -14
- package/src/core/nav-diagnostics.ts +3 -3
- package/src/core/navigation.ts +247 -67
- package/src/core/probe.ts +7 -19
- package/src/core/project-graph.ts +27 -4
- package/src/core/schema.ts +219 -67
- package/src/core/site-url.ts +27 -0
- package/src/core/sources/assets.ts +2 -0
- package/src/core/sources/cache.ts +16 -8
- package/src/core/sources/github-releases.ts +39 -11
- package/src/core/sources/mdx-remote.ts +4 -0
- package/src/core/sources/normalize.ts +93 -22
- package/src/core/sources/notion.ts +76 -22
- package/src/core/sources/portable-text.ts +48 -12
- package/src/core/sources/resolve.ts +1 -0
- package/src/core/sources/sanity.ts +68 -14
- package/src/core/sources/types.ts +17 -1
- package/src/core/sources/watch.ts +1 -1
- package/src/core/standard-schema.ts +9 -3
- package/src/core/text-width.ts +26 -0
- package/src/core/tsconfig-aliases.ts +9 -5
- package/src/core/types.ts +45 -2
- package/src/core/ui-packs/index.ts +9 -1
- package/src/core/version-cut.ts +301 -0
- package/src/core/version.ts +2 -0
- package/src/core/versions.ts +170 -0
- package/src/deploy/adapter-output.ts +5 -2
- package/src/deploy/cloudflare-negotiation.ts +40 -11
- package/src/deploy/robots.ts +2 -1
- package/src/deploy/rss.ts +2 -1
- package/src/deploy/sitemap.ts +89 -8
- package/src/deploy/vercel-negotiation.ts +11 -4
- package/src/eval/agents.ts +13 -10
- package/src/eval/report.ts +5 -18
- package/src/eval/run.ts +2 -2
- package/src/eval/schema.ts +1 -1
- package/src/markdown/base-links.ts +6 -6
- package/src/markdown/directives.ts +7 -1
- package/src/markdown/heading-anchors.ts +17 -6
- package/src/markdown/index.ts +73 -24
- package/src/markdown/inline-code.ts +14 -2
- package/src/markdown/language-icon.ts +6 -2
- package/src/markdown/mdast.ts +18 -4
- package/src/markdown/package-commands.ts +63 -58
- package/src/markdown/table-wrap.ts +4 -1
- package/src/markdown/twoslash.ts +2 -0
- package/src/og/card.ts +50 -33
- package/src/og/derive.ts +43 -27
- package/src/openapi/asyncapi.ts +366 -0
- package/src/openapi/model.ts +135 -66
- package/src/openapi/parse.ts +166 -33
- package/src/openapi/references.ts +47 -22
- package/src/openapi/render-mdx.ts +137 -59
- package/src/openapi/scalar.ts +8 -10
- package/src/openapi/source.ts +126 -29
- package/src/registry/eject.ts +7 -2
- package/src/search/documents.ts +103 -39
- package/src/search/facets.ts +7 -5
- package/src/search/orama-index.ts +117 -32
- package/src/search/popular.ts +10 -5
- package/src/search/providers.ts +2 -2
- package/src/search/sync/index.ts +2 -0
- package/src/search/sync/typesense.ts +4 -2
- package/src/seo/jsonld.ts +24 -6
- package/src/seo/x-handle.ts +8 -3
- package/src/theme/chrome-icons.ts +7 -2
- package/src/theme/fonts.ts +8 -4
- package/src/theme/icons.ts +4 -2
- package/src/theme/palette.ts +27 -15
- package/src/translate/ledger.ts +4 -2
- package/src/translate/meta.ts +15 -6
- package/src/translate/report.ts +10 -19
- package/src/translate/run.ts +29 -38
- package/src/translate/validate.ts +52 -17
- package/src/translate/work-list.ts +0 -0
- package/src/cli/coalesce.ts +0 -43
package/src/core/i18n.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ResolvedConfig,
|
|
3
|
+
ResolvedI18nConfig,
|
|
4
|
+
ResolvedVersionsConfig,
|
|
5
|
+
} from "./schema.ts";
|
|
2
6
|
import type { Diagnostic, PageRecord } from "./types.ts";
|
|
3
7
|
import { UI_PACKS } from "./ui-packs/index.ts";
|
|
4
8
|
|
|
@@ -70,10 +74,7 @@ export const localizeRoute = (
|
|
|
70
74
|
* matched as a leading segment. Returns the resolved locale and the remaining
|
|
71
75
|
* (locale-stripped) segments.
|
|
72
76
|
*/
|
|
73
|
-
export const detectLocale = (
|
|
74
|
-
parts: string[],
|
|
75
|
-
i18n: ResolvedI18nConfig
|
|
76
|
-
): { locale: string; rest: string[] } => {
|
|
77
|
+
export const detectLocale = (parts: string[], i18n: ResolvedI18nConfig) => {
|
|
77
78
|
// BCP 47 codes are case-insensitive: a conventional lowercase folder
|
|
78
79
|
// (`pt-br/`) must match a configured `pt-BR`. The configured casing is what
|
|
79
80
|
// flows into routes and labels.
|
|
@@ -102,7 +103,7 @@ export const localePlacement = (
|
|
|
102
103
|
rel: string,
|
|
103
104
|
ext: string,
|
|
104
105
|
i18n: ResolvedI18nConfig
|
|
105
|
-
)
|
|
106
|
+
) => {
|
|
106
107
|
const base = rel.slice(0, rel.length - ext.length);
|
|
107
108
|
|
|
108
109
|
// Shared `$` file: the same content in every locale. A shared file placed
|
|
@@ -187,17 +188,24 @@ export const localeTargetPath = (
|
|
|
187
188
|
*/
|
|
188
189
|
export const i18nDiagnostics = (
|
|
189
190
|
pages: PageRecord[],
|
|
190
|
-
i18n: ResolvedI18nConfig
|
|
191
|
+
i18n: ResolvedI18nConfig,
|
|
192
|
+
versions?: ResolvedVersionsConfig
|
|
191
193
|
): Diagnostic[] => {
|
|
192
194
|
const configured = new Set(
|
|
193
195
|
i18n.locales.map((locale) => locale.code.toLowerCase())
|
|
194
196
|
);
|
|
197
|
+
const versionDirs = new Set(versions?.archived.map((version) => version.id));
|
|
195
198
|
const seen = new Set<string>();
|
|
196
199
|
const diagnostics: Diagnostic[] = [];
|
|
197
200
|
for (const page of pages) {
|
|
198
201
|
// The locale-looking folder is the first segment of the source-local ref
|
|
199
202
|
// (e.g. `fr/guide.md`), not the namespaced id (`filesystem:fr/guide.md`).
|
|
200
|
-
|
|
203
|
+
// Inside a version snapshot the locale dir sits one level deeper
|
|
204
|
+
// (`v1.0/fr/guide.md`), so a configured version segment is skipped first.
|
|
205
|
+
const parts = page.source.ref.split("/");
|
|
206
|
+
const first = (
|
|
207
|
+
parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0]
|
|
208
|
+
)?.toLowerCase();
|
|
201
209
|
if (
|
|
202
210
|
first &&
|
|
203
211
|
!seen.has(first) &&
|
package/src/core/load-module.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { createJiti } from "jiti";
|
|
|
6
6
|
* is called with. `moduleCache: false` ensures edits are picked up on each load,
|
|
7
7
|
* which is what makes dev-server regeneration reflect config/meta changes.
|
|
8
8
|
*/
|
|
9
|
+
// oxlint-disable-next-line anti-slop/no-unknown-returns -- user-authored modules can export anything; callers validate the loaded value at their own boundary
|
|
9
10
|
export const createModuleLoader = (): ((file: string) => Promise<unknown>) => {
|
|
10
11
|
const jiti = createJiti(import.meta.url, { moduleCache: false });
|
|
11
12
|
return async (file: string) => {
|
package/src/core/manifest.ts
CHANGED
|
@@ -8,9 +8,39 @@ import type {
|
|
|
8
8
|
ProjectContext,
|
|
9
9
|
RouteAlternate,
|
|
10
10
|
RouteManifestEntry,
|
|
11
|
+
VersionAlternate,
|
|
11
12
|
} from "./types.ts";
|
|
12
13
|
import { getBlumeVersion } from "./version.ts";
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Key for the same logical page across versions within one locale: the
|
|
17
|
+
* version- and locale-agnostic route plus the locale code. NUL never appears
|
|
18
|
+
* in either part, so the join is unambiguous.
|
|
19
|
+
*/
|
|
20
|
+
const versionAlternateKey = (versionKey: string, locale: string): string =>
|
|
21
|
+
`${versionKey}\u0000${locale}`;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get-or-create the shared alternate list for a (versionKey, locale) pair.
|
|
25
|
+
* Lists are attached to route entries by reference and mutated as fallback
|
|
26
|
+
* routes materialize, then sorted once at the end — every holder sees the
|
|
27
|
+
* final list.
|
|
28
|
+
*/
|
|
29
|
+
const versionAlternatesFor = (
|
|
30
|
+
byKey: Map<string, VersionAlternate[]>,
|
|
31
|
+
versionKey: string,
|
|
32
|
+
locale: string
|
|
33
|
+
): VersionAlternate[] => {
|
|
34
|
+
const key = versionAlternateKey(versionKey, locale);
|
|
35
|
+
const existing = byKey.get(key);
|
|
36
|
+
if (existing) {
|
|
37
|
+
return existing;
|
|
38
|
+
}
|
|
39
|
+
const list: VersionAlternate[] = [];
|
|
40
|
+
byKey.set(key, list);
|
|
41
|
+
return list;
|
|
42
|
+
};
|
|
43
|
+
|
|
14
44
|
/** The current manifest schema version. */
|
|
15
45
|
export const MANIFEST_VERSION = 1;
|
|
16
46
|
|
|
@@ -37,7 +67,8 @@ const buildFallbackRoutes = (
|
|
|
37
67
|
graph: ContentGraph,
|
|
38
68
|
i18n: NonNullable<ResolvedConfig["i18n"]>,
|
|
39
69
|
alternatesByKey: Map<string, RouteAlternate[]>,
|
|
40
|
-
basePath: string
|
|
70
|
+
basePath: string,
|
|
71
|
+
versionAlternatesByKey: Map<string, VersionAlternate[]> | undefined
|
|
41
72
|
): RouteManifestEntry[] => {
|
|
42
73
|
const fallback = resolveFallbackLocale(i18n);
|
|
43
74
|
if (!fallback) {
|
|
@@ -62,6 +93,17 @@ const buildFallbackRoutes = (
|
|
|
62
93
|
if (present.has(key)) {
|
|
63
94
|
continue;
|
|
64
95
|
}
|
|
96
|
+
const path = withBasePath(basePath, localizeRoute(key, code, i18n));
|
|
97
|
+
// A fallback route is a real prerendered page, so it registers as a
|
|
98
|
+
// version alternate too — the switcher on a sibling version's page
|
|
99
|
+
// lands here instead of bouncing to the version root. Its own path is
|
|
100
|
+
// recorded (not the fallback source's), keeping the target in-locale.
|
|
101
|
+
const versionAlternates: VersionAlternate[] = versionAlternatesByKey
|
|
102
|
+
? versionAlternatesFor(versionAlternatesByKey, source.versionKey, code)
|
|
103
|
+
: [];
|
|
104
|
+
if (versionAlternatesByKey) {
|
|
105
|
+
versionAlternates.push({ path, version: source.version });
|
|
106
|
+
}
|
|
65
107
|
routes.push({
|
|
66
108
|
alternates: alternatesByKey.get(key) ?? [],
|
|
67
109
|
collection: source.collection ?? "docs",
|
|
@@ -75,10 +117,12 @@ const buildFallbackRoutes = (
|
|
|
75
117
|
indexable: false,
|
|
76
118
|
lastModified: source.lastModified,
|
|
77
119
|
locale: code,
|
|
78
|
-
path
|
|
120
|
+
path,
|
|
79
121
|
source: source.source,
|
|
80
122
|
sourcePath: source.sourcePath,
|
|
81
123
|
title: source.title,
|
|
124
|
+
version: source.version,
|
|
125
|
+
versionAlternates,
|
|
82
126
|
});
|
|
83
127
|
}
|
|
84
128
|
}
|
|
@@ -106,6 +150,23 @@ export const buildManifest = (options: {
|
|
|
106
150
|
}
|
|
107
151
|
}
|
|
108
152
|
|
|
153
|
+
// The same logical page across versions, within each locale — for the
|
|
154
|
+
// version switcher and the canonical-to-latest lookup. Built only under
|
|
155
|
+
// versioning; lists are shared by reference and finalized (fallback routes
|
|
156
|
+
// appended, then sorted) before the manifest is returned.
|
|
157
|
+
const versionAlternatesByKey = config.versions
|
|
158
|
+
? new Map<string, VersionAlternate[]>()
|
|
159
|
+
: undefined;
|
|
160
|
+
if (versionAlternatesByKey) {
|
|
161
|
+
for (const page of graph.pages) {
|
|
162
|
+
versionAlternatesFor(
|
|
163
|
+
versionAlternatesByKey,
|
|
164
|
+
page.versionKey,
|
|
165
|
+
page.locale
|
|
166
|
+
).push({ path: page.route, version: page.version });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
109
170
|
const routes: RouteManifestEntry[] = graph.pages.map((page) => ({
|
|
110
171
|
alternates: alternatesByKey.get(page.translationKey) ?? [],
|
|
111
172
|
collection: page.collection ?? "docs",
|
|
@@ -122,14 +183,42 @@ export const buildManifest = (options: {
|
|
|
122
183
|
source: page.source,
|
|
123
184
|
sourcePath: page.sourcePath,
|
|
124
185
|
title: page.title,
|
|
186
|
+
version: page.version,
|
|
187
|
+
versionAlternates: versionAlternatesByKey
|
|
188
|
+
? versionAlternatesFor(
|
|
189
|
+
versionAlternatesByKey,
|
|
190
|
+
page.versionKey,
|
|
191
|
+
page.locale
|
|
192
|
+
)
|
|
193
|
+
: [],
|
|
125
194
|
}));
|
|
126
195
|
|
|
127
196
|
if (i18n) {
|
|
128
197
|
routes.push(
|
|
129
|
-
...buildFallbackRoutes(
|
|
198
|
+
...buildFallbackRoutes(
|
|
199
|
+
graph,
|
|
200
|
+
i18n,
|
|
201
|
+
alternatesByKey,
|
|
202
|
+
config.basePath,
|
|
203
|
+
versionAlternatesByKey
|
|
204
|
+
)
|
|
130
205
|
);
|
|
131
206
|
}
|
|
132
207
|
|
|
208
|
+
// Alternate lists read current-first, then archived versions in configured
|
|
209
|
+
// (switcher) order. Sorted once here — every route holding a list by
|
|
210
|
+
// reference sees the final ordering.
|
|
211
|
+
if (versionAlternatesByKey && config.versions) {
|
|
212
|
+
const rank = new Map<string, number>(
|
|
213
|
+
config.versions.archived.map((version, index) => [version.id, index + 1])
|
|
214
|
+
);
|
|
215
|
+
for (const list of versionAlternatesByKey.values()) {
|
|
216
|
+
list.sort(
|
|
217
|
+
(a, b) => (rank.get(a.version) ?? 0) - (rank.get(b.version) ?? 0)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
133
222
|
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
134
223
|
|
|
135
224
|
return {
|
package/src/core/meta.ts
CHANGED
|
@@ -17,9 +17,23 @@ const META_FILES = [
|
|
|
17
17
|
"**/meta.$.mjs",
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* A meta module's default export before validation: `folderMetaSchema` parses
|
|
22
|
+
* it only after any factory is resolved, so it carries the loader's raw type.
|
|
23
|
+
*/
|
|
24
|
+
type MetaModuleExport = Awaited<
|
|
25
|
+
ReturnType<ReturnType<typeof createModuleLoader>>
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
/** A factory-style meta module default-exports a function returning the meta. */
|
|
29
|
+
const isMetaFactory = (
|
|
30
|
+
mod: MetaModuleExport
|
|
31
|
+
): mod is () => MetaModuleExport | Promise<MetaModuleExport> =>
|
|
32
|
+
typeof mod === "function";
|
|
33
|
+
|
|
20
34
|
/** Resolve a meta module's default export, calling it if it is a factory. */
|
|
21
|
-
const resolveMeta = async (mod:
|
|
22
|
-
|
|
35
|
+
const resolveMeta = async (mod: MetaModuleExport) =>
|
|
36
|
+
isMetaFactory(mod) ? await mod() : mod;
|
|
23
37
|
|
|
24
38
|
/** A filesystem content source to scan for folder meta: its on-disk root and
|
|
25
39
|
* optional route prefix. The prefix is folded into every key so meta lines up
|
|
@@ -58,18 +72,28 @@ const metaKeyFor = (prefix: string | undefined, dir: string): string => {
|
|
|
58
72
|
* group path starts with the source prefix, so a locale directory found at a
|
|
59
73
|
* source root is hoisted in front of the prefix (`docs/fr/guides/meta.ts` keys
|
|
60
74
|
* to `fr/docs/guides`, not `docs/fr/guides`).
|
|
75
|
+
*
|
|
76
|
+
* `versionDirs` names the archived-version snapshot directories, which sit
|
|
77
|
+
* outermost on disk — a locale directory inside a snapshot is one level deeper.
|
|
78
|
+
* Both are hoisted, version first (`v1.0/fr/guides/meta.ts` keys to
|
|
79
|
+
* `v1.0/fr/<prefix>/guides`), matching navigation's version-aware meta prefix.
|
|
61
80
|
*/
|
|
62
81
|
export const discoverFolderMeta = async (
|
|
63
82
|
sources: string | FolderMetaSource[],
|
|
64
|
-
options: {
|
|
83
|
+
options: {
|
|
84
|
+
localeDirs?: readonly string[];
|
|
85
|
+
versionDirs?: readonly string[];
|
|
86
|
+
} = {}
|
|
65
87
|
): Promise<{
|
|
66
88
|
meta: Map<string, FolderMeta>;
|
|
67
89
|
shared: Map<string, FolderMeta>;
|
|
68
90
|
diagnostics: Diagnostic[];
|
|
69
91
|
}> => {
|
|
70
|
-
const list: FolderMetaSource[] =
|
|
71
|
-
|
|
92
|
+
const list: FolderMetaSource[] = Array.isArray(sources)
|
|
93
|
+
? sources
|
|
94
|
+
: [{ root: sources }];
|
|
72
95
|
const localeDirs = new Set(options.localeDirs);
|
|
96
|
+
const versionDirs = new Set(options.versionDirs);
|
|
73
97
|
|
|
74
98
|
const load = createModuleLoader();
|
|
75
99
|
const meta = new Map<string, FolderMeta>();
|
|
@@ -103,6 +127,8 @@ export const discoverFolderMeta = async (
|
|
|
103
127
|
value: await resolveMeta(await load(file)),
|
|
104
128
|
};
|
|
105
129
|
} catch (error) {
|
|
130
|
+
// SAFETY: jiti surfaces load/evaluate failures as Error
|
|
131
|
+
// instances, and only `message` is read downstream.
|
|
106
132
|
return { error: error as Error, file, ok: false };
|
|
107
133
|
}
|
|
108
134
|
}
|
|
@@ -115,16 +141,20 @@ export const discoverFolderMeta = async (
|
|
|
115
141
|
for (const { loaded, source } of perSource) {
|
|
116
142
|
for (const entry of loaded) {
|
|
117
143
|
const dir = relative(source.root, dirname(entry.file));
|
|
144
|
+
// A version snapshot dir is outermost, with a locale dir one level
|
|
145
|
+
// deeper; both are hoisted in front of the (prefixed) group path, in
|
|
146
|
+
// that order — the lookup key reads `version/locale/prefix/dir`.
|
|
118
147
|
const [head, ...tail] = dir.split("/");
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
const version = head && versionDirs.has(head) ? head : "";
|
|
149
|
+
const afterVersion = version ? tail : [head ?? "", ...tail];
|
|
150
|
+
const [localeHead, ...localeTail] = afterVersion;
|
|
151
|
+
const locale = localeHead && localeDirs.has(localeHead) ? localeHead : "";
|
|
152
|
+
const rest = (locale ? localeTail : afterVersion)
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join("/");
|
|
155
|
+
const key = [version, locale, metaKeyFor(source.prefix, rest)]
|
|
156
|
+
.filter(Boolean)
|
|
157
|
+
.join("/");
|
|
128
158
|
|
|
129
159
|
if (!entry.ok) {
|
|
130
160
|
diagnostics.push({
|
|
@@ -9,7 +9,7 @@ import type { Diagnostic, NavNode, Navigation, PageRecord } from "./types.ts";
|
|
|
9
9
|
* covers every source (config, folder meta, frontmatter) at once.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const ICON_FORMAT_HINT =
|
|
13
13
|
"Use a built-in icon name, an image path/URL, or inline SVG markup.";
|
|
14
14
|
|
|
15
15
|
/** Flatten a sidebar tree to every node, descending into groups. */
|
|
@@ -80,7 +80,7 @@ const unknownIconDiagnostics = (
|
|
|
80
80
|
|
|
81
81
|
/** Warn about icon names that aren't in Blume's set (skipping image/SVG icons). */
|
|
82
82
|
export const validateNavIcons = (navigation: Navigation): Diagnostic[] =>
|
|
83
|
-
unknownIconDiagnostics(collectIcons(navigation),
|
|
83
|
+
unknownIconDiagnostics(collectIcons(navigation), ICON_FORMAT_HINT);
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Warn about unknown icons on curated `search.popular` links. Same accepted
|
|
@@ -94,7 +94,7 @@ export const validateSearchPopularIcons = (
|
|
|
94
94
|
? [{ icon: link.icon, where: `popular link "${link.label}"` }]
|
|
95
95
|
: []
|
|
96
96
|
);
|
|
97
|
-
return unknownIconDiagnostics(icons,
|
|
97
|
+
return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
/** Whether an internal path resolves to a page or a section that has pages. */
|