blume 1.7.0 → 1.7.2

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cli/{chunk-9qs6acpw.js → chunk-12dxjqk7.js} +32 -43
  3. package/dist/cli/{chunk-9qs6acpw.js.map → chunk-12dxjqk7.js.map} +2 -2
  4. package/dist/cli/{chunk-s5dsk8bj.js → chunk-196vjxp9.js} +65 -64
  5. package/dist/cli/chunk-196vjxp9.js.map +13 -0
  6. package/dist/cli/{chunk-27gtm2ym.js → chunk-2mzebbbz.js} +1 -1
  7. package/dist/cli/{chunk-s5e5jt53.js → chunk-2z47ypj8.js} +1 -1
  8. package/dist/cli/{chunk-tnskyrej.js → chunk-30e87n55.js} +15 -24
  9. package/dist/cli/{chunk-tnskyrej.js.map → chunk-30e87n55.js.map} +2 -2
  10. package/dist/cli/{chunk-4trphnvy.js → chunk-3w7b2vcx.js} +10 -13
  11. package/dist/cli/{chunk-4trphnvy.js.map → chunk-3w7b2vcx.js.map} +2 -2
  12. package/dist/cli/{chunk-v5mm027v.js → chunk-450a7rcr.js} +8 -8
  13. package/dist/cli/{chunk-v5mm027v.js.map → chunk-450a7rcr.js.map} +1 -1
  14. package/dist/cli/{chunk-5d4q7121.js → chunk-5n7t497w.js} +142 -134
  15. package/dist/cli/{chunk-5d4q7121.js.map → chunk-5n7t497w.js.map} +5 -5
  16. package/dist/cli/{chunk-xv91q4nm.js → chunk-61j18dwk.js} +37 -9
  17. package/dist/cli/{chunk-xv91q4nm.js.map → chunk-61j18dwk.js.map} +3 -3
  18. package/dist/cli/{chunk-3r94j3tc.js → chunk-688e0dde.js} +2 -2
  19. package/dist/cli/{chunk-vxv4x1n8.js → chunk-88by27n5.js} +2 -2
  20. package/dist/cli/{chunk-cfw6x4rm.js → chunk-8cd8tj54.js} +28 -35
  21. package/dist/cli/{chunk-cfw6x4rm.js.map → chunk-8cd8tj54.js.map} +2 -2
  22. package/dist/cli/{chunk-8gnpdsn1.js → chunk-9bkjd11x.js} +2 -2
  23. package/dist/cli/{chunk-qq9nm3qd.js → chunk-9he6crym.js} +21 -28
  24. package/dist/cli/{chunk-qq9nm3qd.js.map → chunk-9he6crym.js.map} +2 -2
  25. package/dist/cli/{chunk-jk1zwka1.js → chunk-aztttvb3.js} +27 -33
  26. package/dist/cli/{chunk-jk1zwka1.js.map → chunk-aztttvb3.js.map} +2 -2
  27. package/dist/cli/{chunk-ckh3a410.js → chunk-cvky9gb2.js} +18 -24
  28. package/dist/cli/{chunk-ckh3a410.js.map → chunk-cvky9gb2.js.map} +2 -2
  29. package/dist/cli/{chunk-kwx90v78.js → chunk-eevwt1sc.js} +23 -32
  30. package/dist/cli/{chunk-kwx90v78.js.map → chunk-eevwt1sc.js.map} +2 -2
  31. package/dist/cli/{chunk-agy5rzxy.js → chunk-ejjx8znq.js} +126 -77
  32. package/dist/cli/chunk-ejjx8znq.js.map +15 -0
  33. package/dist/cli/{chunk-ye9zdkgv.js → chunk-exeeb35e.js} +3 -3
  34. package/dist/cli/{chunk-v2ymm99c.js → chunk-fmceyezb.js} +41 -50
  35. package/dist/cli/{chunk-v2ymm99c.js.map → chunk-fmceyezb.js.map} +2 -2
  36. package/dist/cli/{chunk-s102bysw.js → chunk-hdm2dkd2.js} +209 -97
  37. package/dist/cli/{chunk-s102bysw.js.map → chunk-hdm2dkd2.js.map} +6 -5
  38. package/dist/cli/{chunk-zr3ygrq3.js → chunk-hs3gbh8p.js} +10 -15
  39. package/dist/cli/{chunk-zr3ygrq3.js.map → chunk-hs3gbh8p.js.map} +2 -2
  40. package/dist/cli/{chunk-n0nyat6g.js → chunk-jbj4qhfw.js} +3 -3
  41. package/dist/cli/{chunk-ev67ycx0.js → chunk-jq5n4avg.js} +1 -1
  42. package/dist/cli/{chunk-jxkxjsc1.js → chunk-mqb2ka8m.js} +22 -30
  43. package/dist/cli/{chunk-jxkxjsc1.js.map → chunk-mqb2ka8m.js.map} +2 -2
  44. package/dist/cli/{chunk-drke6t0h.js → chunk-mt76t7dj.js} +26 -34
  45. package/dist/cli/{chunk-drke6t0h.js.map → chunk-mt76t7dj.js.map} +2 -2
  46. package/dist/cli/{chunk-jtb45atp.js → chunk-n9sra6sy.js} +13 -13
  47. package/dist/cli/{chunk-jtb45atp.js.map → chunk-n9sra6sy.js.map} +1 -1
  48. package/dist/cli/{chunk-x66c5yjn.js → chunk-ppfvdcd4.js} +2 -2
  49. package/dist/cli/{chunk-wd27zjcz.js → chunk-q4rae3bg.js} +1 -1
  50. package/dist/cli/{chunk-pxj10x8y.js → chunk-ra1v2nc2.js} +1 -1
  51. package/dist/cli/{chunk-y3g15rvv.js → chunk-t3tj0dgr.js} +26 -33
  52. package/dist/cli/{chunk-y3g15rvv.js.map → chunk-t3tj0dgr.js.map} +2 -2
  53. package/dist/cli/{chunk-j6pxe0dt.js → chunk-tqa1s0k8.js} +4 -4
  54. package/dist/cli/{chunk-cbjnx4s8.js → chunk-vh9w1sgp.js} +1 -1
  55. package/dist/cli/{chunk-0qhq7b8q.js → chunk-vkrsvbr5.js} +13 -17
  56. package/dist/cli/{chunk-0qhq7b8q.js.map → chunk-vkrsvbr5.js.map} +2 -2
  57. package/dist/cli/{chunk-sbdqrjbb.js → chunk-vrfp10qk.js} +1 -1
  58. package/dist/cli/{chunk-ynacq3ev.js → chunk-wjt80jps.js} +27 -40
  59. package/dist/cli/{chunk-ynacq3ev.js.map → chunk-wjt80jps.js.map} +3 -4
  60. package/dist/cli/{chunk-18tjv4f7.js → chunk-xhtpx3ff.js} +21 -30
  61. package/dist/cli/{chunk-18tjv4f7.js.map → chunk-xhtpx3ff.js.map} +2 -2
  62. package/dist/cli/{chunk-5hs6gb7n.js → chunk-yzhm0j9q.js} +1 -1
  63. package/dist/cli/index.js +397 -34
  64. package/dist/cli/index.js.map +12 -4
  65. package/dist/types/components/layout/nav-utils.d.ts +15 -2
  66. package/dist/types/core/config-input.d.ts +33 -0
  67. package/dist/types/core/schema.d.ts +29 -3
  68. package/docs/08-faq.mdx +21 -0
  69. package/docs/configuration/ask-ai.mdx +63 -0
  70. package/docs/content/sources.mdx +2 -2
  71. package/package.json +1 -1
  72. package/src/ai/ask.ts +31 -4
  73. package/src/ai/cors.ts +87 -0
  74. package/src/astro/generate.ts +3 -0
  75. package/src/astro/templates.ts +192 -83
  76. package/src/components/content/YouTube.astro +1 -1
  77. package/src/components/layout/Header.astro +1 -1
  78. package/src/components/layout/NavTree.astro +75 -66
  79. package/src/components/layout/RootLayout.astro +4 -1
  80. package/src/components/layout/Search.astro +11 -0
  81. package/src/components/layout/nav-utils.ts +47 -15
  82. package/src/core/adapter.ts +61 -0
  83. package/src/core/config-input.ts +33 -0
  84. package/src/core/schema.ts +61 -0
  85. package/src/core/sources/assets.ts +162 -26
  86. package/src/core/sources/notion.ts +60 -1
  87. package/src/registry/eject.ts +3 -0
  88. package/src/theme/entry.ts +9 -0
  89. package/dist/cli/chunk-2aj8ddew.js +0 -72
  90. package/dist/cli/chunk-2aj8ddew.js.map +0 -10
  91. package/dist/cli/chunk-4xyggvgf.js +0 -21
  92. package/dist/cli/chunk-4xyggvgf.js.map +0 -10
  93. package/dist/cli/chunk-6kzzpsx8.js +0 -26
  94. package/dist/cli/chunk-6kzzpsx8.js.map +0 -10
  95. package/dist/cli/chunk-agy5rzxy.js.map +0 -15
  96. package/dist/cli/chunk-bcy492zc.js +0 -16
  97. package/dist/cli/chunk-bcy492zc.js.map +0 -10
  98. package/dist/cli/chunk-btfr9yvw.js +0 -41
  99. package/dist/cli/chunk-btfr9yvw.js.map +0 -10
  100. package/dist/cli/chunk-ey89bjj1.js +0 -209
  101. package/dist/cli/chunk-ey89bjj1.js.map +0 -11
  102. package/dist/cli/chunk-s5dsk8bj.js.map +0 -13
  103. package/dist/cli/chunk-vt8fgygt.js +0 -23
  104. package/dist/cli/chunk-vt8fgygt.js.map +0 -10
  105. /package/dist/cli/{chunk-27gtm2ym.js.map → chunk-2mzebbbz.js.map} +0 -0
  106. /package/dist/cli/{chunk-s5e5jt53.js.map → chunk-2z47ypj8.js.map} +0 -0
  107. /package/dist/cli/{chunk-3r94j3tc.js.map → chunk-688e0dde.js.map} +0 -0
  108. /package/dist/cli/{chunk-vxv4x1n8.js.map → chunk-88by27n5.js.map} +0 -0
  109. /package/dist/cli/{chunk-8gnpdsn1.js.map → chunk-9bkjd11x.js.map} +0 -0
  110. /package/dist/cli/{chunk-ye9zdkgv.js.map → chunk-exeeb35e.js.map} +0 -0
  111. /package/dist/cli/{chunk-n0nyat6g.js.map → chunk-jbj4qhfw.js.map} +0 -0
  112. /package/dist/cli/{chunk-ev67ycx0.js.map → chunk-jq5n4avg.js.map} +0 -0
  113. /package/dist/cli/{chunk-x66c5yjn.js.map → chunk-ppfvdcd4.js.map} +0 -0
  114. /package/dist/cli/{chunk-wd27zjcz.js.map → chunk-q4rae3bg.js.map} +0 -0
  115. /package/dist/cli/{chunk-pxj10x8y.js.map → chunk-ra1v2nc2.js.map} +0 -0
  116. /package/dist/cli/{chunk-j6pxe0dt.js.map → chunk-tqa1s0k8.js.map} +0 -0
  117. /package/dist/cli/{chunk-cbjnx4s8.js.map → chunk-vh9w1sgp.js.map} +0 -0
  118. /package/dist/cli/{chunk-sbdqrjbb.js.map → chunk-vrfp10qk.js.map} +0 -0
  119. /package/dist/cli/{chunk-5hs6gb7n.js.map → chunk-yzhm0j9q.js.map} +0 -0
