@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.
- package/README.md +65 -64
- package/bin/tada.ts +61 -66
- package/build/build-manifest.ts +14 -3
- package/build/bundle.ts +0 -1
- package/build/config-files.ts +183 -0
- package/build/config-loader.ts +159 -0
- package/build/copy.ts +0 -83
- package/build/generate-content-assets.ts +75 -160
- package/build/globals.ts +48 -89
- package/build/log.ts +27 -18
- package/build/pagefind.ts +33 -26
- package/build/pipeline.ts +98 -79
- package/build/reachability.ts +51 -248
- package/build/serve.ts +35 -6
- package/build/site-variables.ts +20 -11
- package/build/source-model.ts +400 -0
- package/build/{watch/records.ts → source-records.ts} +111 -49
- package/build/template-globals.ts +91 -0
- package/build/templates.ts +50 -40
- package/build/test-helpers.ts +15 -0
- package/build/toc-plugin.ts +5 -1
- package/build/types.d.ts +9 -1
- package/build/util.ts +8 -0
- package/build/utils/code.ts +6 -16
- package/build/utils/content-files.ts +57 -126
- package/build/utils/final-html.ts +239 -0
- package/build/utils/markdown.ts +237 -17
- package/build/utils/paths.ts +2 -1
- package/build/utils/render.ts +108 -99
- package/build/validate-config-links.ts +91 -14
- package/build/watch/assets.ts +0 -2
- package/build/watch/build-full.ts +14 -15
- package/build/watch/build-incremental.ts +15 -8
- package/build/watch/compiler-types.d.ts +6 -1
- package/build/watch/compiler.ts +27 -15
- package/build/watch/index.ts +2 -7
- package/build/watch/planner.ts +32 -16
- package/build/watch/reload.ts +4 -0
- package/build/watch/runtime.ts +29 -45
- package/build/watch/snapshot.ts +50 -402
- package/build/watch/validation.ts +6 -3
- package/build/watch-reachability-state.ts +16 -23
- package/build/watch-reload-client.ts +11 -4
- package/init/content/index.md +1 -1
- package/init/content/labs/02/index.md +61 -0
- package/init/content/labs/02/selection_sort.py +23 -0
- package/init/content/labs/index.md +4 -2
- package/init/content/lectures/01/index.md +1 -1
- package/init/content/markdown.md +1 -1
- package/package.json +3 -4
- package/src/code/index.ts +46 -18
- package/src/code.scss +2 -1
- package/src/globals.ts +47 -0
- package/src/index.ts +1 -0
- package/src/navigate/index.ts +2 -5
- package/src/navigate/lifecycle.ts +3 -1
- package/src/navigate/runtime.ts +6 -5
- package/src/page-update/index.ts +20 -16
- package/src/search/index.ts +53 -25
- package/src/slides/index.ts +467 -0
- package/src/slides/style.scss +104 -0
- package/src/style.scss +8 -3
- package/src/timezone/index.ts +16 -20
- package/src/top/index.ts +2 -4
- package/src/trace/index.ts +91 -7
- package/src/trace/style.scss +1 -1
- package/src/util.ts +6 -2
- package/templates/_author.html +2 -2
- package/templates/_heading.html +1 -1
- package/templates/_nav.html +2 -2
- package/templates/_theme.scss +3 -3
- package/templates/_top.html +14 -14
- package/templates/default.html +8 -0
- package/types/dev.d.ts +1 -1
- package/types/site-variables.d.ts +4 -5
- package/watch/fs-commit.ts +63 -10
- package/build/apply-base-path-plugin.ts +0 -186
- package/build/content-watch.ts +0 -104
- 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 '
|
|
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 '
|
|
11
|
-
import { extensionIsMarkdown, isLiterateJava } from '
|
|
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 '
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
86
|
-
traceToolAvailability
|
|
148
|
+
traceCache?: TraceCache;
|
|
149
|
+
traceToolAvailability?: TraceToolAvailability;
|
|
87
150
|
cachedTraceSourceDir?: string;
|
|
88
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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(),
|