@abreen/tada 1.9.0 → 1.10.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.
Files changed (75) hide show
  1. package/README.md +24 -34
  2. package/bin/tada.ts +1 -1
  3. package/build/apply-base-path-plugin.ts +79 -17
  4. package/build/bundle.ts +9 -1
  5. package/build/content-watch.ts +3 -3
  6. package/build/copy.ts +30 -0
  7. package/build/generate-content-assets.ts +45 -59
  8. package/build/globals.ts +1 -1
  9. package/build/pagefind.ts +0 -7
  10. package/build/serve.ts +4 -0
  11. package/build/types.d.ts +19 -1
  12. package/build/util.ts +3 -0
  13. package/build/utils/content-files.ts +98 -0
  14. package/build/utils/include.ts +4 -0
  15. package/build/utils/python-runner/trace_runner.py +272 -0
  16. package/build/utils/render.ts +34 -8
  17. package/build/utils/trace-core.ts +203 -0
  18. package/build/utils/trace-java.ts +132 -0
  19. package/build/utils/trace-layout.ts +31 -3
  20. package/build/utils/trace-python.ts +26 -0
  21. package/build/utils/trace.ts +174 -294
  22. package/build/validate-internal-links-plugin.ts +16 -4
  23. package/build/watch/assets.ts +111 -0
  24. package/build/watch/build-full.ts +124 -0
  25. package/build/watch/build-incremental.ts +139 -0
  26. package/build/watch/compiler-types.d.ts +19 -0
  27. package/build/watch/compiler.ts +112 -0
  28. package/build/watch/index.ts +22 -0
  29. package/build/watch/mutations.ts +75 -0
  30. package/build/watch/planner.ts +230 -0
  31. package/build/watch/records.ts +190 -0
  32. package/build/watch/runtime.ts +143 -0
  33. package/build/watch/snapshot.ts +602 -0
  34. package/build/watch/validation.ts +36 -0
  35. package/build/watch-reload-client.ts +10 -5
  36. package/init/content/labs/01/index.md +6 -0
  37. package/init/content/labs/01/trace_demo.py +16 -0
  38. package/package.json +47 -24
  39. package/python/command.ts +95 -0
  40. package/python/module.ts +18 -0
  41. package/src/_alerts.scss +11 -8
  42. package/src/_base.scss +25 -29
  43. package/src/_layout.scss +17 -14
  44. package/src/_mixins.scss +10 -9
  45. package/src/anchor/style.scss +4 -2
  46. package/src/code/index.ts +9 -2
  47. package/src/code/style.scss +14 -13
  48. package/src/code.scss +14 -13
  49. package/src/header/_base.scss +21 -24
  50. package/src/header/style.scss +16 -12
  51. package/src/index.ts +3 -0
  52. package/src/literate/style.scss +4 -4
  53. package/src/navigate/index.ts +43 -279
  54. package/src/navigate/lifecycle.ts +1 -1
  55. package/src/navigate/runtime.ts +341 -0
  56. package/src/page-update/README.md +4 -0
  57. package/src/page-update/index.ts +280 -0
  58. package/src/page-update/style.scss +53 -0
  59. package/src/print/style.scss +4 -4
  60. package/src/question/style.scss +10 -6
  61. package/src/search/index.ts +9 -12
  62. package/src/search/style.scss +44 -42
  63. package/src/style.scss +63 -58
  64. package/src/timezone/index.ts +13 -3
  65. package/src/timezone/style.scss +11 -5
  66. package/src/toc/style.scss +17 -16
  67. package/src/trace/style.scss +27 -27
  68. package/src/validators.ts +45 -0
  69. package/templates/_footer.html +1 -1
  70. package/templates/_theme.scss +8 -19
  71. package/watch/engine.ts +296 -0
  72. package/watch/events.ts +10 -0
  73. package/watch/fs-commit.ts +102 -0
  74. package/watch/types.d.ts +103 -0
  75. package/build/watch.ts +0 -493