@@ -1,33 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- PUBLIC_API_URL,
4
- UI_PACKS,
3
+ getBlumeVersion,
4
+ BlumeError,
5
+ diagnosticsFromIssues,
6
+ diagnosticsFromZod
7
+ } from "./index.js";
8
+ import {
5
9
  applyDeploymentEnv,
10
+ slugify,
6
11
  blumeReferences,
7
12
  createModuleLoader,
8
- folderMetaSchema,
9
- loadConfig,
10
- pageMetaSchema,
11
13
  resolveProjectContext,
12
- slugify
13
- } from "./chunk-xv91q4nm.js";
14
+ PUBLIC_API_URL,
15
+ UI_PACKS,
16
+ pageMetaSchema,
17
+ folderMetaSchema,
18
+ loadConfig
19
+ } from "./chunk-61j18dwk.js";
14
20
  import {
15
- isInternalPath,
16
- stripBasePath,
17
21
  trimChar,
18
- withBasePath
19
- } from "./chunk-27gtm2ym.js";
20
- import {
21
- getBlumeVersion
22
- } from "./chunk-4xyggvgf.js";
23
- import {
24
- BlumeError,
25
- diagnosticsFromIssues,
26
- diagnosticsFromZod
27
- } from "./chunk-ey89bjj1.js";
28
- import {
29
- __require
30
- } from "./chunk-2aj8ddew.js";
22
+ isInternalPath,
23
+ withBasePath,
24
+ stripBasePath
25
+ } from "./chunk-2mzebbbz.js";
31
26
 
