@abreen/tada 1.11.3 → 1.13.4

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 (85) 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/utils/trace-core.ts +3 -7
  31. package/build/validate-config-links.ts +91 -14
  32. package/build/watch/assets.ts +0 -2
  33. package/build/watch/build-full.ts +14 -15
  34. package/build/watch/build-incremental.ts +15 -8
  35. package/build/watch/compiler-types.d.ts +6 -1
  36. package/build/watch/compiler.ts +27 -15
  37. package/build/watch/index.ts +2 -7
  38. package/build/watch/planner.ts +32 -16
  39. package/build/watch/reload.ts +4 -0
  40. package/build/watch/runtime.ts +29 -45
  41. package/build/watch/snapshot.ts +50 -402
  42. package/build/watch/validation.ts +6 -3
  43. package/build/watch-reachability-state.ts +16 -23
  44. package/build/watch-reload-client.ts +11 -4
  45. package/init/content/index.md +1 -1
  46. package/init/content/labs/02/index.md +61 -0
  47. package/init/content/labs/02/selection_sort.py +23 -0
  48. package/init/content/labs/index.md +4 -2
  49. package/init/content/lectures/01/index.md +1 -1
  50. package/init/content/markdown.md +1 -1
  51. package/package.json +3 -4
  52. package/src/_base.scss +67 -0
  53. package/src/anchor/README.md +9 -0
  54. package/src/anchor/index.ts +115 -16
  55. package/src/anchor/style.scss +70 -10
  56. package/src/code/index.ts +46 -18
  57. package/src/code.scss +2 -1
  58. package/src/globals.ts +47 -0
  59. package/src/index.ts +1 -0
  60. package/src/navigate/index.ts +2 -5
  61. package/src/navigate/lifecycle.ts +3 -1
  62. package/src/navigate/runtime.ts +6 -5
  63. package/src/page-update/index.ts +20 -16
  64. package/src/print/style.scss +0 -4
  65. package/src/search/index.ts +53 -25
  66. package/src/slides/index.ts +481 -0
  67. package/src/slides/style.scss +104 -0
  68. package/src/style.scss +8 -3
  69. package/src/timezone/index.ts +16 -20
  70. package/src/top/index.ts +2 -4
  71. package/src/trace/index.ts +116 -14
  72. package/src/trace/style.scss +1 -1
  73. package/src/util.ts +6 -2
  74. package/templates/_author.html +2 -2
  75. package/templates/_heading.html +1 -1
  76. package/templates/_nav.html +2 -2
  77. package/templates/_theme.scss +4 -3
  78. package/templates/_top.html +14 -14
  79. package/templates/default.html +9 -0
  80. package/types/dev.d.ts +1 -1
  81. package/types/site-variables.d.ts +4 -5
  82. package/watch/fs-commit.ts +63 -10
  83. package/build/apply-base-path-plugin.ts +0 -186
  84. package/build/content-watch.ts +0 -104
  85. package/build/validate-internal-links-plugin.ts +0 -207