package/build/types.d.ts CHANGED
@@ -50,6 +50,7 @@ export interface ContentRenderResult {
50
50
  errors: Error[];
51
51
  changedHtmlAssetPaths: Set<string>;
52
52
  removedHtmlAssetPaths: Set<string>;
53
+ removedOutputRelPaths: Set<string>;
53
54
  htmlAssetsByPath: Map<string, string>;
54
55
  buildContentFiles: string[];
55
56
  }
@@ -75,6 +76,8 @@ export interface RenderPlainTextOptions {
75
76
  validInternalTargets: Set<string>;
76
77
  assetFiles: string[];
77
78
  literateJavaOutputPaths?: Set<string>;
79
+ dependencyCollector?: RenderDependencyCollector;
80
+ cachedTraceSourceDir?: string;
78
81
  traceCache?: Map<
79
82
  string,
80
83
  {
@@ -84,7 +87,12 @@ export interface RenderPlainTextOptions {
84
87
  mtime: number;
85
88
  }
86
89
  >;
87
- javacAvailable?: boolean;
90
+ traceToolAvailability?: TraceToolAvailability;
91
+ }
92
+
93
+ export interface TraceToolAvailability {
94
+ java?: boolean;
95
+ python?: boolean;
88
96
  }
89
97
 
90
98
  /** Options for code page asset rendering */
@@ -94,6 +102,7 @@ export interface RenderCodePageOptions {
94
102
  distDir: string;
95
103
  siteVariables: SiteVariables;
96
104
  assetFiles: string[];
105
+ dependencyCollector?: RenderDependencyCollector;
97
106
  }
98
107
 
99
108
  /** Options for literate Java asset rendering */
@@ -106,6 +115,7 @@ export interface RenderLiterateJavaOptions {
106
115
  skipExecution?: boolean;
107
116
  validInternalTargets: Set<string>;
108
117
  literateJavaOutputPaths?: Set<string>;
118
+ dependencyCollector?: RenderDependencyCollector;
109
119
  }
110
120
 
111
121
  /** Options for copied content asset rendering */
@@ -115,6 +125,14 @@ export interface RenderCopiedContentOptions {
115
125
  siteVariables: SiteVariables;
116
126
  }
117
127
 
128
+ export interface RenderDependencyCollector {
129
+ partials?: Set<string>;
130
+ traceFiles?: Set<string>;
131
+ internalTargets?: Set<string>;
132
+ generatedOutputPaths?: Set<string>;
133
+ setAuthorKey?: (authorKey: string) => void;
134
+ }
135
+
118
136
  /** Front matter parse result (from parseFrontMatterAndContent) */
119
137
  export interface ParsedContent {
120
138
  pageVariables: Record<string, unknown>;
package/build/util.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export {
2
2
  getBuildContentFiles,
3
3
  getContentFiles,
4
+ getContentOutputRelPaths,
5
+ getContentSourceOutputRelPaths,
4
6
  getFilesByExtensions,
5
7
  getValidInternalTargets,
6
8
  shouldSkipContentFile,
@@ -15,6 +17,7 @@ export {
15
17
  getProjectDir,
16
18
  getPublicDir,
17
19
  normalizeOutputPath,
20
+ toPosix,
18
21
  } from './utils/paths';
19
22
  export {
20
23
  injectAssetTags,
@@ -51,6 +51,104 @@ export function getBuildContentFiles(
51
51
  );
52
52
  }
53
53
 
54
+ export function getContentSourceOutputRelPaths(
55
+ contentDir: string,
56
+ filePath: string,
57
+ codeExtensions: string[],
58
+ codeEnabled: boolean,
59
+ ): Set<string> {
60
+ const processedExtSet = new Set(getProcessedExtensions(codeExtensions));
61
+ const relPath = toPosix(path.relative(contentDir, filePath));
62
+ const ext = path.extname(filePath).slice(1).toLowerCase();
63
+ const parsed = path.parse(relPath);
64
+ const outputs = new Set<string>();
65
+
66
+ if (!processedExtSet.has(ext)) {
67
+ outputs.add(relPath);
68
+ return outputs;
69
+ }
70
+
71
+ const buildContentFiles = new Set(
72
+ getBuildContentFiles(contentDir, codeExtensions),
73
+ );
74
+ if (!buildContentFiles.has(filePath)) {
75
+ return outputs;
76
+ }
77
+
78
+ if (isLiterateJava(filePath)) {
79
+ outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
80
+ outputs.add(toPosix(path.join(parsed.dir, parsed.name)));
81
+ return outputs;
82
+ }
83
+
84
+ if (
85
+ extensionIsMarkdown(parsed.ext.toLowerCase()) ||
86
+ parsed.ext.toLowerCase() === '.html'
87
+ ) {
88
+ outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
89
+ return outputs;
90
+ }
91
+
92
+ const codeExtensionSet = new Set(
93
+ codeExtensions.map(value => value.toLowerCase()),
94
+ );
95
+ if (codeExtensionSet.has(ext)) {
96
+ outputs.add(relPath);
97
+ if (codeEnabled) {
98
+ outputs.add(`${relPath}.html`);
99
+ }
100
+ }
101
+
102
+ return outputs;
103
+ }
104
+
105
+ export function getContentOutputRelPaths(
106
+ contentDir: string,
107
+ codeExtensions: string[],
108
+ codeEnabled: boolean,
109
+ ): Set<string> {
110
+ const buildContentFiles = getBuildContentFiles(contentDir, codeExtensions);
111
+ const codeExtensionSet = new Set(
112
+ codeExtensions.map(ext => ext.toLowerCase()),
113
+ );
114
+ const outputs = new Set<string>();
115
+
116
+ for (const filePath of buildContentFiles) {
117
+ const relPath = toPosix(path.relative(contentDir, filePath));
118
+ const parsed = path.parse(relPath);
119
+ const ext = parsed.ext.toLowerCase();
120
+
121
+ if (isLiterateJava(filePath)) {
122
+ outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
123
+ outputs.add(toPosix(path.join(parsed.dir, parsed.name)));
124
+ continue;
125
+ }
126
+
127
+ if (extensionIsMarkdown(ext) || ext === '.html') {
128
+ outputs.add(toPosix(path.join(parsed.dir, `${parsed.name}.html`)));
129
+ continue;
130
+ }
131
+
132
+ if (codeExtensionSet.has(ext.slice(1))) {
133
+ outputs.add(relPath);
134
+ if (codeEnabled) {
135
+ outputs.add(`${relPath}.html`);
136
+ }
137
+ }
138
+ }
139
+
140
+ const processedExtSet = new Set(getProcessedExtensions(codeExtensions));
141
+ for (const filePath of walkFiles(contentDir)) {
142
+ const ext = path.extname(filePath).slice(1).toLowerCase();
143
+ if (processedExtSet.has(ext)) {
144
+ continue;
145
+ }
146
+ outputs.add(toPosix(path.relative(contentDir, filePath)));
147
+ }
148
+
149
+ return outputs;
150
+ }
151
+
54
152
  function addGeneratedRouteAliases(
55
153
  pathSet: Set<string>,
56
154
  outputPath: string,
@@ -3,12 +3,14 @@ import path from 'path';
3
3
  import _ from 'lodash';
4
4
  import { isPartial } from './file-types';
5
5
  import { stripHtmlComments } from './render';
6
+ import type { RenderDependencyCollector } from '../types';
6
7
 
7
8
  const MAX_INCLUDE_DEPTH = 10;
8
9
 
9
10
  export function createIncludeFunction(
10
11
  callerFilePath: string,
11
12
  templateParams: Record<string, unknown>,
13
+ collector?: RenderDependencyCollector,
12
14
  depth: number = 0,
13
15
  ): (relativePath: string) => string {
14
16
  return (relativePath: string): string => {
@@ -25,6 +27,7 @@ export function createIncludeFunction(
25
27
  `${callerFilePath}: include target must start with "_": ${relativePath}`,
26
28
  );
27
29
  }
30
+ collector?.partials?.add(resolved);
28
31
  if (depth >= MAX_INCLUDE_DEPTH) {
29
32
  throw new Error(
30
33
  `${callerFilePath}: maximum include depth (${MAX_INCLUDE_DEPTH}) exceeded`,
@@ -37,6 +40,7 @@ export function createIncludeFunction(
37
40
  const nestedInclude = createIncludeFunction(
38
41
  resolved,
39
42
  templateParams,
43
+ collector,
40
44
  depth + 1,
41
45
  );
42
46
  const nestedParams = { ...templateParams, include: nestedInclude };
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import io
4
+ import json
5
+ import os
6
+ import pdb
7
+ import sys
8
+ import types
9
+
10
+
11
+ class CaptureStream(io.TextIOBase):
12
+ def __init__(self, sink):
13
+ self._sink = sink
14
+
15
+ def write(self, s):
16
+ if not isinstance(s, str):
17
+ s = str(s)
18
+ self._sink.append(s)
19
+ return len(s)
20
+
21
+ def flush(self):
22
+ return None
23
+
24
+ def isatty(self):
25
+ return False
26
+
27
+
28
+ class TracePdb(pdb.Pdb):
29
+ SYNTHETIC_FRAME_NAMES = {"<listcomp>", "<dictcomp>", "<setcomp>"}
30
+
31
+ def __init__(self, target_path, trace_writer, output_chunks):
32
+ super().__init__(nosigint=True, readrc=False)
33
+ self.target_path = os.path.abspath(target_path)
34
+ self.trace_writer = trace_writer
35
+ self.output_chunks = output_chunks
36
+ self.pending_snapshot = None
37
+ self.object_ids = {}
38
+ self.next_object_id = 1
39
+
40
+ def user_line(self, frame):
41
+ if not self._is_traced_frame(frame):
42
+ self.set_step()
43
+ return
44
+
45
+ snapshot = self._capture_snapshot(frame)
46
+ self._emit_pending()
47
+ self.pending_snapshot = snapshot
48
+ self.set_step()
49
+
50
+ def user_call(self, frame, argument_list):
51
+ if self._is_traced_frame(frame):
52
+ self.set_step()
53
+
54
+ def user_return(self, frame, return_value):
55
+ if self._is_traced_frame(frame):
56
+ snapshot = self._capture_snapshot(frame)
57
+ self._emit_pending()
58
+ self.pending_snapshot = snapshot
59
+ self.set_step()
60
+
61
+ def user_exception(self, frame, exc_info):
62
+ if self._is_traced_frame(frame):
63
+ self.set_step()
64
+
65
+ def flush_pending(self):
66
+ self._emit_pending()
67
+
68
+ def _emit_pending(self):
69
+ if self.pending_snapshot is None:
70
+ return
71
+ payload = dict(self.pending_snapshot)
72
+ payload["stdout"] = self._drain_output()
73
+ self.trace_writer.write(json.dumps(payload) + "\n")
74
+ self.trace_writer.flush()
75
+ self.pending_snapshot = None
76
+
77
+ def _drain_output(self):
78
+ if not self.output_chunks:
79
+ return ""
80
+ output = "".join(self.output_chunks)
81
+ self.output_chunks.clear()
82
+ return output
83
+
84
+ def _capture_snapshot(self, frame):
85
+ reachable = {}
86
+ return {
87
+ "line": frame.f_lineno,
88
+ "file": os.path.basename(frame.f_code.co_filename),
89
+ "stack": self._serialize_stack(frame, reachable),
90
+ "heap": self._serialize_heap(reachable),
91
+ }
92
+
93
+ def _serialize_stack(self, frame, reachable):
94
+ frames = []
95
+ current = frame
96
+ while current is not None:
97
+ if self._is_traced_frame(current):
98
+ locals_json = {}
99
+ for name, value in current.f_locals.items():
100
+ if self._should_skip_local(name, value):
101
+ continue
102
+ locals_json[name] = self._serialize_value(value, reachable)
103
+ frames.append(
104
+ {
105
+ "method": current.f_code.co_name,
106
+ "class": "Python",
107
+ "line": current.f_lineno,
108
+ "locals": locals_json,
109
+ }
110
+ )
111
+ current = current.f_back
112
+ return frames
113
+
114
+ def _serialize_heap(self, reachable):
115
+ heap = {}
116
+ queue = list(reachable.items())
117
+ seen = set()
118
+ idx = 0
119
+ while idx < len(queue):
120
+ obj_id, obj = queue[idx]
121
+ idx += 1
122
+ if obj_id in seen:
123
+ continue
124
+ seen.add(obj_id)
125
+ heap[obj_id] = self._serialize_heap_object(obj, reachable)
126
+ for new_id, new_obj in reachable.items():
127
+ if new_id not in seen:
128
+ queue.append((new_id, new_obj))
129
+ return heap
130
+
131
+ def _serialize_heap_object(self, obj, reachable):
132
+ if isinstance(obj, str):
133
+ return {"type": "String", "value": obj}
134
+
135
+ if isinstance(obj, (list, tuple)):
136
+ return {
137
+ "type": type(obj).__name__,
138
+ "elements": [self._serialize_value(value, reachable) for value in obj],
139
+ }
140
+
141
+ if isinstance(obj, dict) and all(isinstance(key, str) for key in obj.keys()):
142
+ return {
143
+ "type": "dict",
144
+ "fields": {
145
+ key: self._serialize_value(value, reachable)
146
+ for key, value in obj.items()
147
+ },
148
+ }
149
+
150
+ obj_dict = getattr(obj, "__dict__", None)
151
+ if isinstance(obj_dict, dict):
152
+ return {
153
+ "type": type(obj).__name__,
154
+ "fields": {
155
+ key: self._serialize_value(value, reachable)
156
+ for key, value in obj_dict.items()
157
+ if not key.startswith("__") and not self._should_skip_local(key, value)
158
+ },
159
+ }
160
+
161
+ return {"type": type(obj).__name__, "value": self._safe_repr(obj)}
162
+
163
+ def _serialize_value(self, value, reachable):
164
+ if value is None:
165
+ return {"type": "null"}
166
+ if isinstance(value, bool):
167
+ return {"type": "boolean", "value": value}
168
+ if isinstance(value, int) and not isinstance(value, bool):
169
+ return {"type": "int", "value": value}
170
+ if isinstance(value, float):
171
+ return {"type": "double", "value": value}
172
+ if isinstance(value, str):
173
+ obj_id = self._get_object_id(value)
174
+ reachable.setdefault(obj_id, value)
175
+ return {"type": "ref", "id": obj_id}
176
+
177
+ if self._is_heap_value(value):
178
+ obj_id = self._get_object_id(value)
179
+ reachable.setdefault(obj_id, value)
180
+ return {"type": "ref", "id": obj_id}
181
+
182
+ return {"type": "unknown"}
183
+
184
+ def _is_heap_value(self, value):
185
+ if isinstance(value, str):
186
+ return True
187
+ if isinstance(value, (list, tuple)):
188
+ return True
189
+ if isinstance(value, dict):
190
+ return all(isinstance(key, str) for key in value.keys())
191
+ obj_dict = getattr(value, "__dict__", None)
192
+ return isinstance(obj_dict, dict)
193
+
194
+ def _get_object_id(self, obj):
195
+ key = id(obj)
196
+ if key not in self.object_ids:
197
+ self.object_ids[key] = f"obj_{self.next_object_id}"
198
+ self.next_object_id += 1
199
+ return self.object_ids[key]
200
+
201
+ def _safe_repr(self, value):
202
+ try:
203
+ return repr(value)
204
+ except Exception:
205
+ return f"<{type(value).__name__}>"
206
+
207
+ def _is_traced_frame(self, frame):
208
+ return (
209
+ os.path.abspath(frame.f_code.co_filename) == self.target_path
210
+ and frame.f_code.co_name not in self.SYNTHETIC_FRAME_NAMES
211
+ )
212
+
213
+ def _should_skip_local(self, name, value):
214
+ if name.startswith("."):
215
+ return True
216
+ if name.startswith("__"):
217
+ return True
218
+ if isinstance(value, (types.ModuleType, types.FunctionType, type)):
219
+ return True
220
+ return False
221
+
222
+
223
+ def main():
224
+ if len(sys.argv) != 2:
225
+ print("Usage: trace_runner.py <python_file>", file=sys.stderr)
226
+ sys.exit(1)
227
+
228
+ target_path = os.path.abspath(sys.argv[1])
229
+ target_dir = os.path.dirname(target_path)
230
+ target_name = os.path.basename(target_path)
231
+
232
+ if target_dir and target_dir not in sys.path:
233
+ sys.path.insert(0, target_dir)
234
+
235
+ trace_writer = sys.stdout
236
+ output_chunks = []
237
+ capture = CaptureStream(output_chunks)
238
+ debugger = TracePdb(target_path, trace_writer, output_chunks)
239
+
240
+ code_globals = {
241
+ "__name__": "__main__",
242
+ "__file__": target_path,
243
+ "__package__": None,
244
+ "__cached__": None,
245
+ "__builtins__": __builtins__,
246
+ }
247
+
248
+ original_stdout = sys.stdout
249
+ original_stderr = sys.stderr
250
+
251
+ try:
252
+ with open(target_path, "r", encoding="utf-8") as f:
253
+ source = f.read()
254
+ compiled = compile(source, target_path, "exec")
255
+
256
+ sys.stdout = capture
257
+ sys.stderr = capture
258
+
259
+ try:
260
+ debugger.set_step()
261
+ debugger.runcall(exec, compiled, code_globals, code_globals)
262
+ except SystemExit:
263
+ pass
264
+ finally:
265
+ debugger.flush_pending()
266
+ finally:
267
+ sys.stdout = original_stdout
268
+ sys.stderr = original_stderr
269
+
270
+
271
+ if __name__ == "__main__":
272
+ main()
@@ -43,6 +43,8 @@ import type {
43
43
  RenderCodePageOptions,
44
44
  RenderLiterateJavaOptions,
45
45
  RenderCopiedContentOptions,
46
+ RenderDependencyCollector,
47
+ TraceToolAvailability,
46
48
  } from '../types';
47
49
 
48
50
  const log = makeLogger(import.meta.url);
@@ -81,6 +83,7 @@ interface TemplateParametersInput {
81
83
  function resolveAuthor(
82
84
  pageVariables: Record<string, unknown>,
83
85
  filePath: string,
86
+ dependencyCollector?: RenderDependencyCollector,
84
87
  ): void {
85
88
  if (!pageVariables.author) {
86
89
  return;
@@ -94,6 +97,7 @@ function resolveAuthor(
94
97
  );
95
98
  }
96
99
  const authorKey = pageVariables.author as string;
100
+ dependencyCollector?.setAuthorKey?.(authorKey);
97
101
  const authorEntry = authors[authorKey];
98
102
  if (!authorEntry) {
99
103
  throw new Error(
@@ -205,8 +209,10 @@ export function renderPlainTextPageAsset({
205
209
  validInternalTargets,
206
210
  assetFiles,
207
211
  literateJavaOutputPaths,
212
+ dependencyCollector,
213
+ cachedTraceSourceDir,
208
214
  traceCache,
209
- javacAvailable,
215
+ traceToolAvailability,
210
216
  }: RenderPlainTextOptions): Asset[] {
211
217
  const { dir, name, ext } = path.parse(filePath);
212
218
  const subPath = toPosix(path.relative(contentDir, path.join(dir, name)));
@@ -223,11 +229,13 @@ export function renderPlainTextPageAsset({
223
229
  watchMode,
224
230
  {
225
231
  validateInternalLinks: extensionIsMarkdown(ext.toLowerCase()),
232
+ dependencyCollector,
226
233
  traceCache,
227
234
  contentDir,
228
235
  distDir,
229
236
  literateJavaOutputPaths,
230
- javacAvailable,
237
+ cachedTraceSourceDir,
238
+ traceToolAvailability,
231
239
  },
232
240
  );
233
241
 
@@ -378,13 +386,16 @@ function renderPlainTextContent(
378
386
  isWatchMode: boolean,
379
387
  {
380
388
  validateInternalLinks = true,
389
+ dependencyCollector,
381
390
  traceCache,
382
391
  contentDir,
383
392
  distDir,
384
393
  literateJavaOutputPaths,
385
- javacAvailable,
394
+ cachedTraceSourceDir,
395
+ traceToolAvailability,
386
396
  }: {
387
397
  validateInternalLinks?: boolean;
398
+ dependencyCollector?: RenderDependencyCollector;
388
399
  traceCache?: Map<
389
400
  string,
390
401
  {
@@ -397,7 +408,8 @@ function renderPlainTextContent(
397
408
  contentDir?: string;
398
409
  distDir?: string;
399
410
  literateJavaOutputPaths?: Set<string>;
400
- javacAvailable?: boolean;
411
+ cachedTraceSourceDir?: string;
412
+ traceToolAvailability?: TraceToolAvailability;
401
413
  } = {},
402
414
  ): {
403
415
  content: string | null;
@@ -415,6 +427,9 @@ function renderPlainTextContent(
415
427
  codeExtensions: isFeatureEnabled(siteVariables, 'code')
416
428
  ? Object.keys(siteVariables.codeLanguages!)
417
429
  : [],
430
+ onValidLink: (targetPath: string) => {
431
+ dependencyCollector?.internalTargets?.add(targetPath);
432
+ },
418
433
  },
419
434
  literateJavaOutputPaths,
420
435
  sourceUrlPath,
@@ -446,7 +461,7 @@ function renderPlainTextContent(
446
461
  renderInlineField(md, pageVariablesProcessed, 'title');
447
462
  renderInlineField(md, pageVariablesProcessed, 'description');
448
463
 
449
- resolveAuthor(pageVariablesProcessed, filePath);
464
+ resolveAuthor(pageVariablesProcessed, filePath, dependencyCollector);
450
465
 
451
466
  const parentError = validateParentLink(
452
467
  pageVariablesProcessed.parent,
@@ -456,6 +471,11 @@ function renderPlainTextContent(
456
471
  if (parentError) {
457
472
  throw new Error(parentError);
458
473
  }
474
+ if (typeof pageVariablesProcessed.parent === 'string') {
475
+ dependencyCollector?.internalTargets?.add(
476
+ normalizeOutputPath(pageVariablesProcessed.parent),
477
+ );
478
+ }
459
479
 
460
480
  const strippedContent = stripHtmlComments(content);
461
481
 
@@ -475,12 +495,14 @@ function renderPlainTextContent(
475
495
  distDir,
476
496
  applyBasePath,
477
497
  cache: traceCache,
478
- javacAvailable,
498
+ toolAvailability: traceToolAvailability,
499
+ dependencyCollector,
500
+ cachedTraceSourceDir,
479
501
  });
480
502
  params.renderTrace = helpers.renderTrace;
481
503
  }
482
504
 
483
- params.include = createIncludeFunction(filePath, params);
505
+ params.include = createIncludeFunction(filePath, params, dependencyCollector);
484
506
 
485
507
  let html: string;
486
508
  try {
@@ -522,6 +544,7 @@ export function renderLiterateJavaPageAsset({
522
544
  skipExecution,
523
545
  validInternalTargets,
524
546
  literateJavaOutputPaths,
547
+ dependencyCollector,
525
548
  }: RenderLiterateJavaOptions): Asset[] {
526
549
  const { dir, name } = path.parse(filePath);
527
550
  const className = deriveClassName(filePath);
@@ -581,6 +604,9 @@ export function renderLiterateJavaPageAsset({
581
604
  codeExtensions: isFeatureEnabled(siteVariables, 'code')
582
605
  ? Object.keys(siteVariables.codeLanguages!)
583
606
  : [],
607
+ onValidLink: (targetPath: string) => {
608
+ dependencyCollector?.internalTargets?.add(targetPath);
609
+ },
584
610
  },
585
611
  literateJavaOutputPaths,
586
612
  sourceUrlPath,
@@ -656,7 +682,7 @@ export function renderLiterateJavaPageAsset({
656
682
  );
657
683
  }
658
684
 
659
- resolveAuthor(pageVariables, filePath);
685
+ resolveAuthor(pageVariables, filePath, dependencyCollector);
660
686
 
661
687
  const templateParameters = createTemplateParameters({
662
688
  pageVariables,