@abreen/tada 1.11.3 → 1.13.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 (79) hide show
  1. package/README.md +65 -64
  2. package/bin/tada.ts +61 -66
  3. package/build/build-manifest.ts +14 -3
  4. package/build/bundle.ts +0 -1
  5. package/build/config-files.ts +183 -0
  6. package/build/config-loader.ts +159 -0
  7. package/build/copy.ts +0 -83
  8. package/build/generate-content-assets.ts +75 -160
  9. package/build/globals.ts +48 -89
  10. package/build/log.ts +27 -18
  11. package/build/pagefind.ts +33 -26
  12. package/build/pipeline.ts +98 -79
  13. package/build/reachability.ts +51 -248
  14. package/build/serve.ts +35 -6
  15. package/build/site-variables.ts +20 -11
  16. package/build/source-model.ts +400 -0
  17. package/build/{watch/records.ts → source-records.ts} +111 -49
  18. package/build/template-globals.ts +91 -0
  19. package/build/templates.ts +50 -40
  20. package/build/test-helpers.ts +15 -0
  21. package/build/toc-plugin.ts +5 -1
  22. package/build/types.d.ts +9 -1
  23. package/build/util.ts +8 -0
  24. package/build/utils/code.ts +6 -16
  25. package/build/utils/content-files.ts +57 -126
  26. package/build/utils/final-html.ts +239 -0
  27. package/build/utils/markdown.ts +237 -17
  28. package/build/utils/paths.ts +2 -1
  29. package/build/utils/render.ts +108 -99
  30. package/build/validate-config-links.ts +91 -14
  31. package/build/watch/assets.ts +0 -2
  32. package/build/watch/build-full.ts +14 -15
  33. package/build/watch/build-incremental.ts +15 -8
  34. package/build/watch/compiler-types.d.ts +6 -1
  35. package/build/watch/compiler.ts +27 -15
  36. package/build/watch/index.ts +2 -7
  37. package/build/watch/planner.ts +32 -16
  38. package/build/watch/reload.ts +4 -0
  39. package/build/watch/runtime.ts +29 -45
  40. package/build/watch/snapshot.ts +50 -402
  41. package/build/watch/validation.ts +6 -3
  42. package/build/watch-reachability-state.ts +16 -23
  43. package/build/watch-reload-client.ts +11 -4
  44. package/init/content/index.md +1 -1
  45. package/init/content/labs/02/index.md +61 -0
  46. package/init/content/labs/02/selection_sort.py +23 -0
  47. package/init/content/labs/index.md +4 -2
  48. package/init/content/lectures/01/index.md +1 -1
  49. package/init/content/markdown.md +1 -1
  50. package/package.json +3 -4
  51. package/src/code/index.ts +46 -18
  52. package/src/code.scss +2 -1
  53. package/src/globals.ts +47 -0
  54. package/src/index.ts +1 -0
  55. package/src/navigate/index.ts +2 -5
  56. package/src/navigate/lifecycle.ts +3 -1
  57. package/src/navigate/runtime.ts +6 -5
  58. package/src/page-update/index.ts +20 -16
  59. package/src/search/index.ts +53 -25
  60. package/src/slides/index.ts +467 -0
  61. package/src/slides/style.scss +104 -0
  62. package/src/style.scss +8 -3
  63. package/src/timezone/index.ts +16 -20
  64. package/src/top/index.ts +2 -4
  65. package/src/trace/index.ts +91 -7
  66. package/src/trace/style.scss +1 -1
  67. package/src/util.ts +6 -2
  68. package/templates/_author.html +2 -2
  69. package/templates/_heading.html +1 -1
  70. package/templates/_nav.html +2 -2
  71. package/templates/_theme.scss +3 -3
  72. package/templates/_top.html +14 -14
  73. package/templates/default.html +8 -0
  74. package/types/dev.d.ts +1 -1
  75. package/types/site-variables.d.ts +4 -5
  76. package/watch/fs-commit.ts +63 -10
  77. package/build/apply-base-path-plugin.ts +0 -186
  78. package/build/content-watch.ts +0 -104
  79. package/build/validate-internal-links-plugin.ts +0 -207