@@ -0,0 +1,400 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { parseFrontMatterAndContent } from './utils/front-matter';
4
+ import { getExtensionToShikiLanguage } from './site-variables';
5
+ import {
6
+ extensionIsMarkdown,
7
+ getProcessedExtensions,
8
+ isLiterateJava,
9
+ isPartial,
10
+ } from './utils/file-types';
11
+ import {
12
+ getContentDir,
13
+ getDistDir,
14
+ getPublicDir,
15
+ normalizeOutputPath,
16
+ toPosix,
17
+ } from './utils/paths';
18
+ import type { ChangeBatch } from '../watch/types';
19
+ import type { SiteVariables } from './types';
20
+
21
+ export interface TadaProjectScan {
22
+ contentDir: string;
23
+ publicDir: string;
24
+ distDir: string;
25
+ contentFiles: Set<string>;
26
+ buildContentFiles: Set<string>;
27
+ publicFiles: Set<string>;
28
+ validTargets: Set<string>;
29
+ literateJavaOutputPaths: Set<string>;
30
+ processedExts: Set<string>;
31
+ contentOwners: Map<string, string>;
32
+ publicOwners: Map<string, string>;
33
+ sourceOutputPaths: Map<string, Set<string>>;
34
+ sourceTargetPaths: Map<string, Set<string>>;
35
+ }
36
+
37
+ export interface TadaProjectScanSnapshotLike {
38
+ processedExts: Set<string>;
39
+ contentFiles: Set<string>;
40
+ buildContentFiles: Set<string>;
41
+ publicFiles: Set<string>;
42
+ literateJavaOutputPaths: Set<string>;
43
+ contentOwners: Map<string, string>;
44
+ publicOwners: Map<string, string>;
45
+ sourceOutputPaths: Map<string, Set<string>>;
46
+ sourceTargetPaths: Map<string, Set<string>>;
47
+ }
48
+
49
+ export function shouldSkipContentFile(filePath: string): boolean {
50
+ const ext = path.extname(filePath).toLowerCase();
51
+ if (!(extensionIsMarkdown(ext) || ext === '.html')) {
52
+ return false;
53
+ }
54
+
55
+ const raw = fs.readFileSync(filePath, 'utf-8');
56
+ const { pageVariables } = parseFrontMatterAndContent(raw, ext);
57
+ return pageVariables?.skip === true;
58
+ }
59
+
60
+ export function getProcessedExts(codeExtensions: string[]): Set<string> {
61
+ return new Set(
62
+ getProcessedExtensions(codeExtensions).map(ext => ext.toLowerCase()),
63
+ );
64
+ }
65
+
66
+ export function isBuildContentSource(
67
+ filePath: string,
68
+ processedExts: Set<string>,
69
+ ): boolean {
70
+ const ext = path.extname(filePath).slice(1).toLowerCase();
71
+ if (!processedExts.has(ext) || isPartial(filePath)) {
72
+ return false;
73
+ }
74
+
75
+ return !shouldSkipContentFile(filePath);
76
+ }
77
+
78
+ export function addGeneratedRouteAliases(
79
+ pathSet: Set<string>,
80
+ outputPath: string,
81
+ ): void {
82
+ const normalizedPath = normalizeOutputPath(outputPath);
83
+ pathSet.add(normalizedPath);
84
+
85
+ if (!normalizedPath.endsWith('/index.html')) {
86
+ return;
87
+ }
88
+
89
+ const base = normalizedPath.slice(0, -'index.html'.length);
90
+ pathSet.add(base);
91
+ if (base.endsWith('/') && base.length > 1) {
92
+ pathSet.add(base.slice(0, -1));
93
+ }
94
+ }
95
+
96
+ function walkFiles(dir: string): string[] {
97
+ if (!fs.existsSync(dir)) {
98
+ return [];
99
+ }
100
+
101
+ return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
102
+ const fullPath = path.join(dir, entry.name);
103
+ if (entry.isDirectory()) {
104
+ return walkFiles(fullPath);
105
+ }
106
+ if (entry.isFile()) {
107
+ return [fullPath];
108
+ }
109
+ return [];
110
+ });
111
+ }
112
+
113
+ function cloneSetMap(
114
+ source: Map<string, Set<string>>,
115
+ ): Map<string, Set<string>> {
116
+ return new Map(
117
+ [...source.entries()].map(([key, values]) => [key, new Set(values)]),
118
+ );
119
+ }
120
+
121
+ function addContentSourceToScan({
122
+ scan,
123
+ filePath,
124
+ }: {
125
+ scan: TadaProjectScan;
126
+ filePath: string;
127
+ }): void {
128
+ const buildContent = isBuildContentSource(filePath, scan.processedExts);
129
+
130
+ scan.contentFiles.add(filePath);
131
+ if (buildContent) {
132
+ scan.buildContentFiles.add(filePath);
133
+ }
134
+ if (buildContent && isLiterateJava(filePath)) {
135
+ const parsed = path.parse(path.relative(scan.contentDir, filePath));
136
+ scan.literateJavaOutputPaths.add(
137
+ `/${toPosix(path.join(parsed.dir, parsed.name))}`,
138
+ );
139
+ }
140
+
141
+ const outputs = getSourceOutputPaths({
142
+ contentDir: scan.contentDir,
143
+ filePath,
144
+ processedExts: scan.processedExts,
145
+ buildContent,
146
+ });
147
+ scan.sourceOutputPaths.set(filePath, outputs);
148
+ for (const outputPath of outputs) {
149
+ scan.contentOwners.set(outputPath, filePath);
150
+ }
151
+
152
+ const targets = getSourceTargetPaths({
153
+ kind: 'content',
154
+ rootDir: scan.contentDir,
155
+ filePath,
156
+ processedExts: scan.processedExts,
157
+ buildContent,
158
+ });
159
+ scan.sourceTargetPaths.set(filePath, targets);
160
+ for (const target of targets) {
161
+ scan.validTargets.add(target);
162
+ }
163
+ }
164
+
165
+ function addPublicSourceToScan({
166
+ scan,
167
+ filePath,
168
+ }: {
169
+ scan: TadaProjectScan;
170
+ filePath: string;
171
+ }): void {
172
+ const relPath = toPosix(path.relative(scan.publicDir, filePath));
173
+
174
+ scan.publicFiles.add(filePath);
175
+ scan.publicOwners.set(relPath, filePath);
176
+ scan.sourceOutputPaths.set(filePath, new Set([relPath]));
177
+
178
+ const targets = getSourceTargetPaths({
179
+ kind: 'public',
180
+ rootDir: scan.publicDir,
181
+ filePath,
182
+ processedExts: scan.processedExts,
183
+ buildContent: false,
184
+ });
185
+ scan.sourceTargetPaths.set(filePath, targets);
186
+ for (const target of targets) {
187
+ scan.validTargets.add(target);
188
+ }
189
+ }
190
+
191
+ function collectValidTargets(
192
+ sourceTargetPaths: Map<string, Set<string>>,
193
+ ): Set<string> {
194
+ const validTargets = new Set<string>();
195
+ for (const targets of sourceTargetPaths.values()) {
196
+ for (const target of targets) {
197
+ validTargets.add(target);
198
+ }
199
+ }
200
+ return validTargets;
201
+ }
202
+
203
+ function createEmptyScan(processedExts: Set<string>): TadaProjectScan {
204
+ return {
205
+ contentDir: getContentDir(),
206
+ publicDir: getPublicDir(),
207
+ distDir: getDistDir(),
208
+ contentFiles: new Set(),
209
+ buildContentFiles: new Set(),
210
+ publicFiles: new Set(),
211
+ validTargets: new Set(),
212
+ literateJavaOutputPaths: new Set(),
213
+ processedExts,
214
+ contentOwners: new Map(),
215
+ publicOwners: new Map(),
216
+ sourceOutputPaths: new Map(),
217
+ sourceTargetPaths: new Map(),
218
+ };
219
+ }
220
+
221
+ export function getSourceOutputPaths({
222
+ contentDir,
223
+ filePath,
224
+ processedExts,
225
+ buildContent,
226
+ }: {
227
+ contentDir: string;
228
+ filePath: string;
229
+ processedExts: Set<string>;
230
+ buildContent: boolean;
231
+ }): Set<string> {
232
+ const relPath = toPosix(path.relative(contentDir, filePath));
233
+ const parsed = path.parse(relPath);
234
+ const ext = path.extname(filePath).slice(1).toLowerCase();
235
+ const outputs = new Set<string>();
236
+
237
+ if (!processedExts.has(ext)) {
238
+ outputs.add(relPath);
239
+ return outputs;
240
+ }
241
+
242
+ if (!buildContent) {
243
+ return outputs;
244
+ }
245
+
246
+ if (isLiterateJava(filePath)) {
247
+ outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
248
+ outputs.add(toPosix(path.join(parsed.dir, parsed.name)));
249
+ return outputs;
250
+ }
251
+
252
+ if (
253
+ extensionIsMarkdown(parsed.ext.toLowerCase()) ||
254
+ parsed.ext.toLowerCase() === '.html'
255
+ ) {
256
+ outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
257
+ return outputs;
258
+ }
259
+
260
+ outputs.add(relPath);
261
+ outputs.add(`${relPath}.html`);
262
+ return outputs;
263
+ }
264
+
265
+ export function getSourceTargetPaths({
266
+ kind,
267
+ rootDir,
268
+ filePath,
269
+ processedExts,
270
+ buildContent,
271
+ }: {
272
+ kind: 'content' | 'public';
273
+ rootDir: string;
274
+ filePath: string;
275
+ processedExts: Set<string>;
276
+ buildContent: boolean;
277
+ }): Set<string> {
278
+ const targets = new Set<string>();
279
+ const relPath = toPosix(path.relative(rootDir, filePath));
280
+
281
+ if (kind === 'public') {
282
+ targets.add(normalizeOutputPath(`/${relPath}`));
283
+ return targets;
284
+ }
285
+
286
+ const ext = path.extname(filePath).slice(1).toLowerCase();
287
+ if (!processedExts.has(ext)) {
288
+ targets.add(normalizeOutputPath(`/${relPath}`));
289
+ return targets;
290
+ }
291
+
292
+ if (!buildContent || isPartial(filePath)) {
293
+ return targets;
294
+ }
295
+
296
+ const parsed = path.parse(relPath);
297
+ const subPath = toPosix(path.join(parsed.dir, parsed.name));
298
+
299
+ if (isLiterateJava(filePath)) {
300
+ const javaSubPath = toPosix(path.join(parsed.dir, parsed.name));
301
+ addGeneratedRouteAliases(targets, `/${javaSubPath}.html`);
302
+ targets.add(normalizeOutputPath(`/${javaSubPath}`));
303
+ return targets;
304
+ }
305
+
306
+ if (
307
+ extensionIsMarkdown(parsed.ext.toLowerCase()) ||
308
+ parsed.ext.toLowerCase() === '.html'
309
+ ) {
310
+ addGeneratedRouteAliases(targets, `/${subPath}.html`);
311
+ return targets;
312
+ }
313
+
314
+ addGeneratedRouteAliases(targets, `/${relPath}.html`);
315
+ targets.add(normalizeOutputPath(`/${relPath}`));
316
+ return targets;
317
+ }
318
+
319
+ export function scanProject(siteVariables: SiteVariables): TadaProjectScan {
320
+ const scan = createEmptyScan(
321
+ getProcessedExts(Object.keys(getExtensionToShikiLanguage(siteVariables))),
322
+ );
323
+
324
+ for (const filePath of walkFiles(scan.contentDir).sort()) {
325
+ addContentSourceToScan({ scan, filePath });
326
+ }
327
+
328
+ for (const filePath of walkFiles(scan.publicDir).sort()) {
329
+ addPublicSourceToScan({ scan, filePath });
330
+ }
331
+
332
+ return scan;
333
+ }
334
+
335
+ export function updateProjectScan(
336
+ snapshot: TadaProjectScanSnapshotLike,
337
+ batch: ChangeBatch,
338
+ ): TadaProjectScan {
339
+ const scan = createEmptyScan(snapshot.processedExts);
340
+ scan.contentFiles = new Set(snapshot.contentFiles);
341
+ scan.buildContentFiles = new Set(snapshot.buildContentFiles);
342
+ scan.publicFiles = new Set(snapshot.publicFiles);
343
+ scan.literateJavaOutputPaths = new Set(snapshot.literateJavaOutputPaths);
344
+ scan.contentOwners = new Map(snapshot.contentOwners);
345
+ scan.publicOwners = new Map(snapshot.publicOwners);
346
+ scan.sourceOutputPaths = cloneSetMap(snapshot.sourceOutputPaths);
347
+ scan.sourceTargetPaths = cloneSetMap(snapshot.sourceTargetPaths);
348
+ scan.validTargets = collectValidTargets(scan.sourceTargetPaths);
349
+
350
+ for (const change of batch.changes) {
351
+ const sourcePath = path.resolve(change.path);
352
+ const inContent = sourcePath.startsWith(`${scan.contentDir}${path.sep}`);
353
+ const inPublic = sourcePath.startsWith(`${scan.publicDir}${path.sep}`);
354
+ if (!inContent && !inPublic) {
355
+ continue;
356
+ }
357
+
358
+ for (const outputPath of scan.sourceOutputPaths.get(sourcePath) || []) {
359
+ if (inContent) {
360
+ scan.contentOwners.delete(outputPath);
361
+ } else {
362
+ scan.publicOwners.delete(outputPath);
363
+ }
364
+ }
365
+
366
+ scan.sourceOutputPaths.delete(sourcePath);
367
+ scan.sourceTargetPaths.delete(sourcePath);
368
+ scan.contentFiles.delete(sourcePath);
369
+ scan.buildContentFiles.delete(sourcePath);
370
+ scan.publicFiles.delete(sourcePath);
371
+
372
+ if (inContent && isLiterateJava(sourcePath)) {
373
+ const parsed = path.parse(path.relative(scan.contentDir, sourcePath));
374
+ scan.literateJavaOutputPaths.delete(
375
+ `/${toPosix(path.join(parsed.dir, parsed.name))}`,
376
+ );
377
+ }
378
+
379
+ if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
380
+ continue;
381
+ }
382
+
383
+ if (inContent) {
384
+ addContentSourceToScan({ scan, filePath: sourcePath });
385
+ continue;
386
+ }
387
+
388
+ addPublicSourceToScan({ scan, filePath: sourcePath });
389
+ }
390
+
391
+ scan.validTargets = collectValidTargets(scan.sourceTargetPaths);
392
+ return scan;
393
+ }
394
+
395
+ export function assertNoOutputPathConflicts(scan: TadaProjectScan): string[] {
396
+ const conflicts = [...scan.contentOwners.keys()].filter(relPath =>
397
+ scan.publicOwners.has(relPath),
398
+ );
399
+ return conflicts.sort();
400
+ }
@@ -1,27 +1,35 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { getExtensionToShikiLanguage } from '../site-variables';
3
+ import { getExtensionToShikiLanguage } from './site-variables';
4
4
  import {
5
5
  renderCodePageAsset,
6
6
  renderCopiedContentAsset,
7
7
  renderLiterateJavaPageAsset,
8
8
  renderPlainTextPageAsset,
9
9
  toPosix,
10
- } from '../util';
11
- import { extensionIsMarkdown, isLiterateJava } from '../utils/file-types';
10
+ } from './util';
11
+ import { extensionIsMarkdown, isLiterateJava } from './utils/file-types';
12
12
  import type {
13
13
  Asset,
14
14
  RenderDependencyCollector,
15
15
  SiteVariables,
16
16
  TraceToolAvailability,
17
- } from '../types';
17
+ } from './types';
18
+ import type { TadaProjectScan } from './source-model';
18
19
  import {
20
+ collectSourceHtmlAnalysis,
19
21
  collectSourceOutputs,
20
- type TadaProjectScan,
21
22
  type TadaSourceRecord,
22
- } from './snapshot';
23
- import type { TraceCache } from './compiler-types';
24
- import { writeAssets } from './assets';
23
+ } from './watch/snapshot';
24
+ import type { TraceCache } from './watch/compiler-types';
25
+
26
+ export type TadaSourceRenderKind =
27
+ | 'skip'
28
+ | 'plain-text-page'
29
+ | 'literate-java'
30
+ | 'code-page'
31
+ | 'content-copy'
32
+ | 'public-copy';
25
33
 
