@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,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
|
-
|
|
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 {
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
183
|
-
const
|
|
96
|
+
const previousRecord = this.getCachedRecord(filePath);
|
|
97
|
+
const nextOutputPaths = new Set(record.outputs.keys());
|
|
184
98
|
|
|
185
|
-
for (const
|
|
186
|
-
if (
|
|
99
|
+
for (const outputPath of previousRecord?.outputs.keys() ?? []) {
|
|
100
|
+
if (nextOutputPaths.has(outputPath)) {
|
|
187
101
|
continue;
|
|
188
102
|
}
|
|
189
|
-
removedOutputRelPaths.add(
|
|
190
|
-
if (
|
|
191
|
-
removedHtmlAssetPaths.add(
|
|
103
|
+
removedOutputRelPaths.add(outputPath);
|
|
104
|
+
if (outputPath.endsWith('.html')) {
|
|
105
|
+
removedHtmlAssetPaths.add(outputPath);
|
|
192
106
|
}
|
|
193
107
|
}
|
|
194
108
|
|
|
195
|
-
for (const
|
|
196
|
-
changedOutputRelPaths.add(
|
|
197
|
-
if (
|
|
198
|
-
changedHtmlAssetPaths.add(
|
|
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,
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
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,
|
|
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
|
|
227
|
-
for (const
|
|
228
|
-
removedOutputRelPaths.add(
|
|
229
|
-
if (
|
|
230
|
-
removedHtmlAssetPaths.add(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
275
|
-
|
|
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
|
|
207
|
+
let record: TadaSourceRecord;
|
|
302
208
|
try {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
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
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
}
|