@@ -0,0 +1,159 @@
1
+ import fs from 'fs';
2
+ import _ from 'lodash';
3
+ import {
4
+ resolveProjectConfigFile,
5
+ resolveSiteConfigFile,
6
+ type ConfigName,
7
+ type ProjectConfigName,
8
+ type ResolvedProjectConfigFile,
9
+ type SiteConfigName,
10
+ type SiteEnv,
11
+ } from './config-files';
12
+ import type { SiteVariables } from './types';
13
+
14
+ export interface LoadedProjectConfigFile<
15
+ T = unknown,
16
+ Name extends ConfigName = ConfigName,
17
+ > extends ResolvedProjectConfigFile<Name> {
18
+ value: T;
19
+ }
20
+
21
+ interface ConfigTemplateContext {
22
+ vars: Record<string, unknown>;
23
+ site: SiteVariables;
24
+ }
25
+
26
+ const EXACT_INTERPOLATION_RE = /^\s*<%=\s*([\s\S]+?)\s*%>\s*$/;
27
+
28
+ export function parseConfigText(text: string, fileName: string): unknown {
29
+ try {
30
+ return Bun.YAML.parse(text);
31
+ } catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ throw new Error(`${fileName}: ${message}`, { cause: error });
34
+ }
35
+ }
36
+
37
+ function getTemplateErrorMessage(error: unknown): string {
38
+ return error instanceof Error ? error.message : String(error);
39
+ }
40
+
41
+ function renderConfigTemplateString(
42
+ value: string,
43
+ context: ConfigTemplateContext,
44
+ fileName: string,
45
+ ): string {
46
+ try {
47
+ return _.template(value)(context);
48
+ } catch (error) {
49
+ throw new Error(
50
+ `${fileName}: Lodash template error in config: ${getTemplateErrorMessage(error)}`,
51
+ { cause: error },
52
+ );
53
+ }
54
+ }
55
+
56
+ function evaluateConfigTemplateExpression(
57
+ expression: string,
58
+ context: ConfigTemplateContext,
59
+ fileName: string,
60
+ ): unknown {
61
+ let captured: unknown;
62
+
63
+ try {
64
+ _.template(`<% __capture(( ${expression} )); %>`)({
65
+ ...context,
66
+ __capture(value: unknown) {
67
+ captured = value;
68
+ },
69
+ });
70
+ } catch (error) {
71
+ throw new Error(
72
+ `${fileName}: Lodash template error in config: ${getTemplateErrorMessage(error)}`,
73
+ { cause: error },
74
+ );
75
+ }
76
+
77
+ return captured;
78
+ }
79
+
80
+ function interpolateConfigValue(
81
+ value: unknown,
82
+ context: ConfigTemplateContext,
83
+ fileName: string,
84
+ ): unknown {
85
+ if (typeof value === 'string') {
86
+ if (!value.includes('<%')) {
87
+ return value;
88
+ }
89
+
90
+ const exactInterpolation = value.match(EXACT_INTERPOLATION_RE);
91
+ if (exactInterpolation) {
92
+ return evaluateConfigTemplateExpression(
93
+ exactInterpolation[1],
94
+ context,
95
+ fileName,
96
+ );
97
+ }
98
+
99
+ return renderConfigTemplateString(value, context, fileName);
100
+ }
101
+
102
+ if (Array.isArray(value)) {
103
+ return value.map(item => interpolateConfigValue(item, context, fileName));
104
+ }
105
+
106
+ if (value && typeof value === 'object') {
107
+ return Object.fromEntries(
108
+ Object.entries(value).map(([key, item]) => [
109
+ key,
110
+ interpolateConfigValue(item, context, fileName),
111
+ ]),
112
+ );
113
+ }
114
+
115
+ return value;
116
+ }
117
+
118
+ export function loadResolvedConfigFile<
119
+ T = unknown,
120
+ Name extends ConfigName = ConfigName,
121
+ >(
122
+ resolved: ResolvedProjectConfigFile<Name>,
123
+ {
124
+ interpolate = false,
125
+ siteVariables,
126
+ }: { interpolate?: boolean; siteVariables?: SiteVariables } = {},
127
+ ): LoadedProjectConfigFile<T, Name> {
128
+ const rawText = fs.readFileSync(resolved.filePath, 'utf-8');
129
+ const parsed = parseConfigText(rawText, resolved.fileName);
130
+ const value = interpolate
131
+ ? interpolateConfigValue(
132
+ parsed,
133
+ { vars: siteVariables?.vars || {}, site: siteVariables! },
134
+ resolved.fileName,
135
+ )
136
+ : parsed;
137
+
138
+ return { ...resolved, value: value as T };
139
+ }
140
+
141
+ export function loadSiteConfig(
142
+ projectDir: string,
143
+ env: SiteEnv,
144
+ ): LoadedProjectConfigFile<unknown, SiteConfigName> {
145
+ const resolved = resolveSiteConfigFile(projectDir, env);
146
+ return loadResolvedConfigFile(resolved);
147
+ }
148
+
149
+ export function loadProjectConfig(
150
+ projectDir: string,
151
+ name: ProjectConfigName,
152
+ siteVariables: SiteVariables,
153
+ ): LoadedProjectConfigFile<unknown, ProjectConfigName> | undefined {
154
+ const resolved = resolveProjectConfigFile(projectDir, name);
155
+ if (!resolved) {
156
+ return undefined;
157
+ }
158
+ return loadResolvedConfigFile(resolved, { interpolate: true, siteVariables });
159
+ }
package/build/copy.ts CHANGED
@@ -80,86 +80,3 @@ export function copyContentAssets(
80
80
  }
