@abreen/tada 1.8.1 → 1.10.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 +24 -34
- package/bin/tada.ts +1 -1
- package/bin/validators.ts +1 -1
- package/build/apply-base-path-plugin.ts +79 -17
- package/build/bundle.ts +9 -1
- package/build/content-watch.ts +3 -3
- package/build/copy.ts +30 -0
- package/build/generate-content-assets.ts +45 -59
- package/build/pagefind.ts +0 -7
- package/build/serve.ts +4 -0
- package/build/templates.ts +0 -2
- package/build/types.d.ts +19 -1
- package/build/util.ts +3 -0
- package/build/utils/content-files.ts +98 -0
- package/build/utils/include.ts +4 -0
- package/build/utils/python-runner/trace_runner.py +272 -0
- package/build/utils/render.ts +47 -15
- package/build/utils/source-template.ts +27 -0
- package/build/utils/trace-core.ts +203 -0
- package/build/utils/trace-java.ts +132 -0
- package/build/utils/trace-layout.ts +31 -3
- package/build/utils/trace-python.ts +26 -0
- package/build/utils/trace.ts +174 -294
- package/build/validate-internal-links-plugin.ts +16 -4
- package/build/watch/assets.ts +111 -0
- package/build/watch/build-full.ts +124 -0
- package/build/watch/build-incremental.ts +139 -0
- package/build/watch/compiler-types.d.ts +19 -0
- package/build/watch/compiler.ts +112 -0
- package/build/watch/index.ts +22 -0
- package/build/watch/mutations.ts +75 -0
- package/build/watch/planner.ts +230 -0
- package/build/watch/records.ts +190 -0
- package/build/watch/runtime.ts +143 -0
- package/build/watch/snapshot.ts +602 -0
- package/build/watch/validation.ts +36 -0
- package/build/watch-reload-client.ts +10 -5
- package/init/content/labs/01/index.md +6 -0
- package/init/content/labs/01/trace_demo.py +16 -0
- package/init/content/lectures/01/Rectangle.java +2 -0
- package/init/content/lectures/01/index.md +6 -0
- package/init/content/lectures/01/rectangle.py +5 -0
- package/package.json +42 -23
- package/python/command.ts +95 -0
- package/python/module.ts +18 -0
- package/src/_alerts.scss +11 -8
- package/src/_base.scss +25 -29
- package/src/_layout.scss +17 -14
- package/src/_mixins.scss +10 -9
- package/src/anchor/style.scss +4 -2
- package/src/code/index.ts +9 -2
- package/src/code/style.scss +14 -13
- package/src/code.scss +14 -13
- package/src/header/_base.scss +21 -24
- package/src/header/style.scss +16 -12
- package/src/index.ts +3 -0
- package/src/literate/style.scss +4 -4
- package/src/navigate/index.ts +43 -279
- package/src/navigate/lifecycle.ts +1 -1
- package/src/navigate/runtime.ts +341 -0
- package/src/page-update/README.md +4 -0
- package/src/page-update/index.ts +280 -0
- package/src/page-update/style.scss +53 -0
- package/src/print/style.scss +4 -4
- package/src/question/style.scss +10 -6
- package/src/search/index.ts +9 -12
- package/src/search/style.scss +44 -42
- package/src/style.scss +63 -58
- package/src/timezone/style.scss +5 -5
- package/src/toc/style.scss +17 -16
- package/src/trace/style.scss +27 -27
- package/src/validators.ts +45 -0
- package/templates/_footer.html +1 -1
- package/templates/_theme.scss +8 -19
- package/watch/engine.ts +296 -0
- package/watch/events.ts +10 -0
- package/watch/fs-commit.ts +102 -0
- package/watch/types.d.ts +103 -0
- package/build/watch.ts +0 -493
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ A static site generator. The successor to Presto.
|
|
|
9
9
|
|
|
10
10
|
- Modern design (light & dark following system, floating header, styled lists)
|
|
11
11
|
- Fast client-side navigation
|
|
12
|
+
- Detects a new version of the current page and allows the user to reload
|
|
12
13
|
- Clickable/linkable landmarks (headings, deflists, alert boxes)
|
|
13
14
|
- Dynamic table of contents
|
|
14
15
|
* Floats on the side of the screen when window is large enough
|
|
@@ -21,8 +22,11 @@ A static site generator. The successor to Presto.
|
|
|
21
22
|
* Dynamic table of contents for each method/function
|
|
22
23
|
* Converts new Markdown comment syntax ([added in Java 23][jep467]) to HTML
|
|
23
24
|
* Indexed by Pagefind (classes, interfaces, methods, and fields)
|
|
25
|
+
- Interactive execution traces via `renderTrace()`
|
|
26
|
+
* Supports Java and Python source files
|
|
27
|
+
* Renders step-by-step source highlighting, output, and memory diagrams
|
|
24
28
|
- PDF files are copied into `dist/`
|
|
25
|
-
- Text of each PDF page is extracted using
|
|
29
|
+
- Text of each PDF page is extracted using `mutool` (if present) and indexed
|
|
26
30
|
- External link handling (special visual treatment for external links)
|
|
27
31
|
- Internal link validation at build time (broken links fail the build)
|
|
28
32
|
- Internal links automatically prefixed with base path, if specified
|
|
@@ -38,16 +42,31 @@ A static site generator. The successor to Presto.
|
|
|
38
42
|
- Automatically generated favicon
|
|
39
43
|
* Text, color, font and font weight taken from config file
|
|
40
44
|
|
|
45
|
+
## Prerequisites
|
|
46
|
+
|
|
47
|
+
- [Bun](https://bun.com/)
|
|
48
|
+
- [MuPDF](https://mupdf.com/) (optional)
|
|
49
|
+
- macOS (using [Homebrew][homebrew]): `brew install mupdf-tools`
|
|
50
|
+
- Fedora Linux: `sudo dnf install mupdf`
|
|
51
|
+
- Ubuntu Linux: `sudo apt-get install mupdf-tools`
|
|
52
|
+
- Windows: `winget install mutool`
|
|
53
|
+
|
|
54
|
+
> [!NOTE]
|
|
55
|
+
> You may skip MuPDF if you don't need search results to include links to PDF
|
|
56
|
+
> pages. You can also turn off `features.search` in the config to disable
|
|
57
|
+
> search entirely.
|
|
58
|
+
|
|
59
|
+
If you plan to use `renderTrace()` to generate traces of Java or Python code,
|
|
60
|
+
the appropriate compiler/interpreter must be installed.
|
|
41
61
|
|
|
42
62
|
## Installation
|
|
43
63
|
|
|
44
|
-
Install
|
|
64
|
+
Install Tada globally:
|
|
45
65
|
|
|
46
66
|
```
|
|
47
|
-
bun
|
|
67
|
+
bun install --global @abreen/tada
|
|
48
68
|
```
|
|
49
69
|
|
|
50
|
-
|
|
51
70
|
## Quick start
|
|
52
71
|
|
|
53
72
|
Create a new site:
|
|
@@ -69,7 +88,6 @@ tada serve
|
|
|
69
88
|
|
|
70
89
|
Visit [http://localhost:8080/index.html](http://localhost:8080/index.html).
|
|
71
90
|
|
|
72
|
-
|
|
73
91
|
## CLI commands
|
|
74
92
|
|
|
75
93
|
### `tada init <dirname>`
|
|
@@ -92,30 +110,25 @@ You can also override specific defaults with flags:
|
|
|
92
110
|
Available flags: `--title`, `--symbol`, `--theme-color`, `--tint-hue`,
|
|
93
111
|
`--tint-amount`, `--default-time-zone`, `--prod-base`, `--prod-base-path`.
|
|
94
112
|
|
|
95
|
-
|
|
96
113
|
### `tada dev`
|
|
97
114
|
|
|
98
115
|
Build the site for local development (using `site.dev.json`)
|
|
99
116
|
into the `dist/` directory.
|
|
100
117
|
|
|
101
|
-
|
|
102
118
|
### `tada serve`
|
|
103
119
|
|
|
104
120
|
Start a development web server at `http://localhost:8080` which serves the
|
|
105
121
|
files in the `dist/` directory.
|
|
106
122
|
|
|
107
|
-
|
|
108
123
|
### `tada watch`
|
|
109
124
|
|
|
110
125
|
Start a development web server, watch for changes and rebuild automatically.
|
|
111
126
|
|
|
112
|
-
|
|
113
127
|
### `tada clean`
|
|
114
128
|
|
|
115
129
|
Remove the `dist/` directory. Pass `--prod` to also prune old production
|
|
116
130
|
builds (keeps the latest two versions).
|
|
117
131
|
|
|
118
|
-
|
|
119
132
|
### `tada prod`
|
|
120
133
|
|
|
121
134
|
Build the site for production (uses `site.prod.json`). Each prod build is
|
|
@@ -123,7 +136,6 @@ saved to a versioned directory under `dist-prod/` (e.g., `dist-prod/v1/`,
|
|
|
123
136
|
`dist-prod/v2/`) with a manifest file that records the SHA-256 hash of every
|
|
124
137
|
output file.
|
|
125
138
|
|
|
126
|
-
|
|
127
139
|
### `tada diff`
|
|
128
140
|
|
|
129
141
|
Compare two production builds and list added, changed, and removed files.
|
|
@@ -139,7 +151,6 @@ Use `--copy <dir>` to copy only the changed and added files to a directory:
|
|
|
139
151
|
|
|
140
152
|
The output directory will also include a `manifest.json` for the newer build.
|
|
141
153
|
|
|
142
|
-
|
|
143
154
|
## Development vs. production builds
|
|
144
155
|
|
|
145
156
|
`tada dev` and `tada watch` build to the `dist/` directory using
|
|
@@ -151,7 +162,6 @@ for previewing a `tada dev` build. Dev builds overwrite `dist/` each time.
|
|
|
151
162
|
it runs. Previous production builds are preserved, so you can compare any
|
|
152
163
|
two versions with `tada diff`.
|
|
153
164
|
|
|
154
|
-
|
|
155
165
|
## Deploying to S3
|
|
156
166
|
|
|
157
167
|
If you host your site in an S3 bucket, `tada diff --copy` lets you upload
|
|
@@ -176,20 +186,6 @@ tada diff --copy upload/
|
|
|
176
186
|
If `tada diff` reports removed files, delete those from your S3 bucket
|
|
177
187
|
manually.
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
## Prerequisites
|
|
181
|
-
|
|
182
|
-
- [Bun](https://bun.sh/)
|
|
183
|
-
- [MuPDF](https://mupdf.com/) (optional, for PDF text extraction in search)
|
|
184
|
-
- On macOS: `brew install mupdf-tools`
|
|
185
|
-
- On Fedora: `dnf install mupdf`
|
|
186
|
-
- On Ubuntu: `apt-get install mupdf-tools`
|
|
187
|
-
|
|
188
|
-
> You may skip MuPDF if you don't need search results to include links to PDF
|
|
189
|
-
> pages. You can also turn off `features.search` in the config to disable
|
|
190
|
-
> search entirely.
|
|
191
|
-
|
|
192
|
-
|
|
193
189
|
## Configuration
|
|
194
190
|
|
|
195
191
|
Build-time site config lives in:
|
|
@@ -241,7 +237,6 @@ Example site configuration JSON file:
|
|
|
241
237
|
| `faviconFontWeight` | *Optional*, font weight used for favicon text (default `700`) |
|
|
242
238
|
| `vars` | Arbitrary key/value variables exposed to templates/content as `vars.*` (e.g., `<%= vars.staffEmail %>`) |
|
|
243
239
|
|
|
244
|
-
|
|
245
240
|
#### `nav.json`
|
|
246
241
|
|
|
247
242
|
Defines the site navigation structure. The file contains an array of section
|
|
@@ -275,7 +270,6 @@ but three or more sections are supported.
|
|
|
275
270
|
]
|
|
276
271
|
```
|
|
277
272
|
|
|
278
|
-
|
|
279
273
|
#### `authors.json`
|
|
280
274
|
|
|
281
275
|
Maps author handles (used in front matter `author` fields) to display names
|
|
@@ -293,7 +287,6 @@ with `name`, `avatar`, and optionally `url`.
|
|
|
293
287
|
}
|
|
294
288
|
```
|
|
295
289
|
|
|
296
|
-
|
|
297
290
|
## Content
|
|
298
291
|
|
|
299
292
|
Site content lives in the `content/` directory. Markdown is converted to HTML.
|
|
@@ -304,7 +297,6 @@ Any other kinds of files are copied into `dist/` in the same locations.
|
|
|
304
297
|
All files in `public/` are copied directly into `dist/` with zero processing.
|
|
305
298
|
Files in `public/` are **not** included in the search index.
|
|
306
299
|
|
|
307
|
-
|
|
308
300
|
### Front matter fields
|
|
309
301
|
|
|
310
302
|
Each file in `content/` should start with "front matter" (a YAML-formatted
|
|
@@ -323,7 +315,6 @@ list of variables parsed using the [`front-matter`][front-matter] library).
|
|
|
323
315
|
You may also add arbitrary fields in a page's front matter, and access them
|
|
324
316
|
using Lodash syntax (see below).
|
|
325
317
|
|
|
326
|
-
|
|
327
318
|
### Variable substitution
|
|
328
319
|
|
|
329
320
|
Plain text content (e.g., HTML and Markdown) are processed using [Lodash
|
|
@@ -335,11 +326,10 @@ templates][lodash].
|
|
|
335
326
|
under `vars` (e.g., `<%= vars.staffEmail %>`)
|
|
336
327
|
|
|
337
328
|
|
|
338
|
-
|
|
339
329
|
[inter]: https://fonts.google.com/specimen/Inter
|
|
340
330
|
[lodash]: https://lodash.info/doc/template
|
|
341
331
|
[front-matter]: https://www.npmjs.com/package/front-matter
|
|
342
332
|
[pagefind]: https://pagefind.app/
|
|
343
|
-
[mutool]: https://mupdf.readthedocs.io/en/latest/tools/mutool.html
|
|
344
333
|
[jep467]: https://openjdk.org/jeps/467
|
|
345
334
|
[katex]: https://katex.org/
|
|
335
|
+
[homebrew]: https://brew.sh/
|
package/bin/tada.ts
CHANGED
|
@@ -621,7 +621,7 @@ try {
|
|
|
621
621
|
case 'watch': {
|
|
622
622
|
requireSiteConfig('dev');
|
|
623
623
|
const { runWatch } = await import(
|
|
624
|
-
path.join(packageDir, 'build/watch.ts')
|
|
624
|
+
path.join(packageDir, 'build/watch/index.ts')
|
|
625
625
|
);
|
|
626
626
|
await runWatch({
|
|
627
627
|
httpPort: parsePortArg('--port'),
|
package/bin/validators.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import type MarkdownIt from 'markdown-it';
|
|
3
3
|
import type Token from 'markdown-it/lib/token.mjs';
|
|
4
|
+
import { JSDOM } from 'jsdom';
|
|
4
5
|
import { createApplyBasePath } from './utils/paths';
|
|
5
6
|
import { isFeatureEnabled } from './features';
|
|
6
7
|
import { isInternalLink } from './utils/link';
|
|
@@ -9,6 +10,47 @@ import type { SiteVariables } from './types';
|
|
|
9
10
|
|
|
10
11
|
const log = makeLogger(import.meta.url);
|
|
11
12
|
|
|
13
|
+
function serializeStartTag(element: Element): string {
|
|
14
|
+
const attrs = Array.from(element.attributes).map(attr =>
|
|
15
|
+
attr.value === '' ? attr.name : `${attr.name}="${attr.value}"`,
|
|
16
|
+
);
|
|
17
|
+
return `<${element.tagName.toLowerCase()}${attrs.length > 0 ? ` ${attrs.join(' ')}` : ''}>`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isStandaloneOpeningAnchorTag(html: string): boolean {
|
|
21
|
+
if (!/^<a\b/i.test(html)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let i = 2;
|
|
26
|
+
let quote: '"' | "'" | null = null;
|
|
27
|
+
|
|
28
|
+
while (i < html.length) {
|
|
29
|
+
const char = html[i];
|
|
30
|
+
if (quote) {
|
|
31
|
+
if (char === quote) {
|
|
32
|
+
quote = null;
|
|
33
|
+
}
|
|
34
|
+
i += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (char === '"' || char === "'") {
|
|
39
|
+
quote = char;
|
|
40
|
+
i += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (char === '>') {
|
|
45
|
+
return html.slice(i + 1).trim() === '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
i += 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
12
54
|
interface ApplyBasePathOptions {
|
|
13
55
|
literateJavaOutputPaths?: Set<string>;
|
|
14
56
|
sourceUrlPath?: string;
|
|
@@ -91,25 +133,45 @@ export default function applyBasePathPlugin(
|
|
|
91
133
|
token.attrSet('src', afterApply);
|
|
92
134
|
}
|
|
93
135
|
} else if (token.type === 'html_block' || token.type === 'html_inline') {
|
|
94
|
-
token.content
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
if (isStandaloneOpeningAnchorTag(token.content)) {
|
|
137
|
+
const dom = new JSDOM(`<body>${token.content}__tada__</a></body>`);
|
|
138
|
+
const anchor = dom.window.document.body.querySelector('a[href]');
|
|
139
|
+
const href = anchor?.getAttribute('href');
|
|
140
|
+
if (anchor && href && href.startsWith('/')) {
|
|
141
|
+
const afterApply = applyBasePath(href);
|
|
142
|
+
log.debug`Applying base path to raw HTML anchor: ${href} -> ${afterApply}`;
|
|
143
|
+
anchor.setAttribute('href', afterApply);
|
|
144
|
+
token.content = serializeStartTag(anchor);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const dom = new JSDOM(`<body>${token.content}</body>`);
|
|
148
|
+
const { document } = dom.window;
|
|
149
|
+
let changed = false;
|
|
150
|
+
|
|
151
|
+
for (const anchor of document.body.querySelectorAll('a[href]')) {
|
|
152
|
+
const href = anchor.getAttribute('href');
|
|
153
|
+
if (href && href.startsWith('/')) {
|
|
99
154
|
const afterApply = applyBasePath(href);
|
|
100
|
-
log.debug`Applying base path to
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
155
|
+
log.debug`Applying base path to raw HTML anchor: ${href} -> ${afterApply}`;
|
|
156
|
+
anchor.setAttribute('href', afterApply);
|
|
157
|
+
changed = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const image of document.body.querySelectorAll('img[src]')) {
|
|
162
|
+
const src = image.getAttribute('src');
|
|
163
|
+
if (src && src.startsWith('/')) {
|
|
108
164
|
const afterApply = applyBasePath(src);
|
|
109
|
-
log.debug`Applying base path to
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
165
|
+
log.debug`Applying base path to raw HTML image: ${src} -> ${afterApply}`;
|
|
166
|
+
image.setAttribute('src', afterApply);
|
|
167
|
+
changed = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (changed) {
|
|
172
|
+
token.content = document.body.innerHTML;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
113
175
|
}
|
|
114
176
|
|
|
115
177
|
token.children?.forEach(checkAndApplyBasePath);
|
package/build/bundle.ts
CHANGED
|
@@ -14,6 +14,14 @@ import type { PluginBuilder } from 'bun';
|
|
|
14
14
|
import type { SiteVariables } from './types';
|
|
15
15
|
import timezones from '../src/timezone/timezones.json' with { type: 'json' };
|
|
16
16
|
|
|
17
|
+
function formatCssNumber(value: number, precision = 4): string {
|
|
18
|
+
if (Number.isInteger(value)) {
|
|
19
|
+
return String(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return value.toFixed(precision).replace(/\.?0+$/, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
function renderThemeScss(siteVariables: SiteVariables): string {
|
|
18
26
|
const templatePath = path.join(getPackageDir(), 'templates/_theme.scss');
|
|
19
27
|
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
@@ -27,7 +35,7 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
27
35
|
const iconColorHoverDark = `hsl(${tintHue}deg ${(6 * tintAmount) / 100}% 60%)`;
|
|
28
36
|
const iconColorTranslucentDark = `hsl(${tintHue}deg ${(85 * tintAmount) / 100}% 90%)`;
|
|
29
37
|
|
|
30
|
-
const linkHue = deriveLinkHue(tintHue);
|
|
38
|
+
const linkHue = formatCssNumber(deriveLinkHue(tintHue));
|
|
31
39
|
const linkColor = `hsl(${linkHue}deg 44.4% 49.4%)`;
|
|
32
40
|
const linkColorHover = `hsl(${linkHue}deg 34% 60%)`;
|
|
33
41
|
const linkColorDark = `hsl(${linkHue}deg 50% 72%)`;
|
package/build/content-watch.ts
CHANGED
|
@@ -85,10 +85,10 @@ export class ContentChangeDetector {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const
|
|
89
|
-
f.endsWith('.java'),
|
|
88
|
+
const changedTraceFiles = [...changedContentFiles].filter(
|
|
89
|
+
f => f.endsWith('.java') || f.endsWith('.py'),
|
|
90
90
|
);
|
|
91
|
-
for (const filePath of
|
|
91
|
+
for (const filePath of changedTraceFiles) {
|
|
92
92
|
log.event`${B`${path.basename(filePath)}`} changed, re-running traces`;
|
|
93
93
|
}
|
|
94
94
|
|
package/build/copy.ts
CHANGED
|
@@ -102,6 +102,36 @@ function copySingleFile(
|
|
|
102
102
|
log.info`Copying ${sourceLabel} file ${B`${rel}`}`;
|
|
103
103
|
}
|
|
104
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
|
+
|
|
105
135
|
export function copyPublicFile(
|
|
106
136
|
publicDir: string,
|
|
107
137
|
distDir: string,
|
|
@@ -15,13 +15,14 @@ import {
|
|
|
15
15
|
renderPlainTextPageAsset,
|
|
16
16
|
} from './util';
|
|
17
17
|
import { isLiterateJava } from './utils/file-types';
|
|
18
|
-
import {
|
|
18
|
+
import { checkTraceToolAvailability, isTraceSourceFile } from './utils/trace';
|
|
19
19
|
import { toPosix } from './utils/paths';
|
|
20
20
|
import type {
|
|
21
21
|
SiteVariables,
|
|
22
22
|
Asset,
|
|
23
23
|
ContentRenderOptions,
|
|
24
24
|
ContentRenderResult,
|
|
25
|
+
TraceToolAvailability,
|
|
25
26
|
WatchState,
|
|
26
27
|
} from './types';
|
|
27
28
|
|
|
@@ -33,6 +34,7 @@ export class ContentRenderer {
|
|
|
33
34
|
private sourceFileCache: Map<string, Asset[]>;
|
|
34
35
|
private lastBuildFiles: Set<string>;
|
|
35
36
|
private javacAvailable: boolean | undefined;
|
|
37
|
+
private traceToolAvailability: TraceToolAvailability | undefined;
|
|
36
38
|
private traceCache: Map<
|
|
37
39
|
string,
|
|
38
40
|
{
|
|
@@ -78,12 +80,10 @@ export class ContentRenderer {
|
|
|
78
80
|
return new Set(buildContentFiles);
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
// If any
|
|
83
|
+
// If any trace source changed, the trace cache will be stale, so rebuild
|
|
82
84
|
// all content pages to pick up the new trace output.
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
);
|
|
86
|
-
if (javaFileChanged) {
|
|
85
|
+
const traceFileChanged = [...changedContentFiles].some(isTraceSourceFile);
|
|
86
|
+
if (traceFileChanged) {
|
|
87
87
|
return new Set(buildContentFiles);
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -103,28 +103,6 @@ export class ContentRenderer {
|
|
|
103
103
|
return Object.prototype.hasOwnProperty.call(codeExtensions, ext.slice(1));
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
getDirtyCopiedSourceFiles(
|
|
107
|
-
buildContentFiles: string[],
|
|
108
|
-
{ changedContentFiles }: WatchState = {},
|
|
109
|
-
): string[] {
|
|
110
|
-
if (this.lastBuildFiles.size === 0 || !changedContentFiles) {
|
|
111
|
-
return buildContentFiles.filter(filePath =>
|
|
112
|
-
this.isCopiedAssetSource(filePath),
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const buildFileSet = new Set(buildContentFiles);
|
|
117
|
-
const dirtyCopiedSourceFiles: string[] = [];
|
|
118
|
-
for (const filePath of changedContentFiles) {
|
|
119
|
-
if (!buildFileSet.has(filePath) || !this.isCopiedAssetSource(filePath)) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
dirtyCopiedSourceFiles.push(filePath);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return dirtyCopiedSourceFiles;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
106
|
renderSourceAssets(
|
|
129
107
|
filePath: string,
|
|
130
108
|
contentDir: string,
|
|
@@ -163,7 +141,7 @@ export class ContentRenderer {
|
|
|
163
141
|
assetFiles,
|
|
164
142
|
literateJavaOutputPaths,
|
|
165
143
|
traceCache: this.traceCache,
|
|
166
|
-
|
|
144
|
+
traceToolAvailability: this.traceToolAvailability,
|
|
167
145
|
}),
|
|
168
146
|
);
|
|
169
147
|
return assets;
|
|
@@ -190,27 +168,23 @@ export class ContentRenderer {
|
|
|
190
168
|
return assets;
|
|
191
169
|
}
|
|
192
170
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
contentDir: string,
|
|
197
|
-
): void {
|
|
198
|
-
for (const filePath of copiedSourceFiles) {
|
|
199
|
-
for (const asset of renderCopiedContentAsset({
|
|
200
|
-
filePath,
|
|
201
|
-
contentDir,
|
|
202
|
-
siteVariables: this.siteVariables,
|
|
203
|
-
})) {
|
|
204
|
-
const outPath = path.join(distDir, asset.assetPath);
|
|
205
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
206
|
-
fs.writeFileSync(outPath, asset.content);
|
|
207
|
-
}
|
|
171
|
+
renderCopiedSourceAssets(filePath: string, contentDir: string): Asset[] {
|
|
172
|
+
if (!this.isCopiedAssetSource(filePath)) {
|
|
173
|
+
return [];
|
|
208
174
|
}
|
|
175
|
+
|
|
176
|
+
return renderCopiedContentAsset({
|
|
177
|
+
filePath,
|
|
178
|
+
contentDir,
|
|
179
|
+
siteVariables: this.siteVariables,
|
|
180
|
+
});
|
|
209
181
|
}
|
|
210
182
|
|
|
211
183
|
updateSourceCache(
|
|
212
184
|
filePath: string,
|
|
213
185
|
assets: Asset[],
|
|
186
|
+
changedOutputRelPaths: Set<string>,
|
|
187
|
+
removedOutputRelPaths: Set<string>,
|
|
214
188
|
changedHtmlAssetPaths: Set<string>,
|
|
215
189
|
removedHtmlAssetPaths: Set<string>,
|
|
216
190
|
): void {
|
|
@@ -221,12 +195,14 @@ export class ContentRenderer {
|
|
|
221
195
|
if (nextAssetPaths.has(asset.assetPath)) {
|
|
222
196
|
continue;
|
|
223
197
|
}
|
|
198
|
+
removedOutputRelPaths.add(asset.assetPath);
|
|
224
199
|
if (asset.assetPath.endsWith('.html')) {
|
|
225
200
|
removedHtmlAssetPaths.add(asset.assetPath);
|
|
226
201
|
}
|
|
227
202
|
}
|
|
228
203
|
|
|
229
204
|
for (const asset of assets) {
|
|
205
|
+
changedOutputRelPaths.add(asset.assetPath);
|
|
230
206
|
if (asset.assetPath.endsWith('.html')) {
|
|
231
207
|
changedHtmlAssetPaths.add(asset.assetPath);
|
|
232
208
|
}
|
|
@@ -248,6 +224,7 @@ export class ContentRenderer {
|
|
|
248
224
|
|
|
249
225
|
pruneRemovedSources(
|
|
250
226
|
buildFileSet: Set<string>,
|
|
227
|
+
removedOutputRelPaths: Set<string>,
|
|
251
228
|
removedHtmlAssetPaths: Set<string>,
|
|
252
229
|
): void {
|
|
253
230
|
for (const filePath of [...this.lastBuildFiles]) {
|
|
@@ -257,6 +234,7 @@ export class ContentRenderer {
|
|
|
257
234
|
|
|
258
235
|
const previousAssets = this.getCachedAssets(filePath);
|
|
259
236
|
for (const asset of previousAssets) {
|
|
237
|
+
removedOutputRelPaths.add(asset.assetPath);
|
|
260
238
|
if (asset.assetPath.endsWith('.html')) {
|
|
261
239
|
removedHtmlAssetPaths.add(asset.assetPath);
|
|
262
240
|
}
|
|
@@ -280,10 +258,8 @@ export class ContentRenderer {
|
|
|
280
258
|
buildContentFiles,
|
|
281
259
|
watchState,
|
|
282
260
|
);
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
watchState,
|
|
286
|
-
);
|
|
261
|
+
const changedOutputRelPaths = new Set<string>();
|
|
262
|
+
const removedOutputRelPaths = new Set<string>();
|
|
287
263
|
const changedHtmlAssetPaths = new Set<string>();
|
|
288
264
|
const removedHtmlAssetPaths = new Set<string>();
|
|
289
265
|
const validInternalTargets = getValidInternalTargets(
|
|
@@ -312,7 +288,11 @@ export class ContentRenderer {
|
|
|
312
288
|
errors.push(new Error(msg));
|
|
313
289
|
}
|
|
314
290
|
|
|
315
|
-
this.pruneRemovedSources(
|
|
291
|
+
this.pruneRemovedSources(
|
|
292
|
+
buildFileSet,
|
|
293
|
+
removedOutputRelPaths,
|
|
294
|
+
removedHtmlAssetPaths,
|
|
295
|
+
);
|
|
316
296
|
|
|
317
297
|
if (dirtySourceFiles.size > 0) {
|
|
318
298
|
const noun = dirtySourceFiles.size === 1 ? 'file' : 'files';
|
|
@@ -320,7 +300,8 @@ export class ContentRenderer {
|
|
|
320
300
|
}
|
|
321
301
|
|
|
322
302
|
if (this.javacAvailable === undefined && dirtySourceFiles.size > 0) {
|
|
323
|
-
this.
|
|
303
|
+
this.traceToolAvailability = checkTraceToolAvailability();
|
|
304
|
+
this.javacAvailable = this.traceToolAvailability.java;
|
|
324
305
|
if (!this.javacAvailable) {
|
|
325
306
|
log.warn`javac was not found; literate Java pages will not include execution output`;
|
|
326
307
|
}
|
|
@@ -329,14 +310,17 @@ export class ContentRenderer {
|
|
|
329
310
|
for (const filePath of dirtySourceFiles) {
|
|
330
311
|
let assets: Asset[];
|
|
331
312
|
try {
|
|
332
|
-
assets =
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
313
|
+
assets = [
|
|
314
|
+
...this.renderSourceAssets(
|
|
315
|
+
filePath,
|
|
316
|
+
contentDir,
|
|
317
|
+
distDir,
|
|
318
|
+
validInternalTargets,
|
|
319
|
+
assetFiles,
|
|
320
|
+
literateJavaOutputPaths,
|
|
321
|
+
),
|
|
322
|
+
...this.renderCopiedSourceAssets(filePath, contentDir),
|
|
323
|
+
];
|
|
340
324
|
} catch (err) {
|
|
341
325
|
errors.push(err as Error);
|
|
342
326
|
continue;
|
|
@@ -344,13 +328,14 @@ export class ContentRenderer {
|
|
|
344
328
|
this.updateSourceCache(
|
|
345
329
|
filePath,
|
|
346
330
|
assets,
|
|
331
|
+
changedOutputRelPaths,
|
|
332
|
+
removedOutputRelPaths,
|
|
347
333
|
changedHtmlAssetPaths,
|
|
348
334
|
removedHtmlAssetPaths,
|
|
349
335
|
);
|
|
350
336
|
}
|
|
351
337
|
|
|
352
338
|
this.writeCachedAssets(distDir, buildContentFiles);
|
|
353
|
-
this.writeUncachedAssets(distDir, dirtyCopiedSourceFiles, contentDir);
|
|
354
339
|
this.lastBuildFiles = buildFileSet;
|
|
355
340
|
|
|
356
341
|
// Collect HTML asset content for Pagefind
|
|
@@ -368,6 +353,7 @@ export class ContentRenderer {
|
|
|
368
353
|
errors,
|
|
369
354
|
changedHtmlAssetPaths,
|
|
370
355
|
removedHtmlAssetPaths,
|
|
356
|
+
removedOutputRelPaths,
|
|
371
357
|
htmlAssetsByPath,
|
|
372
358
|
buildContentFiles,
|
|
373
359
|
};
|
package/build/pagefind.ts
CHANGED
|
@@ -245,13 +245,6 @@ export async function runPagefind({
|
|
|
245
245
|
pdfSourceByOutputPath,
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
-
try {
|
|
249
|
-
const pagefind = await getPagefind();
|
|
250
|
-
await pagefind.close();
|
|
251
|
-
} catch {
|
|
252
|
-
// Best-effort cleanup
|
|
253
|
-
}
|
|
254
|
-
|
|
255
248
|
const finishedAt = Date.now();
|
|
256
249
|
log.debug`Search index built in ${finishedAt - snapshotReadyAt}ms (${finishedAt - start}ms total)`;
|
|
257
250
|
}
|
package/build/serve.ts
CHANGED
|
@@ -62,6 +62,10 @@ export function startServer(options: StartServerOptions): void {
|
|
|
62
62
|
return new Response(null, { status: 304, headers });
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
if (req.method === 'HEAD') {
|
|
66
|
+
return new Response(null, { headers });
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
return new Response(Bun.file(result.filePath), { headers });
|
|
66
70
|
}
|
|
67
71
|
|
package/build/templates.ts
CHANGED