blume 2.0.2 → 2.0.3
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 +65 -0
- package/dist/cli/{chunk-6k4ftwze.js → chunk-1d7ve1dm.js} +1 -1
- package/dist/cli/{chunk-zp79m0ts.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-2hsdwb9n.js} +19 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-2hsdwb9n.js.map} +1 -1
- package/dist/cli/{chunk-g698a744.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-35d4wj9f.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-35d4wj9f.js.map} +3 -3
- package/dist/cli/{chunk-mqc662a6.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-3em5wd2y.js} +21 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5f86nr5m.js} +15 -15
- package/dist/cli/{chunk-hr8ne106.js → chunk-5m5nmvyq.js} +68 -43
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-5xvm6tfj.js} +14 -14
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-6dsbexzp.js} +14 -14
- package/dist/cli/{chunk-00gs3wqs.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-j85scx15.js → chunk-9t7a85s3.js} +130 -44
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-nfcyttvj.js → chunk-acanzt5p.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-acanzt5p.js.map} +1 -1
- package/dist/cli/{chunk-wdrt2k2v.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-xh43dwgw.js → chunk-c8chx29p.js} +42 -29
- package/dist/cli/{chunk-xh43dwgw.js.map → chunk-c8chx29p.js.map} +9 -9
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-crgn1q09.js} +21 -12
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-4e9b9ra6.js → chunk-ey84smr6.js} +3 -3
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-g4hq16wv.js} +13 -13
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-hm3vjy5s.js} +64 -45
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-5r8g91qn.js → chunk-j85vccga.js} +306 -107
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-7vtckvaw.js → chunk-p73c0m7w.js} +14 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-p73c0m7w.js.map} +1 -1
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-pehfxfta.js} +3 -3
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-pv29h0wf.js} +1223 -978
- package/dist/cli/{chunk-v6ya5kcb.js.map → chunk-pv29h0wf.js.map} +47 -46
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6dtt0zfn.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-r9rcc4w7.js} +7 -7
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-3yce002v.js → chunk-vg9r4eb9.js} +6 -6
- package/dist/cli/{chunk-3yce002v.js.map → chunk-vg9r4eb9.js.map} +4 -4
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-wrr3j9w9.js} +16 -17
- package/dist/cli/{chunk-hqp2ajnh.js.map → chunk-wrr3j9w9.js.map} +7 -7
- package/dist/cli/{chunk-ddndchfr.js → chunk-yfyb25rh.js} +38 -26
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +20 -18
- package/dist/cli/index.js.map +3 -3
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +9 -7
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/core/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +9 -7
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/meta.d.ts +8 -0
- package/dist/types/core/schema.d.ts +7 -0
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -0
- package/dist/types/deploy/platforms/types.d.ts +8 -0
- package/dist/types/deploy/redirects.d.ts +24 -13
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/docs/02-deployment.mdx +4 -2
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/cli/audit.mdx +17 -1
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +20 -1
- package/docs/configuration/customization.mdx +2 -0
- package/docs/configuration/search.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +2 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +3 -1
- package/docs/content/sources.mdx +1 -5
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +2 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +1 -1
- package/docs/references/openapi.mdx +5 -3
- package/package.json +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +7 -2
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/link-headers.ts +14 -5
- package/src/ai/llms.ts +18 -7
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/tar.ts +139 -12
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +1 -1
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +81 -37
- package/src/audit/checks/links.ts +8 -1
- package/src/audit/graph.ts +3 -1
- package/src/audit/run.ts +12 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/cli/args.ts +32 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/Badge.astro +5 -3
- package/src/components/content/Component.astro +2 -2
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -0
- package/src/components/content/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- package/src/components/islands/assistant.tsx +21 -4
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +35 -19
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +2 -2
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/NavSelector.astro +2 -2
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +7 -6
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/RootLayout.astro +5 -5
- package/src/components/layout/Search.astro +33 -13
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/Operation.astro +2 -0
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestPanel.astro +5 -2
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/snippets.ts +85 -6
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +62 -16
- package/src/core/config-input.ts +9 -7
- package/src/core/diagnostics.ts +2 -0
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n.ts +31 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/locale-links.ts +5 -1
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +28 -8
- package/src/core/project-graph.ts +27 -5
- package/src/core/schema.ts +24 -4
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +325 -90
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +27 -6
- package/src/deploy/cloudflare-negotiation.ts +4 -12
- package/src/deploy/headers.ts +8 -4
- package/src/deploy/node-headers.ts +1 -1
- package/src/deploy/platforms/cloudflare.ts +12 -3
- package/src/deploy/platforms/netlify.ts +1 -0
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +1 -0
- package/src/deploy/redirects.ts +54 -18
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +7 -6
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -4
- package/src/og/card.ts +129 -5
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +22 -14
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +41 -2
- package/src/registry/eject.ts +181 -24
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +67 -26
- package/src/search/popular.ts +2 -1
- package/src/seo/jsonld.ts +7 -3
- package/src/translate/meta.ts +68 -24
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +4 -1
- package/src/translate/work-list.ts +56 -17
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- /package/dist/cli/{chunk-6k4ftwze.js.map → chunk-1d7ve1dm.js.map} +0 -0
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-mqc662a6.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5f86nr5m.js.map} +0 -0
- /package/dist/cli/{chunk-91ws1n6j.js.map → chunk-5xvm6tfj.js.map} +0 -0
- /package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-6dsbexzp.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-ey84smr6.js.map} +0 -0
- /package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-g4hq16wv.js.map} +0 -0
- /package/dist/cli/{chunk-qkb5a8sa.js.map → chunk-pehfxfta.js.map} +0 -0
- /package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-r9rcc4w7.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-tzmab476.js.map} +0 -0
package/src/core/diagnostics.ts
CHANGED
|
@@ -69,8 +69,10 @@ const DOCS_PATHS = new Map(
|
|
|
69
69
|
BLUME_TRANSLATE_MISSING: "/docs/cli/translate",
|
|
70
70
|
BLUME_TRANSLATE_STALE: "/docs/cli/translate",
|
|
71
71
|
BLUME_UNKNOWN_COMPONENT: "/docs/configuration/customization",
|
|
72
|
+
BLUME_UNKNOWN_DIRECTIVE: "/docs/content/syntax",
|
|
72
73
|
BLUME_UNKNOWN_ICON: DOCS_CONTENT_NAVIGATION,
|
|
73
74
|
BLUME_UNKNOWN_OPTION: "/docs/cli",
|
|
75
|
+
BLUME_UNLOADABLE_FILE_NAME: "/docs/content",
|
|
74
76
|
BLUME_WIKILINK_AMBIGUOUS: DOCS_CONTENT_SOURCES,
|
|
75
77
|
BLUME_WIKILINK_UNRESOLVED: DOCS_CONTENT_SOURCES,
|
|
76
78
|
BLUME_YARN_PNP: "/docs/quickstart",
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Nodes } from "mdast";
|
|
2
|
+
import { mdxToMdast } from "satteri";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CALLOUT_ALIASES,
|
|
6
|
+
CALLOUT_TYPES,
|
|
7
|
+
calloutTypeFor,
|
|
8
|
+
} from "../markdown/directives.ts";
|
|
9
|
+
import { MDX_BODY_FEATURES } from "../markdown/features.ts";
|
|
10
|
+
import { strippedLineOffset } from "./sources/normalize.ts";
|
|
11
|
+
import type { SourceEntry } from "./sources/types.ts";
|
|
12
|
+
import type { Diagnostic } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
// A line that may open a container directive: a colon fence and a name, after
|
|
15
|
+
// any quote or list indentation. A page is parsed only when one of these
|
|
16
|
+
// names something other than a callout, so pages without one cost a scan.
|
|
17
|
+
const CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
|
|
18
|
+
|
|
19
|
+
const codeList = (names: Iterable<string>): string =>
|
|
20
|
+
[...names].map((name) => `\`${name}\``).join(", ");
|
|
21
|
+
|
|
22
|
+
const CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
|
|
23
|
+
|
|
24
|
+
/** A container directive that isn't a callout, and its line in `text`. */
|
|
25
|
+
interface UnknownContainer {
|
|
26
|
+
line: number;
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Every container directive in an `.mdx` body whose name isn't a callout, in
|
|
32
|
+
* source order. The body is parsed as the renderer reads it, so a fence in a
|
|
33
|
+
* code block or a `::: name` that isn't a directive at all never counts. A
|
|
34
|
+
* body MDX can't parse fails to render anyway, with its own error.
|
|
35
|
+
*/
|
|
36
|
+
const unknownContainers = (text: string): UnknownContainer[] => {
|
|
37
|
+
const named = [...text.matchAll(CONTAINER_OPENING)].some(
|
|
38
|
+
(match) => calloutTypeFor(match.groups?.name ?? "") === null
|
|
39
|
+
);
|
|
40
|
+
if (!named) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
let tree: Nodes;
|
|
44
|
+
try {
|
|
45
|
+
tree = mdxToMdast(text, { features: MDX_BODY_FEATURES });
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const found: UnknownContainer[] = [];
|
|
50
|
+
const walk = (node: Nodes): void => {
|
|
51
|
+
if (
|
|
52
|
+
node.type === "containerDirective" &&
|
|
53
|
+
calloutTypeFor(node.name) === null
|
|
54
|
+
) {
|
|
55
|
+
found.push({ line: node.position?.start.line ?? 1, name: node.name });
|
|
56
|
+
}
|
|
57
|
+
if ("children" in node) {
|
|
58
|
+
for (const child of node.children) {
|
|
59
|
+
walk(child);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(tree);
|
|
64
|
+
return found;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Warn about each `:::name` container in an `.mdx` entry that isn't a callout.
|
|
69
|
+
* The page keeps its content — the body renders between the literal `:::`
|
|
70
|
+
* lines (see `markdown/directives.ts`) — but a typo like `:::warnig`, or a
|
|
71
|
+
* `:::details` carried over from another docs tool, should read as the
|
|
72
|
+
* mistake it is rather than as a finished page. Lines point into the file
|
|
73
|
+
* the author wrote, a partial's own file for a container an `<include>`
|
|
74
|
+
* brought in.
|
|
75
|
+
*/
|
|
76
|
+
export const unknownDirectiveDiagnostics = (
|
|
77
|
+
entry: SourceEntry,
|
|
78
|
+
sourceName: string
|
|
79
|
+
): Diagnostic[] => {
|
|
80
|
+
if (entry.body.format !== "mdx") {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
84
|
+
const offset =
|
|
85
|
+
entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
86
|
+
return unknownContainers(entry.expanded?.text ?? entry.body.text).map(
|
|
87
|
+
({ line, name }) => {
|
|
88
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
89
|
+
return {
|
|
90
|
+
code: "BLUME_UNKNOWN_DIRECTIVE",
|
|
91
|
+
file: origin?.file ?? page,
|
|
92
|
+
line: origin?.line ?? line + offset,
|
|
93
|
+
message: `\`:::${name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
|
|
94
|
+
severity: "warning",
|
|
95
|
+
suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
};
|
package/src/core/frontmatter.ts
CHANGED
|
@@ -19,13 +19,18 @@ type StringifyArgs = Parameters<typeof baseMatter.stringify>;
|
|
|
19
19
|
/** Front matter data as gray-matter types it (`GrayMatterFile["data"]`). */
|
|
20
20
|
type FrontMatterData = ReturnType<typeof baseMatter>["data"];
|
|
21
21
|
|
|
22
|
+
/** A YAML load result: a mapping (or list) Astro keeps as front matter data. */
|
|
23
|
+
const isYamlCollection = (
|
|
24
|
+
value: ReturnType<typeof load>
|
|
25
|
+
): value is FrontMatterData => typeof value === "object" && value !== null;
|
|
26
|
+
|
|
22
27
|
const yamlEngine = {
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// A block that loads to a scalar (`title` alone, or prose between two
|
|
29
|
+
// `---` lines) is no metadata: Astro reads it as `{}`, and so does Blume.
|
|
30
|
+
parse: (input: string): FrontMatterData => {
|
|
31
|
+
const value = load(input, { schema: YAML_SCHEMA });
|
|
32
|
+
return isYamlCollection(value) ? value : {};
|
|
33
|
+
},
|
|
29
34
|
stringify: (data: FrontMatterData): string => dump(data),
|
|
30
35
|
};
|
|
31
36
|
|
|
@@ -45,23 +50,21 @@ const withYamlEngine = <O extends { engines?: object } | undefined>(
|
|
|
45
50
|
|
|
46
51
|
/**
|
|
47
52
|
* True when a document's leading `---` line is a CommonMark thematic break,
|
|
48
|
-
* not a front matter fence.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* not a front matter fence: no closing `---` line follows. Astro's own front
|
|
54
|
+
* matter match requires that close too, while gray-matter would swallow the
|
|
55
|
+
* whole document as one unclosed YAML block and hand it to js-yaml, which
|
|
56
|
+
* crashes on ordinary Markdown (`> quote` → "a line break is expected").
|
|
57
|
+
*
|
|
58
|
+
* A blank line after the opening `---` is still front matter: Astro strips
|
|
59
|
+
* the block from the page either way, so reading it as a divider here would
|
|
60
|
+
* ignore its `draft`, `slug`, and `hidden` while its YAML leaked into search
|
|
61
|
+
* and llms.txt.
|
|
56
62
|
*/
|
|
57
63
|
const opensWithThematicBreak = (input: string): boolean => {
|
|
58
|
-
const [first = ""
|
|
64
|
+
const [first = ""] = input.split(/\r?\n/u, 1);
|
|
59
65
|
if (!/^-{3}\s*$/u.test(first)) {
|
|
60
66
|
return false;
|
|
61
67
|
}
|
|
62
|
-
if (second === undefined || second.trim() === "") {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
68
|
// gray-matter closes the block at the next line-leading `---`; matching its
|
|
66
69
|
// search exactly keeps this guard from firing on any document it parses.
|
|
67
70
|
return !input.includes("\n---", 1);
|
|
@@ -55,17 +55,12 @@ export interface HeadingMarkers {
|
|
|
55
55
|
* the heading count (mid-text brackets are ordinary prose); when a marker kind
|
|
56
56
|
* repeats, the last one written wins.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
* bracket
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* The render-time hast plugin never passes it (resolved links are already
|
|
63
|
-
* elements by the time it runs); the raw-source scanner does.
|
|
58
|
+
* Callers pass the heading's trailing text node after a Markdown parse, so a
|
|
59
|
+
* bracket that a link-reference definition turns into a CommonMark shortcut
|
|
60
|
+
* link (`[toc]` with a `[toc]: …` definition) is already an element there and
|
|
61
|
+
* never reaches this parse.
|
|
64
62
|
*/
|
|
65
|
-
export const parseHeadingMarkers = (
|
|
66
|
-
text: string,
|
|
67
|
-
isRefDefined?: (label: string) => boolean
|
|
68
|
-
): HeadingMarkers => {
|
|
63
|
+
export const parseHeadingMarkers = (text: string): HeadingMarkers => {
|
|
69
64
|
let remaining = text;
|
|
70
65
|
let id: string | undefined;
|
|
71
66
|
let toc: HeadingMarkers["toc"];
|
|
@@ -75,14 +70,6 @@ export const parseHeadingMarkers = (
|
|
|
75
70
|
match = MARKER.exec(remaining)
|
|
76
71
|
) {
|
|
77
72
|
const explicitId = match.groups.curlyId ?? match.groups.id;
|
|
78
|
-
const label =
|
|
79
|
-
explicitId === undefined
|
|
80
|
-
? `${match.groups.hide ?? ""}toc`
|
|
81
|
-
: `#${explicitId}`;
|
|
82
|
-
// A `{#id}` is never a shortcut link, whatever the definitions say.
|
|
83
|
-
if (match.groups.curlyId === undefined && isRefDefined?.(label)) {
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
73
|
remaining = remaining.slice(0, match.index);
|
|
87
74
|
if (explicitId === undefined) {
|
|
88
75
|
// Stripping runs right-to-left, so keeping the first capture of each
|
package/src/core/i18n.ts
CHANGED
|
@@ -219,11 +219,31 @@ export const localeTargetPath = (
|
|
|
219
219
|
return `${folder}/${rel}`;
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* The warning for a top-level folder named after the default locale
|
|
224
|
+
* (`docs/en/` when `defaultLocale` is `en`). Only non-default codes are
|
|
225
|
+
* locale folders — the default locale lives at the content root — so the
|
|
226
|
+
* folder is ordinary default-locale content, published one level down at
|
|
227
|
+
* `/en/…` (`/en/en/…` with the default prefix shown) and filled into every
|
|
228
|
+
* other locale at `/fr/en/…`.
|
|
229
|
+
*/
|
|
230
|
+
const defaultLocaleFolderDiagnostic = (
|
|
231
|
+
folder: string,
|
|
232
|
+
i18n: ResolvedI18nConfig
|
|
233
|
+
): Diagnostic => ({
|
|
234
|
+
code: "BLUME_I18N_DEFAULT_LOCALE_FOLDER",
|
|
235
|
+
message: `Folder "${folder}/" is named after the default locale, but "${i18n.defaultLocale}" pages live at the content root — its pages publish at ${localizeRoute(`/${folder}`, i18n.defaultLocale, i18n)}/… instead.`,
|
|
236
|
+
severity: "warning",
|
|
237
|
+
suggestion: `Move the contents of "${folder}/" up to the content root; only the other locales get a folder.`,
|
|
238
|
+
});
|
|
239
|
+
|
|
222
240
|
/**
|
|
223
241
|
* Warn about top-level content folders that look like a locale (a code Blume
|
|
224
242
|
* recognizes) but aren't declared in `i18n.locales`. Without this they're
|
|
225
243
|
* silently treated as default-locale content under a `/<code>/…` route, which
|
|
226
244
|
* is almost never intended — usually a translation that wasn't registered.
|
|
245
|
+
* A folder named after the default locale gets the same treatment (see
|
|
246
|
+
* {@link defaultLocaleFolderDiagnostic}).
|
|
227
247
|
*/
|
|
228
248
|
export const i18nDiagnostics = (
|
|
229
249
|
pages: PageRecord[],
|
|
@@ -242,9 +262,17 @@ export const i18nDiagnostics = (
|
|
|
242
262
|
// Inside a version snapshot the locale dir sits one level deeper
|
|
243
263
|
// (`v1.0/fr/guide.md`), so a configured version segment is skipped first.
|
|
244
264
|
const parts = page.source.ref.split("/");
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
265
|
+
const folder = parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0];
|
|
266
|
+
const first = folder?.toLowerCase();
|
|
267
|
+
if (
|
|
268
|
+
folder !== undefined &&
|
|
269
|
+
first === i18n.defaultLocale.toLowerCase() &&
|
|
270
|
+
!seen.has(first)
|
|
271
|
+
) {
|
|
272
|
+
seen.add(first);
|
|
273
|
+
diagnostics.push(defaultLocaleFolderDiagnostic(folder, i18n));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
248
276
|
if (
|
|
249
277
|
first &&
|
|
250
278
|
!seen.has(first) &&
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
2
3
|
|
|
3
|
-
import { relative } from "pathe";
|
|
4
|
+
import { normalize, relative } from "pathe";
|
|
4
5
|
|
|
5
6
|
import type { Diagnostic } from "./types.ts";
|
|
6
7
|
|
|
@@ -58,6 +59,23 @@ export const parseGitLog = (output: string): Map<string, string> => {
|
|
|
58
59
|
return times;
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* `path` with every symlink resolved, the spelling git prints paths in: a
|
|
64
|
+
* project opened through a link (`/tmp/site` on macOS, where `/tmp` links to
|
|
65
|
+
* `/private/tmp`) gets `/private/tmp/site` from `rev-parse --show-toplevel`,
|
|
66
|
+
* so the project's own spelling would compare as outside the repository and
|
|
67
|
+
* every page would go undated. The native realpath also expands a Windows
|
|
68
|
+
* 8.3 short name (`RUNNER~1`), which git does too. A path that doesn't exist
|
|
69
|
+
* stays as written.
|
|
70
|
+
*/
|
|
71
|
+
export const realPath = (path: string): string => {
|
|
72
|
+
try {
|
|
73
|
+
return normalize(realpathSync.native(path));
|
|
74
|
+
} catch {
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
61
79
|
/**
|
|
62
80
|
* The toplevel of the repository containing `root`, or null when git is
|
|
63
81
|
* unavailable or the project isn't a repo. Callers use it to decide which
|
|
@@ -108,6 +126,10 @@ export const gitLastModifiedTimes = (
|
|
|
108
126
|
if (gitRoot === null) {
|
|
109
127
|
return new Map();
|
|
110
128
|
}
|
|
129
|
+
// Git names paths by their real location, so the pathspecs and the paths
|
|
130
|
+
// read back against the log are compared in that spelling too (see
|
|
131
|
+
// `realPath`).
|
|
132
|
+
const top = realPath(gitRoot);
|
|
111
133
|
try {
|
|
112
134
|
const output = execFileSync(
|
|
113
135
|
// oxlint-disable-next-line sonarjs/no-os-command-from-path -- git is a required dev-tool dependency resolved from PATH
|
|
@@ -121,14 +143,14 @@ export const gitLastModifiedTimes = (
|
|
|
121
143
|
"--format=%x00%cI",
|
|
122
144
|
"--name-only",
|
|
123
145
|
"--",
|
|
124
|
-
...contentRoots,
|
|
146
|
+
...contentRoots.map(realPath),
|
|
125
147
|
],
|
|
126
148
|
{ encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 }
|
|
127
149
|
);
|
|
128
150
|
const byRepoPath = parseGitLog(output);
|
|
129
151
|
const result = new Map<string, string>();
|
|
130
152
|
for (const sourcePath of sourcePaths) {
|
|
131
|
-
const iso = byRepoPath.get(relative(
|
|
153
|
+
const iso = byRepoPath.get(relative(top, realPath(sourcePath)));
|
|
132
154
|
if (iso) {
|
|
133
155
|
result.set(sourcePath, iso);
|
|
134
156
|
}
|
package/src/core/locale-links.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isInternalPath,
|
|
3
|
+
mountBasePath,
|
|
3
4
|
normalizePath,
|
|
4
5
|
stripBasePath,
|
|
5
6
|
withBasePath,
|
|
@@ -120,7 +121,10 @@ export const localizeHref = (
|
|
|
120
121
|
if (localized === based) {
|
|
121
122
|
return href;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
// The base came off above, so it goes back on unconditionally: a localized
|
|
125
|
+
// path that starts with the base's own name (`/ja/…` under base `/ja`) is
|
|
126
|
+
// still served under it.
|
|
127
|
+
return `${mountBasePath(options.deployBase, localized)}${suffix}`;
|
|
124
128
|
};
|
|
125
129
|
|
|
126
130
|
/** Every `<a …>` opening tag; `\s` keeps `<abbr>`/`<astro-island>` out. */
|
package/src/core/meta.ts
CHANGED
|
@@ -89,6 +89,38 @@ const withinInclude = (
|
|
|
89
89
|
(pattern) => !pattern.startsWith("!") && onPathOf(dir, globBase(pattern))
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The folder-meta files a filesystem source contributes: `patterns` (every
|
|
94
|
+
* meta module name, shared `meta.$.*` included, by default) under the source's
|
|
95
|
+
* root, minus its `exclude` globs, dependencies, and build output, and only in
|
|
96
|
+
* folders its `include` globs reach. `blume translate` finds the meta files it
|
|
97
|
+
* translates through this too, so it covers exactly the ones the scan reads.
|
|
98
|
+
*/
|
|
99
|
+
export const findFolderMetaFiles = async (
|
|
100
|
+
source: FolderMetaSource,
|
|
101
|
+
patterns: readonly string[] = META_FILES
|
|
102
|
+
): Promise<string[]> => {
|
|
103
|
+
const found = await glob([...patterns], {
|
|
104
|
+
absolute: true,
|
|
105
|
+
cwd: source.root,
|
|
106
|
+
// Never descend into dependencies or build output — relevant when the
|
|
107
|
+
// root is the project root (e.g. a `.`-rooted or all-staged project).
|
|
108
|
+
// The source's own `exclude` applies too: a `meta.ts` under an excluded
|
|
109
|
+
// folder (`src/lib/meta.ts` beside `exclude: ["src/**"]`) is application
|
|
110
|
+
// code, not folder meta, and importing it would fail.
|
|
111
|
+
ignore: [
|
|
112
|
+
...(source.exclude ?? []),
|
|
113
|
+
"**/node_modules/**",
|
|
114
|
+
"**/.blume/**",
|
|
115
|
+
"**/dist/**",
|
|
116
|
+
],
|
|
117
|
+
onlyFiles: true,
|
|
118
|
+
});
|
|
119
|
+
return found.filter((file) =>
|
|
120
|
+
withinInclude(relative(source.root, dirname(file)), source.include)
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
92
124
|
/**
|
|
93
125
|
* The folder-meta key for a directory. Mirrors the sidebar group path: the
|
|
94
126
|
* source's route prefix (`docs`) followed by the directory relative to the
|
|
@@ -155,25 +187,7 @@ export const discoverFolderMeta = async (
|
|
|
155
187
|
// `content.root` still contributes its folder meta.
|
|
156
188
|
const perSource = await Promise.all(
|
|
157
189
|
list.map(async (source) => {
|
|
158
|
-
const
|
|
159
|
-
absolute: true,
|
|
160
|
-
cwd: source.root,
|
|
161
|
-
// Never descend into dependencies or build output — relevant when the
|
|
162
|
-
// root is the project root (e.g. a `.`-rooted or all-staged project).
|
|
163
|
-
// The source's own `exclude` applies too: a `meta.ts` under an
|
|
164
|
-
// excluded folder (`src/lib/meta.ts` beside `exclude: ["src/**"]`)
|
|
165
|
-
// is application code, not folder meta, and importing it would fail.
|
|
166
|
-
ignore: [
|
|
167
|
-
...(source.exclude ?? []),
|
|
168
|
-
"**/node_modules/**",
|
|
169
|
-
"**/.blume/**",
|
|
170
|
-
"**/dist/**",
|
|
171
|
-
],
|
|
172
|
-
onlyFiles: true,
|
|
173
|
-
});
|
|
174
|
-
const files = found.filter((file) =>
|
|
175
|
-
withinInclude(relative(source.root, dirname(file)), source.include)
|
|
176
|
-
);
|
|
190
|
+
const files = await findFolderMetaFiles(source);
|
|
177
191
|
const loaded = await Promise.all(
|
|
178
192
|
files.map(
|
|
179
193
|
async (
|
package/src/core/navigation.ts
CHANGED
|
@@ -115,11 +115,29 @@ const numericOrder = (segment: string): number => {
|
|
|
115
115
|
return value ? Math.trunc(Number(value)) : Number.POSITIVE_INFINITY;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The name inside a group folder's parentheses — `(guides)`, or `01-(guides)`
|
|
120
|
+
* with its ordering prefix outside them — else undefined. A prefix written
|
|
121
|
+
* inside (`(01-guides)`) stays in the name, where it sorts and drops like any
|
|
122
|
+
* folder's.
|
|
123
|
+
*/
|
|
124
|
+
const groupName = (raw: string): string | undefined =>
|
|
125
|
+
(raw.match(GROUP_FOLDER) ?? stripOrderingPrefix(raw).match(GROUP_FOLDER))
|
|
126
|
+
?.groups?.label;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A folder's sidebar order: its ordering prefix, or — for a group folder
|
|
130
|
+
* without one outside the parentheses — the prefix inside them, so
|
|
131
|
+
* `01-(guides)` and `(01-guides)` both sort like `01-guides`.
|
|
132
|
+
*/
|
|
133
|
+
const folderOrder = (raw: string): number =>
|
|
134
|
+
numericOrder(
|
|
135
|
+
orderingPrefix(raw) === undefined ? (groupName(raw) ?? raw) : raw
|
|
136
|
+
);
|
|
137
|
+
|
|
118
138
|
/** The nav key of a raw path segment: group label or numeric-stripped name. */
|
|
119
|
-
const segmentKey = (raw: string): string =>
|
|
120
|
-
|
|
121
|
-
return stripOrderingPrefix(group ?? raw);
|
|
122
|
-
};
|
|
139
|
+
const segmentKey = (raw: string): string =>
|
|
140
|
+
stripOrderingPrefix(groupName(raw) ?? raw);
|
|
123
141
|
|
|
124
142
|
/**
|
|
125
143
|
* Whether a filename stem is a directory index, ignoring an ordering prefix:
|
|
@@ -221,8 +239,8 @@ const ensureGroup = (
|
|
|
221
239
|
const group = createGroup(
|
|
222
240
|
segmentKey(rawSegment),
|
|
223
241
|
path,
|
|
224
|
-
humanize(rawSegment
|
|
225
|
-
|
|
242
|
+
humanize(groupName(rawSegment) ?? rawSegment),
|
|
243
|
+
folderOrder(rawSegment)
|
|
226
244
|
);
|
|
227
245
|
parent.index.set(rawSegment, group);
|
|
228
246
|
parent.children.push(group);
|
|
@@ -722,7 +740,9 @@ const buildFileSystemSidebar = (
|
|
|
722
740
|
const folderParts = isIndexStem(stem)
|
|
723
741
|
? routeSegments
|
|
724
742
|
: routeSegments.slice(0, -1);
|
|
725
|
-
const routeDirCount = dirs.filter(
|
|
743
|
+
const routeDirCount = dirs.filter(
|
|
744
|
+
(dir) => groupName(dir) === undefined
|
|
745
|
+
).length;
|
|
726
746
|
const offset = Math.max(0, folderParts.length - routeDirCount);
|
|
727
747
|
// A frontmatter `slug` publishes the page away from its folder, so its
|
|
728
748
|
// route says nothing about where the folder lives (`guides/a.md` with
|
|
@@ -744,7 +764,7 @@ const buildFileSystemSidebar = (
|
|
|
744
764
|
const folders: string[] = [];
|
|
745
765
|
for (const dir of dirs) {
|
|
746
766
|
parent = ensureGroup(parent, dir);
|
|
747
|
-
if (
|
|
767
|
+
if (groupName(dir) === undefined) {
|
|
748
768
|
consumed += 1;
|
|
749
769
|
folders.push(stripOrderingPrefix(dir));
|
|
750
770
|
}
|
|
@@ -3,6 +3,7 @@ import { isAbsolute, relative } from "pathe";
|
|
|
3
3
|
import { CHANGELOG_INDEX_ROUTE, hasChangelogIndex } from "./changelog-index.ts";
|
|
4
4
|
import { loadConfig } from "./config.ts";
|
|
5
5
|
import { customStaticRoutes, discoverPages } from "./custom-pages.ts";
|
|
6
|
+
import { unknownDirectiveDiagnostics } from "./directive-diagnostics.ts";
|
|
6
7
|
import { buildContentGraph } from "./graph.ts";
|
|
7
8
|
import { i18nDiagnostics } from "./i18n.ts";
|
|
8
9
|
import { expandIncludes, hasIncludeStatements } from "./includes.ts";
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
gitLastModifiedTimes,
|
|
11
12
|
gitRepositoryRoot,
|
|
12
13
|
lastModifiedShallowWarning,
|
|
14
|
+
realPath,
|
|
13
15
|
resolveLastModifiedConfig,
|
|
14
16
|
} from "./last-modified.ts";
|
|
15
17
|
import { buildManifest } from "./manifest.ts";
|
|
@@ -196,11 +198,34 @@ const normalizeLoadedEntries = (
|
|
|
196
198
|
}
|
|
197
199
|
pages.push(...normalized.pages);
|
|
198
200
|
allDiagnostics.push(...normalized.diagnostics);
|
|
201
|
+
if (normalized.pages.length > 0) {
|
|
202
|
+
allDiagnostics.push(...unknownDirectiveDiagnostics(entry, source.name));
|
|
203
|
+
}
|
|
199
204
|
}
|
|
200
205
|
}
|
|
201
206
|
return { diagnostics: allDiagnostics, droppedPages, pages };
|
|
202
207
|
};
|
|
203
208
|
|
|
209
|
+
/**
|
|
210
|
+
* The local sources' on-disk roots a `git log` pathspec can cover: those
|
|
211
|
+
* inside the repository at `gitRoot`. A root outside it would fail the log
|
|
212
|
+
* outright and can never yield dates. Compared by real path, since git spells
|
|
213
|
+
* its toplevel with every symlink resolved (`/private/tmp/site` for a project
|
|
214
|
+
* opened as `/tmp/site`); the roots themselves keep the project's spelling.
|
|
215
|
+
*/
|
|
216
|
+
const gitContentRoots = (
|
|
217
|
+
sources: readonly ContentSource[],
|
|
218
|
+
gitRoot: string | null
|
|
219
|
+
): string[] => {
|
|
220
|
+
if (gitRoot === null) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const top = realPath(gitRoot);
|
|
224
|
+
return sources
|
|
225
|
+
.flatMap((source) => (source.contentRoot ? [source.contentRoot] : []))
|
|
226
|
+
.filter((dir) => isWithin(top, realPath(dir)));
|
|
227
|
+
};
|
|
228
|
+
|
|
204
229
|
/**
|
|
205
230
|
* Run the full core pipeline for a project root: load config, resolve paths,
|
|
206
231
|
* discover content and folder meta, build the graph, and assemble the manifest.
|
|
@@ -362,12 +387,9 @@ export const scanProject = async (
|
|
|
362
387
|
// The git pathspecs must cover where the pages actually live: each local
|
|
363
388
|
// source's own on-disk root — a filesystem source's `root`, or a staged
|
|
364
389
|
// local source's tree (an Obsidian vault) — which diverges from the global
|
|
365
|
-
// `content.root`.
|
|
366
|
-
// and can never yield dates, so those are dropped up front.
|
|
390
|
+
// `content.root`.
|
|
367
391
|
const gitRoot = gitRepositoryRoot(context.root);
|
|
368
|
-
const contentRoots = sources
|
|
369
|
-
.flatMap((source) => (source.contentRoot ? [source.contentRoot] : []))
|
|
370
|
-
.filter((dir) => gitRoot !== null && isWithin(gitRoot, dir));
|
|
392
|
+
const contentRoots = gitContentRoots(sources, gitRoot);
|
|
371
393
|
const gitTimes = gitLastModifiedTimes(
|
|
372
394
|
context.root,
|
|
373
395
|
contentRoots,
|
package/src/core/schema.ts
CHANGED
|
@@ -1303,9 +1303,9 @@ const ogConfigSchema = z.strictObject({
|
|
|
1303
1303
|
*/
|
|
1304
1304
|
enabled: z.boolean().optional(),
|
|
1305
1305
|
/**
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1306
|
+
* Fonts for the generated card, replacing the default stack (Takumi's
|
|
1307
|
+
* Latin-only built-in font plus a Noto fallback per script). Google
|
|
1308
|
+
* families are fetched from Google Fonts at build.
|
|
1309
1309
|
*/
|
|
1310
1310
|
fonts: z.array(ogFontSchema).optional(),
|
|
1311
1311
|
/**
|
|
@@ -1687,6 +1687,20 @@ const DATE_FORM_FIELDS = [
|
|
|
1687
1687
|
"day",
|
|
1688
1688
|
] as const;
|
|
1689
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Whether `Intl` knows a time zone. An unknown one (`Asia/Tokio`) throws a
|
|
1692
|
+
* RangeError the first time a date is formatted, deep in a page render, so it
|
|
1693
|
+
* is caught here where the error can point at `dateFormat.timeZone`.
|
|
1694
|
+
*/
|
|
1695
|
+
const isKnownTimeZone = (timeZone: string): boolean => {
|
|
1696
|
+
try {
|
|
1697
|
+
Intl.DateTimeFormat("en", { timeZone });
|
|
1698
|
+
return true;
|
|
1699
|
+
} catch {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1690
1704
|
const dateFormatConfigSchema = z
|
|
1691
1705
|
.strictObject({
|
|
1692
1706
|
/** Calendar system (e.g. `japanese`, `buddhist`). */
|
|
@@ -1702,7 +1716,13 @@ const dateFormatConfigSchema = z
|
|
|
1702
1716
|
/** Numbering system (e.g. `latn`, `arab`). */
|
|
1703
1717
|
numberingSystem: z.string().optional(),
|
|
1704
1718
|
/** IANA time zone (e.g. `Asia/Tokyo`). Defaults to `UTC`. */
|
|
1705
|
-
timeZone: z
|
|
1719
|
+
timeZone: z
|
|
1720
|
+
.string()
|
|
1721
|
+
.refine(isKnownTimeZone, {
|
|
1722
|
+
message:
|
|
1723
|
+
'Unknown time zone; use an IANA name like "Asia/Tokyo" or "UTC".',
|
|
1724
|
+
})
|
|
1725
|
+
.optional(),
|
|
1706
1726
|
/** Weekday representation. */
|
|
1707
1727
|
weekday: z.enum(["long", "short", "narrow"]).optional(),
|
|
1708
1728
|
/** Year representation. */
|