81
81
  return contentAssetRelPaths;
82
82
  }
83
-
84
- function copySingleFile(
85
- sourceDir: string,
86
- distDir: string,
87
- filePath: string,
88
- sourceLabel: string,
89
- conflictLabel: string,
90
- conflictSet?: Set<string>,
91
- ): void {
92
- const rel = toPosix(path.relative(sourceDir, filePath));
93
- if (conflictSet?.has(rel)) {
94
- log.error`${sourceLabel}/${B`${rel}`} conflicts with ${conflictLabel}/${B`${rel}`}`;
95
- throw new Error(
96
- `${sourceLabel}/${rel} and ${conflictLabel}/${rel} have the same path`,
97
- );
98
- }
99
- const dest = path.join(distDir, rel);
100
- fs.mkdirSync(path.dirname(dest), { recursive: true });
101
- fs.copyFileSync(filePath, dest);
102
- log.info`Copying ${sourceLabel} file ${B`${rel}`}`;
103
- }
104
-
105
- export function deleteOutputPath(distDir: string, relPath: string): void {
106
- const root = path.resolve(distDir);
107
- const target = path.resolve(distDir, relPath);
108
- const relative = path.relative(root, target);
109
- if (
110
- relative.startsWith('..') ||
111
- path.isAbsolute(relative) ||
112
- relative.length === 0
113
- ) {
114
- throw new Error(`refusing to delete path outside dist/: ${relPath}`);
115
- }
116
-
117
- fs.rmSync(target, { force: true });
118
- log.info`Removing output ${B`${toPosix(relPath)}`}`;
119
-
120
- let currentDir = path.dirname(target);
121
- while (currentDir !== root) {
122
- try {
123
- fs.rmdirSync(currentDir);
124
- } catch (err) {
125
- const code = (err as NodeJS.ErrnoException).code;
126
- if (code === 'ENOTEMPTY' || code === 'ENOENT') {
127
- break;
128
- }
129
- throw err;
130
- }
131
- currentDir = path.dirname(currentDir);
132
- }
133
- }
134
-
135
- export function copyPublicFile(
136
- publicDir: string,
137
- distDir: string,
138
- filePath: string,
139
- contentAssetRelPaths?: Set<string>,
140
- ): void {
141
- copySingleFile(
142
- publicDir,
143
- distDir,
144
- filePath,
145
- 'public',
146
- 'content',
147
- contentAssetRelPaths,
148
- );
149
- }
150
-
151
- export function copyContentFile(
152
- contentDir: string,
153
- distDir: string,
154
- filePath: string,
155
- publicRelPaths?: Set<string>,
156
- ): void {
157
- copySingleFile(
158
- contentDir,
159
- distDir,
160
- filePath,
161
- 'content',
162
- 'public',
163
- publicRelPaths,
164
- );
165
- }
@@ -1,39 +1,33 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { makeLogger } from './log';
4
- import {
5
- getExtensionToShikiLanguage,
6
- getRuntimeBundledShikiLanguages,
7
- } from './site-variables';
4
+ import { getRuntimeBundledShikiLanguages } from './site-variables';
5
+ import { createContentRecord } from './source-records';
8
6
  import { validateConfigLinks } from './validate-config-links';
9
- import { json } from './templates';
7
+ import { config, getConfigFileName } from './templates';
10
8
  import { initHighlighter } from './utils/shiki-highlighter';
11
- import {
12
- getBuildContentFiles,
13
- getContentDir,
14
- getValidInternalTargets,
15
- renderCodePageAsset,
16
- renderCopiedContentAsset,
17
- renderLiterateJavaPageAsset,
18
- renderPlainTextPageAsset,
19
- } from './util';
20
- import { isLiterateJava } from './utils/file-types';
21
9
  import { checkTraceToolAvailability, isTraceSourceFile } from './utils/trace';
