@abreen/tada 1.1.0 → 1.3.1
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 +69 -17
- package/bin/tada.ts +317 -25
- package/bin/validators.test.ts +4 -3
- package/bin/validators.ts +4 -2
- package/build/apply-base-path-plugin.ts +24 -13
- package/build/build-manifest.test.ts +349 -0
- package/build/build-manifest.ts +165 -0
- package/build/bundle.ts +19 -20
- package/build/code.test.ts +7 -3
- package/build/content-watch.ts +6 -6
- package/build/copy.ts +2 -2
- package/build/deflist-id-plugin.ts +1 -1
- package/build/external-links-plugin.ts +2 -2
- package/build/features.ts +1 -1
- package/build/generate-content-assets.ts +12 -7
- package/build/generate-favicon.ts +13 -6
- package/build/generate-fonts.ts +2 -2
- package/build/generate-katex-assets.ts +35 -0
- package/build/generate-manifest.ts +2 -2
- package/build/globals.test.ts +10 -11
- package/build/globals.ts +14 -7
- package/build/json-schema.test.ts +1 -1
- package/build/log.test.ts +1 -1
- package/build/log.ts +2 -2
- package/build/markdown-plugins.test.ts +116 -10
- package/build/pagefind.test.ts +2 -2
- package/build/pagefind.ts +5 -5
- package/build/pdf-text.test.ts +52 -0
- package/build/pipeline.ts +42 -15
- package/build/reachability.test.ts +1 -1
- package/build/serve.ts +20 -8
- package/build/site-variables.ts +13 -5
- package/build/templates.ts +16 -7
- package/build/toc-plugin.test.ts +2 -2
- package/build/toc-plugin.ts +1 -1
- package/build/types.ts +15 -6
- package/build/util.ts +6 -6
- package/build/utils/code.ts +5 -5
- package/build/utils/content-files.ts +3 -3
- package/build/utils/derive-theme.test.ts +1 -1
- package/build/utils/derive-theme.ts +1 -1
- package/build/utils/file-types.test.ts +1 -1
- package/build/utils/front-matter.test.ts +1 -4
- package/build/utils/front-matter.ts +2 -2
- package/build/utils/jdi-runner/LiterateRunner.java +13 -0
- package/build/utils/literate-java.ts +24 -12
- package/build/utils/markdown.ts +41 -9
- package/build/utils/paths.test.ts +2 -2
- package/build/utils/paths.ts +5 -1
- package/build/utils/render.ts +57 -36
- package/build/utils/shiki-highlighter.ts +1 -1
- package/build/validate-internal-links-plugin.test.ts +1 -1
- package/build/validate-internal-links-plugin.ts +1 -1
- package/build/watch-reachability-state.test.ts +2 -2
- package/build/watch-reachability-state.ts +1 -1
- package/build/watch.ts +91 -110
- package/init/content/labs/00/VowelCounter.java.md +129 -0
- package/init/content/labs/00/index.md +29 -0
- package/init/content/labs/index.md +9 -0
- package/{content → init/content}/lectures/01/Pair.java.md +1 -0
- package/{content → init/content}/lectures/index.md +1 -1
- package/{content → init/content}/markdown.md +26 -7
- package/package.json +19 -7
- package/{templates → schema}/nav.schema.json +1 -2
- package/{build → schema}/site.schema.json +3 -5
- package/src/_alerts.scss +4 -0
- package/src/_base.scss +90 -6
- package/src/_layout.scss +39 -2
- package/src/anchor/index.ts +0 -1
- package/src/index.ts +3 -3
- package/src/question/style.scss +8 -3
- package/src/search/index.test.ts +203 -0
- package/src/search/index.ts +3 -79
- package/src/search/pdf-utils.ts +79 -0
- package/src/search/style.scss +2 -20
- package/src/style.scss +20 -101
- package/src/timezone/index.ts +9 -25
- package/src/util.ts +1 -1
- package/templates/_top.html +2 -1
- package/types/dev.d.ts +1 -3
- package/types/site-variables.d.ts +6 -10
- package/types/untyped-modules.d.ts +7 -0
- package/build/utils/jdi-runner/LiterateRunner.class +0 -0
- package/config/authors.json +0 -1
- package/config/nav.json +0 -28
- /package/{content → init/content}/index.md +0 -0
- /package/{content → init/content}/lectures/01/Rectangle.java +0 -0
- /package/{content → init/content}/lectures/01/demo.py +0 -0
- /package/{content → init/content}/lectures/01/index.md +0 -0
- /package/{content → init/content}/lectures/01/lecture1.pdf +0 -0
- /package/{content → init/content}/problem_sets/index.html +0 -0
- /package/{public → init/public}/avatars/alex.jpg +0 -0
- /package/{public → init/public}/test.txt +0 -0
- /package/{templates → schema}/authors.schema.json +0 -0
package/build/utils/render.ts
CHANGED
|
@@ -2,31 +2,28 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import { stripHtml } from 'string-strip-html';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
5
|
+
import { isFeatureEnabled } from '../features';
|
|
6
|
+
import { makeLogger } from '../log';
|
|
7
|
+
import { B } from '../colors';
|
|
8
|
+
import createGlobals from '../globals';
|
|
9
|
+
import { render, json } from '../templates';
|
|
9
10
|
import {
|
|
10
11
|
extractJavaMethodToc,
|
|
11
12
|
renderCodeSegment,
|
|
12
13
|
renderCodeWithComments,
|
|
13
|
-
} from './code
|
|
14
|
-
import { extensionIsMarkdown } from './file-types
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from './paths.js';
|
|
20
|
-
import { parseFrontMatterAndContent } from './front-matter.js';
|
|
21
|
-
import { createMarkdown } from './markdown.js';
|
|
22
|
-
import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin.js';
|
|
14
|
+
} from './code';
|
|
15
|
+
import { extensionIsMarkdown } from './file-types';
|
|
16
|
+
import { createApplyBasePath, normalizeOutputPath } from './paths';
|
|
17
|
+
import { parseFrontMatterAndContent } from './front-matter';
|
|
18
|
+
import { createMarkdown } from './markdown';
|
|
19
|
+
import { generateTocHtml, generateCodeTocHtml } from '../toc-plugin';
|
|
23
20
|
import {
|
|
24
21
|
parseLiterateJava,
|
|
25
22
|
hasMainMethod,
|
|
26
23
|
deriveClassName,
|
|
27
24
|
compileJavaSource,
|
|
28
25
|
executeLiterateJava,
|
|
29
|
-
} from './literate-java
|
|
26
|
+
} from './literate-java';
|
|
30
27
|
import type {
|
|
31
28
|
Asset,
|
|
32
29
|
SiteVariables,
|
|
@@ -34,7 +31,7 @@ import type {
|
|
|
34
31
|
RenderCodePageOptions,
|
|
35
32
|
RenderLiterateJavaOptions,
|
|
36
33
|
RenderCopiedContentOptions,
|
|
37
|
-
} from '../types
|
|
34
|
+
} from '../types';
|
|
38
35
|
|
|
39
36
|
const log = makeLogger(__filename);
|
|
40
37
|
|
|
@@ -112,6 +109,7 @@ export function injectAssetTags(
|
|
|
112
109
|
html: string,
|
|
113
110
|
assetFiles: string[],
|
|
114
111
|
applyBasePath: (subPath: string) => string,
|
|
112
|
+
distDir: string,
|
|
115
113
|
): string {
|
|
116
114
|
const jsAssets = assetFiles.filter(f => f.endsWith('.js'));
|
|
117
115
|
const cssAssets = assetFiles.filter(f => f.endsWith('.css'));
|
|
@@ -121,8 +119,6 @@ export function injectAssetTags(
|
|
|
121
119
|
.join('');
|
|
122
120
|
const criticalAssets = cssAssets.filter(f => f.includes('critical.bundle.'));
|
|
123
121
|
const asyncAssets = cssAssets.filter(f => !f.includes('critical.bundle.'));
|
|
124
|
-
|
|
125
|
-
const distDir = getDistDir();
|
|
126
122
|
const criticalTags = criticalAssets
|
|
127
123
|
.map(asset => {
|
|
128
124
|
const css = fs.readFileSync(path.join(distDir, asset), 'utf-8');
|
|
@@ -131,9 +127,7 @@ export function injectAssetTags(
|
|
|
131
127
|
.join('');
|
|
132
128
|
const asyncLinkTags = asyncAssets
|
|
133
129
|
.map(
|
|
134
|
-
asset =>
|
|
135
|
-
`<link href="${applyBasePath('/' + asset)}" rel="stylesheet" media="print" onload="this.media='all'">` +
|
|
136
|
-
`<noscript><link href="${applyBasePath('/' + asset)}" rel="stylesheet"></noscript>`,
|
|
130
|
+
asset => `<link href="${applyBasePath('/' + asset)}" rel="stylesheet">`,
|
|
137
131
|
)
|
|
138
132
|
.join('');
|
|
139
133
|
|
|
@@ -142,6 +136,15 @@ export function injectAssetTags(
|
|
|
142
136
|
.replace('</head>', `${scriptTags}</head>`);
|
|
143
137
|
}
|
|
144
138
|
|
|
139
|
+
export function injectKatexStylesheet(
|
|
140
|
+
html: string,
|
|
141
|
+
applyBasePath: (subPath: string) => string,
|
|
142
|
+
): string {
|
|
143
|
+
const href = applyBasePath('/katex/katex.min.css');
|
|
144
|
+
const tag = `<link href="${href}" rel="stylesheet">`;
|
|
145
|
+
return html.replace('<head>', `<head>${tag}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
145
148
|
function toContentAssetPath(contentDir: string, filePath: string): string {
|
|
146
149
|
return path
|
|
147
150
|
.relative(contentDir, filePath)
|
|
@@ -152,6 +155,7 @@ function toContentAssetPath(contentDir: string, filePath: string): string {
|
|
|
152
155
|
export function renderPlainTextPageAsset({
|
|
153
156
|
filePath,
|
|
154
157
|
contentDir,
|
|
158
|
+
distDir,
|
|
155
159
|
siteVariables,
|
|
156
160
|
validInternalTargets,
|
|
157
161
|
assetFiles,
|
|
@@ -190,11 +194,14 @@ export function renderPlainTextPageAsset({
|
|
|
190
194
|
subPath,
|
|
191
195
|
});
|
|
192
196
|
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
197
|
+
const templateHtml = render(
|
|
198
|
+
`${pageVariables.template}.html`,
|
|
199
|
+
templateParameters,
|
|
200
|
+
) as string;
|
|
201
|
+
let html = injectAssetTags(templateHtml, assetFiles, applyBasePath, distDir);
|
|
202
|
+
if (templateHtml.includes('class="katex"')) {
|
|
203
|
+
html = injectKatexStylesheet(html, applyBasePath);
|
|
204
|
+
}
|
|
198
205
|
|
|
199
206
|
return [
|
|
200
207
|
{
|
|
@@ -207,6 +214,7 @@ export function renderPlainTextPageAsset({
|
|
|
207
214
|
export function renderCodePageAsset({
|
|
208
215
|
filePath,
|
|
209
216
|
contentDir,
|
|
217
|
+
distDir,
|
|
210
218
|
siteVariables,
|
|
211
219
|
assetFiles,
|
|
212
220
|
}: RenderCodePageOptions): Asset[] {
|
|
@@ -247,6 +255,7 @@ export function renderCodePageAsset({
|
|
|
247
255
|
render('code.html', templateParameters) as string,
|
|
248
256
|
assetFiles,
|
|
249
257
|
applyBasePath,
|
|
258
|
+
distDir,
|
|
250
259
|
);
|
|
251
260
|
|
|
252
261
|
return [
|
|
@@ -288,10 +297,9 @@ function renderPlainTextContent(
|
|
|
288
297
|
filePath,
|
|
289
298
|
sourceUrlPath,
|
|
290
299
|
validTargets: validInternalTargets,
|
|
291
|
-
codeExtensions:
|
|
292
|
-
siteVariables.
|
|
293
|
-
|
|
294
|
-
: Object.keys(siteVariables.codeLanguages!),
|
|
300
|
+
codeExtensions: isFeatureEnabled(siteVariables, 'code')
|
|
301
|
+
? Object.keys(siteVariables.codeLanguages!)
|
|
302
|
+
: [],
|
|
295
303
|
},
|
|
296
304
|
});
|
|
297
305
|
|
|
@@ -381,6 +389,7 @@ export function stripHtmlComments(str: string): string {
|
|
|
381
389
|
export function renderLiterateJavaPageAsset({
|
|
382
390
|
filePath,
|
|
383
391
|
contentDir,
|
|
392
|
+
distDir,
|
|
384
393
|
siteVariables,
|
|
385
394
|
assetFiles,
|
|
386
395
|
skipExecution,
|
|
@@ -403,6 +412,9 @@ export function renderLiterateJavaPageAsset({
|
|
|
403
412
|
|
|
404
413
|
validateFrontMatter(pageVariables, filePath);
|
|
405
414
|
|
|
415
|
+
const stdin =
|
|
416
|
+
typeof pageVariables.stdin === 'string' ? pageVariables.stdin : undefined;
|
|
417
|
+
|
|
406
418
|
// Compile and execute the concatenated Java source
|
|
407
419
|
let tempDir: string | undefined;
|
|
408
420
|
let blockOutputMap: Map<number, string> | null = null;
|
|
@@ -416,6 +428,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
416
428
|
className,
|
|
417
429
|
tempDir,
|
|
418
430
|
codeBlocks,
|
|
431
|
+
stdin,
|
|
419
432
|
);
|
|
420
433
|
blockOutputMap = new Map(
|
|
421
434
|
outputEntries.map(e => [e.blockIndex, e.output]),
|
|
@@ -434,9 +447,17 @@ export function renderLiterateJavaPageAsset({
|
|
|
434
447
|
validatorOptions: { enabled: false },
|
|
435
448
|
});
|
|
436
449
|
let fenceIndex = 0;
|
|
450
|
+
const defaultFence = md.renderer.rules.fence!;
|
|
437
451
|
|
|
438
|
-
md.renderer.rules.fence = (tokens, idx) => {
|
|
452
|
+
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
|
439
453
|
const token = tokens[idx];
|
|
454
|
+
const lang = token.info.trim();
|
|
455
|
+
|
|
456
|
+
// Non-Java fences render with the default renderer (no line numbers)
|
|
457
|
+
if (lang !== '' && lang !== 'java') {
|
|
458
|
+
return defaultFence(tokens, idx, options, env, self);
|
|
459
|
+
}
|
|
460
|
+
|
|
440
461
|
const code = token.content;
|
|
441
462
|
const lines = code.endsWith('\n')
|
|
442
463
|
? code.slice(0, -1).split('\n')
|
|
@@ -509,11 +530,11 @@ export function renderLiterateJavaPageAsset({
|
|
|
509
530
|
subPath,
|
|
510
531
|
});
|
|
511
532
|
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
applyBasePath
|
|
516
|
-
|
|
533
|
+
const templateHtml = render('literate.html', templateParameters) as string;
|
|
534
|
+
let html = injectAssetTags(templateHtml, assetFiles, applyBasePath, distDir);
|
|
535
|
+
if (templateHtml.includes('class="katex"')) {
|
|
536
|
+
html = injectKatexStylesheet(html, applyBasePath);
|
|
537
|
+
}
|
|
517
538
|
|
|
518
539
|
return [
|
|
519
540
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
2
|
import MarkdownIt from 'markdown-it';
|
|
3
|
-
import validateInternalLinks from './validate-internal-links-plugin
|
|
3
|
+
import validateInternalLinks from './validate-internal-links-plugin';
|
|
4
4
|
|
|
5
5
|
function createMd(
|
|
6
6
|
validTargets: string[],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { collectReachableSiteAssets } from './reachability
|
|
3
|
-
import WatchReachabilityState from './watch-reachability-state
|
|
2
|
+
import { collectReachableSiteAssets } from './reachability';
|
|
3
|
+
import WatchReachabilityState from './watch-reachability-state';
|
|
4
4
|
|
|
5
5
|
function createReader(htmlByPath: Map<string, string>) {
|
|
6
6
|
const calls: string[] = [];
|
package/build/watch.ts
CHANGED
|
@@ -4,37 +4,33 @@ import { fork } from 'child_process';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import chokidar from 'chokidar';
|
|
6
6
|
import WebSocket, { WebSocketServer } from 'ws';
|
|
7
|
-
import type { SiteVariables, WatchState } from './types
|
|
8
|
-
import { B } from './colors
|
|
9
|
-
import { makeLogger, printFlair } from './log
|
|
10
|
-
import { getDevSiteVariables } from './site-variables
|
|
11
|
-
import {
|
|
12
|
-
compileTemplates,
|
|
13
|
-
getHtmlTemplatesDir,
|
|
14
|
-
getJsonDataDir,
|
|
15
|
-
JSON_DATA_FILES,
|
|
16
|
-
} from './templates.js';
|
|
7
|
+
import type { SiteVariables, WatchState } from './types';
|
|
8
|
+
import { B } from './colors';
|
|
9
|
+
import { makeLogger, printFlair } from './log';
|
|
10
|
+
import { getDevSiteVariables } from './site-variables';
|
|
11
|
+
import { compileTemplates, getJsonDataDir, JSON_DATA_FILES } from './templates';
|
|
17
12
|
import {
|
|
18
13
|
getContentDir,
|
|
19
14
|
getPublicDir,
|
|
20
15
|
getDistDir,
|
|
21
16
|
getPackageDir,
|
|
22
|
-
} from './utils/paths
|
|
23
|
-
import { isFeatureEnabled } from './features
|
|
24
|
-
import { bundle } from './bundle
|
|
25
|
-
import { copyFonts } from './generate-fonts
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
17
|
+
} from './utils/paths';
|
|
18
|
+
import { isFeatureEnabled } from './features';
|
|
19
|
+
import { bundle } from './bundle';
|
|
20
|
+
import { copyFonts } from './generate-fonts';
|
|
21
|
+
import { copyKatexAssets } from './generate-katex-assets';
|
|
22
|
+
import { generateFavicons } from './generate-favicon';
|
|
23
|
+
import { generateManifest } from './generate-manifest';
|
|
28
24
|
import {
|
|
29
25
|
copyPublicFiles,
|
|
30
26
|
copyContentAssets,
|
|
31
27
|
copyContentFile,
|
|
32
28
|
copyPublicFile,
|
|
33
|
-
} from './copy
|
|
34
|
-
import { getProcessedExtensions } from './utils/file-types
|
|
35
|
-
import { ContentRenderer } from './generate-content-assets
|
|
36
|
-
import { WatchPagefindRunner } from './pagefind
|
|
37
|
-
import { ContentChangeDetector } from './content-watch
|
|
29
|
+
} from './copy';
|
|
30
|
+
import { getProcessedExtensions } from './utils/file-types';
|
|
31
|
+
import { ContentRenderer } from './generate-content-assets';
|
|
32
|
+
import { WatchPagefindRunner } from './pagefind';
|
|
33
|
+
import { ContentChangeDetector } from './content-watch';
|
|
38
34
|
|
|
39
35
|
function getArg(name: string): number | null {
|
|
40
36
|
const idx = process.argv.indexOf(name);
|
|
@@ -74,14 +70,15 @@ async function bundleReloadClient(): Promise<string[]> {
|
|
|
74
70
|
);
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
type ChangeCategory = 'content' | 'public' | '
|
|
73
|
+
type ChangeCategory = 'content' | 'public' | 'config';
|
|
78
74
|
|
|
79
75
|
const log = makeLogger(__filename);
|
|
80
76
|
const wslog = makeLogger('WebSocket');
|
|
81
77
|
|
|
82
|
-
// --- WebSocket server
|
|
78
|
+
// --- WebSocket server ---
|
|
83
79
|
|
|
84
80
|
let webSocketsReady = false;
|
|
81
|
+
let watcherReady = false;
|
|
85
82
|
let webServerReady = false;
|
|
86
83
|
let webServerTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
87
84
|
let serveStarted = false;
|
|
@@ -92,6 +89,12 @@ try {
|
|
|
92
89
|
|
|
93
90
|
wss.on('connection', (conn: WebSocket) => {
|
|
94
91
|
wslog.debug`WebSocket client connected`;
|
|
92
|
+
if (watcherReady && conn.readyState === WebSocket.OPEN) {
|
|
93
|
+
if (initialBuildFailed) {
|
|
94
|
+
conn.send('error');
|
|
95
|
+
}
|
|
96
|
+
conn.send('ready');
|
|
97
|
+
}
|
|
95
98
|
conn.on('close', () => {
|
|
96
99
|
wslog.debug`WebSocket client disconnected`;
|
|
97
100
|
});
|
|
@@ -196,12 +199,9 @@ function toPublicRelativePath(filePath: string): string | null {
|
|
|
196
199
|
const contentDir: string = getContentDir();
|
|
197
200
|
const publicDir: string = getPublicDir();
|
|
198
201
|
const distDir: string = getDistDir();
|
|
199
|
-
const packageDir: string = getPackageDir();
|
|
200
202
|
|
|
201
|
-
let siteVariables: SiteVariables
|
|
202
|
-
let processedExtSet = new Set<string>(
|
|
203
|
-
getProcessedExtensions(Object.keys(siteVariables.codeLanguages || {})),
|
|
204
|
-
);
|
|
203
|
+
let siteVariables: SiteVariables;
|
|
204
|
+
let processedExtSet = new Set<string>();
|
|
205
205
|
let publicRelPaths: Set<string> = new Set();
|
|
206
206
|
let contentAssetRelPaths: Set<string> = new Set();
|
|
207
207
|
let contentRenderer: ContentRenderer;
|
|
@@ -213,6 +213,10 @@ const pendingChanges = new Set<string>();
|
|
|
213
213
|
let rebuilding = false;
|
|
214
214
|
|
|
215
215
|
async function initialBuild(): Promise<void> {
|
|
216
|
+
siteVariables = getDevSiteVariables();
|
|
217
|
+
processedExtSet = new Set(
|
|
218
|
+
getProcessedExtensions(Object.keys(siteVariables.codeLanguages || {})),
|
|
219
|
+
);
|
|
216
220
|
fs.mkdirSync(distDir, { recursive: true });
|
|
217
221
|
|
|
218
222
|
compileTemplates(siteVariables);
|
|
@@ -231,6 +235,7 @@ async function initialBuild(): Promise<void> {
|
|
|
231
235
|
];
|
|
232
236
|
|
|
233
237
|
copyFonts(distDir);
|
|
238
|
+
copyKatexAssets(distDir);
|
|
234
239
|
|
|
235
240
|
if (isFeatureEnabled(siteVariables, 'favicon')) {
|
|
236
241
|
await generateFavicons(siteVariables, distDir);
|
|
@@ -270,8 +275,6 @@ function classifyChange(filePath: string): ChangeCategory | null {
|
|
|
270
275
|
const resolved = path.resolve(filePath);
|
|
271
276
|
const resolvedContentDir = path.resolve(contentDir) + path.sep;
|
|
272
277
|
const resolvedPublicDir = path.resolve(publicDir) + path.sep;
|
|
273
|
-
const resolvedSrcDir = path.resolve(packageDir, 'src') + path.sep;
|
|
274
|
-
const htmlTemplatesDir = path.resolve(getHtmlTemplatesDir()) + path.sep;
|
|
275
278
|
const jsonDataDir = getJsonDataDir();
|
|
276
279
|
const siteConfigPath = path.resolve('site.dev.json');
|
|
277
280
|
|
|
@@ -281,16 +284,10 @@ function classifyChange(filePath: string): ChangeCategory | null {
|
|
|
281
284
|
if (resolved.startsWith(resolvedPublicDir)) {
|
|
282
285
|
return 'public';
|
|
283
286
|
}
|
|
284
|
-
if (resolved.startsWith(resolvedSrcDir)) {
|
|
285
|
-
return 'src';
|
|
286
|
-
}
|
|
287
287
|
if (
|
|
288
|
-
resolved
|
|
288
|
+
resolved === siteConfigPath ||
|
|
289
289
|
JSON_DATA_FILES.some(f => resolved === path.resolve(jsonDataDir, f))
|
|
290
290
|
) {
|
|
291
|
-
return 'templates';
|
|
292
|
-
}
|
|
293
|
-
if (resolved === siteConfigPath) {
|
|
294
291
|
return 'config';
|
|
295
292
|
}
|
|
296
293
|
return null;
|
|
@@ -316,6 +313,8 @@ async function rebuild(): Promise<void> {
|
|
|
316
313
|
}
|
|
317
314
|
}
|
|
318
315
|
|
|
316
|
+
let succeeded = false;
|
|
317
|
+
|
|
319
318
|
try {
|
|
320
319
|
// Site config changed, full restart
|
|
321
320
|
if (categories.has('config')) {
|
|
@@ -337,61 +336,36 @@ async function rebuild(): Promise<void> {
|
|
|
337
336
|
...(await bundleReloadClient()),
|
|
338
337
|
];
|
|
339
338
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
broadcast('reload');
|
|
347
|
-
if (pagefindRunner) {
|
|
348
|
-
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
349
|
-
setImmediate(() => pagefindRunner!.run());
|
|
350
|
-
}
|
|
339
|
+
copyFonts(distDir);
|
|
340
|
+
copyKatexAssets(distDir);
|
|
341
|
+
|
|
342
|
+
if (isFeatureEnabled(siteVariables, 'favicon')) {
|
|
343
|
+
await generateFavicons(siteVariables, distDir);
|
|
344
|
+
generateManifest(siteVariables, distDir);
|
|
351
345
|
}
|
|
352
|
-
rebuilding = false;
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
346
|
|
|
356
|
-
// Source changed, re-bundle, then re-render all content
|
|
357
|
-
if (categories.has('src')) {
|
|
358
|
-
assetFiles = [
|
|
359
|
-
...(await bundle(siteVariables, { mode: 'development' })),
|
|
360
|
-
...(await bundleReloadClient()),
|
|
361
|
-
];
|
|
362
|
-
// Force full content re-render since asset filenames may have changed
|
|
363
347
|
const result = contentRenderer.processContent({ distDir, assetFiles });
|
|
364
348
|
for (const err of result.errors) {
|
|
365
349
|
log.error`${err.message}`;
|
|
366
350
|
}
|
|
367
351
|
if (result.errors.length === 0) {
|
|
368
352
|
printFlair();
|
|
353
|
+
if (!serveStarted) {
|
|
354
|
+
serveStarted = true;
|
|
355
|
+
serve();
|
|
356
|
+
}
|
|
369
357
|
broadcast('reload');
|
|
370
358
|
if (pagefindRunner) {
|
|
371
359
|
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
372
360
|
setImmediate(() => pagefindRunner!.run());
|
|
373
361
|
}
|
|
362
|
+
succeeded = true;
|
|
374
363
|
}
|
|
375
|
-
rebuilding = false;
|
|
376
364
|
return;
|
|
377
365
|
}
|
|
378
366
|
|
|
379
|
-
// Templates/data changed, recompile templates, re-render all content
|
|
380
|
-
if (categories.has('templates')) {
|
|
381
|
-
const detection = changeDetector.detectChanges(changes);
|
|
382
|
-
if (detection.templateError) {
|
|
383
|
-
log.error`Template error: ${detection.templateError.message}`;
|
|
384
|
-
rebuilding = false;
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
367
|
// Public file changed, copy just that file
|
|
390
|
-
if (
|
|
391
|
-
categories.has('public') &&
|
|
392
|
-
!categories.has('content') &&
|
|
393
|
-
!categories.has('templates')
|
|
394
|
-
) {
|
|
368
|
+
if (categories.has('public') && !categories.has('content')) {
|
|
395
369
|
for (const filePath of changes) {
|
|
396
370
|
if (classifyChange(filePath) === 'public') {
|
|
397
371
|
const absPath = path.resolve(filePath);
|
|
@@ -407,17 +381,12 @@ async function rebuild(): Promise<void> {
|
|
|
407
381
|
}
|
|
408
382
|
printFlair();
|
|
409
383
|
broadcast('reload');
|
|
410
|
-
|
|
384
|
+
succeeded = true;
|
|
411
385
|
return;
|
|
412
386
|
}
|
|
413
387
|
|
|
414
|
-
// Content
|
|
388
|
+
// Content changed, incremental rebuild
|
|
415
389
|
const detection = changeDetector.detectChanges(changes);
|
|
416
|
-
if (detection.templateError) {
|
|
417
|
-
log.error`Template error: ${detection.templateError.message}`;
|
|
418
|
-
rebuilding = false;
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
390
|
|
|
422
391
|
if (detection.needsRestart) {
|
|
423
392
|
log.event`Content structure changed, full rebuild`;
|
|
@@ -493,16 +462,24 @@ async function rebuild(): Promise<void> {
|
|
|
493
462
|
|
|
494
463
|
if (result.errors.length === 0) {
|
|
495
464
|
printFlair();
|
|
465
|
+
if (!serveStarted) {
|
|
466
|
+
serveStarted = true;
|
|
467
|
+
serve();
|
|
468
|
+
}
|
|
496
469
|
broadcast('reload');
|
|
497
470
|
|
|
498
471
|
if (pagefindRunner) {
|
|
499
472
|
pagefindRunner.update(distDir, result.htmlAssetsByPath);
|
|
500
473
|
setImmediate(() => pagefindRunner!.run());
|
|
501
474
|
}
|
|
475
|
+
succeeded = true;
|
|
502
476
|
}
|
|
503
477
|
} catch (err) {
|
|
504
478
|
log.error`Build failed: ${(err as Error).message}`;
|
|
505
479
|
} finally {
|
|
480
|
+
if (!succeeded) {
|
|
481
|
+
broadcast('error');
|
|
482
|
+
}
|
|
506
483
|
rebuilding = false;
|
|
507
484
|
|
|
508
485
|
// If more changes accumulated during rebuild, schedule another
|
|
@@ -526,35 +503,36 @@ function onFileChange(filePath: string): void {
|
|
|
526
503
|
|
|
527
504
|
// --- Start ---
|
|
528
505
|
|
|
506
|
+
let initialBuildFailed = false;
|
|
507
|
+
|
|
529
508
|
initialBuild()
|
|
530
|
-
.
|
|
531
|
-
|
|
509
|
+
.catch(err => {
|
|
510
|
+
log.error`Initial build failed: ${(err as Error).message}`;
|
|
511
|
+
initialBuildFailed = true;
|
|
512
|
+
})
|
|
513
|
+
.finally(() => {
|
|
532
514
|
const watchPaths: string[] = [contentDir, publicDir];
|
|
533
515
|
|
|
534
|
-
// Watch
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (fs.existsSync(srcDir)) {
|
|
538
|
-
watchPaths.push(srcDir);
|
|
539
|
-
}
|
|
540
|
-
if (fs.existsSync(templatesDir)) {
|
|
541
|
-
watchPaths.push(templatesDir);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Watch data files
|
|
516
|
+
// Watch config files (nav.json, authors.json, site config) via the
|
|
517
|
+
// project root directory (depth 0). Chokidar v4 doesn't emit unlink
|
|
518
|
+
// for individually watched files, so we must watch the directory.
|
|
545
519
|
const jsonDataDir = getJsonDataDir();
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
520
|
+
const configFilePaths = new Set([
|
|
521
|
+
...JSON_DATA_FILES.map(f => path.resolve(jsonDataDir, f)),
|
|
522
|
+
path.resolve('site.dev.json'),
|
|
523
|
+
]);
|
|
524
|
+
const configWatcher = chokidar.watch(jsonDataDir, {
|
|
525
|
+
ignoreInitial: true,
|
|
526
|
+
depth: 0,
|
|
527
|
+
});
|
|
528
|
+
const onConfigFileChange = (filePath: string) => {
|
|
529
|
+
if (configFilePaths.has(path.resolve(filePath))) {
|
|
530
|
+
onFileChange(filePath);
|
|
550
531
|
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (fs.existsSync(siteConfigPath)) {
|
|
556
|
-
watchPaths.push(siteConfigPath);
|
|
557
|
-
}
|
|
532
|
+
};
|
|
533
|
+
configWatcher.on('add', onConfigFileChange);
|
|
534
|
+
configWatcher.on('change', onConfigFileChange);
|
|
535
|
+
configWatcher.on('unlink', onConfigFileChange);
|
|
558
536
|
|
|
559
537
|
const watcher = chokidar.watch(watchPaths, {
|
|
560
538
|
ignoreInitial: true,
|
|
@@ -565,9 +543,12 @@ initialBuild()
|
|
|
565
543
|
watcher.on('add', onFileChange);
|
|
566
544
|
watcher.on('unlink', onFileChange);
|
|
567
545
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
546
|
+
watcher.on('ready', () => {
|
|
547
|
+
watcherReady = true;
|
|
548
|
+
if (initialBuildFailed) {
|
|
549
|
+
broadcast('error');
|
|
550
|
+
}
|
|
551
|
+
broadcast('ready');
|
|
552
|
+
log.info`Watching for changes...`;
|
|
553
|
+
});
|
|
573
554
|
});
|