32
27
  // src/core/i18n.ts
33
28
  var KNOWN_LOCALES = new Set([...Object.keys(UI_PACKS), "en"].map((code) => code.toLowerCase()));
@@ -60,7 +55,7 @@ var localePlacement = (rel, ext, i18n) => {
60
55
  if (base.endsWith(".$")) {
61
56
  const shared = `${base.slice(0, -2)}${ext}`;
62
57
  return {
63
- locales: i18n.locales.map((locale2) => locale2.code),
58
+ locales: i18n.locales.map((locale) => locale.code),
64
59
  navPath: i18n.parser === "dir" ? detectLocale(shared.split("/"), i18n).rest.join("/") : shared
65
60
  };
66
61
  }
@@ -68,7 +63,7 @@ var localePlacement = (rel, ext, i18n) => {
68
63
  const lastDot = base.lastIndexOf(".");
69
64
  if (lastDot > base.lastIndexOf("/")) {
70
65
  const suffix = base.slice(lastDot + 1).toLowerCase();
71
- const matched = i18n.locales.find((locale2) => locale2.code.toLowerCase() === suffix);
66
+ const matched = i18n.locales.find((locale) => locale.code.toLowerCase() === suffix);
72
67
  if (matched) {
73
68
  return {
74
69
  locales: [matched.code],
@@ -885,15 +880,15 @@ var buildNavigation = (pages, options) => {
885
880
  const diagnostics = options.diagnostics ?? [];
886
881
  diagnostics.push(...sidebarDisplayIgnoredDiagnostics(pages, Boolean(options.sidebar)));
887
882
  if (options.sidebar) {
888
- const sidebar2 = buildConfigSidebar(options.sidebar, byRoute, display, basePath);
883
+ const sidebar = buildConfigSidebar(options.sidebar, byRoute, display, basePath);
889
884
  return {
890
885
  actions,
891
886
  cta,
892
887
  featured,
893
888
  root: rootTabPath,
894
889
  selectors,
895
- sidebar: sidebar2,
896
- tabs: withTabHrefs(tabs, sidebar2)
890
+ sidebar,
891
+ tabs: withTabHrefs(tabs, sidebar)
897
892
  };
898
893
  }
899
894
  const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, sharedMetaPrefix, display, new Set(tabs.flatMap((tab) => isRootTab(tab, rootTabPath) ? [] : [tab.path])), diagnostics);
@@ -1791,13 +1786,13 @@ var resolveIncludePath = (target, filePath, ctx, line) => {
1791
1786
  error: includeError("BLUME_INCLUDE_OUTSIDE_ROOT", `Include target ${target} is root-relative, but no content root is configured here.`, filePath, line, "Use a path relative to the including file instead.")
1792
1787
  };
1793
1788
  }
1794
- const path2 = join(ctx.contentRoot, target);
1795
- if (escapesRoot(ctx.contentRoot, path2)) {
1789
+ const path = join(ctx.contentRoot, target);
1790
+ if (escapesRoot(ctx.contentRoot, path)) {
1796
1791
  return {
1797
1792
  error: includeError("BLUME_INCLUDE_OUTSIDE_ROOT", `Include target ${target} resolves outside the content root.`, filePath, line, "Root-relative include paths resolve from the content root and cannot climb above it.")
1798
1793
  };
1799
1794
  }
1800
- return { path: path2 };
1795
+ return { path };
1801
1796
  }
1802
1797
  const path = resolve(dirname(filePath), target);
1803
1798
  if (ctx.contentRoot && escapesRoot(ctx.contentRoot, path)) {
@@ -1871,10 +1866,10 @@ var expandStatement = async (statement, filePath, line, ctx, stack) => {
1871
1866
  ctx.includes.add(path);
1872
1867
  const asContent = !statement.attributes.lang && CONTENT_EXTENSIONS.has(extname3(path).toLowerCase());
1873
1868
  if (!asContent) {
1874
- const lines2 = codeBlockLines(content, path, statement.attributes);
1869
+ const lines = codeBlockLines(content, path, statement.attributes);
1875
1870
  return {
1876
- lines: lines2,
1877
- origins: lines2.map((_, i) => ({ file: path, line: Math.max(1, i) }))
1871
+ lines,
1872
+ origins: lines.map((_, i) => ({ file: path, line: Math.max(1, i) }))
1878
1873
  };
1879
1874
  }
1880
1875
  const parsed = frontmatter_default(content);
@@ -2114,7 +2109,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
2114
2109
  const list = Array.isArray(sources) ? sources : [{ root: sources }];
2115
2110
  const localeDirs = new Set(options.localeDirs);
2116
2111
  const versionDirs = new Set(options.versionDirs);
2117
- const load2 = createModuleLoader();
2112
+ const load = createModuleLoader();
2118
2113
  const meta = new Map;
2119
2114
  const shared = new Map;
2120
2115
  const diagnostics = [];
@@ -2130,7 +2125,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
2130
2125
  return {
2131
2126
  file,
2132
2127
  ok: true,
2133
- value: await resolveMeta(await load2(file))
2128
+ value: await resolveMeta(await load(file))
2134
2129
  };
2135
2130
  } catch (error) {
2136
2131
  return { error, file, ok: false };
@@ -2181,7 +2176,7 @@ import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
2181
2176
  import { join as join2 } from "pathe";
2182
2177
  var hashText = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
2183
2178
  var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
2184
- var pollingWatch = (load2, intervalSeconds, seed) => (onChange) => {
2179
+ var pollingWatch = (load, intervalSeconds, seed) => (onChange) => {
2185
2180
  let last = "";
2186
2181
  if (seed) {
2187
2182
  const seedBaseline = async () => {
@@ -2194,7 +2189,7 @@ var pollingWatch = (load2, intervalSeconds, seed) => (onChange) => {
2194
2189
  }
2195
2190
  const tick = async () => {
2196
2191
  try {
2197
- const { entries } = await load2();
2192
+ const { entries } = await load();
2198
2193
  const next = entriesDigest(entries);
2199
2194
  if (last && next !== last) {
2200
2195
  onChange();
@@ -3283,25 +3278,25 @@ var tagFolderMeta = (spec, tags) => {
3283
3278
  var parseReference = async (reference, ctx) => {
3284
3279
  const options = { cacheDir: ctx.cacheDir, refresh: ctx.refresh };
3285
3280
  if (reference.kind === "asyncapi") {
3286
- const { document: document2, warnings: warnings2 } = await parseAsyncApiSpec(reference.spec, ctx.projectRoot, options);
3287
- const extracted2 = extractAsyncApiOperations(document2, reference.route);
3281
+ const { document, warnings } = await parseAsyncApiSpec(reference.spec, ctx.projectRoot, options);
3282
+ const extracted = extractAsyncApiOperations(document, reference.route);
3288
3283
  return {
3289
- document: document2,
3290
- extractWarnings: extracted2.warnings,
3291
- operations: extracted2.operations,
3292
- tags: extracted2.tags,
3293
- warnings: warnings2
3284
+ document,
3285
+ extractWarnings: extracted.warnings,
3286
+ operations: extracted.operations,
3287
+ tags: extracted.tags,
3288
+ warnings
3294
3289
  };
3295
3290
  }
3296
3291
  if (reference.kind === "graphql") {
3297
- const { document: document2, warnings: warnings2 } = await parseGraphqlSpec(reference.spec, ctx.projectRoot, options);
3298
- const extracted2 = extractGraphqlOperations(document2, reference.route);
3292
+ const { document, warnings } = await parseGraphqlSpec(reference.spec, ctx.projectRoot, options);
3293
+ const extracted = extractGraphqlOperations(document, reference.route);
3299
3294
  return {
3300
- document: document2,
3301
- extractWarnings: extracted2.warnings,
3302
- operations: extracted2.operations,
3303
- tags: extracted2.tags,
3304
- warnings: warnings2
3295
+ document,
3296
+ extractWarnings: extracted.warnings,
3297
+ operations: extracted.operations,
3298
+ tags: extracted.tags,
3299
+ warnings
3305
3300
  };
3306
3301
  }
3307
3302
  const { document, warnings } = await parseSpec(reference.spec, ctx.projectRoot, options);
@@ -3388,7 +3383,7 @@ var openApiSource = (references, ctx) => {
3388
3383
  };
3389
3384
  }
3390
3385
  };
3391
- const load2 = async () => {
3386
+ const load = async () => {
3392
3387
  const results = await Promise.all(references.map(loadReference));
3393
3388
  const entries = [];
3394
3389
  const diagnostics = references.flatMap((reference) => (reference.collisions ?? []).map((message) => ({
@@ -3413,7 +3408,7 @@ var openApiSource = (references, ctx) => {
3413
3408
  };
3414
3409
  return {
3415
3410
  kind: "openapi-source",
3416
- load: load2,
3411
+ load,
3417
3412
  name: "openapi",
3418
3413
  openApiData: () => parsed,
3419
3414
  staged: true
@@ -3454,7 +3449,7 @@ var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS, ign
3454
3449
  // src/core/sources/filesystem.ts
3455
3450
  var filesystemSource = (options) => {
3456
3451
  const contentRoot = isAbsolute2(options.root) ? options.root : join4(resolve2(options.projectRoot), options.root);
3457
- const load2 = async () => {
3452
+ const load = async () => {
3458
3453
  const files = await glob2(options.include, {
3459
3454
  absolute: true,
3460
3455
  cwd: contentRoot,
@@ -3501,7 +3496,7 @@ var filesystemSource = (options) => {
3501
3496
  };
3502
3497
  return {
3503
3498
  contentRoot,
3504
- load: load2,
3499
+ load,
3505
3500
  name: options.name,
3506
3501
  prefix: options.prefix,
3507
3502
  read: (ref) => readFile4(join4(contentRoot, ref), "utf-8"),
@@ -3614,7 +3609,7 @@ var githubReleasesSource = (options, ctx) => {
3614
3609
  }
3615
3610
  return collected.slice(0, max);
3616
3611
  };
3617
- const load2 = async (refresh = ctx.refresh ?? true) => {
3612
+ const load = async (refresh = ctx.refresh ?? true) => {
3618
3613
  try {
3619
3614
  const result = await loadWithCache(options.name, cache, async () => {
3620
3615
  const releases = await fetchReleases();
@@ -3646,12 +3641,12 @@ var githubReleasesSource = (options, ctx) => {
3646
3641
  return entry?.raw ?? entry?.body.text ?? "";
3647
3642
  };
3648
3643
  return {
3649
- load: load2,
3644
+ load,
3650
3645
  name: options.name,
3651
3646
  prefix: options.prefix,
3652
3647
  read,
3653
3648
  staged: true,
3654
- watch: options.pollInterval ? pollingWatch(() => load2(true), options.pollInterval, () => load2()) : undefined
3649
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
3655
3650
  };
3656
3651
  };
3657
3652
 
@@ -3742,7 +3737,7 @@ var mdxRemoteSource = (options, ctx) => {
3742
3737
  ref: item.ref
3743
3738
  };
3744
3739
  };
3745
- const load2 = async (refresh = ctx.refresh ?? true) => {
3740
+ const load = async (refresh = ctx.refresh ?? true) => {
3746
3741
  assertConfigured();
3747
3742
  const skipped = [];
3748
3743
  const result = await loadWithCache(options.name, cache, async () => {
@@ -3789,12 +3784,12 @@ var mdxRemoteSource = (options, ctx) => {
3789
3784
  return entry?.raw ?? entry?.body.text ?? "";
3790
3785
  };
3791
3786
  return {
3792
- load: load2,
3787
+ load,
3793
3788
  name: options.name,
3794
3789
  prefix: options.prefix,
3795
3790
  read,
3796
3791
  staged: true,
3797
- watch: options.pollInterval ? pollingWatch(() => load2(true), options.pollInterval, () => load2()) : undefined
3792
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
3798
3793
  };
3799
3794
  };
3800
3795
 
@@ -3803,21 +3798,81 @@ import { setTimeout as sleep2 } from "node:timers/promises";
3803
3798
  import pLimit from "p-limit";
3804
3799
  import { join as join6 } from "pathe";
3805
3800
 
3801
+ // src/components/content/youtube.ts
3802
+ var BARE_ID = /^[\w-]{11}$/u;
3803
+ var URL_ID = /(?:youtu\.be\/|\/embed\/|\/shorts\/|\/live\/|[?&]v=)(?<id>[\w-]{11})/u;
3804
+ var parseYouTubeId = (input) => {
3805
+ const value = input.trim();
3806
+ if (!value) {
3807
+ return null;
3808
+ }
3809
+ if (BARE_ID.test(value)) {
3810
+ return value;
3811
+ }
3812
+ return URL_ID.exec(value)?.groups?.id ?? null;
3813
+ };
3814
+
3806
3815
  // src/core/sources/assets.ts
3807
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
3816
+ import { randomUUID } from "node:crypto";
3817
+ import {
3818
+ access,
3819
+ mkdir as mkdir3,
3820
+ readdir,
3821
+ rename,
3822
+ rm,
3823
+ writeFile as writeFile3
3824
+ } from "node:fs/promises";
3808
3825
  import { extname as extname5, join as join5 } from "pathe";
3809
3826
  var MD_IMAGE2 = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
3827
+ var HTML_VIDEO_SRC = /(?<open><video\b[^>]*?\ssrc=")(?<url>[^"]+)(?<close>")/gu;
3810
3828
  var REMOTE = /^https?:\/\//u;
3811
3829
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
3812
3830
  var CODE_FENCE_BLOCK = /^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[^\n]*(?=\n|$)/gmu;
3813
3831
  var FENCE_TOKEN = /\u0000blume-fence-(?<index>\d+)\u0000/gu;
3814
- var extFor = (url) => {
3832
+ var EXT_BY_MIME = new Map([
3833
+ ["image/avif", ".avif"],
3834
+ ["image/gif", ".gif"],
3835
+ ["image/jpeg", ".jpg"],
3836
+ ["image/png", ".png"],
3837
+ ["image/svg+xml", ".svg"],
3838
+ ["image/webp", ".webp"],
3839
+ ["video/mp4", ".mp4"],
3840
+ ["video/ogg", ".ogv"],
3841
+ ["video/quicktime", ".mov"],
3842
+ ["video/webm", ".webm"]
3843
+ ]);
3844
+ var UNKNOWN_EXT = ".bin";
3845
+ var NON_MEDIA_TYPE = /^(?:text\/|application\/(?:[\w.-]+\+)?(?:json|xml|javascript))/u;
3846
+ var PART_SUFFIX = ".part";
3847
+ var DEFAULT_TIMEOUT_MS = 120000;
3848
+ var extFromUrl = (url) => {
3815
3849
  const clean = url.split("?")[0] ?? url;
3816
3850
  const ext = extname5(clean);
3817
- return SAFE_EXT.test(ext) ? ext.toLowerCase() : ".png";
3851
+ return SAFE_EXT.test(ext) ? ext.toLowerCase() : null;
3852
+ };
3853
+ var mediaType = (res) => res.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
3854
+ var exists = async (path) => {
3855
+ try {
3856
+ await access(path);
3857
+ return true;
3858
+ } catch {
3859
+ return false;
3860
+ }
3861
+ };
3862
+ var completedFor = async (dir, stem) => {
3863
+ let names;
3864
+ try {
3865
+ names = await readdir(dir);
3866
+ } catch {
3867
+ return null;
3868
+ }
3869
+ const candidates = names.filter((name) => name.startsWith(`${stem}.`) && !name.endsWith(PART_SUFFIX));
3870
+ return candidates.length === 1 ? candidates[0] ?? null : null;
3818
3871
  };
3819
3872
  var materializeAssets = async (markdown, ctx) => {
3820
3873
  const doFetch = ctx.fetchImpl ?? globalThis.fetch;
3874
+ const limit = ctx.limit ?? ((task) => task());
3875
+ const timeoutMs = ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3821
3876
  const diagnostics = [];
3822
3877
  const fences = [];
3823
3878
  const masked = markdown.replace(CODE_FENCE_BLOCK, (block) => {
@@ -3825,23 +3880,52 @@ var materializeAssets = async (markdown, ctx) => {
3825
3880
  return `\x00blume-fence-${fences.length - 1}\x00`;
3826
3881
  });
3827
3882
  const urls = new Set;
3828
- for (const match of masked.matchAll(MD_IMAGE2)) {
3829
- const url = match.groups?.url;
3830
- if (url && REMOTE.test(url)) {
3831
- urls.add(url);
3883
+ for (const pattern of [MD_IMAGE2, HTML_VIDEO_SRC]) {
3884
+ for (const match of masked.matchAll(pattern)) {
3885
+ const url = match.groups?.url;
3886
+ if (url && REMOTE.test(url)) {
3887
+ urls.add(url);
3888
+ }
3832
3889
  }
3833
3890
  }
3891
+ const download = async (url) => {
3892
+ const stem = hashText(url.split("?")[0] ?? url);
3893
+ const urlExt = extFromUrl(url);
3894
+ if (urlExt && await exists(join5(ctx.assetsDir, `${stem}${urlExt}`))) {
3895
+ return `${stem}${urlExt}`;
3896
+ }
3897
+ const completed = urlExt ? null : await completedFor(ctx.assetsDir, stem);
3898
+ if (completed) {
3899
+ return completed;
3900
+ }
3901
+ const res = await doFetch(url, { signal: AbortSignal.timeout(timeoutMs) });
3902
+ if (!res.ok) {
3903
+ throw new Error(`${res.status}`);
3904
+ }
3905
+ const type = mediaType(res);
3906
+ if (NON_MEDIA_TYPE.test(type)) {
3907
+ throw new Error(`responded with ${type}, not a media file`);
3908
+ }
3909
+ if (!res.body) {
3910
+ throw new Error("empty response body");
3911
+ }
3912
+ const file = `${stem}${urlExt ?? EXT_BY_MIME.get(type) ?? UNKNOWN_EXT}`;
3913
+ const target = join5(ctx.assetsDir, file);
3914
+ await mkdir3(ctx.assetsDir, { recursive: true });
3915
+ const part = `${target}.${randomUUID()}${PART_SUFFIX}`;
3916
+ try {
3917
+ await writeFile3(part, res.body);
3918
+ } catch (error) {
3919
+ await rm(part, { force: true });
3920
+ throw error;
3921
+ }
3922
+ await rename(part, target);
3923
+ return file;
3924
+ };
3834
3925
  const rewrites = new Map;
3835
3926
  await Promise.all([...urls].map(async (url) => {
3836
3927
  try {
3837
- const res = await doFetch(url);
3838
- if (!res.ok) {
3839
- throw new Error(`${res.status}`);
3840
- }
3841
- const bytes = new Uint8Array(await res.arrayBuffer());
3842
- const file = `${hashText(url.split("?")[0] ?? url)}${extFor(url)}`;
3843
- await mkdir3(ctx.assetsDir, { recursive: true });
3844
- await writeFile3(join5(ctx.assetsDir, file), bytes);
3928
+ const file = await limit(() => download(url));
3845
3929
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
3846
3930
  } catch (error) {
3847
3931
  diagnostics.push({
@@ -3854,6 +3938,9 @@ var materializeAssets = async (markdown, ctx) => {
3854
3938
  const rewritten = masked.replaceAll(MD_IMAGE2, (match, alt, url) => {
3855
3939
  const local = rewrites.get(url);
3856
3940
  return local ? `![${alt}](${local})` : match;
3941
+ }).replaceAll(HTML_VIDEO_SRC, (match, open, url, close) => {
3942
+ const local = rewrites.get(url);
3943
+ return local ? `${open}${local}${close}` : match;
3857
3944
  }).replaceAll(FENCE_TOKEN, (token, index) => fences[Number(index)] ?? token);
3858
3945
  return { diagnostics, markdown: rewritten };
3859
3946
  };
@@ -3886,6 +3973,7 @@ var MAX_RETRIES = 4;
3886
3973
  var BASE_DELAY_MS = 500;
3887
3974
  var SECOND_MS2 = 1000;
3888
3975
  var DEFAULT_CONCURRENCY = 3;
3976
+ var ASSET_DOWNLOAD_CONCURRENCY = 4;
3889
3977
  var withNotionRetry = async (call, attempt = 0) => {
3890
3978
  try {
3891
3979
  return await call();
@@ -3911,6 +3999,25 @@ var LIST_BLOCKS = new Set([
3911
3999
  "to_do"
3912
4000
  ]);
3913
4001
  var isListItem = (block) => block !== undefined && LIST_BLOCKS.has(block.type);
4002
+ var richToPlain = (rich = []) => rich.map((node) => node.plain_text).join("");
4003
+ var jsxString = (value) => `{${JSON.stringify(value)}}`;
4004
+ var YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
4005
+ var isYouTubeUrl = (url) => {
4006
+ const host = URL.parse(url)?.hostname ?? "";
4007
+ return YOUTUBE_HOST.test(host) && parseYouTubeId(url) !== null;
4008
+ };
4009
+ var renderVideo = (data) => {
4010
+ const url = data.external?.url ?? data.file?.url;
4011
+ if (!url) {
4012
+ return "";
4013
+ }
4014
+ const caption = richToMarkdown(data.caption);
4015
+ const title = caption ? ` title=${jsxString(richToPlain(data.caption))}` : "";
4016
+ const media = isYouTubeUrl(url) ? `<YouTube${title} url=${JSON.stringify(url)} />` : `<video controls src=${JSON.stringify(url)} />`;
4017
+ return caption ? `<Frame caption=${jsxString(caption)}>
4018
+ ${media}
4019
+ </Frame>` : media;
4020
+ };
3914
4021
  var renderLeaf = (block) => {
3915
4022
  const data = payloadOf(block) ?? {};
3916
4023
  const text = richToMarkdown(blockField(block));
@@ -3951,6 +4058,9 @@ ${text}
3951
4058
  const url = data.external?.url ?? data.file?.url;
3952
4059
  return url ? `![${richToMarkdown(data.caption)}](${url})` : "";
3953
4060
  }
4061
+ case "video": {
4062
+ return renderVideo(data);
4063
+ }
3954
4064
  default: {
3955
4065
  return null;
3956
4066
  }
@@ -3959,6 +4069,7 @@ ${text}
3959
4069
  var notionSource = (options, ctx) => {
3960
4070
  const props = options.properties ?? {};
3961
4071
  const limit = pLimit(Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY));
4072
+ const downloads = pLimit(ASSET_DOWNLOAD_CONCURRENCY);
3962
4073
  const notionCall = (call) => withNotionRetry(() => limit(call));
3963
4074
  const cache = snapshotCache(ctx?.cacheDir ?? join6(".blume", "cache", options.name));
3964
4075
  const assetsDir = ctx?.assetsDir ?? join6(".blume", "public", "blume-assets", options.name);
@@ -3998,7 +4109,7 @@ ${body}
3998
4109
  </Callout>`;
3999
4110
  }
4000
4111
  if (block.type === "toggle") {
4001
- const title = JSON.stringify(richToMarkdown(blockField(block)));
4112
+ const title = jsxString(richToMarkdown(blockField(block)));
4002
4113
  return `<Accordion>
4003
4114
  <AccordionItem title=${title}>
4004
4115
  ${await children(block)}
@@ -4093,13 +4204,14 @@ ${nested}`;
4093
4204
  const slug = slugifyPath(slugProp || title) || page.id;
4094
4205
  return { data, slug };
4095
4206
  };
4096
- const toEntry2 = async (client, page) => {
4207
+ const toEntry = async (client, page) => {
4097
4208
  const { data, slug } = frontmatter(page);
4098
4209
  const mdx = await renderBlocks(client, await childrenOf(client, page.id));
4099
4210
  const assets = await materializeAssets(mdx, {
4100
4211
  assetsBaseUrl,
4101
4212
  assetsDir,
4102
- fetchImpl: options.fetchImpl
4213
+ fetchImpl: options.fetchImpl,
4214
+ limit: downloads
4103
4215
  });
4104
4216
  const raw = frontmatter_default.stringify(assets.markdown, data);
4105
4217
  return {
@@ -4130,13 +4242,13 @@ ${nested}`;
4130
4242
  data_source_id: dataSourceId,
4131
4243
  start_cursor: cursor
4132
4244
  }));
4133
- const load2 = async (refresh = ctx?.refresh ?? true) => {
4245
+ const load = async (refresh = ctx?.refresh ?? true) => {
4134
4246
  const assetDiagnostics = [];
4135
4247
  const result = await loadWithCache(options.name, cache, async () => {
4136
4248
  const client = await resolveClient();
4137
4249
  const dataSourceId = await resolveDataSource(client);
4138
4250
  const pages = await collectAll((cursor) => queryDataSource(client, dataSourceId, cursor));
4139
- const built = await Promise.all(pages.map((page) => toEntry2(client, page)));
4251
+ const built = await Promise.all(pages.map((page) => toEntry(client, page)));
4140
4252
  for (const item of built) {
4141
4253
  assetDiagnostics.push(...item.diagnostics);
4142
4254
  }
@@ -4157,18 +4269,18 @@ ${nested}`;
4157
4269
  return all.find((e) => e.ref === ref)?.raw ?? "";
4158
4270
  };
4159
4271
  return {
4160
- load: load2,
4272
+ load,
4161
4273
  name: options.name,
4162
4274
  prefix: options.prefix,
4163
4275
  read,
4164
4276
  staged: true,
4165
- watch: options.pollInterval ? pollingWatch(() => load2(true), options.pollInterval, () => load2()) : undefined
4277
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
4166
4278
  };
4167
4279
  };
4168
4280
 
4169
4281
  // src/core/sources/obsidian.ts
4170
4282
  import { existsSync as existsSync3, watch as fsWatch2, statSync } from "node:fs";
4171
- import { readdir, readFile as readFile5, stat } from "node:fs/promises";
4283
+ import { readdir as readdir2, readFile as readFile5, stat } from "node:fs/promises";
4172
4284
  import { basename as basename2, isAbsolute as isAbsolute3, join as join7, relative as relative5, resolve as resolve3 } from "pathe";
4173
4285
  var MARKDOWN_FILE = /\.md$/iu;
4174
4286
  var OBSIDIAN_COMMENT = /%%.*?%%/gu;
@@ -4528,7 +4640,7 @@ var classify = async (dir, entry) => {
4528
4640
  var byVaultOrder = (a, b) => Number(b.directory) - Number(a.directory) || a.name.localeCompare(b.name, "en", { numeric: true, sensitivity: "base" }) || (a.name < b.name ? -1 : 1);
4529
4641
  var walkVault = async (dir, root, exclude) => {
4530
4642
  const found = [];
4531
- const listed = await readdir(dir, { withFileTypes: true });
4643
+ const listed = await readdir2(dir, { withFileTypes: true });
4532
4644
  const entries = await Promise.all(listed.filter((entry) => !(isHidden(entry.name) || exclude.has(entry.name))).map((entry) => classify(dir, entry)));
4533
4645
  for (const entry of entries.toSorted(byVaultOrder)) {
4534
4646
  const full = join7(dir, entry.name);
@@ -4573,10 +4685,10 @@ var unresolvedDiagnostics = (name, unresolved) => {
4573
4685
  var obsidianSource = (options, ctx) => {
4574
4686
  const vaultDir = resolve3(ctx.projectRoot, options.vault);
4575
4687
  const exclude = new Set([...BLUME_IGNORE_DIRS, ...options.exclude ?? []]);
4576
- const load2 = async () => {
4688
+ const load = async () => {
4577
4689
  const files = await walkVault(vaultDir, vaultDir, exclude);
4578
- const read2 = await Promise.all(files.map((rel) => readNote(vaultDir, rel, options)));
4579
- const notes = read2.filter((note) => note !== null);
4690
+ const read = await Promise.all(files.map((rel) => readNote(vaultDir, rel, options)));
4691
+ const notes = read.filter((note) => note !== null);
4580
4692
  const { index, pairs } = buildLinkIndex(notes, options);
4581
4693
  const unresolved = noTargets();
4582
4694
  const entries = pairs.map((pair) => noteToEntry(pair, index, options, unresolved));
@@ -4622,7 +4734,7 @@ var obsidianSource = (options, ctx) => {
4622
4734
  };
4623
4735
  return {
4624
4736
  contentRoot: vaultDir,
4625
- load: load2,
4737
+ load,
4626
4738
  name: options.name,
4627
4739
  prefix: options.prefix,
4628
4740
  read,
@@ -4780,7 +4892,7 @@ var sanitySource = (options, ctx) => {
4780
4892
  const fields = options.fields ?? {};
4781
4893
  const cache = snapshotCache(ctx?.cacheDir ?? join8(".blume", "cache", options.name));
4782
4894
  let snapshot = new Map;
4783
- const toEntry2 = (doc) => {
4895
+ const toEntry = (doc) => {
4784
4896
  const slugValue = asString(getPath(doc, fields.slug ?? "slug.current")) ?? asString(doc._id) ?? "untitled";
4785
4897
  const slug = slugifyPath(slugValue) || slugify2(asString(doc._id) ?? "") || "untitled";
4786
4898
  const data = {};
@@ -4810,11 +4922,11 @@ var sanitySource = (options, ctx) => {
4810
4922
  ref: `${slug}.md`
4811
4923
  };
4812
4924
  };
4813
- const load2 = async (refresh = ctx?.refresh ?? true) => {
4925
+ const load = async (refresh = ctx?.refresh ?? true) => {
4814
4926
  const result = await loadWithCache(options.name, cache, async () => {
4815
4927
  const client = await resolveClient(options, ctx?.preview ?? false);
4816
4928
  const docs = await client.fetch(options.query);
4817
- return docs.map(toEntry2);
4929
+ return docs.map(toEntry);
4818
4930
  }, refresh);
4819
4931
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
4820
4932
  return result;
@@ -4828,12 +4940,12 @@ var sanitySource = (options, ctx) => {
4828
4940
  return all.find((e) => e.ref === ref)?.raw ?? "";
4829
4941
  };
4830
4942
  return {
4831
- load: load2,
4943
+ load,
4832
4944
  name: options.name,
4833
4945
  prefix: options.prefix,
4834
4946
  read,
4835
4947
  staged: true,
4836
- watch: options.pollInterval ? pollingWatch(() => load2(true), options.pollInterval, () => load2()) : undefined
4948
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
4837
4949
  };
4838
4950
  };
4839
4951
 
@@ -5164,7 +5276,7 @@ var scanProject = async (root, options = {}) => {
5164
5276
  };
5165
5277
  };
5166
5278
 
5167
- export { YAML_SCHEMA, frontmatter_default, TOC_HIDDEN_KEY, parseHeadingMarkers, BLUME_IGNORE_DIRS, i18nEnabled, localeCodes, resolveFallbackLocale, localePrefix, localizeRoute, localeTargetPath, validateSearchPopularIcons, validateNavTargets, VERSION_LIKE, versionizeRoute, nextFenceState, HTML_COMMENT, INLINE_CODE, scanBody, MD_IMAGE, targetOffsetIn, hasIncludeStatements, expandIncludes, buildIncludeGraph, contentIndexable, buildManifest, hashText, specOf, operationOf, specAddresses, apiNamePhrase, operationSignature, isOpenApiSource, resolveDocsCollection, isWithin, scanProject };
5279
+ export { YAML_SCHEMA, frontmatter_default, TOC_HIDDEN_KEY, parseHeadingMarkers, BLUME_IGNORE_DIRS, i18nEnabled, localeCodes, resolveFallbackLocale, localePrefix, localizeRoute, localeTargetPath, validateSearchPopularIcons, validateNavTargets, VERSION_LIKE, versionizeRoute, nextFenceState, HTML_COMMENT, INLINE_CODE, scanBody, MD_IMAGE, targetOffsetIn, hasIncludeStatements, expandIncludes, buildIncludeGraph, contentIndexable, buildManifest, hashText, specOf, operationOf, specAddresses, apiNamePhrase, operationSignature, isOpenApiSource, parseYouTubeId, resolveDocsCollection, isWithin, scanProject };
5168
5280
 
5169
- //# debugId=3D5AE0D317E6447664756E2164756E21
5170
- //# sourceMappingURL=chunk-s102bysw.js.map
5281
+ //# debugId=FDD686362F0089E964756E2164756E21
5282
+ //# sourceMappingURL=chunk-hdm2dkd2.js.map