22
- import { toPosix } from './utils/paths';
23
10
  import type {
24
11
  SiteVariables,
25
- Asset,
26
12
  ContentRenderOptions,
27
13
  ContentRenderResult,
14
+ HtmlOutputAnalysis,
28
15
  TraceToolAvailability,
29
16
  WatchState,
30
17
  } from './types';
18
+ import type { TadaSourceRecord } from './watch/snapshot';
31
19
 
32
20
  const log = makeLogger(import.meta.url);
33
21
 
22
+ function cloneHtmlOutputAnalysis(
23
+ analysis: HtmlOutputAnalysis,
24
+ ): HtmlOutputAnalysis {
25
+ return { outgoingTargets: new Set(analysis.outgoingTargets) };
26
+ }
27
+
34
28
  export class ContentRenderer {
35
29
  private siteVariables: SiteVariables;
36
- private sourceFileCache: Map<string, Asset[]>;
30
+ private sourceFileCache: Map<string, TadaSourceRecord>;
37
31
  private lastBuildFiles: Set<string>;
38
32
  private javacAvailable: boolean | undefined;
39
33
  private traceToolAvailability: TraceToolAvailability | undefined;
@@ -53,8 +47,8 @@ export class ContentRenderer {
53
47
  this.lastBuildFiles = new Set();
54
48
  }
55
49
 
56
- private getCachedAssets(filePath: string): Asset[] {
57
- return this.sourceFileCache.get(filePath) ?? [];
50
+ private getCachedRecord(filePath: string): TadaSourceRecord | undefined {
51
+ return this.sourceFileCache.get(filePath);
58
52
  }
59
53
 
60
54
  async initHighlighter(): Promise<void> {
@@ -91,124 +85,47 @@ export class ContentRenderer {
91
85
  return dirtySourceFiles;
92
86
  }
93
87
 
94
- isCopiedAssetSource(filePath: string): boolean {
95
- const ext = path.extname(filePath).toLowerCase();
96
- const codeExtensions = getExtensionToShikiLanguage(this.siteVariables);
97
- return Object.prototype.hasOwnProperty.call(codeExtensions, ext.slice(1));
98
- }
99
-
100
- renderSourceAssets(
101
- filePath: string,
102
- contentDir: string,
103
- distDir: string,
104
- validInternalTargets: Set<string>,
105
- assetFiles: string[],
106
- literateJavaOutputPaths: Set<string>,
107
- ): Asset[] {
108
- const ext = path.extname(filePath).toLowerCase();
109
- const assets: Asset[] = [];
110
-
111
- if (isLiterateJava(filePath)) {
112
- assets.push(
113
- ...renderLiterateJavaPageAsset({
114
- filePath,
115
- contentDir,
116
- distDir,
117
- siteVariables: this.siteVariables,
118
- assetFiles,
119
- skipExecution: !this.javacAvailable,
120
- validInternalTargets,
121
- literateJavaOutputPaths,
122
- }),
123
- );
124
- return assets;
125
- }
126
-
127
- if (ext === '.html' || ext === '.md' || ext === '.markdown') {
128
- assets.push(
129
- ...renderPlainTextPageAsset({
130
- filePath,
131
- contentDir,
132
- distDir,
133
- siteVariables: this.siteVariables,
134
- validInternalTargets,
135
- assetFiles,
136
- literateJavaOutputPaths,
137
- traceCache: this.traceCache,
138
- traceToolAvailability: this.traceToolAvailability,
139
- }),
140
- );
141
- return assets;
142
- }
143
-
144
- const codeExtensions = getExtensionToShikiLanguage(this.siteVariables);
145
- if (ext.slice(1) in codeExtensions) {
146
- assets.push(
147
- ...renderCodePageAsset({
148
- filePath,
149
- contentDir,
150
- distDir,
151
- siteVariables: this.siteVariables,
152
- validInternalTargets,
153
- assetFiles,
154
- literateJavaOutputPaths,
155
- }),
156
- );
157
- }
158
-
159
- return assets;
160
- }
161
-
162
- renderCopiedSourceAssets(filePath: string, contentDir: string): Asset[] {
163
- if (!this.isCopiedAssetSource(filePath)) {
164
- return [];
165
- }
166
-
167
- return renderCopiedContentAsset({
168
- filePath,
169
- contentDir,
170
- siteVariables: this.siteVariables,
171
- });
172
- }
173
-
174
88
  updateSourceCache(
175
89
  filePath: string,
176
- assets: Asset[],
90
+ record: TadaSourceRecord,
177
91
  changedOutputRelPaths: Set<string>,
178
92
  removedOutputRelPaths: Set<string>,
179
93
  changedHtmlAssetPaths: Set<string>,
180
94
  removedHtmlAssetPaths: Set<string>,
181
95
  ): void {
182
- const previousAssets = this.getCachedAssets(filePath);
183
- const nextAssetPaths = new Set(assets.map(asset => asset.assetPath));
96
+ const previousRecord = this.getCachedRecord(filePath);
97
+ const nextOutputPaths = new Set(record.outputs.keys());
184
98
 
185
- for (const asset of previousAssets) {
186
- if (nextAssetPaths.has(asset.assetPath)) {
99
+ for (const outputPath of previousRecord?.outputs.keys() ?? []) {
100
+ if (nextOutputPaths.has(outputPath)) {
187
101
  continue;
188
102
  }
189
- removedOutputRelPaths.add(asset.assetPath);
190
- if (asset.assetPath.endsWith('.html')) {
191
- removedHtmlAssetPaths.add(asset.assetPath);
103
+ removedOutputRelPaths.add(outputPath);
104
+ if (outputPath.endsWith('.html')) {
105
+ removedHtmlAssetPaths.add(outputPath);
192
106
  }
193
107
  }
194
108
 
195
- for (const asset of assets) {
196
- changedOutputRelPaths.add(asset.assetPath);
197
- if (asset.assetPath.endsWith('.html')) {
198
- changedHtmlAssetPaths.add(asset.assetPath);
109
+ for (const outputPath of record.outputs.keys()) {
110
+ changedOutputRelPaths.add(outputPath);
111
+ if (outputPath.endsWith('.html')) {
112
+ changedHtmlAssetPaths.add(outputPath);
199
113
  }
200
114
  }
201
115
 
202
- this.sourceFileCache.set(filePath, assets);
116
+ this.sourceFileCache.set(filePath, record);
203
117
  }
204
118
 
205
119
  writeCachedAssets(distDir: string, buildContentFiles: string[]): void {
206
120
  for (const filePath of buildContentFiles) {
207
- const assets = this.getCachedAssets(filePath);
208
- for (const asset of assets) {
209
- const outPath = path.join(distDir, asset.assetPath);
121
+ const record = this.getCachedRecord(filePath);
122
+ if (!record) {
123
+ continue;
124
+ }
125
+ for (const [outputPath, content] of record.outputs) {
126
+ const outPath = path.join(distDir, outputPath);
210
127
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
211
- fs.writeFileSync(outPath, asset.content);
128
+ fs.writeFileSync(outPath, content);
212
129
  }
213
130
  }
214
131
  }
@@ -223,11 +140,11 @@ export class ContentRenderer {
223
140
  continue;
224
141
  }
225
142
 
226
- const previousAssets = this.getCachedAssets(filePath);
227
- for (const asset of previousAssets) {
228
- removedOutputRelPaths.add(asset.assetPath);
229
- if (asset.assetPath.endsWith('.html')) {
230
- removedHtmlAssetPaths.add(asset.assetPath);
143
+ const previousRecord = this.getCachedRecord(filePath);
144
+ for (const outputPath of previousRecord?.outputs.keys() ?? []) {
145
+ removedOutputRelPaths.add(outputPath);
146
+ if (outputPath.endsWith('.html')) {
147
+ removedHtmlAssetPaths.add(outputPath);
231
148
  }
232
149
  }
233
150
  this.sourceFileCache.delete(filePath);
@@ -237,13 +154,10 @@ export class ContentRenderer {
237
154
  processContent({
238
155
  distDir,
239
156
  assetFiles,
157
+ scan,
240
158
  watchState,
241
159
  }: ContentRenderOptions): ContentRenderResult {
242
- const contentDir: string = getContentDir();
243
- const buildContentFiles: string[] = getBuildContentFiles(
244
- contentDir,
245
- Object.keys(getExtensionToShikiLanguage(this.siteVariables)),
246
- );
160
+ const buildContentFiles = [...scan.buildContentFiles];
247
161
  const buildFileSet = new Set<string>(buildContentFiles);
248
162
  const dirtySourceFiles = this.getDirtySourceFiles(
249
163
  buildContentFiles,
@@ -253,26 +167,18 @@ export class ContentRenderer {
253
167
  const removedOutputRelPaths = new Set<string>();
254
168
  const changedHtmlAssetPaths = new Set<string>();
255
169
  const removedHtmlAssetPaths = new Set<string>();
256
- const validInternalTargets = getValidInternalTargets(
257
- contentDir,
258
- buildContentFiles,
259
- Object.keys(getExtensionToShikiLanguage(this.siteVariables)),
260
- );
261
- const literateJavaOutputPaths = new Set<string>();
262
- for (const filePath of buildContentFiles) {
263
- if (isLiterateJava(filePath)) {
264
- const parsed = path.parse(path.relative(contentDir, filePath));
265
- const javaPath = toPosix(path.join(parsed.dir, parsed.name));
266
- literateJavaOutputPaths.add(`/${javaPath}`);
267
- }
268
- }
170
+ const validInternalTargets = scan.validTargets;
269
171
 
270
172
  const errors: Error[] = [];
271
173
 
272
174
  const configLinkErrors = validateConfigLinks(
273
175
  validInternalTargets,
274
- json('nav.json'),
275
- json('authors.json'),
176
+ config('nav'),
177
+ config('authors'),
178
+ {
179
+ navFileName: getConfigFileName('nav'),
180
+ authorsFileName: getConfigFileName('authors'),
181
+ },
276
182
  );
277
183
  for (const msg of configLinkErrors) {
278
184
  errors.push(new Error(msg));
@@ -298,26 +204,25 @@ export class ContentRenderer {
298
204
  }
299
205
 
300
206
  for (const filePath of dirtySourceFiles) {
301
- let assets: Asset[];
207
+ let record: TadaSourceRecord;
302
208
  try {
303
- assets = [
304
- ...this.renderSourceAssets(
305
- filePath,
306
- contentDir,
307
- distDir,
308
- validInternalTargets,
309
- assetFiles,
310
- literateJavaOutputPaths,
311
- ),
312
- ...this.renderCopiedSourceAssets(filePath, contentDir),
313
- ];
209
+ record = createContentRecord({
210
+ filePath,
211
+ siteVariables: this.siteVariables,
212
+ scan,
213
+ assetFiles,
214
+ outputDir: distDir,
215
+ traceCache: this.traceCache,
216
+ traceToolAvailability: this.traceToolAvailability,
217
+ skipLiterateJavaExecution: !this.javacAvailable,
218
+ });
314
219
  } catch (err) {
315
220
  errors.push(err as Error);
316
221
  continue;
317
222
  }
318
223
  this.updateSourceCache(
319
224
  filePath,
320
- assets,
225
+ record,
321
226
  changedOutputRelPaths,
322
227
  removedOutputRelPaths,
323
228
  changedHtmlAssetPaths,
@@ -328,13 +233,22 @@ export class ContentRenderer {
328
233
  this.writeCachedAssets(distDir, buildContentFiles);
329
234
  this.lastBuildFiles = buildFileSet;
330
235
 
331
- // Collect HTML asset content for Pagefind
236
+ // Collect HTML asset content and analysis for Pagefind
332
237
  const htmlAssetsByPath = new Map<string, string>();
238
+ const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
333
239
  for (const filePath of buildContentFiles) {
334
- const assets = this.getCachedAssets(filePath);
335
- for (const asset of assets) {
336
- if (asset.assetPath.endsWith('.html')) {
337
- htmlAssetsByPath.set(asset.assetPath, asset.content as string);
240
+ const record = this.getCachedRecord(filePath);
241
+ if (!record) {
242
+ continue;
243
+ }
244
+ for (const [outputPath, content] of record.outputs) {
245
+ if (!outputPath.endsWith('.html') || typeof content !== 'string') {
246
+ continue;
247
+ }
248
+ htmlAssetsByPath.set(outputPath, content);
249
+ const analysis = record.htmlAnalysisByOutputPath?.get(outputPath);
250
+ if (analysis) {
251
+ htmlAnalysisByPath.set(outputPath, cloneHtmlOutputAnalysis(analysis));
338
252
  }
339
253
  }
340
254
  }
@@ -345,6 +259,7 @@ export class ContentRenderer {
345
259
  removedHtmlAssetPaths,
346
260
  removedOutputRelPaths,
347
261
  htmlAssetsByPath,
262
+ htmlAnalysisByPath,
348
263
  buildContentFiles,
349
264
  };
350
265
  }