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/ai/tar.ts
CHANGED
|
@@ -22,42 +22,169 @@ export interface TarEntry {
|
|
|
22
22
|
path: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/** ustar `name` field capacity
|
|
25
|
+
/** ustar `name` field capacity. */
|
|
26
26
|
const NAME_MAX = 100;
|
|
27
|
+
/** ustar `prefix` field capacity; `prefix/name` reaches 255 bytes. */
|
|
28
|
+
const PREFIX_MAX = 155;
|
|
29
|
+
|
|
30
|
+
/** ustar header byte offsets nanotar leaves for us to patch. */
|
|
31
|
+
const CHECKSUM_OFFSET = 148;
|
|
32
|
+
const CHECKSUM_SIZE = 8;
|
|
33
|
+
const TYPEFLAG_OFFSET = 156;
|
|
34
|
+
const PREFIX_OFFSET = 345;
|
|
35
|
+
const BLOCK_SIZE = 512;
|
|
36
|
+
|
|
37
|
+
/** The two zero blocks that mark the end of an archive. */
|
|
38
|
+
const END_OF_ARCHIVE = 2 * BLOCK_SIZE;
|
|
39
|
+
/** tar's default record size (20 blocks), which the archive is padded to. */
|
|
40
|
+
const RECORD_SIZE = 20 * BLOCK_SIZE;
|
|
41
|
+
|
|
42
|
+
/** The PAX extended-header typeflag (`x`). */
|
|
43
|
+
const PAX_TYPEFLAG = 0x78;
|
|
27
44
|
|
|
28
45
|
const encoder = new TextEncoder();
|
|
29
46
|
|
|
47
|
+
const byteLength = (value: string): number => encoder.encode(value).byteLength;
|
|
48
|
+
|
|
49
|
+
/** `value` cut to at most `max` UTF-8 bytes, on a character boundary. */
|
|
50
|
+
const truncateBytes = (value: string, max: number): string =>
|
|
51
|
+
value.slice(0, encoder.encodeInto(value, new Uint8Array(max)).read);
|
|
52
|
+
|
|
53
|
+
/** One header nanotar writes, plus the fields it cannot. */
|
|
54
|
+
interface Block {
|
|
55
|
+
content: Uint8Array;
|
|
56
|
+
mode: string;
|
|
57
|
+
name: string;
|
|
58
|
+
/** The PAX extended header ahead of an over-long path. */
|
|
59
|
+
pax?: boolean;
|
|
60
|
+
/** ustar `prefix`, joined to `name` with a `/` by readers. */
|
|
61
|
+
prefix?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Split a path over the `name` field at a `/` so the head fits `prefix` and
|
|
66
|
+
* the tail fits `name`. The longest head that fits leaves the shortest tail,
|
|
67
|
+
* so if that tail is still too long no split works.
|
|
68
|
+
*/
|
|
69
|
+
const splitUstar = (
|
|
70
|
+
path: string
|
|
71
|
+
): { name: string; prefix: string } | undefined => {
|
|
72
|
+
let slash = path.lastIndexOf("/");
|
|
73
|
+
while (slash > 0 && byteLength(path.slice(0, slash)) > PREFIX_MAX) {
|
|
74
|
+
slash = path.lastIndexOf("/", slash - 1);
|
|
75
|
+
}
|
|
76
|
+
const name = path.slice(slash + 1);
|
|
77
|
+
if (slash <= 0 || byteLength(name) > NAME_MAX) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return { name, prefix: path.slice(0, slash) };
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A PAX `path` record: `"<length> path=<value>\n"`, where the decimal length
|
|
85
|
+
* counts the whole record including its own digits.
|
|
86
|
+
*/
|
|
87
|
+
const paxPathRecord = (path: string): Uint8Array => {
|
|
88
|
+
const body = ` path=${path}\n`;
|
|
89
|
+
const size = byteLength(body);
|
|
90
|
+
const length = size + String(size + String(size).length).length;
|
|
91
|
+
return encoder.encode(`${length}${body}`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The header blocks for one entry. A path that fits `name` is written as is;
|
|
96
|
+
* up to 255 bytes it splits across `prefix` and `name`; beyond that (or with
|
|
97
|
+
* no usable split) a PAX extended header carries the full path and the ustar
|
|
98
|
+
* `name` holds a truncated fallback for readers that ignore PAX.
|
|
99
|
+
*/
|
|
100
|
+
const entryBlocks = (entry: TarEntry): Block[] => {
|
|
101
|
+
const mode = entry.executable ? "755" : "644";
|
|
102
|
+
const file = { content: entry.content, mode };
|
|
103
|
+
if (byteLength(entry.path) <= NAME_MAX) {
|
|
104
|
+
return [{ ...file, name: entry.path }];
|
|
105
|
+
}
|
|
106
|
+
const split = splitUstar(entry.path);
|
|
107
|
+
if (split) {
|
|
108
|
+
return [{ ...file, ...split }];
|
|
109
|
+
}
|
|
110
|
+
const base = entry.path.slice(entry.path.lastIndexOf("/") + 1);
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
content: paxPathRecord(entry.path),
|
|
114
|
+
mode: "644",
|
|
115
|
+
name: truncateBytes(`PaxHeader/${base}`, NAME_MAX),
|
|
116
|
+
pax: true,
|
|
117
|
+
},
|
|
118
|
+
{ ...file, name: truncateBytes(base, NAME_MAX) },
|
|
119
|
+
];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Recompute a patched header's checksum (nanotar's octal format). */
|
|
123
|
+
const writeChecksum = (header: Uint8Array): void => {
|
|
124
|
+
header.fill(0x20, CHECKSUM_OFFSET, CHECKSUM_OFFSET + CHECKSUM_SIZE);
|
|
125
|
+
const sum = header.reduce((total, byte) => total + byte, 0);
|
|
126
|
+
header.fill(0, CHECKSUM_OFFSET, CHECKSUM_OFFSET + CHECKSUM_SIZE);
|
|
127
|
+
encoder.encodeInto(
|
|
128
|
+
sum.toString(8),
|
|
129
|
+
header.subarray(CHECKSUM_OFFSET, CHECKSUM_OFFSET + CHECKSUM_SIZE)
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
30
133
|
/**
|
|
31
134
|
* Build a gzipped ustar archive of the given files, in the given order. Paths
|
|
32
|
-
*
|
|
33
|
-
* surface a programming error — nanotar would silently truncate an oversized
|
|
34
|
-
* name into a corrupt archive, so the validation stays here.
|
|
135
|
+
* escaping the archive root throw to surface a programming error.
|
|
35
136
|
*/
|
|
36
137
|
export const buildTarGz = (entries: readonly TarEntry[]): Uint8Array => {
|
|
37
138
|
for (const entry of entries) {
|
|
38
|
-
if (encoder.encode(entry.path).byteLength > NAME_MAX) {
|
|
39
|
-
throw new Error(`tar path exceeds ${NAME_MAX} bytes: ${entry.path}`);
|
|
40
|
-
}
|
|
41
139
|
if (entry.path.startsWith("/") || entry.path.split("/").includes("..")) {
|
|
42
140
|
throw new Error(`tar path must be archive-relative: ${entry.path}`);
|
|
43
141
|
}
|
|
44
142
|
}
|
|
143
|
+
const blocks = entries.flatMap(entryBlocks);
|
|
45
144
|
const tar = createTar(
|
|
46
|
-
|
|
145
|
+
blocks.map((block) => ({
|
|
47
146
|
// Root-owned, epoch-mtime, empty owner names: every field a rebuild
|
|
48
147
|
// could vary is pinned so the archive bytes are a function of content.
|
|
49
148
|
attrs: {
|
|
50
149
|
gid: 0,
|
|
51
150
|
group: "",
|
|
52
|
-
mode:
|
|
151
|
+
mode: block.mode,
|
|
53
152
|
mtime: 0,
|
|
54
153
|
uid: 0,
|
|
55
154
|
user: "",
|
|
56
155
|
},
|
|
57
|
-
data:
|
|
58
|
-
name:
|
|
156
|
+
data: block.content,
|
|
157
|
+
name: block.name,
|
|
59
158
|
}))
|
|
60
159
|
);
|
|
160
|
+
// nanotar writes neither `prefix` nor the PAX typeflag, so patch those
|
|
161
|
+
// headers in place and re-sum them.
|
|
162
|
+
let offset = 0;
|
|
163
|
+
for (const block of blocks) {
|
|
164
|
+
const header = tar.subarray(offset, offset + BLOCK_SIZE);
|
|
165
|
+
if (block.prefix) {
|
|
166
|
+
encoder.encodeInto(
|
|
167
|
+
block.prefix,
|
|
168
|
+
header.subarray(PREFIX_OFFSET, PREFIX_OFFSET + PREFIX_MAX)
|
|
169
|
+
);
|
|
170
|
+
writeChecksum(header);
|
|
171
|
+
}
|
|
172
|
+
if (block.pax) {
|
|
173
|
+
header[TYPEFLAG_OFFSET] = PAX_TYPEFLAG;
|
|
174
|
+
writeChecksum(header);
|
|
175
|
+
}
|
|
176
|
+
offset +=
|
|
177
|
+
BLOCK_SIZE * (1 + Math.ceil(block.content.byteLength / BLOCK_SIZE));
|
|
178
|
+
}
|
|
179
|
+
// nanotar pads the entries to a whole record but writes no end-of-archive
|
|
180
|
+
// marker, so entries ending within two blocks of a record boundary leave
|
|
181
|
+
// fewer than the two zero blocks readers expect (GNU tar warns "A lone zero
|
|
182
|
+
// block"). Pad to the first record that fits both; for every other archive
|
|
183
|
+
// that is the size nanotar already wrote, so its bytes are unchanged.
|
|
184
|
+
const archive = new Uint8Array(
|
|
185
|
+
Math.ceil((offset + END_OF_ARCHIVE) / RECORD_SIZE) * RECORD_SIZE
|
|
186
|
+
);
|
|
187
|
+
archive.set(tar.subarray(0, offset));
|
|
61
188
|
// Sync gzip with a pinned level; Node writes no timestamp into the header.
|
|
62
|
-
return new Uint8Array(gzipSync(
|
|
189
|
+
return new Uint8Array(gzipSync(archive, { level: 9 }));
|
|
63
190
|
};
|
package/src/analytics/head.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { hightouchHead } from "./hightouch.ts";
|
|
|
12
12
|
import { hotjarHead } from "./hotjar.ts";
|
|
13
13
|
import { logrocketHead } from "./logrocket.ts";
|
|
14
14
|
import { mixpanelHead } from "./mixpanel.ts";
|
|
15
|
+
import { oneDollarStatsHead } from "./one-dollar-stats.ts";
|
|
15
16
|
import { pirschHead } from "./pirsch.ts";
|
|
16
17
|
import { plausibleHead } from "./plausible.ts";
|
|
17
18
|
import { posthogHead } from "./posthog.ts";
|
|
@@ -92,6 +93,9 @@ const adapterScripts = (
|
|
|
92
93
|
case "mixpanel": {
|
|
93
94
|
return mixpanelHead(adapter.options);
|
|
94
95
|
}
|
|
96
|
+
case "one-dollar-stats": {
|
|
97
|
+
return oneDollarStatsHead(adapter.options);
|
|
98
|
+
}
|
|
95
99
|
case "pirsch": {
|
|
96
100
|
return pirschHead(adapter.options);
|
|
97
101
|
}
|
package/src/analytics/index.ts
CHANGED
|
@@ -58,6 +58,11 @@ export type {
|
|
|
58
58
|
MixpanelOptions,
|
|
59
59
|
MixpanelRegion,
|
|
60
60
|
} from "./mixpanel.ts";
|
|
61
|
+
export { oneDollarStats } from "./one-dollar-stats.ts";
|
|
62
|
+
export type {
|
|
63
|
+
OneDollarStatsAdapter,
|
|
64
|
+
OneDollarStatsOptions,
|
|
65
|
+
} from "./one-dollar-stats.ts";
|
|
61
66
|
export { pirsch } from "./pirsch.ts";
|
|
62
67
|
export type { PirschAdapter, PirschOptions } from "./pirsch.ts";
|
|
63
68
|
export { plausible } from "./plausible.ts";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { AdapterDescriptor } from "../core/adapter.ts";
|
|
4
|
+
import { adapterDescriptorSchema } from "../core/adapter.ts";
|
|
5
|
+
import type { HeadScript } from "./head.ts";
|
|
6
|
+
|
|
7
|
+
/** The script OneDollarStats' install snippet loads. */
|
|
8
|
+
export const ONE_DOLLAR_STATS_SCRIPT_SRC =
|
|
9
|
+
"https://assets.onedollarstats.com/stonks.js";
|
|
10
|
+
|
|
11
|
+
/** The options {@link oneDollarStats} checks itself. */
|
|
12
|
+
export interface OneDollarStatsNamedOptions {
|
|
13
|
+
/**
|
|
14
|
+
* A bare host name, e.g. `docs.example.com`. The tracker reports every
|
|
15
|
+
* event under it on every host, not just `localhost`.
|
|
16
|
+
*/
|
|
17
|
+
hostname?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for {@link oneDollarStats}: any tracker setting, forwarded as a
|
|
22
|
+
* `data-` attribute on the tag (`hostname` → `data-hostname`, `devmode`,
|
|
23
|
+
* `url`, `autocollect`, `"hash-routing"`, …).
|
|
24
|
+
*/
|
|
25
|
+
export type OneDollarStatsOptions = OneDollarStatsNamedOptions & {
|
|
26
|
+
[setting: string]: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const oneDollarStatsOptionsSchema = z
|
|
30
|
+
.object({
|
|
31
|
+
hostname: z
|
|
32
|
+
.string()
|
|
33
|
+
.regex(/^[\p{L}\p{N}-]+(?:\.[\p{L}\p{N}-]+)*(?::\d+)?$/u, {
|
|
34
|
+
message:
|
|
35
|
+
"`hostname` must be a bare host name like `docs.example.com`, without `https://`, a path, a query, or a fragment.",
|
|
36
|
+
})
|
|
37
|
+
.optional(),
|
|
38
|
+
})
|
|
39
|
+
.catchall(z.string());
|
|
40
|
+
|
|
41
|
+
export type OneDollarStatsAdapter = AdapterDescriptor<
|
|
42
|
+
"one-dollar-stats",
|
|
43
|
+
OneDollarStatsOptions
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
export const oneDollarStatsAdapterSchema = adapterDescriptorSchema(
|
|
47
|
+
"one-dollar-stats",
|
|
48
|
+
oneDollarStatsOptionsSchema
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* OneDollarStats. Needs no keys — the dashboard matches events to a site by
|
|
53
|
+
* the domain they come from.
|
|
54
|
+
*/
|
|
55
|
+
export const oneDollarStats = (
|
|
56
|
+
options: OneDollarStatsOptions = {}
|
|
57
|
+
): OneDollarStatsAdapter => ({
|
|
58
|
+
kind: "one-dollar-stats",
|
|
59
|
+
options,
|
|
60
|
+
requiredSecrets: [],
|
|
61
|
+
runtimeDeps: [],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The deferred tag from the install instructions, with every option as its
|
|
66
|
+
* own `data-` attribute. The tracker reads them from `document.currentScript`,
|
|
67
|
+
* so the tag stays a classic external script. It hooks `pushState` and
|
|
68
|
+
* `popstate` itself, so client-router navigations need no extra hook.
|
|
69
|
+
*
|
|
70
|
+
* The tracker turns hash routing on whenever `data-hash-routing` is present,
|
|
71
|
+
* whatever its value, so `"hash-routing": "false"` leaves the attribute off.
|
|
72
|
+
*/
|
|
73
|
+
export const oneDollarStatsHead = (
|
|
74
|
+
options: OneDollarStatsOptions
|
|
75
|
+
): HeadScript[] => {
|
|
76
|
+
const attributes: HeadScript["attributes"] = {};
|
|
77
|
+
for (const [setting, value] of Object.entries(options)) {
|
|
78
|
+
if (setting === "hash-routing" && value === "false") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
attributes[`data-${setting}`] = value;
|
|
82
|
+
}
|
|
83
|
+
attributes.defer = true;
|
|
84
|
+
attributes.src = ONE_DOLLAR_STATS_SCRIPT_SRC;
|
|
85
|
+
return [{ attributes, content: null }];
|
|
86
|
+
};
|
package/src/analytics/schema.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { hightouchAdapterSchema } from "./hightouch.ts";
|
|
|
14
14
|
import { hotjarAdapterSchema } from "./hotjar.ts";
|
|
15
15
|
import { logrocketAdapterSchema } from "./logrocket.ts";
|
|
16
16
|
import { mixpanelAdapterSchema } from "./mixpanel.ts";
|
|
17
|
+
import { oneDollarStatsAdapterSchema } from "./one-dollar-stats.ts";
|
|
17
18
|
import { pirschAdapterSchema } from "./pirsch.ts";
|
|
18
19
|
import { plausibleAdapterSchema } from "./plausible.ts";
|
|
19
20
|
import { posthogAdapterSchema } from "./posthog.ts";
|
|
@@ -37,6 +38,7 @@ export const analyticsAdapterSchema = z.discriminatedUnion("kind", [
|
|
|
37
38
|
hotjarAdapterSchema,
|
|
38
39
|
logrocketAdapterSchema,
|
|
39
40
|
mixpanelAdapterSchema,
|
|
41
|
+
oneDollarStatsAdapterSchema,
|
|
40
42
|
pirschAdapterSchema,
|
|
41
43
|
plausibleAdapterSchema,
|
|
42
44
|
posthogAdapterSchema,
|
package/src/astro/generate.ts
CHANGED
|
@@ -1887,7 +1887,7 @@ export const generateRuntime = async (
|
|
|
1887
1887
|
// `astro.config.mjs` — which would restart the dev server in place.
|
|
1888
1888
|
const contentRoutes = markdownRoutePaths(project);
|
|
1889
1889
|
const homeLinkHeader =
|
|
1890
|
-
buildHomeLinkHeader(config, contentRoutes) ?? undefined;
|
|
1890
|
+
buildHomeLinkHeader(config, contentRoutes, "dev") ?? undefined;
|
|
1891
1891
|
|
|
1892
1892
|
const assistantEnabled = config.ai.assistant?.enabled ?? false;
|
|
1893
1893
|
const exportPdf = config.export.pdf;
|
package/src/astro/include-hmr.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
|
|
3
|
+
import { resolve } from "pathe";
|
|
4
|
+
|
|
3
5
|
import { refreshBlumeContent } from "./integration.ts";
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -18,12 +20,21 @@ import { refreshBlumeContent } from "./integration.ts";
|
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
/** The Vite module-graph slice the plugin touches (structurally typed, like
|
|
21
|
-
* every Blume-authored Vite plugin — see `serverAppResolvePlugin`).
|
|
23
|
+
* every Blume-authored Vite plugin — see `serverAppResolvePlugin`). Every
|
|
24
|
+
* environment keeps its own graph, and the server's own `moduleGraph` only
|
|
25
|
+
* covers `client` and `ssr`: with `@astrojs/cloudflare` the content pages
|
|
26
|
+
* render in the `prerender` environment instead. */
|
|
22
27
|
interface IncludeHmrServer {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
config: { root: string };
|
|
29
|
+
environments: Record<
|
|
30
|
+
string,
|
|
31
|
+
{
|
|
32
|
+
moduleGraph: {
|
|
33
|
+
getModulesByFile: (file: string) => Set<unknown> | undefined;
|
|
34
|
+
invalidateModule: (mod: never) => void;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
>;
|
|
27
38
|
ws: { send: (payload: { type: "full-reload" }) => void };
|
|
28
39
|
}
|
|
29
40
|
|
|
@@ -51,16 +62,24 @@ export const includeHmrPlugin = (graphPath: string): IncludeHmrPlugin => ({
|
|
|
51
62
|
// No graph yet (first run) — nothing to invalidate.
|
|
52
63
|
return;
|
|
53
64
|
}
|
|
54
|
-
|
|
65
|
+
// An ejected app's graph is relative to the project, its Vite root; the
|
|
66
|
+
// hidden runtime's is absolute, which resolving leaves as it is.
|
|
67
|
+
const { config, environments, ws } = ctx.server;
|
|
68
|
+
const includers = Object.entries(graph).find(
|
|
69
|
+
([partial]) => resolve(config.root, partial) === ctx.file
|
|
70
|
+
)?.[1];
|
|
55
71
|
if (!includers || includers.length === 0) {
|
|
56
72
|
return;
|
|
57
73
|
}
|
|
58
|
-
const { moduleGraph
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
for (const { moduleGraph } of Object.values(environments)) {
|
|
75
|
+
for (const includer of includers) {
|
|
76
|
+
for (const mod of moduleGraph.getModulesByFile(
|
|
77
|
+
resolve(config.root, includer)
|
|
78
|
+
) ?? []) {
|
|
79
|
+
// SAFETY: the module came out of this module graph; `never` only
|
|
80
|
+
// reflects that the structural slice doesn't model the node type.
|
|
81
|
+
moduleGraph.invalidateModule(mod as never);
|
|
82
|
+
}
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
85
|
// Plain `.md` pages have no Vite module: their HTML lives in the
|
|
@@ -29,7 +29,10 @@ type StoreEntry = ReturnType<LoaderContext["store"]["entries"]>[number][1];
|
|
|
29
29
|
/** Meta-store key holding the recorded partials digest of an includer. */
|
|
30
30
|
const stateKey = (id: string): string => `blume:include-state:${id}`;
|
|
31
31
|
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The generated include graph: partial → including pages, absolute in the
|
|
34
|
+
* hidden runtime and relative to the project root in an ejected app.
|
|
35
|
+
*/
|
|
33
36
|
type IncludeGraph = Record<string, string[]>;
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -47,14 +50,22 @@ const readGraph = (graphPath: string): IncludeGraph => {
|
|
|
47
50
|
}
|
|
48
51
|
};
|
|
49
52
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Invert the graph: absolute including page → its absolute partials, sorted.
|
|
55
|
+
* Each path resolves against the Astro root, which leaves the hidden runtime's
|
|
56
|
+
* absolute paths as they are.
|
|
57
|
+
*/
|
|
58
|
+
const partialsByPage = (
|
|
59
|
+
graph: IncludeGraph,
|
|
60
|
+
root: string
|
|
61
|
+
): Map<string, string[]> => {
|
|
52
62
|
const byPage = new Map<string, string[]>();
|
|
53
63
|
for (const [partial, pages] of Object.entries(graph)) {
|
|
54
64
|
for (const page of pages) {
|
|
55
|
-
const
|
|
56
|
-
partials.
|
|
57
|
-
|
|
65
|
+
const abs = resolve(root, page);
|
|
66
|
+
const partials = byPage.get(abs) ?? [];
|
|
67
|
+
partials.push(resolve(root, partial));
|
|
68
|
+
byPage.set(abs, partials);
|
|
58
69
|
}
|
|
59
70
|
}
|
|
60
71
|
for (const partials of byPage.values()) {
|
|
@@ -123,11 +134,11 @@ export const evictStaleIncluders = (
|
|
|
123
134
|
context: LoaderContext,
|
|
124
135
|
graphPath: string
|
|
125
136
|
): string[] => {
|
|
126
|
-
const
|
|
137
|
+
const root = fileURLToPath(context.config.root);
|
|
138
|
+
const byPage = partialsByPage(readGraph(graphPath), root);
|
|
127
139
|
if (byPage.size === 0) {
|
|
128
140
|
return [];
|
|
129
141
|
}
|
|
130
|
-
const root = fileURLToPath(context.config.root);
|
|
131
142
|
const evicted: string[] = [];
|
|
132
143
|
for (const [id, entry] of context.store.entries()) {
|
|
133
144
|
if (evictIfStale(context, byPage, root, id, entry)) {
|
package/src/astro/integration.ts
CHANGED
|
@@ -28,13 +28,40 @@ interface OverlayErrorPayload {
|
|
|
28
28
|
|
|
29
29
|
/** The dev server's HMR channel — either `.ws` (Vite ≤5) or `.hot` (Vite 6+). */
|
|
30
30
|
interface OverlayChannel {
|
|
31
|
-
send: (payload: OverlayErrorPayload) => void;
|
|
31
|
+
send: (payload: OverlayErrorPayload | { type: "full-reload" }) => void;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
/** A node in a Vite environment's module graph; only its identity matters. */
|
|
35
|
+
interface DevModuleNode {
|
|
36
|
+
id: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The dev-server slice the integration keeps: the overlay channel, and every
|
|
41
|
+
* Vite environment's module graph for the content re-sync (structurally
|
|
42
|
+
* typed, like every Blume-authored Vite plugin).
|
|
43
|
+
*/
|
|
44
|
+
interface DevServer {
|
|
45
|
+
environments: Record<
|
|
46
|
+
string,
|
|
47
|
+
{
|
|
48
|
+
moduleGraph: {
|
|
49
|
+
getModuleById: (id: string) => DevModuleNode | undefined;
|
|
50
|
+
invalidateModule: (mod: never) => void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
>;
|
|
34
54
|
hot?: OverlayChannel;
|
|
35
55
|
ws?: OverlayChannel;
|
|
36
56
|
}
|
|
37
57
|
|
|
58
|
+
/** The dev server's file watcher, as far as the data-store watch uses it. */
|
|
59
|
+
interface WatchedServer {
|
|
60
|
+
watcher: {
|
|
61
|
+
on: (event: "add" | "change", listener: (path: string) => void) => void;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
/** The parameters Astro hands `astro:server:setup`. */
|
|
39
66
|
type ServerSetupParams = Parameters<
|
|
40
67
|
NonNullable<AstroIntegration["hooks"]["astro:server:setup"]>
|
|
@@ -74,7 +101,7 @@ interface DevNegotiation {
|
|
|
74
101
|
interface DevServerRegistry {
|
|
75
102
|
buildProject: BlumeProject | null;
|
|
76
103
|
negotiation: DevNegotiation | null;
|
|
77
|
-
overlay:
|
|
104
|
+
overlay: DevServer | null;
|
|
78
105
|
refreshContent: RefreshContent | null;
|
|
79
106
|
}
|
|
80
107
|
|
|
@@ -155,6 +182,64 @@ export const publishDevNegotiation = (
|
|
|
155
182
|
: null;
|
|
156
183
|
};
|
|
157
184
|
|
|
185
|
+
/** A dev server's HMR channel: `.ws`, or `.hot` where only that exists. */
|
|
186
|
+
const overlayChannelOf = (
|
|
187
|
+
server: DevServer | null
|
|
188
|
+
): OverlayChannel | undefined => server?.ws ?? server?.hot;
|
|
189
|
+
|
|
190
|
+
/** Astro's content-layer data store, a virtual module in each environment. */
|
|
191
|
+
const DATA_STORE_MODULE_ID = "\0astro:data-layer-content";
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Invalidate Astro's data store in every Vite environment, then reload the
|
|
195
|
+
* browser. Astro invalidates it after a sync in the `ssr` environment only,
|
|
196
|
+
* which is where pages render on most adapters. With `@astrojs/cloudflare`
|
|
197
|
+
* the `ssr` environment runs in workerd and the prerendered pages (every
|
|
198
|
+
* content page) render in the `prerender` environment, whose copy of the
|
|
199
|
+
* store then stayed at its startup snapshot: a page added in dev resolved
|
|
200
|
+
* its route but `getEntry` still missed it, so it 404ed until a restart.
|
|
201
|
+
*/
|
|
202
|
+
const invalidateDataStore = (server: DevServer): void => {
|
|
203
|
+
for (const { moduleGraph } of Object.values(server.environments)) {
|
|
204
|
+
const mod = moduleGraph.getModuleById(DATA_STORE_MODULE_ID);
|
|
205
|
+
if (mod) {
|
|
206
|
+
// SAFETY: the node came out of this same module graph; `never` only
|
|
207
|
+
// reflects that the structural slice doesn't model the node type.
|
|
208
|
+
moduleGraph.invalidateModule(mod as never);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Astro reloaded the browser when the store was written, which can land
|
|
212
|
+
// before the invalidation above; reload again so no page renders the
|
|
213
|
+
// stale copy.
|
|
214
|
+
overlayChannelOf(server)?.send({ type: "full-reload" });
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Where Astro keeps the dev content store: `data-store.json` in the root's
|
|
219
|
+
* `.astro/` directory. Astro reloads its virtual module from this file.
|
|
220
|
+
*/
|
|
221
|
+
const devDataStoreFile = (root: URL): string =>
|
|
222
|
+
fileURLToPath(new URL(".astro/data-store.json", root));
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Invalidate the data store in every environment whenever Astro rewrites it.
|
|
226
|
+
* Astro's own file watcher updates the store on a content edit — a `.md`
|
|
227
|
+
* page's rendered body lives in it — but invalidates the module in `ssr`
|
|
228
|
+
* only, so with `@astrojs/cloudflare` a body edit stayed stale in
|
|
229
|
+
* `prerender` until something structural re-synced it. Keyed on the store
|
|
230
|
+
* write rather than Blume's regeneration, which can finish first.
|
|
231
|
+
*/
|
|
232
|
+
const watchDataStore = (server: DevServer & WatchedServer, root: URL): void => {
|
|
233
|
+
const file = devDataStoreFile(root);
|
|
234
|
+
const onWrite = (path: string): void => {
|
|
235
|
+
if (path === file) {
|
|
236
|
+
invalidateDataStore(server);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
server.watcher.on("add", onWrite);
|
|
240
|
+
server.watcher.on("change", onWrite);
|
|
241
|
+
};
|
|
242
|
+
|
|
158
243
|
/**
|
|
159
244
|
* Re-run Astro's content-layer loaders against the live dev server. Returns
|
|
160
245
|
* `false` when no server has registered one — before the first
|
|
@@ -165,20 +250,21 @@ export const publishDevNegotiation = (
|
|
|
165
250
|
* stale store and the moved page 404s.
|
|
166
251
|
*/
|
|
167
252
|
export const refreshBlumeContent = async (): Promise<boolean> => {
|
|
168
|
-
const { refreshContent } = registry();
|
|
253
|
+
const { overlay, refreshContent } = registry();
|
|
169
254
|
if (!refreshContent) {
|
|
170
255
|
return false;
|
|
171
256
|
}
|
|
172
257
|
// No loader filter: every collection re-syncs (the docs glob and any
|
|
173
258
|
// staged collection alike).
|
|
174
259
|
await refreshContent({});
|
|
260
|
+
if (overlay) {
|
|
261
|
+
invalidateDataStore(overlay);
|
|
262
|
+
}
|
|
175
263
|
return true;
|
|
176
264
|
};
|
|
177
265
|
|
|
178
|
-
const overlayChannel = (): OverlayChannel | undefined =>
|
|
179
|
-
|
|
180
|
-
return overlay?.ws ?? overlay?.hot;
|
|
181
|
-
};
|
|
266
|
+
const overlayChannel = (): OverlayChannel | undefined =>
|
|
267
|
+
overlayChannelOf(registry().overlay);
|
|
182
268
|
|
|
183
269
|
/**
|
|
184
270
|
* Surface Blume's own diagnostics (config/frontmatter/content errors) in the
|
|
@@ -237,8 +323,9 @@ export interface BlumeIntegrationOptions {
|
|
|
237
323
|
/**
|
|
238
324
|
* Homepage `Link` header value for agent discovery (see
|
|
239
325
|
* `ai/link-headers.ts`); the dev-server counterpart of the `_headers` /
|
|
240
|
-
* Vercel-config emission, so `curl -I` against `blume dev` shows
|
|
241
|
-
*
|
|
326
|
+
* Vercel-config emission, so `curl -I` against `blume dev` shows it too —
|
|
327
|
+
* built for the `"dev"` surface, which leaves out the targets only a build
|
|
328
|
+
* writes. Published the same way as `contentRoutes`.
|
|
242
329
|
*/
|
|
243
330
|
homeLinkHeader?: string;
|
|
244
331
|
/**
|
|
@@ -279,7 +366,8 @@ const isHomeUrl = (rawUrl: string | undefined): boolean => {
|
|
|
279
366
|
* variant falls back to the synthesized llms.txt mirror when it's a landing
|
|
280
367
|
* page (see `markdownRoutePaths`). The same
|
|
281
368
|
* middleware also stamps the homepage agent-discovery `Link` header, mirroring
|
|
282
|
-
* what the deployed site sends via `_headers` / the Vercel routing config
|
|
369
|
+
* what the deployed site sends via `_headers` / the Vercel routing config
|
|
370
|
+
* minus the targets the dev server doesn't serve.
|
|
283
371
|
*
|
|
284
372
|
* Request URLs arrive base-less: Astro unshifts its own dev middlewares (base,
|
|
285
373
|
* trailing slash, route guard) ahead of this one from its post-`configureServer`
|
|
@@ -351,9 +439,11 @@ export const blumeIntegration = (
|
|
|
351
439
|
return;
|
|
352
440
|
}
|
|
353
441
|
// `dir` is what Astro reports as the client output — `dist/`, or
|
|
354
|
-
// `dist/client` for a server build
|
|
355
|
-
//
|
|
356
|
-
//
|
|
442
|
+
// `dist/client` for a server build (`dist/client/<base>/` once
|
|
443
|
+
// `@astrojs/cloudflare` moves it under the base, whose `_headers` the
|
|
444
|
+
// writer places at the root served above it) — which is what the
|
|
445
|
+
// platform serves (the Vercel adapter copies it into its Build Output
|
|
446
|
+
// static tree in a later hook).
|
|
357
447
|
await publishBuildArtifacts(project, fileURLToPath(dir), logger);
|
|
358
448
|
},
|
|
359
449
|
"astro:config:done": ({ config, injectTypes }) => {
|
|
@@ -399,6 +489,10 @@ export const blumeIntegration = (
|
|
|
399
489
|
const shared = registry();
|
|
400
490
|
shared.overlay = server;
|
|
401
491
|
shared.refreshContent = refreshContent ?? null;
|
|
492
|
+
// `astro:config:done` always runs first; the root locates the store.
|
|
493
|
+
if (astroRoot) {
|
|
494
|
+
watchDataStore(server, astroRoot);
|
|
495
|
+
}
|
|
402
496
|
// Prepend so the rewrite happens before Astro's own request handler,
|
|
403
497
|
// letting the rewritten URL resolve to the `.md` endpoint.
|
|
404
498
|
server.middlewares.stack.unshift({
|