@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
package/build/globals.ts
CHANGED
|
@@ -1,91 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'December',
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
interface Globals {
|
|
21
|
-
isHomePage: boolean;
|
|
22
|
-
isoDate: (str: string | null | undefined) => string | null;
|
|
23
|
-
readableDate: (date: string | Date | null | undefined) => string;
|
|
24
|
-
classNames: (obj: Record<string, unknown>) => string;
|
|
25
|
-
cx: (obj: Record<string, unknown>) => string;
|
|
26
|
-
renderTimeZoneChooser: () => string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default function createGlobals(
|
|
30
|
-
pageVariables: Record<string, unknown>,
|
|
31
|
-
siteVariables: SiteVariables,
|
|
32
|
-
subPath: string,
|
|
33
|
-
): Globals {
|
|
34
|
-
const defaultTz = timezones.find(
|
|
35
|
-
t => t.value === siteVariables.defaultTimeZone,
|
|
36
|
-
)!;
|
|
37
|
-
const options = timezones
|
|
38
|
-
.map(tz => {
|
|
39
|
-
const label =
|
|
40
|
-
tz.value === 'UTC' ? tz.label : `${tz.label} (${tz.abbreviation})`;
|
|
41
|
-
const selected = tz.value === defaultTz.value ? ' selected' : '';
|
|
42
|
-
return `<option value="${tz.value}"${selected}>${label}</option>`;
|
|
43
|
-
})
|
|
44
|
-
.join('');
|
|
45
|
-
const renderTimeZoneChooser = () =>
|
|
46
|
-
`<span class="time-zone-label" hidden>Times shown in </span><select class="time-zone" hidden disabled aria-label="Time zone">${options}</select><noscript>Times shown in ${defaultTz.abbreviation}.</noscript>`;
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
isHomePage: subPath === 'index',
|
|
50
|
-
isoDate,
|
|
51
|
-
readableDate,
|
|
52
|
-
classNames,
|
|
53
|
-
cx: classNames,
|
|
54
|
-
renderTimeZoneChooser,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function isoDate(str: string | null | undefined): string | null {
|
|
59
|
-
if (str == null || str == '') {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
const date = new Date(str);
|
|
63
|
-
return date.toISOString().slice(0, 10);
|
|
1
|
+
type Sha256Hasher = {
|
|
2
|
+
update: (buffer: ArrayBuffer) => void;
|
|
3
|
+
digest: (encoding: 'hex') => string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export interface Globals {
|
|
7
|
+
createSha256Hasher: () => Sha256Hasher;
|
|
8
|
+
cwd: () => string;
|
|
9
|
+
getEnv: (name: string) => string | undefined;
|
|
10
|
+
now: () => number;
|
|
11
|
+
pid: () => number;
|
|
12
|
+
readFileArrayBuffer: (filePath: string) => Promise<ArrayBuffer>;
|
|
13
|
+
sleepSync: (delayMs: number) => void;
|
|
14
|
+
stderrWrite: (chunk: string) => void;
|
|
15
|
+
stdoutWrite: (chunk: string) => void;
|
|
16
|
+
toISOString: (timestampMs: number) => string;
|
|
64
17
|
}
|
|
65
18
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return '';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
19
|
+
export const globals: Globals = {
|
|
20
|
+
createSha256Hasher() {
|
|
21
|
+
return new Bun.CryptoHasher('sha256');
|
|
22
|
+
},
|
|
23
|
+
cwd() {
|
|
24
|
+
return process.cwd();
|
|
25
|
+
},
|
|
26
|
+
getEnv(name) {
|
|
27
|
+
return process.env[name];
|
|
28
|
+
},
|
|
29
|
+
now() {
|
|
30
|
+
return Date.now();
|
|
31
|
+
},
|
|
32
|
+
pid() {
|
|
33
|
+
return process.pid;
|
|
34
|
+
},
|
|
35
|
+
readFileArrayBuffer(filePath) {
|
|
36
|
+
return Bun.file(filePath).arrayBuffer();
|
|
37
|
+
},
|
|
38
|
+
sleepSync(delayMs) {
|
|
39
|
+
Bun.sleepSync(delayMs);
|
|
40
|
+
},
|
|
41
|
+
stderrWrite(chunk) {
|
|
42
|
+
process.stderr.write(chunk);
|
|
43
|
+
},
|
|
44
|
+
stdoutWrite(chunk) {
|
|
45
|
+
process.stdout.write(chunk);
|
|
46
|
+
},
|
|
47
|
+
toISOString(timestampMs) {
|
|
48
|
+
return new Date(timestampMs).toISOString();
|
|
49
|
+
},
|
|
50
|
+
};
|
package/build/log.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { Gi, L, Ri, P, Yi, Li } from './colors';
|
|
4
|
+
import { globals, type Globals } from './globals';
|
|
4
5
|
import type { Logger } from './types';
|
|
5
6
|
import FLAIR_STRINGS from './flair.json' with { type: 'json' };
|
|
6
7
|
|
|
7
8
|
const LEVELS = ['debug', 'info', 'warn', 'error'] as const;
|
|
8
9
|
|
|
9
10
|
type LogLevel = (typeof LEVELS)[number];
|
|
10
|
-
|
|
11
|
-
const ENV_LOG_LEVEL = process.env.TADA_LOG_LEVEL;
|
|
12
|
-
|
|
13
|
-
if (ENV_LOG_LEVEL && !LEVELS.includes(ENV_LOG_LEVEL as LogLevel)) {
|
|
14
|
-
throw new Error(
|
|
15
|
-
`Invalid TADA_LOG_LEVEL "${ENV_LOG_LEVEL}", must be one of: ${LEVELS.join(', ')}`,
|
|
16
|
-
);
|
|
17
|
-
}
|
|
11
|
+
type LogGlobals = Pick<Globals, 'getEnv' | 'stderrWrite' | 'stdoutWrite'>;
|
|
18
12
|
|
|
19
13
|
function shouldLog(loggerLevel: string, level: string): boolean {
|
|
20
14
|
return (
|
|
@@ -32,20 +26,35 @@ function validateLevel(level: string): void {
|
|
|
32
26
|
|
|
33
27
|
function print(
|
|
34
28
|
strings: string[],
|
|
29
|
+
globals: LogGlobals,
|
|
35
30
|
stream: 'stdout' | 'stderr' = 'stdout',
|
|
36
31
|
end: string = '\n',
|
|
37
32
|
): void {
|
|
38
33
|
for (const s of strings) {
|
|
39
|
-
|
|
34
|
+
if (stream === 'stderr') {
|
|
35
|
+
globals.stderrWrite(s);
|
|
36
|
+
} else {
|
|
37
|
+
globals.stdoutWrite(s);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (stream === 'stderr') {
|
|
41
|
+
globals.stderrWrite(end);
|
|
42
|
+
} else {
|
|
43
|
+
globals.stdoutWrite(end);
|
|
40
44
|
}
|
|
41
|
-
process[stream].write(end);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export function makeLogger(name: string, logLevel: string = 'info'): Logger {
|
|
48
|
+
const envLogLevel = globals.getEnv('TADA_LOG_LEVEL');
|
|
49
|
+
if (envLogLevel && !LEVELS.includes(envLogLevel as LogLevel)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Invalid TADA_LOG_LEVEL "${envLogLevel}", must be one of: ${LEVELS.join(', ')}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
45
54
|
validateLevel(logLevel);
|
|
46
55
|
|
|
47
|
-
if (
|
|
48
|
-
logLevel =
|
|
56
|
+
if (envLogLevel) {
|
|
57
|
+
logLevel = envLogLevel;
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
if (name) {
|
|
@@ -74,29 +83,29 @@ export function makeLogger(name: string, logLevel: string = 'info'): Logger {
|
|
|
74
83
|
},
|
|
75
84
|
debug(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
76
85
|
if (shouldLog(this.minLogLevel, 'debug')) {
|
|
77
|
-
print(getArgs('debug', strings, args, L), 'stderr');
|
|
86
|
+
print(getArgs('debug', strings, args, L), globals, 'stderr');
|
|
78
87
|
}
|
|
79
88
|
},
|
|
80
89
|
info(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
81
90
|
if (shouldLog(this.minLogLevel, 'info')) {
|
|
82
|
-
print(getArgs('info', strings, args, Li));
|
|
91
|
+
print(getArgs('info', strings, args, Li), globals);
|
|
83
92
|
}
|
|
84
93
|
},
|
|
85
94
|
warn(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
86
95
|
if (shouldLog(this.minLogLevel, 'warn')) {
|
|
87
|
-
print(getArgs('warn', strings, args, Yi));
|
|
96
|
+
print(getArgs('warn', strings, args, Yi), globals);
|
|
88
97
|
}
|
|
89
98
|
},
|
|
90
99
|
error(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
91
100
|
if (shouldLog(this.minLogLevel, 'error')) {
|
|
92
|
-
print(getArgs('error', strings, args, Ri));
|
|
101
|
+
print(getArgs('error', strings, args, Ri), globals);
|
|
93
102
|
}
|
|
94
103
|
},
|
|
95
104
|
event(strings: TemplateStringsArray, ...args: unknown[]) {
|
|
96
|
-
print(getArgs('event', strings, args, Gi));
|
|
105
|
+
print(getArgs('event', strings, args, Gi), globals);
|
|
97
106
|
},
|
|
98
107
|
followup(strings: string[]) {
|
|
99
|
-
print(strings);
|
|
108
|
+
print(strings, globals);
|
|
100
109
|
},
|
|
101
110
|
};
|
|
102
111
|
|
package/build/pagefind.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
normalizeOutputPath,
|
|
8
8
|
} from './util';
|
|
9
9
|
import { assertMutoolAvailable, extractPdfPages } from './pdf-text';
|
|
10
|
-
import type {
|
|
10
|
+
import type { HtmlOutputAnalysis } from './types';
|
|
11
11
|
|
|
12
12
|
const log = makeLogger(import.meta.url);
|
|
13
13
|
const PAGEFIND_VERBOSE = process.env.TADA_LOG_LEVEL === 'debug';
|
|
@@ -89,20 +89,26 @@ interface IndexTargets {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function collectIndexTargets(
|
|
92
|
-
|
|
93
|
-
siteVariables: SiteVariables,
|
|
92
|
+
htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>,
|
|
94
93
|
pdfSourceByOutputPath: Map<string, string>,
|
|
95
94
|
): IndexTargets {
|
|
96
|
-
if (
|
|
95
|
+
if (htmlAnalysisByPath.size === 0) {
|
|
97
96
|
return { reachableHtmlPaths: [], reachablePdfPaths: [] };
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
const { reachableHtmlPaths, reachableAssetTargets } =
|
|
100
|
+
collectReachableSiteAssets({
|
|
101
|
+
htmlAnalysisByPath,
|
|
102
|
+
knownAssetTargets: new Set(pdfSourceByOutputPath.keys()),
|
|
103
|
+
rootPath: 'index.html',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
reachableHtmlPaths,
|
|
108
|
+
reachablePdfPaths: reachableAssetTargets.filter(target =>
|
|
109
|
+
pdfSourceByOutputPath.has(target),
|
|
110
|
+
),
|
|
111
|
+
};
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
interface BuildIndexOptions {
|
|
@@ -207,28 +213,25 @@ async function buildIndex({
|
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
interface RunPagefindOptions {
|
|
210
|
-
siteVariables: SiteVariables;
|
|
211
216
|
distPath: string;
|
|
212
217
|
htmlAssetsByPath: Map<string, string>;
|
|
218
|
+
htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>;
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
export async function runPagefind({
|
|
216
|
-
siteVariables,
|
|
217
222
|
distPath,
|
|
218
223
|
htmlAssetsByPath,
|
|
224
|
+
htmlAnalysisByPath,
|
|
219
225
|
}: RunPagefindOptions): Promise<void> {
|
|
220
226
|
const pdfSourceByOutputPath = getPdfSourceByOutputPath();
|
|
221
227
|
const start = Date.now();
|
|
222
228
|
|
|
223
229
|
log.debug`Finding reachable pages for search index`;
|
|
224
230
|
const { reachableHtmlPaths, reachablePdfPaths } = collectIndexTargets(
|
|
225
|
-
|
|
226
|
-
siteVariables,
|
|
231
|
+
htmlAnalysisByPath,
|
|
227
232
|
pdfSourceByOutputPath,
|
|
228
233
|
);
|
|
229
234
|
|
|
230
|
-
const snapshotReadyAt = Date.now();
|
|
231
|
-
|
|
232
235
|
let noun = reachableHtmlPaths.length === 1 ? 'page' : 'pages';
|
|
233
236
|
let message = `Building search index for ${reachableHtmlPaths.length} ${noun}`;
|
|
234
237
|
if (reachablePdfPaths.length > 0) {
|
|
@@ -246,27 +249,32 @@ export async function runPagefind({
|
|
|
246
249
|
});
|
|
247
250
|
|
|
248
251
|
const finishedAt = Date.now();
|
|
249
|
-
log.debug`Search index built in ${finishedAt -
|
|
252
|
+
log.debug`Search index built in ${finishedAt - start}ms`;
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
export class WatchPagefindRunner {
|
|
253
|
-
private siteVariables: SiteVariables;
|
|
254
256
|
private watchRunInProgress: boolean;
|
|
255
257
|
private watchRunQueued: boolean;
|
|
256
258
|
private distPath: string | null;
|
|
257
259
|
private htmlCacheByAssetPath: Map<string, string>;
|
|
260
|
+
private htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>;
|
|
258
261
|
|
|
259
|
-
constructor(
|
|
260
|
-
this.siteVariables = siteVariables;
|
|
262
|
+
constructor() {
|
|
261
263
|
this.watchRunInProgress = false;
|
|
262
264
|
this.watchRunQueued = false;
|
|
263
265
|
this.distPath = null;
|
|
264
266
|
this.htmlCacheByAssetPath = new Map();
|
|
267
|
+
this.htmlAnalysisByPath = new Map();
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
update(
|
|
270
|
+
update(
|
|
271
|
+
distPath: string,
|
|
272
|
+
htmlAssetsByPath: Map<string, string>,
|
|
273
|
+
htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>,
|
|
274
|
+
): void {
|
|
268
275
|
this.distPath = distPath;
|
|
269
276
|
this.htmlCacheByAssetPath = htmlAssetsByPath;
|
|
277
|
+
this.htmlAnalysisByPath = htmlAnalysisByPath;
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
run(): void {
|
|
@@ -280,6 +288,7 @@ export class WatchPagefindRunner {
|
|
|
280
288
|
this.watchRunQueued = false;
|
|
281
289
|
const distPath = this.distPath!;
|
|
282
290
|
const htmlAssetsByPath = new Map(this.htmlCacheByAssetPath);
|
|
291
|
+
const htmlAnalysisByPath = new Map(this.htmlAnalysisByPath);
|
|
283
292
|
const pdfSourceByOutputPath = getPdfSourceByOutputPath();
|
|
284
293
|
const start = Date.now();
|
|
285
294
|
|
|
@@ -289,8 +298,7 @@ export class WatchPagefindRunner {
|
|
|
289
298
|
let reachablePdfPaths: string[];
|
|
290
299
|
try {
|
|
291
300
|
({ reachableHtmlPaths, reachablePdfPaths } = collectIndexTargets(
|
|
292
|
-
|
|
293
|
-
this.siteVariables,
|
|
301
|
+
htmlAnalysisByPath,
|
|
294
302
|
pdfSourceByOutputPath,
|
|
295
303
|
));
|
|
296
304
|
} catch (err) {
|
|
@@ -302,7 +310,6 @@ export class WatchPagefindRunner {
|
|
|
302
310
|
return;
|
|
303
311
|
}
|
|
304
312
|
|
|
305
|
-
const snapshotReadyAt = Date.now();
|
|
306
313
|
log.debug`Building search index in background`;
|
|
307
314
|
buildIndex({
|
|
308
315
|
distPath,
|
|
@@ -313,11 +320,11 @@ export class WatchPagefindRunner {
|
|
|
313
320
|
})
|
|
314
321
|
.then(() => {
|
|
315
322
|
const finishedAt = Date.now();
|
|
316
|
-
log.
|
|
323
|
+
log.info`Search index ready after ${finishedAt - start}ms`;
|
|
317
324
|
})
|
|
318
325
|
.catch(err => {
|
|
319
326
|
const failedAt = Date.now();
|
|
320
|
-
log.warn`Search index failed after ${failedAt -
|
|
327
|
+
log.warn`Search index failed after ${failedAt - start}ms: ${err.message}`;
|
|
321
328
|
})
|
|
322
329
|
.finally(() => {
|
|
323
330
|
this.watchRunInProgress = false;
|
package/build/pipeline.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { getDevSiteVariables, getProdSiteVariables } from './site-variables';
|
|
3
|
-
import { getExtensionToShikiLanguage } from './site-variables';
|
|
4
3
|
import { compileTemplates } from './templates';
|
|
5
|
-
import {
|
|
6
|
-
getDistDir,
|
|
7
|
-
getProdDistDir,
|
|
8
|
-
getContentDir,
|
|
9
|
-
getPublicDir,
|
|
10
|
-
} from './utils/paths';
|
|
4
|
+
import { getDistDir, getProdDistDir } from './utils/paths';
|
|
11
5
|
import { isFeatureEnabled } from './features';
|
|
12
6
|
import { bundle } from './bundle';
|
|
13
7
|
import { copyFonts } from './generate-fonts';
|
|
@@ -15,90 +9,115 @@ import { copyKatexAssets } from './generate-katex-assets';
|
|
|
15
9
|
import { generateFavicons } from './generate-favicon';
|
|
16
10
|
import { generateWebAppManifest } from './generate-web-app-manifest';
|
|
17
11
|
import { copyPublicFiles, copyContentAssets } from './copy';
|
|
18
|
-
import { getProcessedExtensions } from './utils/file-types';
|
|
19
12
|
import { ContentRenderer } from './generate-content-assets';
|
|
20
13
|
import { runPagefind } from './pagefind';
|
|
21
14
|
import { makeLogger, printFlair } from './log';
|
|
22
15
|
import { generateBuildManifest, getNextVersion } from './build-manifest';
|
|
16
|
+
import { scanProject } from './source-model';
|
|
17
|
+
import { validateConfig } from './watch/validation';
|
|
18
|
+
import { applyCommitPlan } from '../watch/fs-commit';
|
|
23
19
|
import path from 'path';
|
|
24
20
|
|
|
25
21
|
const log = makeLogger(import.meta.url);
|
|
26
22
|
|
|
23
|
+
function makeTempOutputDir(targetDir: string): string {
|
|
24
|
+
const parentDir = path.dirname(targetDir);
|
|
25
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
26
|
+
return fs.mkdtempSync(
|
|
27
|
+
path.join(parentDir, `${path.basename(targetDir)}-build-`),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function removeDirIfExists(dir: string): void {
|
|
32
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
export async function runPipeline(
|
|
28
36
|
mode: 'development' | 'production',
|
|
29
37
|
): Promise<void> {
|
|
30
38
|
const isDev = mode === 'development';
|
|
31
39
|
const siteVariables = isDev ? getDevSiteVariables() : getProdSiteVariables();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
const scan = scanProject(siteVariables);
|
|
41
|
+
const publishDir = isDev
|
|
42
|
+
? getDistDir()
|
|
43
|
+
: path.join(getProdDistDir(), 'v-next');
|
|
44
|
+
const distDir = makeTempOutputDir(publishDir);
|
|
45
|
+
let published = false;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const configDiagnostics = validateConfig(scan);
|
|
49
|
+
if (configDiagnostics.length > 0) {
|
|
50
|
+
throw new Error(configDiagnostics[0].message);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
compileTemplates(siteVariables);
|
|
54
|
+
const contentRenderer = new ContentRenderer(siteVariables);
|
|
55
|
+
await contentRenderer.initHighlighter();
|
|
56
|
+
|
|
57
|
+
const parallelTasks: (Promise<unknown> | void)[] = [
|
|
58
|
+
bundle(siteVariables, { mode, distDir }),
|
|
59
|
+
copyFonts(distDir),
|
|
60
|
+
copyKatexAssets(distDir),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (isFeatureEnabled(siteVariables, 'favicon')) {
|
|
64
|
+
parallelTasks.push(generateFavicons(siteVariables, distDir));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const results = await Promise.all(parallelTasks);
|
|
68
|
+
const assetFiles = results[0] as string[]; // bundle output filenames
|
|
69
|
+
|
|
70
|
+
if (isFeatureEnabled(siteVariables, 'favicon')) {
|
|
71
|
+
generateWebAppManifest(siteVariables, distDir);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const publicRelPaths = copyPublicFiles(scan.publicDir, distDir);
|
|
75
|
+
copyContentAssets(
|
|
76
|
+
scan.contentDir,
|
|
77
|
+
distDir,
|
|
78
|
+
[...scan.processedExts],
|
|
79
|
+
publicRelPaths,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { errors, htmlAssetsByPath, htmlAnalysisByPath } =
|
|
83
|
+
contentRenderer.processContent({ distDir, assetFiles, scan });
|
|
84
|
+
|
|
85
|
+
for (const err of errors) {
|
|
86
|
+
log.error`${err.message}`;
|
|
87
|
+
}
|
|
88
|
+
if (errors.length > 0) {
|
|
89
|
+
throw new Error(errors[0].message);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isFeatureEnabled(siteVariables, 'search')) {
|
|
93
|
+
await runPagefind({
|
|
94
|
+
distPath: distDir,
|
|
95
|
+
htmlAssetsByPath,
|
|
96
|
+
htmlAnalysisByPath,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let finalOutputDir = getDistDir();
|
|
101
|
+
|
|
102
|
+
if (!isDev) {
|
|
103
|
+
const prodBase = getProdDistDir();
|
|
104
|
+
const prodVersion = getNextVersion(prodBase);
|
|
105
|
+
finalOutputDir = path.join(prodBase, `v${prodVersion}`);
|
|
106
|
+
const manifestPath = path.join(distDir, 'tada.manifest.json');
|
|
107
|
+
await generateBuildManifest(distDir, manifestPath, prodVersion);
|
|
108
|
+
log.info`Built dist-prod/v${prodVersion}/`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
applyCommitPlan({
|
|
112
|
+
kind: 'replace-root',
|
|
113
|
+
stagedPath: distDir,
|
|
114
|
+
targetPath: finalOutputDir,
|
|
115
|
+
});
|
|
116
|
+
published = true;
|
|
117
|
+
printFlair();
|
|
118
|
+
} finally {
|
|
119
|
+
if (!published) {
|
|
120
|
+
removeDirIfExists(distDir);
|
|
121
|
+
}
|
|
41
122
|
}
|
|
42
|
-
|
|
43
|
-
const contentDir = getContentDir();
|
|
44
|
-
const publicDir = getPublicDir();
|
|
45
|
-
|
|
46
|
-
// Ensure dist/ exists
|
|
47
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
48
|
-
|
|
49
|
-
// Phase 1: Setup
|
|
50
|
-
compileTemplates(siteVariables);
|
|
51
|
-
const contentRenderer = new ContentRenderer(siteVariables);
|
|
52
|
-
await contentRenderer.initHighlighter();
|
|
53
|
-
|
|
54
|
-
// Phase 2: Bundle + assets (parallel)
|
|
55
|
-
const parallelTasks: (Promise<unknown> | void)[] = [
|
|
56
|
-
bundle(siteVariables, { mode, distDir }),
|
|
57
|
-
copyFonts(distDir),
|
|
58
|
-
copyKatexAssets(distDir),
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
if (isFeatureEnabled(siteVariables, 'favicon')) {
|
|
62
|
-
parallelTasks.push(generateFavicons(siteVariables, distDir));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const results = await Promise.all(parallelTasks);
|
|
66
|
-
const assetFiles = results[0] as string[]; // bundle output filenames
|
|
67
|
-
|
|
68
|
-
if (isFeatureEnabled(siteVariables, 'favicon')) {
|
|
69
|
-
generateWebAppManifest(siteVariables, distDir);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const publicRelPaths = copyPublicFiles(publicDir, distDir);
|
|
73
|
-
const processedExtensions = getProcessedExtensions(
|
|
74
|
-
Object.keys(getExtensionToShikiLanguage(siteVariables)),
|
|
75
|
-
);
|
|
76
|
-
copyContentAssets(contentDir, distDir, processedExtensions, publicRelPaths);
|
|
77
|
-
|
|
78
|
-
// Phase 3: Content rendering
|
|
79
|
-
const { errors, htmlAssetsByPath } = contentRenderer.processContent({
|
|
80
|
-
distDir,
|
|
81
|
-
assetFiles,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
for (const err of errors) {
|
|
85
|
-
log.error`${err.message}`;
|
|
86
|
-
}
|
|
87
|
-
if (errors.length > 0) {
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Phase 4: Post-processing
|
|
92
|
-
if (isFeatureEnabled(siteVariables, 'search')) {
|
|
93
|
-
await runPagefind({ siteVariables, distPath: distDir, htmlAssetsByPath });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Phase 5: Build manifest (production only)
|
|
97
|
-
if (!isDev) {
|
|
98
|
-
const manifestPath = path.join(distDir, 'tada.manifest.json');
|
|
99
|
-
await generateBuildManifest(distDir, manifestPath, prodVersion!);
|
|
100
|
-
log.info`Built dist-prod/v${prodVersion!}/`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
printFlair();
|
|
104
123
|
}
|