26
34
  function createDependencyCollector(): {
27
35
  collector: RenderDependencyCollector;
@@ -57,6 +65,19 @@ function createDependencyCollector(): {
57
65
  };
58
66
  }
59
67
 
68
+ function createEmptyContentRecord(filePath: string): TadaSourceRecord {
69
+ return {
70
+ sourcePath: filePath,
71
+ kind: 'content',
72
+ outputs: new Map(),
73
+ htmlAnalysisByOutputPath: new Map(),
74
+ partialDeps: new Set(),
75
+ traceDeps: new Set(),
76
+ internalTargets: new Set(),
77
+ generatedOutputPaths: new Set(),
78
+ };
79
+ }
80
+
60
81
  function createRawContentAsset(filePath: string, contentDir: string): Asset[] {
61
82
  return [
62
83
  {
@@ -66,7 +87,49 @@ function createRawContentAsset(filePath: string, contentDir: string): Asset[] {
66
87
  ];
67
88
  }
68
89
 
69
- export function renderContentRecord({
90
+ export function classifySourceRenderKind({
91
+ filePath,
92
+ scan,
93
+ siteVariables,
94
+ }: {
95
+ filePath: string;
96
+ scan: TadaProjectScan;
97
+ siteVariables: SiteVariables;
98
+ }): TadaSourceRenderKind {
99
+ if (scan.publicFiles.has(filePath)) {
100
+ return 'public-copy';
101
+ }
102
+
103
+ if (!scan.contentFiles.has(filePath)) {
104
+ return 'skip';
105
+ }
106
+
107
+ const ext = path.extname(filePath).slice(1).toLowerCase();
108
+ const lowerExt = path.extname(filePath).toLowerCase();
109
+ if (!scan.processedExts.has(ext)) {
110
+ return 'content-copy';
111
+ }
112
+
113
+ if (!scan.buildContentFiles.has(filePath)) {
114
+ return 'skip';
115
+ }
116
+
117
+ if (isLiterateJava(filePath)) {
118
+ return 'literate-java';
119
+ }
120
+
121
+ if (extensionIsMarkdown(lowerExt) || lowerExt === '.html') {
122
+ return 'plain-text-page';
123
+ }
124
+
125
+ if (Object.hasOwn(getExtensionToShikiLanguage(siteVariables), ext)) {
126
+ return 'code-page';
127
+ }
128
+
129
+ return 'skip';
130
+ }
131
+
132
+ export function createContentRecord({
70
133
  filePath,
71
134
  siteVariables,
72
135
  scan,
@@ -75,43 +138,47 @@ export function renderContentRecord({
75
138
  traceCache,
76
139
  traceToolAvailability,
77
140
  cachedTraceSourceDir,
78
- persistOutputs = true,
141
+ skipLiterateJavaExecution,
79
142
  }: {
80
143
  filePath: string;
81
144
  siteVariables: SiteVariables;
82
145
  scan: TadaProjectScan;
83
146
  assetFiles: string[];
84
147
  outputDir: string;
85
- traceCache: TraceCache;
86
- traceToolAvailability: TraceToolAvailability;
148
+ traceCache?: TraceCache;
149
+ traceToolAvailability?: TraceToolAvailability;
87
150
  cachedTraceSourceDir?: string;
88
- persistOutputs?: boolean;
151
+ skipLiterateJavaExecution?: boolean;
89
152
  }): TadaSourceRecord {
153
+ const renderKind = classifySourceRenderKind({
154
+ filePath,
155
+ scan,
156
+ siteVariables,
157
+ });
158
+ if (renderKind === 'skip' || renderKind === 'public-copy') {
159
+ return createEmptyContentRecord(filePath);
160
+ }
161
+
90
162
  const deps = createDependencyCollector();
91
- const codeExtensions = Object.keys(
92
- getExtensionToShikiLanguage(siteVariables),
93
- ).map(ext => ext.toLowerCase());
94
- const ext = path.extname(filePath).slice(1).toLowerCase();
95
163
  const assets: Asset[] = [];
96
164
 
97
- if (isLiterateJava(filePath)) {
98
- assets.push(
99
- ...renderLiterateJavaPageAsset({
100
- filePath,
101
- contentDir: scan.contentDir,
102
- distDir: outputDir,
103
- siteVariables,
104
- assetFiles,
105
- validInternalTargets: scan.validTargets,
106
- literateJavaOutputPaths: scan.literateJavaOutputPaths,
107
- dependencyCollector: deps.collector,
108
- }),
109
- );
110
- } else if (scan.buildContentFiles.has(filePath)) {
111
- if (
112
- extensionIsMarkdown(path.extname(filePath).toLowerCase()) ||
113
- path.extname(filePath).toLowerCase() === '.html'
114
- ) {
165
+ switch (renderKind) {
166
+ case 'literate-java':
167
+ assets.push(
168
+ ...renderLiterateJavaPageAsset({
169
+ filePath,
170
+ contentDir: scan.contentDir,
171
+ distDir: outputDir,
172
+ siteVariables,
173
+ assetFiles,
174
+ skipExecution: skipLiterateJavaExecution,
175
+ validInternalTargets: scan.validTargets,
176
+ literateJavaOutputPaths: scan.literateJavaOutputPaths,
177
+ dependencyCollector: deps.collector,
178
+ }),
179
+ );
180
+ break;
181
+ case 'plain-text-page':
115
182
  assets.push(
116
183
  ...renderPlainTextPageAsset({
117
184
  filePath,
@@ -127,7 +194,8 @@ export function renderContentRecord({
127
194
  traceToolAvailability,
128
195
  }),
129
196
  );
130
- } else if (codeExtensions.includes(ext)) {
197
+ break;
198
+ case 'code-page':
131
199
  assets.push(
132
200
  ...renderCodePageAsset({
133
201
  filePath,
@@ -147,24 +215,17 @@ export function renderContentRecord({
147
215
  siteVariables,
148
216
  }),
149
217
  );
150
- }
151
- } else if (!scan.processedExts.has(ext)) {
152
- assets.push(...createRawContentAsset(filePath, scan.contentDir));
153
- }
154
-
155
- const outputs = collectSourceOutputs(
156
- assets,
157
- deps.generatedOutputPaths,
158
- outputDir,
159
- );
160
- if (persistOutputs) {
161
- writeAssets(outputDir, outputs);
218
+ break;
219
+ case 'content-copy':
220
+ assets.push(...createRawContentAsset(filePath, scan.contentDir));
221
+ break;
162
222
  }
163
223
 
164
224
  return {
165
225
  sourcePath: filePath,
166
226
  kind: 'content',
167
- outputs,
227
+ outputs: collectSourceOutputs(assets, deps.generatedOutputPaths, outputDir),
228
+ htmlAnalysisByOutputPath: collectSourceHtmlAnalysis(assets),
168
229
  partialDeps: deps.partials,
169
230
  traceDeps: deps.traceFiles,
170
231
  internalTargets: deps.internalTargets,
@@ -182,6 +243,7 @@ export function createPublicRecord(
182
243
  sourcePath: filePath,
183
244
  kind: 'public',
184
245
  outputs: new Map([[relPath, fs.readFileSync(filePath)]]),
246
+ htmlAnalysisByOutputPath: new Map(),
185
247
  partialDeps: new Set(),
186
248
  traceDeps: new Set(),
187
249
  internalTargets: new Set(),