blume 1.6.4 → 1.6.6
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 +41 -0
- package/bin/blume.mjs +3 -2
- package/dist/cli/chunk-0ewz4trd.js +679 -0
- package/dist/cli/chunk-0ewz4trd.js.map +15 -0
- package/dist/cli/chunk-27gtm2ym.js +69 -0
- package/dist/cli/chunk-27gtm2ym.js.map +11 -0
- package/dist/cli/chunk-2aj8ddew.js +72 -0
- package/dist/cli/chunk-2aj8ddew.js.map +10 -0
- package/dist/cli/chunk-3k0kzs6d.js +69 -0
- package/dist/cli/chunk-3k0kzs6d.js.map +11 -0
- package/dist/cli/chunk-3r94j3tc.js +221 -0
- package/dist/cli/chunk-3r94j3tc.js.map +10 -0
- package/dist/cli/chunk-4trphnvy.js +102 -0
- package/dist/cli/chunk-4trphnvy.js.map +11 -0
- package/dist/cli/chunk-4xyggvgf.js +21 -0
- package/dist/cli/chunk-4xyggvgf.js.map +10 -0
- package/dist/cli/chunk-5hs6gb7n.js +32 -0
- package/dist/cli/chunk-5hs6gb7n.js.map +10 -0
- package/dist/cli/chunk-5yvt556e.js +185 -0
- package/dist/cli/chunk-5yvt556e.js.map +11 -0
- package/dist/cli/chunk-62qsssnh.js +3808 -0
- package/dist/cli/chunk-62qsssnh.js.map +36 -0
- package/dist/cli/chunk-6kzzpsx8.js +26 -0
- package/dist/cli/chunk-6kzzpsx8.js.map +10 -0
- package/dist/cli/chunk-8gnpdsn1.js +952 -0
- package/dist/cli/chunk-8gnpdsn1.js.map +12 -0
- package/dist/cli/chunk-9sh49q0h.js +30 -0
- package/dist/cli/chunk-9sh49q0h.js.map +10 -0
- package/dist/cli/chunk-aerwpe14.js +2370 -0
- package/dist/cli/chunk-aerwpe14.js.map +15 -0
- package/dist/cli/chunk-ag1zyr5x.js +176 -0
- package/dist/cli/chunk-ag1zyr5x.js.map +10 -0
- package/dist/cli/chunk-bawgnt8x.js +277 -0
- package/dist/cli/chunk-bawgnt8x.js.map +11 -0
- package/dist/cli/chunk-bcy492zc.js +16 -0
- package/dist/cli/chunk-bcy492zc.js.map +10 -0
- package/dist/cli/chunk-btfr9yvw.js +41 -0
- package/dist/cli/chunk-btfr9yvw.js.map +10 -0
- package/dist/cli/chunk-cbjnx4s8.js +73 -0
- package/dist/cli/chunk-cbjnx4s8.js.map +10 -0
- package/dist/cli/chunk-cnvm6k3e.js +96 -0
- package/dist/cli/chunk-cnvm6k3e.js.map +10 -0
- package/dist/cli/chunk-etsqspj6.js +5170 -0
- package/dist/cli/chunk-etsqspj6.js.map +47 -0
- package/dist/cli/chunk-ev67ycx0.js +15 -0
- package/dist/cli/chunk-ev67ycx0.js.map +10 -0
- package/dist/cli/chunk-ey89bjj1.js +209 -0
- package/dist/cli/chunk-ey89bjj1.js.map +11 -0
- package/dist/cli/chunk-f75cqye8.js +76 -0
- package/dist/cli/chunk-f75cqye8.js.map +10 -0
- package/dist/cli/chunk-j00ezcg5.js +259 -0
- package/dist/cli/chunk-j00ezcg5.js.map +11 -0
- package/dist/cli/chunk-jtb45atp.js +467 -0
- package/dist/cli/chunk-jtb45atp.js.map +14 -0
- package/dist/cli/chunk-m3p3wahd.js +117 -0
- package/dist/cli/chunk-m3p3wahd.js.map +10 -0
- package/dist/cli/chunk-n0y172hf.js +387 -0
- package/dist/cli/chunk-n0y172hf.js.map +12 -0
- package/dist/cli/chunk-n4qjabmt.js +1062 -0
- package/dist/cli/chunk-n4qjabmt.js.map +25 -0
- package/dist/cli/chunk-nyqzjdhj.js +111 -0
- package/dist/cli/chunk-nyqzjdhj.js.map +11 -0
- package/dist/cli/chunk-pxj10x8y.js +35 -0
- package/dist/cli/chunk-pxj10x8y.js.map +10 -0
- package/dist/cli/chunk-s4jn7f1q.js +54 -0
- package/dist/cli/chunk-s4jn7f1q.js.map +10 -0
- package/dist/cli/chunk-s4k1pnvf.js +81 -0
- package/dist/cli/chunk-s4k1pnvf.js.map +10 -0
- package/dist/cli/chunk-s5e5jt53.js +227 -0
- package/dist/cli/chunk-s5e5jt53.js.map +11 -0
- package/dist/cli/chunk-sbdqrjbb.js +81 -0
- package/dist/cli/chunk-sbdqrjbb.js.map +10 -0
- package/dist/cli/chunk-tc89yh2r.js +136 -0
- package/dist/cli/chunk-tc89yh2r.js.map +10 -0
- package/dist/cli/chunk-vt8fgygt.js +23 -0
- package/dist/cli/chunk-vt8fgygt.js.map +10 -0
- package/dist/cli/chunk-vv237fp3.js +1002 -0
- package/dist/cli/chunk-vv237fp3.js.map +13 -0
- package/dist/cli/chunk-vv3f8mb6.js +5314 -0
- package/dist/cli/chunk-vv3f8mb6.js.map +58 -0
- package/dist/cli/chunk-vxv4x1n8.js +17 -0
- package/dist/cli/chunk-vxv4x1n8.js.map +10 -0
- package/dist/cli/chunk-wb067mv3.js +758 -0
- package/dist/cli/chunk-wb067mv3.js.map +13 -0
- package/dist/cli/chunk-wd27zjcz.js +60 -0
- package/dist/cli/chunk-wd27zjcz.js.map +10 -0
- package/dist/cli/chunk-wkq5tbtq.js +1141 -0
- package/dist/cli/chunk-wkq5tbtq.js.map +19 -0
- package/dist/cli/chunk-x1vrdjyk.js +1967 -0
- package/dist/cli/chunk-x1vrdjyk.js.map +34 -0
- package/dist/cli/chunk-x66c5yjn.js +23 -0
- package/dist/cli/chunk-x66c5yjn.js.map +10 -0
- package/dist/cli/index.js +55 -27587
- package/dist/cli/index.js.map +5 -243
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/core/code-fences.d.ts +11 -0
- package/dist/types/core/config-input.d.ts +10 -0
- package/dist/types/core/package-root.d.ts +1 -1
- package/dist/types/core/schema.d.ts +74 -1
- package/docs/02-deployment.mdx +1 -1
- package/docs/configuration/analytics.mdx +21 -2
- package/docs/configuration/ask-ai.mdx +1 -1
- package/docs/configuration/customization.mdx +2 -9
- package/docs/content/syntax.mdx +1 -1
- package/docs/reference/cli.mdx +1 -1
- package/package.json +16 -14
- package/src/ai/api/handlers.ts +4 -7
- package/src/ai/api/paths.ts +8 -0
- package/src/ai/api/spec.ts +2 -1
- package/src/ai/ask-context.ts +378 -22
- package/src/astro/generate.ts +25 -29
- package/src/astro/include-hmr.ts +10 -13
- package/src/astro/include-refresh.ts +0 -0
- package/src/astro/index.ts +6 -1
- package/src/astro/integration.ts +269 -53
- package/src/astro/module-types.ts +74 -0
- package/src/astro/templates.ts +85 -97
- package/src/audit/image-size.ts +10 -8
- package/src/cli/command-meta.ts +77 -0
- package/src/cli/commands/add.ts +2 -4
- package/src/cli/commands/audit.ts +2 -4
- package/src/cli/commands/build.ts +42 -346
- package/src/cli/commands/check.ts +2 -4
- package/src/cli/commands/dev.ts +31 -42
- package/src/cli/commands/doctor.ts +2 -4
- package/src/cli/commands/eject.ts +3 -41
- package/src/cli/commands/eval.ts +2 -5
- package/src/cli/commands/init.ts +2 -4
- package/src/cli/commands/mcp-stdio.ts +2 -5
- package/src/cli/commands/preview.ts +3 -5
- package/src/cli/commands/sync.ts +2 -4
- package/src/cli/commands/translate.ts +2 -5
- package/src/cli/commands/validate.ts +2 -4
- package/src/cli/commands/version.ts +2 -4
- package/src/cli/eject-scripts.ts +0 -45
- package/src/cli/host-args.ts +16 -0
- package/src/cli/index.ts +84 -35
- package/src/cli/lazy-command.ts +47 -0
- package/src/components/content/GithubInfo.astro +4 -1
- package/src/components/content/mermaid-element.ts +8 -0
- package/src/components/layout/Analytics.astro +20 -1
- package/src/components/layout/PageLayout.astro +14 -3
- package/src/components/layout/ReferenceLayout.astro +15 -4
- package/src/components/layout/RootLayout.astro +15 -4
- package/src/components/layout/analytics-client.ts +2 -1
- package/src/components/layout/page-locale.ts +29 -0
- package/src/components/openapi/AsyncApiOperation.astro +5 -3
- package/src/components/openapi/Authorization.astro +4 -6
- package/src/components/openapi/Bindings.astro +2 -2
- package/src/components/openapi/Description.astro +109 -0
- package/src/components/openapi/GraphqlFieldsTable.astro +5 -7
- package/src/components/openapi/GraphqlOperation.astro +4 -3
- package/src/components/openapi/GraphqlType.astro +4 -6
- package/src/components/openapi/ParametersTable.astro +5 -7
- package/src/components/openapi/RequestBody.astro +2 -4
- package/src/components/openapi/Responses.astro +4 -3
- package/src/components/openapi/SchemaProperty.astro +11 -6
- package/src/components/openapi/description.ts +91 -0
- package/src/core/api-name.ts +18 -0
- package/src/core/code-fences.ts +48 -0
- package/src/core/config-input.ts +10 -0
- package/src/core/content-assets.ts +3 -7
- package/src/core/includes.ts +3 -7
- package/src/core/package-root.ts +1 -1
- package/src/core/schema.ts +27 -0
- package/src/core/sources/normalize.ts +2 -37
- package/src/core/sources/obsidian.ts +3 -2
- package/src/core/svg-dimensions.ts +97 -0
- package/src/core/version-cut.ts +2 -2
- package/src/deploy/artifacts.ts +370 -0
- package/src/deploy/cloudflare-negotiation.ts +97 -32
- package/src/deploy/function-bundle.ts +66 -20
- package/src/deploy/sitemap.ts +6 -0
- package/src/deploy/vercel-negotiation.ts +8 -30
- package/src/og/card.ts +6 -12
- package/src/openapi/render-mdx.ts +9 -5
- package/src/registry/eject.ts +0 -2
- package/src/theme/entry.ts +9 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a name already reads as an API's name: it contains `API` or `APIs`
|
|
3
|
+
* as a whole word, anywhere. Word-bounded on both sides, so "OpenAPI" and
|
|
4
|
+
* "AsyncAPI" are product names rather than API names and still take the
|
|
5
|
+
* suffix; unanchored at the end, so "Petstore API v2" is left alone too.
|
|
6
|
+
*/
|
|
7
|
+
const NAMES_AN_API = /\bAPIs?\b/iu;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `name` as the phrase "the ___" in generated prose — the spec title or
|
|
11
|
+
* reference label, followed by ` API` unless it already says so. Keeps
|
|
12
|
+
* "Petstore" reading as "Petstore API" without turning "Petstore API" into
|
|
13
|
+
* "Petstore API API".
|
|
14
|
+
*/
|
|
15
|
+
export const apiNamePhrase = (name: string): string => {
|
|
16
|
+
const trimmed = name.trim();
|
|
17
|
+
return NAMES_AN_API.test(trimmed) ? trimmed : `${trimmed} API`;
|
|
18
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fenced-code state for line scanners. Kept free of imports so request-time
|
|
3
|
+
* code (the Ask AI grounding excerpts) can share it with the build-time source
|
|
4
|
+
* scanners without pulling the filesystem and schema modules along.
|
|
5
|
+
*
|
|
6
|
+
* CommonMark allows backtick *and* tilde fences, three or more characters
|
|
7
|
+
* long. The scanners track which delimiter opened the current fence and how
|
|
8
|
+
* long its run was (`null` when outside one), so a ``` line inside a ~~~
|
|
9
|
+
* block — or inside a ````-delimited block (the wrapper `codeBlockLines`
|
|
10
|
+
* emits around code that contains its own ``` fence) — is content, not a
|
|
11
|
+
* toggle.
|
|
12
|
+
*/
|
|
13
|
+
const CODE_FENCE = /^(?<run>`{3,}|~{3,})/u;
|
|
14
|
+
|
|
15
|
+
/** The open fence's delimiter char and run length, or null outside one. */
|
|
16
|
+
export type FenceState = { delimiter: "`" | "~"; length: number } | null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Advance the fenced-code state for one line: an opening fence records its
|
|
20
|
+
* delimiter and run length, only a bare run of the same character at least as
|
|
21
|
+
* long closes it (CommonMark), and any other line leaves the state untouched.
|
|
22
|
+
*/
|
|
23
|
+
export const nextFenceState = (line: string, fence: FenceState): FenceState => {
|
|
24
|
+
const trimmed = line.trimStart();
|
|
25
|
+
const run = trimmed.match(CODE_FENCE)?.groups?.run;
|
|
26
|
+
if (run === undefined) {
|
|
27
|
+
return fence;
|
|
28
|
+
}
|
|
29
|
+
const rest = trimmed.slice(run.length);
|
|
30
|
+
const delimiter = run.startsWith("`") ? ("`" as const) : ("~" as const);
|
|
31
|
+
if (fence === null) {
|
|
32
|
+
// A backtick fence's info string cannot itself contain a backtick
|
|
33
|
+
// (CommonMark) — a line-leading ```inline``` span is a paragraph, and
|
|
34
|
+
// opening a phantom fence on it would swallow every heading and link
|
|
35
|
+
// after it. Tilde fences carry no such rule.
|
|
36
|
+
if (delimiter === "`" && rest.includes("`")) {
|
|
37
|
+
return fence;
|
|
38
|
+
}
|
|
39
|
+
return { delimiter, length: run.length };
|
|
40
|
+
}
|
|
41
|
+
// A closing fence may be followed only by whitespace: a same-length
|
|
42
|
+
// ```js line inside an open ``` block is content, not a closer.
|
|
43
|
+
return fence.delimiter === delimiter &&
|
|
44
|
+
run.length >= fence.length &&
|
|
45
|
+
rest.trim() === ""
|
|
46
|
+
? null
|
|
47
|
+
: fence;
|
|
48
|
+
};
|
package/src/core/config-input.ts
CHANGED
|
@@ -895,6 +895,16 @@ export interface AnalyticsScript {
|
|
|
895
895
|
|
|
896
896
|
/** Analytics providers. Configure one, several, or none. */
|
|
897
897
|
export interface AnalyticsConfig {
|
|
898
|
+
/**
|
|
899
|
+
* Cloudflare Web Analytics, for a site Cloudflare doesn't proxy (manual
|
|
900
|
+
* setup). Not needed on a proxied zone with automatic RUM enabled — that
|
|
901
|
+
* injects the beacon at the edge, and configuring it here too would count
|
|
902
|
+
* every pageview twice.
|
|
903
|
+
*/
|
|
904
|
+
cloudflare?: {
|
|
905
|
+
/** Site token from the Web Analytics JS snippet (`data-cf-beacon`). */
|
|
906
|
+
token: string;
|
|
907
|
+
};
|
|
898
908
|
/** PostHog product analytics. */
|
|
899
909
|
posthog?: {
|
|
900
910
|
/** API host (for self-hosted / EU). Defaults to PostHog cloud. */
|
|
@@ -11,14 +11,10 @@ import {
|
|
|
11
11
|
} from "pathe";
|
|
12
12
|
|
|
13
13
|
import { normalizeBasePath } from "./base-path.ts";
|
|
14
|
+
import { nextFenceState } from "./code-fences.ts";
|
|
15
|
+
import type { FenceState } from "./code-fences.ts";
|
|
14
16
|
import { hashText } from "./sources/cache.ts";
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
INLINE_CODE,
|
|
18
|
-
MD_IMAGE,
|
|
19
|
-
nextFenceState,
|
|
20
|
-
targetOffsetIn,
|
|
21
|
-
} from "./sources/normalize.ts";
|
|
17
|
+
import { INLINE_CODE, MD_IMAGE, targetOffsetIn } from "./sources/normalize.ts";
|
|
22
18
|
import { readExpandedEntryText } from "./sources/read.ts";
|
|
23
19
|
import type { ContentSource } from "./sources/types.ts";
|
|
24
20
|
import type { PageRecord } from "./types.ts";
|
package/src/core/includes.ts
CHANGED
|
@@ -2,14 +2,10 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import { dirname, extname, join, relative, resolve } from "pathe";
|
|
4
4
|
|
|
5
|
+
import { nextFenceState } from "./code-fences.ts";
|
|
6
|
+
import type { FenceState } from "./code-fences.ts";
|
|
5
7
|
import matter from "./frontmatter.ts";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
INLINE_CODE,
|
|
9
|
-
MD_IMAGE,
|
|
10
|
-
nextFenceState,
|
|
11
|
-
targetOffsetIn,
|
|
12
|
-
} from "./sources/normalize.ts";
|
|
8
|
+
import { INLINE_CODE, MD_IMAGE, targetOffsetIn } from "./sources/normalize.ts";
|
|
13
9
|
import type { Diagnostic } from "./types.ts";
|
|
14
10
|
|
|
15
11
|
/**
|
package/src/core/package-root.ts
CHANGED
|
@@ -28,7 +28,7 @@ let cached: string | undefined;
|
|
|
28
28
|
* Anchoring here — rather than at a fixed offset from `import.meta` — keeps the
|
|
29
29
|
* package's own `src/`, assets, and `node_modules` locatable whether the code
|
|
30
30
|
* runs from source under Bun (`src/...`) or from the published, bundled CLI
|
|
31
|
-
* (`dist/cli
|
|
31
|
+
* (`dist/cli/*.js`). The two layouts sit at different depths, so a relative
|
|
32
32
|
* `../..` resolves to different places; locating `package.json` does not.
|
|
33
33
|
*/
|
|
34
34
|
export const packageRoot = (): string => {
|
package/src/core/schema.ts
CHANGED
|
@@ -176,6 +176,25 @@ export const pageMetaSchema = pageMetaBaseSchema;
|
|
|
176
176
|
export type PageMeta = z.infer<typeof pageMetaBaseSchema>;
|
|
177
177
|
export type PageMetaInput = z.input<typeof pageMetaBaseSchema>;
|
|
178
178
|
|
|
179
|
+
/** The fully-defaulted front matter of a page that declares nothing. */
|
|
180
|
+
const EMPTY_PAGE_META: PageMeta = pageMetaBaseSchema.parse({});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The page schema as the generated content collections declare it, so
|
|
184
|
+
* `entry.data` is typed and normalized (dates as ISO strings, X handles with
|
|
185
|
+
* their `@`) the same way the scan's `PageMeta` is. Two deliberate loosenings
|
|
186
|
+
* over {@link pageMetaSchema}: custom keys (`frontmatter.extend`, per-type
|
|
187
|
+
* maps) pass through instead of failing the strict parse, and a page the
|
|
188
|
+
* strict parse rejects resolves to the empty defaults instead of throwing.
|
|
189
|
+
* The scan has already dropped such a page with a `BLUME_FRONTMATTER_INVALID`
|
|
190
|
+
* diagnostic — Blume continues without it unless `--strict` — so the
|
|
191
|
+
* collection must not turn that dropped page into a failed content sync.
|
|
192
|
+
*/
|
|
193
|
+
export const pageCollectionSchema = pageMetaBaseSchema
|
|
194
|
+
.loose()
|
|
195
|
+
// oxlint-disable-next-line promise/prefer-await-to-then -- zod's catch, not a promise
|
|
196
|
+
.catch(() => EMPTY_PAGE_META);
|
|
197
|
+
|
|
179
198
|
/** Built-in page frontmatter keys; custom keys must never redeclare one. */
|
|
180
199
|
const BUILT_IN_PAGE_META_KEYS = new Set<string>(
|
|
181
200
|
pageMetaBaseSchema.keyof().options
|
|
@@ -1220,6 +1239,14 @@ const analyticsScriptSchema = z
|
|
|
1220
1239
|
});
|
|
1221
1240
|
|
|
1222
1241
|
const analyticsConfigSchema = z.strictObject({
|
|
1242
|
+
// Cloudflare Web Analytics in manual (JS snippet) mode; the token comes from
|
|
1243
|
+
// the site's snippet in the dashboard. A zone Cloudflare proxies with
|
|
1244
|
+
// automatic RUM injection on needs no config at all.
|
|
1245
|
+
cloudflare: z
|
|
1246
|
+
.strictObject({
|
|
1247
|
+
token: z.string().min(1),
|
|
1248
|
+
})
|
|
1249
|
+
.optional(),
|
|
1223
1250
|
posthog: z
|
|
1224
1251
|
.strictObject({
|
|
1225
1252
|
host: z.string().optional(),
|
|
@@ -4,6 +4,8 @@ import GithubSlugger from "github-slugger";
|
|
|
4
4
|
import { extname } from "pathe";
|
|
5
5
|
|
|
6
6
|
import { withBasePath } from "../base-path.ts";
|
|
7
|
+
import { nextFenceState } from "../code-fences.ts";
|
|
8
|
+
import type { FenceState } from "../code-fences.ts";
|
|
7
9
|
import { diagnosticsFromIssues, diagnosticsFromZod } from "../diagnostics.ts";
|
|
8
10
|
import { occupySlug, parseHeadingMarkers } from "../heading-markers.ts";
|
|
9
11
|
import { localePlacement, localizeRoute } from "../i18n.ts";
|
|
@@ -139,43 +141,6 @@ const mapRoute = (relativePath: string): MappedRoute => {
|
|
|
139
141
|
return { groups, route, segments };
|
|
140
142
|
};
|
|
141
143
|
|
|
142
|
-
// CommonMark allows backtick *and* tilde fences, three or more characters
|
|
143
|
-
// long. The scanners track which delimiter opened the current fence and how
|
|
144
|
-
// long its run was (`null` when outside one), so a ``` line inside a ~~~
|
|
145
|
-
// block — or inside a ````-delimited block (the wrapper `codeBlockLines`
|
|
146
|
-
// emits around code that contains its own ``` fence) — is content, not a
|
|
147
|
-
// toggle. See `nextFenceState`.
|
|
148
|
-
const CODE_FENCE = /^(?<run>`{3,}|~{3,})/u;
|
|
149
|
-
|
|
150
|
-
/** The open fence's delimiter char and run length, or null outside one. */
|
|
151
|
-
export type FenceState = { delimiter: "`" | "~"; length: number } | null;
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Advance the fenced-code state for one line: an opening fence records its
|
|
155
|
-
* delimiter and run length, only a run of the same character at least as long
|
|
156
|
-
* closes it (CommonMark), and any other line leaves the state untouched.
|
|
157
|
-
*/
|
|
158
|
-
export const nextFenceState = (line: string, fence: FenceState): FenceState => {
|
|
159
|
-
const trimmed = line.trimStart();
|
|
160
|
-
const run = trimmed.match(CODE_FENCE)?.groups?.run;
|
|
161
|
-
if (run === undefined) {
|
|
162
|
-
return fence;
|
|
163
|
-
}
|
|
164
|
-
const delimiter = run.startsWith("`") ? ("`" as const) : ("~" as const);
|
|
165
|
-
if (fence === null) {
|
|
166
|
-
// A backtick fence's info string cannot itself contain a backtick
|
|
167
|
-
// (CommonMark) — a line-leading ```inline``` span is a paragraph, and
|
|
168
|
-
// opening a phantom fence on it would swallow every heading and link
|
|
169
|
-
// after it. Tilde fences carry no such rule.
|
|
170
|
-
if (delimiter === "`" && trimmed.slice(run.length).includes("`")) {
|
|
171
|
-
return fence;
|
|
172
|
-
}
|
|
173
|
-
return { delimiter, length: run.length };
|
|
174
|
-
}
|
|
175
|
-
return fence.delimiter === delimiter && run.length >= fence.length
|
|
176
|
-
? null
|
|
177
|
-
: fence;
|
|
178
|
-
};
|
|
179
144
|
// A closing hash sequence must be preceded by whitespace (CommonMark), so a
|
|
180
145
|
// heading like `## What is C#` keeps its trailing `#`. Up to 3 leading spaces
|
|
181
146
|
// are allowed; 4+ is an indented code block.
|
|
@@ -4,18 +4,19 @@ import { readdir, readFile, stat } from "node:fs/promises";
|
|
|
4
4
|
|
|
5
5
|
import { basename, isAbsolute, join, relative, resolve } from "pathe";
|
|
6
6
|
|
|
7
|
+
import { nextFenceState } from "../code-fences.ts";
|
|
8
|
+
import type { FenceState } from "../code-fences.ts";
|
|
7
9
|
import { BlumeError } from "../diagnostics.ts";
|
|
8
10
|
import matter from "../frontmatter.ts";
|
|
9
11
|
import { pageMetaSchema } from "../schema.ts";
|
|
10
12
|
import type { ResolvedI18nConfig, ResolvedVersionsConfig } from "../schema.ts";
|
|
11
13
|
import type { Diagnostic } from "../types.ts";
|
|
12
14
|
import { hashText } from "./cache.ts";
|
|
13
|
-
import type { EntryPlacement
|
|
15
|
+
import type { EntryPlacement } from "./normalize.ts";
|
|
14
16
|
import {
|
|
15
17
|
extractHeadings,
|
|
16
18
|
isStringValue,
|
|
17
19
|
localizedRoute,
|
|
18
|
-
nextFenceState,
|
|
19
20
|
placeEntryRef,
|
|
20
21
|
resolveEntryRoute,
|
|
21
22
|
slugifyPath,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel dimensions declared by an SVG document's root element.
|
|
3
|
+
*
|
|
4
|
+
* Blume needs an SVG's size in two places — the header logo's reserved
|
|
5
|
+
* `<img>` box and the OG card's brand mark — and both read it through this
|
|
6
|
+
* one parser so the header and the card can't disagree about one logo. It
|
|
7
|
+
* inspects the root `<svg …>` tag only: explicit `width`/`height` win, a
|
|
8
|
+
* `viewBox` fills in whatever is missing (scaled to a lone explicit length
|
|
9
|
+
* when there is one), and lengths carry CSS units at 96dpi. Percentages have
|
|
10
|
+
* no pixel meaning and are ignored, so a percent-sized SVG measures by its
|
|
11
|
+
* viewBox alone.
|
|
12
|
+
*/
|
|
13
|
+
export interface SvgDimensions {
|
|
14
|
+
height: number;
|
|
15
|
+
width: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The root tag, tolerating `>` inside a quoted attribute value. */
|
|
19
|
+
const ROOT = /<svg\s(?:[^>"']|"[^"]*"|'[^']*')*>/u;
|
|
20
|
+
const WIDTH = /\swidth=(?<quote>['"])(?<value>[^%]+?)\k<quote>/u;
|
|
21
|
+
const HEIGHT = /\sheight=(?<quote>['"])(?<value>[^%]+?)\k<quote>/u;
|
|
22
|
+
const VIEWBOX = /\sviewBox=(?<quote>['"])(?<value>.+?)\k<quote>/iu;
|
|
23
|
+
|
|
24
|
+
/** Pixels per CSS unit at 96dpi; an omitted unit means pixels. */
|
|
25
|
+
const INCH_CM = 2.54;
|
|
26
|
+
const UNITS = new Map([
|
|
27
|
+
["cm", 96 / INCH_CM],
|
|
28
|
+
["em", 16],
|
|
29
|
+
["ex", 8],
|
|
30
|
+
["in", 96],
|
|
31
|
+
["m", (96 / INCH_CM) * 100],
|
|
32
|
+
["mm", 96 / INCH_CM / 10],
|
|
33
|
+
["pc", (96 / 72) * 12],
|
|
34
|
+
["pt", 96 / 72],
|
|
35
|
+
["px", 1],
|
|
36
|
+
]);
|
|
37
|
+
const LENGTH =
|
|
38
|
+
/^(?<number>[0-9.]+(?:e\d+)?)(?<unit>in|cm|em|ex|m|mm|pc|pt|px)?$/u;
|
|
39
|
+
|
|
40
|
+
/** A CSS length as whole pixels, or undefined when it isn't one. */
|
|
41
|
+
const parseLength = (value: string | undefined): number | undefined => {
|
|
42
|
+
const groups = value === undefined ? undefined : LENGTH.exec(value)?.groups;
|
|
43
|
+
if (!groups) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const pixels = Math.round(
|
|
47
|
+
Number(groups.number) * (UNITS.get(groups.unit ?? "px") ?? 1)
|
|
48
|
+
);
|
|
49
|
+
return pixels > 0 ? pixels : undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** An attribute's raw value on the root tag, or undefined when absent. */
|
|
53
|
+
const attribute = (root: string, pattern: RegExp): string | undefined =>
|
|
54
|
+
pattern.exec(root)?.groups?.value;
|
|
55
|
+
|
|
56
|
+
/** The viewBox's own width and height (its third and fourth numbers). */
|
|
57
|
+
const viewBoxSize = (root: string): SvgDimensions | null => {
|
|
58
|
+
const raw = attribute(root, VIEWBOX);
|
|
59
|
+
if (raw === undefined) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const bounds = raw.split(" ");
|
|
63
|
+
const width = parseLength(bounds[2]);
|
|
64
|
+
const height = parseLength(bounds[3]);
|
|
65
|
+
return width && height ? { height, width } : null;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The SVG's pixel size, or null when the root carries no usable size: no
|
|
70
|
+
* root tag, neither explicit dimensions nor a viewBox, a viewBox that isn't
|
|
71
|
+
* space-separated, or a zero or unparsable length.
|
|
72
|
+
*/
|
|
73
|
+
export const svgDimensions = (svg: string): SvgDimensions | null => {
|
|
74
|
+
const root = ROOT.exec(svg)?.[0];
|
|
75
|
+
if (root === undefined) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const width = parseLength(attribute(root, WIDTH));
|
|
79
|
+
const height = parseLength(attribute(root, HEIGHT));
|
|
80
|
+
if (width && height) {
|
|
81
|
+
return { height, width };
|
|
82
|
+
}
|
|
83
|
+
const box = viewBoxSize(root);
|
|
84
|
+
if (!box) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const ratio = box.width / box.height;
|
|
88
|
+
if (width) {
|
|
89
|
+
const scaled = Math.floor(width / ratio);
|
|
90
|
+
return scaled > 0 ? { height: scaled, width } : null;
|
|
91
|
+
}
|
|
92
|
+
if (height) {
|
|
93
|
+
const scaled = Math.floor(height * ratio);
|
|
94
|
+
return scaled > 0 ? { height, width: scaled } : null;
|
|
95
|
+
}
|
|
96
|
+
return box;
|
|
97
|
+
};
|
package/src/core/version-cut.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { cp, readdir, readFile, rm } from "node:fs/promises";
|
|
|
4
4
|
import { join, relative } from "pathe";
|
|
5
5
|
|
|
6
6
|
import { stripBasePath } from "./base-path.ts";
|
|
7
|
+
import { nextFenceState } from "./code-fences.ts";
|
|
8
|
+
import type { FenceState } from "./code-fences.ts";
|
|
7
9
|
import { writeTextAtomic } from "./fs-atomic.ts";
|
|
8
10
|
import { localizeRoute } from "./i18n.ts";
|
|
9
11
|
import type { BlumeProject } from "./project-graph.ts";
|
|
10
12
|
import { isWithin, scanProject } from "./project-graph.ts";
|
|
11
13
|
import { VERSION_ID } from "./schema.ts";
|
|
12
|
-
import { nextFenceState } from "./sources/normalize.ts";
|
|
13
|
-
import type { FenceState } from "./sources/normalize.ts";
|
|
14
14
|
import { VERSION_LIKE, versionizeRoute } from "./versions.ts";
|
|
15
15
|
|
|
16
16
|
/** What `cutVersion` did, for the CLI to report. */
|