@abreen/tada 1.17.0 → 1.18.0
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 +41 -7
- package/bin/tada.ts +23 -9
- package/bin/validators.ts +34 -1
- package/build/branding.ts +41 -0
- package/build/build-manifest.ts +10 -5
- package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
- package/build/build-validation.ts +176 -0
- package/build/bundle.ts +75 -65
- package/build/columns-plugin.ts +27 -1
- package/build/config-loader.ts +3 -3
- package/build/custom-fonts.ts +8 -5
- package/build/deflist-id-plugin.ts +2 -2
- package/build/generate-favicon.ts +7 -6
- package/build/generate-fonts.ts +8 -15
- package/build/generate-katex-assets.ts +19 -17
- package/build/generate-web-app-manifest.ts +3 -11
- package/build/lodash-template.ts +14 -0
- package/build/output-publication.ts +238 -0
- package/build/pagefind.ts +49 -21
- package/build/pdf-text.ts +41 -1
- package/build/pipeline.ts +95 -101
- package/build/site-assets.ts +46 -0
- package/build/site-build.ts +267 -0
- package/build/source-model.ts +223 -229
- package/build/source-records.ts +69 -81
- package/build/template-globals.ts +3 -0
- package/build/templates.ts +12 -8
- package/build/toc-plugin.ts +21 -21
- package/build/types.d.ts +38 -38
- package/build/util.ts +0 -4
- package/build/utils/code.ts +51 -117
- package/build/utils/final-html.ts +171 -47
- package/build/utils/jdi-runner/LiterateRunner.java +9 -1
- package/build/utils/literate-java.ts +11 -7
- package/build/utils/markdown-partials.ts +4 -3
- package/build/utils/markdown.ts +6 -17
- package/build/utils/paths.ts +3 -0
- package/build/utils/plain-text.ts +18 -0
- package/build/utils/render.ts +197 -154
- package/build/utils/shiki-highlighter.ts +11 -0
- package/build/utils/source-template.ts +2 -2
- package/build/utils/trace-core.ts +8 -83
- package/build/utils/trace.ts +38 -95
- package/build/validate-config-links.ts +39 -6
- package/build/watch/build-incremental.ts +46 -103
- package/build/watch/compiler.ts +158 -37
- package/build/watch/engine.ts +207 -162
- package/build/watch/index.ts +9 -2
- package/build/watch/planner.ts +88 -144
- package/build/watch/runtime.ts +21 -34
- package/build/watch/types.d.ts +32 -64
- package/package.json +4 -5
- package/schema/site.schema.json +18 -2
- package/src/_content.scss +447 -0
- package/src/_fonts.scss +90 -0
- package/src/_layout.scss +50 -35
- package/src/appearance-picker/{style.scss → _index.scss} +1 -1
- package/src/code/{style.scss → _index.scss} +61 -0
- package/src/header/{_base.scss → _index.scss} +98 -6
- package/src/index.ts +1 -17
- package/src/navigate/{style.scss → _index.scss} +20 -0
- package/src/navigate/eligible.ts +0 -40
- package/src/navigate/index.ts +50 -5
- package/src/navigate/lifecycle.ts +19 -2
- package/src/navigate/runtime.ts +20 -4
- package/src/page-update/index.ts +33 -20
- package/src/print/{style.scss → _index.scss} +3 -1
- package/src/search/index.ts +30 -2
- package/src/slides/{style.scss → _index.scss} +5 -0
- package/src/style.scss +24 -536
- package/src/timezone/index.ts +29 -3
- package/src/timezone/time-format.ts +4 -0
- package/src/toc/index.ts +6 -5
- package/src/trace/index.ts +11 -1
- package/templates/_author.html +2 -2
- package/templates/_download.html +1 -1
- package/templates/_footer.html +1 -1
- package/templates/_heading.html +3 -3
- package/templates/_nav.html +2 -2
- package/templates/_page-bottom.html +6 -0
- package/templates/_top.html +18 -14
- package/templates/code.html +22 -21
- package/templates/default.html +12 -11
- package/templates/literate.html +6 -5
- package/build/copy.ts +0 -82
- package/build/generate-content-assets.ts +0 -121
- package/build/utils/content-files.ts +0 -170
- package/build/watch/assets.ts +0 -103
- package/build/watch/build-full.ts +0 -97
- package/build/watch/build-helpers.ts +0 -66
- package/build/watch/build-result.ts +0 -19
- package/build/watch/fs-commit.ts +0 -153
- package/build/watch/mutations.ts +0 -73
- package/build/watch/snapshot.ts +0 -201
- package/build/watch/validation.ts +0 -50
- package/src/code.scss +0 -60
- package/src/critical.scss +0 -5
- package/src/header/style.scss +0 -91
- /package/src/{literate.scss → _literate.scss} +0 -0
- /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
- /package/src/anchor/{style.scss → _index.scss} +0 -0
- /package/src/page-update/{style.scss → _index.scss} +0 -0
- /package/src/question/{style.scss → _index.scss} +0 -0
- /package/src/search/{style.scss → _index.scss} +0 -0
- /package/src/timezone/{style.scss → _index.scss} +0 -0
- /package/src/toc/{style.scss → _index.scss} +0 -0
- /package/src/trace/{style.scss → _index.scss} +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { bundle, bundleReloadClient } from './bundle';
|
|
2
|
+
import { isFeatureEnabled } from './features';
|
|
3
|
+
import { generateFavicons } from './generate-favicon';
|
|
4
|
+
import { getFontOutputs } from './generate-fonts';
|
|
5
|
+
import { getKatexOutputs } from './generate-katex-assets';
|
|
6
|
+
import { createWebAppManifest } from './generate-web-app-manifest';
|
|
7
|
+
import type { OutputContent, SiteVariables } from './types';
|
|
8
|
+
|
|
9
|
+
/** Files Tada generates itself, as opposed to files rendered from sources */
|
|
10
|
+
interface SiteAssets {
|
|
11
|
+
/** CSS and JavaScript bundles that every page links to */
|
|
12
|
+
assetFiles: string[];
|
|
13
|
+
/** Every generated file, keyed by output path */
|
|
14
|
+
outputs: Map<string, OutputContent>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function generateSiteAssets(
|
|
18
|
+
siteVariables: SiteVariables,
|
|
19
|
+
{
|
|
20
|
+
mode,
|
|
21
|
+
isWatchMode,
|
|
22
|
+
}: { mode: 'development' | 'production'; isWatchMode: boolean },
|
|
23
|
+
): Promise<SiteAssets> {
|
|
24
|
+
const generateFaviconFiles =
|
|
25
|
+
isFeatureEnabled(siteVariables, 'favicon') && !siteVariables.favicon;
|
|
26
|
+
const [bundles, reloadClient, favicons] = await Promise.all([
|
|
27
|
+
bundle(siteVariables, { mode }),
|
|
28
|
+
isWatchMode ? bundleReloadClient() : new Map<string, string>(),
|
|
29
|
+
generateFaviconFiles
|
|
30
|
+
? generateFavicons(siteVariables)
|
|
31
|
+
: new Map<string, string | Uint8Array>(),
|
|
32
|
+
]);
|
|
33
|
+
const bundleFiles = new Map([...bundles, ...reloadClient]);
|
|
34
|
+
|
|
35
|
+
const outputs = new Map<string, OutputContent>([
|
|
36
|
+
...bundleFiles,
|
|
37
|
+
...getFontOutputs(),
|
|
38
|
+
...getKatexOutputs(),
|
|
39
|
+
...favicons,
|
|
40
|
+
]);
|
|
41
|
+
if (generateFaviconFiles) {
|
|
42
|
+
outputs.set('manifest.json', createWebAppManifest(siteVariables));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { assetFiles: [...bundleFiles.keys()], outputs };
|
|
46
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { compileTemplates, config } from './templates';
|
|
3
|
+
import { getProjectDir, toPosix } from './utils/paths';
|
|
4
|
+
import { makeLogger } from './log';
|
|
5
|
+
import { getRuntimeBundledShikiLanguages } from './site-variables';
|
|
6
|
+
import { initHighlighter } from './utils/shiki-highlighter';
|
|
7
|
+
import { generateSiteAssets } from './site-assets';
|
|
8
|
+
import { getPdfSources } from './pagefind';
|
|
9
|
+
import { checkTraceToolAvailability } from './utils/trace';
|
|
10
|
+
import { scanProject, sourcePaths, type TadaProjectScan } from './source-model';
|
|
11
|
+
import {
|
|
12
|
+
createContentRecord,
|
|
13
|
+
createPublicRecord,
|
|
14
|
+
type TadaSourceRecord,
|
|
15
|
+
} from './source-records';
|
|
16
|
+
import {
|
|
17
|
+
diagnosticFromError,
|
|
18
|
+
findOutputConflicts,
|
|
19
|
+
validateConfig,
|
|
20
|
+
validateProjectConfigLinks,
|
|
21
|
+
} from './build-validation';
|
|
22
|
+
import type { OutputFile } from './output-publication';
|
|
23
|
+
import type {
|
|
24
|
+
OutputContent,
|
|
25
|
+
SiteVariables,
|
|
26
|
+
TraceToolAvailability,
|
|
27
|
+
} from './types';
|
|
28
|
+
import type { BuildDiagnostic, TadaBuildMeta, TraceCache } from './build-types';
|
|
29
|
+
|
|
30
|
+
const log = makeLogger(import.meta.url);
|
|
31
|
+
|
|
32
|
+
/** The `sourcePath` recorded for files Tada generates itself */
|
|
33
|
+
export const GENERATED_SOURCE = 'tada';
|
|
34
|
+
|
|
35
|
+
/** Everything one successful build produced, kept by watch mode */
|
|
36
|
+
export interface TadaSnapshot {
|
|
37
|
+
siteVariables: SiteVariables;
|
|
38
|
+
assetFiles: string[];
|
|
39
|
+
generatedOutputs: ReadonlyMap<string, OutputContent>;
|
|
40
|
+
authorsData: unknown;
|
|
41
|
+
records: ReadonlyMap<string, TadaSourceRecord>;
|
|
42
|
+
outputs: ReadonlyMap<string, OutputFile>;
|
|
43
|
+
fileDependents: ReadonlyMap<string, ReadonlySet<string>>;
|
|
44
|
+
targetDependents: ReadonlyMap<string, ReadonlySet<string>>;
|
|
45
|
+
authorDependents: ReadonlyMap<string, ReadonlySet<string>>;
|
|
46
|
+
scan: TadaProjectScan;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type SnapshotInputs = Pick<
|
|
50
|
+
TadaSnapshot,
|
|
51
|
+
'siteVariables' | 'assetFiles' | 'authorsData' | 'records' | 'scan'
|
|
52
|
+
> & { generatedOutputs?: ReadonlyMap<string, OutputContent> };
|
|
53
|
+
|
|
54
|
+
export type SiteBuildResult =
|
|
55
|
+
| { ok: true; snapshot: TadaSnapshot }
|
|
56
|
+
| { ok: false; diagnostics: BuildDiagnostic[] };
|
|
57
|
+
|
|
58
|
+
export interface RenderContext {
|
|
59
|
+
siteVariables: SiteVariables;
|
|
60
|
+
scan: TadaProjectScan;
|
|
61
|
+
assetFiles: string[];
|
|
62
|
+
isWatchMode: boolean;
|
|
63
|
+
traceCache: TraceCache;
|
|
64
|
+
traceToolAvailability: TraceToolAvailability;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createSnapshot(inputs: SnapshotInputs): TadaSnapshot {
|
|
68
|
+
const generatedOutputs = inputs.generatedOutputs ?? new Map();
|
|
69
|
+
const outputs = new Map<string, OutputFile>();
|
|
70
|
+
for (const [outputPath, content] of generatedOutputs) {
|
|
71
|
+
outputs.set(outputPath, { sourcePath: GENERATED_SOURCE, content });
|
|
72
|
+
}
|
|
73
|
+
const fileDependents = new Map<string, Set<string>>();
|
|
74
|
+
const targetDependents = new Map<string, Set<string>>();
|
|
75
|
+
const authorDependents = new Map<string, Set<string>>();
|
|
76
|
+
for (const record of inputs.records.values()) {
|
|
77
|
+
for (const [outputPath, content] of record.outputs) {
|
|
78
|
+
outputs.set(outputPath, { sourcePath: record.sourcePath, content });
|
|
79
|
+
}
|
|
80
|
+
for (const [index, keys] of [
|
|
81
|
+
[fileDependents, [...record.partialDeps, ...record.traceDeps]],
|
|
82
|
+
[targetDependents, record.internalTargets],
|
|
83
|
+
[authorDependents, record.authorKey ? [record.authorKey] : []],
|
|
84
|
+
] as const) {
|
|
85
|
+
for (const key of keys) {
|
|
86
|
+
if (!index.has(key)) {
|
|
87
|
+
index.set(key, new Set());
|
|
88
|
+
}
|
|
89
|
+
index.get(key)!.add(record.sourcePath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...inputs,
|
|
95
|
+
generatedOutputs,
|
|
96
|
+
outputs,
|
|
97
|
+
fileDependents,
|
|
98
|
+
targetDependents,
|
|
99
|
+
authorDependents,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createBuildMeta(snapshot: TadaSnapshot): TadaBuildMeta {
|
|
104
|
+
const htmlAssetsByPath = new Map<string, string>();
|
|
105
|
+
const htmlAnalysisByPath: TadaBuildMeta['htmlAnalysisByPath'] = new Map();
|
|
106
|
+
for (const record of snapshot.records.values()) {
|
|
107
|
+
if (record.kind !== 'content') {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const [outputPath, content] of record.outputs) {
|
|
111
|
+
if (outputPath.endsWith('.html') && typeof content === 'string') {
|
|
112
|
+
htmlAssetsByPath.set(outputPath, content);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const [outputPath, analysis] of record.htmlAnalysisByOutputPath ??
|
|
116
|
+
[]) {
|
|
117
|
+
htmlAnalysisByPath.set(outputPath, analysis);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
htmlAssetsByPath,
|
|
122
|
+
htmlAnalysisByPath,
|
|
123
|
+
pdfSourceByOutputPath: getPdfSources(snapshot.scan),
|
|
124
|
+
siteVariables: snapshot.siteVariables,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function ensureHighlighter(
|
|
129
|
+
siteVariables: SiteVariables,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
await initHighlighter(getRuntimeBundledShikiLanguages(siteVariables));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Renders each source into a record. A source that fails to render becomes a
|
|
136
|
+
* diagnostic, and rendering continues so every page error is reported.
|
|
137
|
+
*/
|
|
138
|
+
export function renderSources(
|
|
139
|
+
filePaths: Iterable<string>,
|
|
140
|
+
context: RenderContext,
|
|
141
|
+
): { records: Map<string, TadaSourceRecord>; diagnostics: BuildDiagnostic[] } {
|
|
142
|
+
const records = new Map<string, TadaSourceRecord>();
|
|
143
|
+
const diagnostics: BuildDiagnostic[] = [];
|
|
144
|
+
for (const filePath of filePaths) {
|
|
145
|
+
try {
|
|
146
|
+
const record =
|
|
147
|
+
context.scan.sources.get(filePath)?.kind === 'public'
|
|
148
|
+
? createPublicRecord(filePath, context.scan.publicDir)
|
|
149
|
+
: createContentRecord({
|
|
150
|
+
filePath,
|
|
151
|
+
siteVariables: context.siteVariables,
|
|
152
|
+
scan: context.scan,
|
|
153
|
+
assetFiles: context.assetFiles,
|
|
154
|
+
isWatchMode: context.isWatchMode,
|
|
155
|
+
traceCache: context.traceCache,
|
|
156
|
+
traceToolAvailability: context.traceToolAvailability,
|
|
157
|
+
skipLiterateJavaExecution: !context.traceToolAvailability.java,
|
|
158
|
+
});
|
|
159
|
+
if (record.outputs.size > 0) {
|
|
160
|
+
records.set(filePath, record);
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const { message } = diagnosticFromError(error);
|
|
164
|
+
// Every page error names its file.
|
|
165
|
+
const relPath = toPosix(path.relative(getProjectDir(), filePath));
|
|
166
|
+
diagnostics.push({
|
|
167
|
+
message:
|
|
168
|
+
message.includes(filePath) || message.includes(relPath)
|
|
169
|
+
? message
|
|
170
|
+
: `${filePath}: ${message}`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { records, diagnostics };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Validates rendered records and assembles the snapshot of a build. */
|
|
178
|
+
export function finishBuild({
|
|
179
|
+
siteVariables,
|
|
180
|
+
scan,
|
|
181
|
+
assetFiles,
|
|
182
|
+
generatedOutputs,
|
|
183
|
+
records,
|
|
184
|
+
diagnostics,
|
|
185
|
+
}: {
|
|
186
|
+
siteVariables: SiteVariables;
|
|
187
|
+
scan: TadaProjectScan;
|
|
188
|
+
assetFiles: string[];
|
|
189
|
+
generatedOutputs: ReadonlyMap<string, OutputContent>;
|
|
190
|
+
records: ReadonlyMap<string, TadaSourceRecord>;
|
|
191
|
+
diagnostics: BuildDiagnostic[];
|
|
192
|
+
}): SiteBuildResult {
|
|
193
|
+
const allDiagnostics = [
|
|
194
|
+
...diagnostics,
|
|
195
|
+
...validateProjectConfigLinks(scan.validTargets),
|
|
196
|
+
...findOutputConflicts(scan, siteVariables, generatedOutputs, records),
|
|
197
|
+
];
|
|
198
|
+
if (allDiagnostics.length > 0) {
|
|
199
|
+
return { ok: false, diagnostics: allDiagnostics };
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
ok: true,
|
|
203
|
+
snapshot: createSnapshot({
|
|
204
|
+
siteVariables,
|
|
205
|
+
assetFiles,
|
|
206
|
+
generatedOutputs,
|
|
207
|
+
authorsData: config('authors'),
|
|
208
|
+
scan,
|
|
209
|
+
records,
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Builds every source and generated file in memory. Nothing is written. */
|
|
215
|
+
export async function buildSite({
|
|
216
|
+
siteVariables,
|
|
217
|
+
mode,
|
|
218
|
+
isWatchMode,
|
|
219
|
+
traceCache,
|
|
220
|
+
traceToolAvailability,
|
|
221
|
+
scan = scanProject(siteVariables),
|
|
222
|
+
}: {
|
|
223
|
+
siteVariables: SiteVariables;
|
|
224
|
+
mode: 'development' | 'production';
|
|
225
|
+
isWatchMode: boolean;
|
|
226
|
+
traceCache: TraceCache;
|
|
227
|
+
/** Probed only if omitted and the site has content pages */
|
|
228
|
+
traceToolAvailability?: TraceToolAvailability;
|
|
229
|
+
/** A scan the caller already made with the same site variables */
|
|
230
|
+
scan?: TadaProjectScan;
|
|
231
|
+
}): Promise<SiteBuildResult> {
|
|
232
|
+
const configDiagnostics = validateConfig(scan, siteVariables);
|
|
233
|
+
if (configDiagnostics.length > 0) {
|
|
234
|
+
return { ok: false, diagnostics: configDiagnostics };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
compileTemplates(siteVariables);
|
|
238
|
+
await ensureHighlighter(siteVariables);
|
|
239
|
+
const assets = await generateSiteAssets(siteVariables, { mode, isWatchMode });
|
|
240
|
+
|
|
241
|
+
const pageCount = [...sourcePaths(scan, 'content', true)].length;
|
|
242
|
+
if (pageCount > 0) {
|
|
243
|
+
log.info`Processing ${pageCount} content ${pageCount === 1 ? 'file' : 'files'}`;
|
|
244
|
+
}
|
|
245
|
+
if (!traceToolAvailability) {
|
|
246
|
+
traceToolAvailability = pageCount > 0 ? checkTraceToolAvailability() : {};
|
|
247
|
+
if (pageCount > 0 && !traceToolAvailability.java) {
|
|
248
|
+
log.warn`javac was not found; literate Java pages will not include execution output`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const { records, diagnostics } = renderSources(scan.sources.keys(), {
|
|
252
|
+
siteVariables,
|
|
253
|
+
scan,
|
|
254
|
+
assetFiles: assets.assetFiles,
|
|
255
|
+
isWatchMode,
|
|
256
|
+
traceCache,
|
|
257
|
+
traceToolAvailability,
|
|
258
|
+
});
|
|
259
|
+
return finishBuild({
|
|
260
|
+
siteVariables,
|
|
261
|
+
scan,
|
|
262
|
+
assetFiles: assets.assetFiles,
|
|
263
|
+
generatedOutputs: assets.outputs,
|
|
264
|
+
records,
|
|
265
|
+
diagnostics,
|
|
266
|
+
});
|
|
267
|
+
}
|