@abreen/tada 1.4.1 → 1.5.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.
Files changed (103) hide show
  1. package/README.md +4 -2
  2. package/bin/tada.ts +81 -57
  3. package/build/apply-base-path-plugin.ts +22 -6
  4. package/build/code.test.ts +108 -1
  5. package/build/colors.test.ts +66 -0
  6. package/build/columns-plugin.ts +116 -0
  7. package/build/content-watch.ts +30 -21
  8. package/build/copy.ts +1 -1
  9. package/build/external-links-plugin.ts +1 -1
  10. package/build/generate-content-assets.ts +48 -6
  11. package/build/generate-favicon.ts +1 -1
  12. package/build/generate-fonts.ts +1 -1
  13. package/build/generate-katex-assets.ts +1 -1
  14. package/build/log.test.ts +7 -1
  15. package/build/log.ts +4 -1
  16. package/build/markdown-plugins.test.ts +65 -3
  17. package/build/pagefind.ts +1 -1
  18. package/build/pipeline.ts +4 -20
  19. package/build/serve.test.ts +59 -0
  20. package/build/serve.ts +29 -63
  21. package/build/templates.ts +2 -2
  22. package/build/toc-plugin.test.ts +40 -25
  23. package/build/toc-plugin.ts +9 -2
  24. package/build/{types.ts → types.d.ts} +88 -1
  25. package/build/util.ts +1 -1
  26. package/build/utils/code.ts +76 -3
  27. package/build/utils/content-files.test.ts +56 -0
  28. package/build/utils/content-files.ts +15 -11
  29. package/build/utils/file-types.test.ts +18 -0
  30. package/build/utils/file-types.ts +4 -0
  31. package/build/utils/include.test.ts +117 -0
  32. package/build/utils/include.ts +53 -0
  33. package/build/utils/jdi-runner/TraceRunner.java +416 -0
  34. package/build/utils/literate-java.ts +2 -2
  35. package/build/utils/markdown.ts +32 -14
  36. package/build/utils/paths.ts +1 -1
  37. package/build/utils/render.ts +102 -11
  38. package/build/utils/shiki-highlighter.ts +1 -1
  39. package/build/utils/trace-layout.test.ts +570 -0
  40. package/build/utils/trace-layout.ts +848 -0
  41. package/build/utils/trace-svg.test.ts +425 -0
  42. package/build/utils/trace-svg.ts +905 -0
  43. package/build/utils/trace.test.ts +169 -0
  44. package/build/utils/trace.ts +382 -0
  45. package/build/validate-internal-links-plugin.test.ts +30 -2
  46. package/build/validate-internal-links-plugin.ts +3 -3
  47. package/build/watch-reload-client.ts +4 -20
  48. package/build/watch.ts +436 -448
  49. package/init/content/index.md +10 -5
  50. package/init/content/labs/00/index.md +1 -1
  51. package/init/content/labs/01/SearchTreeDemo.java +31 -0
  52. package/init/content/labs/01/TraceDemo.java +22 -0
  53. package/init/content/labs/01/index.md +27 -0
  54. package/init/content/labs/index.md +2 -0
  55. package/init/content/lectures/01/Rectangle.java +2 -0
  56. package/init/content/lectures/01/index.md +2 -1
  57. package/init/content/lectures/01/rectangle.py +45 -0
  58. package/init/content/lectures/02/_pr1.md +5 -0
  59. package/init/content/lectures/02/index.md +11 -0
  60. package/init/content/lectures/02/subdir/_foobar.html +3 -0
  61. package/init/content/lectures/02/subdir/_pr2.md +6 -0
  62. package/init/content/lectures/index.md +3 -0
  63. package/init/content/markdown.md +30 -0
  64. package/package.json +21 -4
  65. package/src/_base.scss +13 -2
  66. package/src/_layout.scss +6 -5
  67. package/src/anchor/index.test.ts +64 -0
  68. package/src/code/index.test.ts +71 -0
  69. package/src/code/style.scss +23 -0
  70. package/src/code.scss +5 -3
  71. package/src/header/_base.scss +48 -9
  72. package/src/header/index.test.ts +89 -0
  73. package/src/header/index.ts +1 -1
  74. package/src/header/style.scss +1 -5
  75. package/src/index.ts +18 -20
  76. package/src/literate/style.scss +15 -7
  77. package/src/navigate/eligible.test.ts +101 -0
  78. package/src/navigate/eligible.ts +62 -0
  79. package/src/navigate/index.test.ts +82 -0
  80. package/src/navigate/index.ts +365 -0
  81. package/src/navigate/lifecycle.test.ts +26 -0
  82. package/src/navigate/lifecycle.ts +65 -0
  83. package/src/navigate/style.scss +61 -0
  84. package/src/print/index.test.ts +71 -0
  85. package/src/question/index.test.ts +82 -0
  86. package/src/question/style.scss +12 -0
  87. package/src/search/index.ts +38 -14
  88. package/src/search/style.scss +23 -14
  89. package/src/style.scss +25 -12
  90. package/src/timezone/index.test.ts +301 -1
  91. package/src/timezone/index.ts +68 -56
  92. package/src/toc/index.test.ts +237 -0
  93. package/src/toc/index.ts +29 -69
  94. package/src/toc/style.scss +17 -0
  95. package/src/top/index.test.ts +46 -0
  96. package/src/top/index.ts +2 -2
  97. package/src/trace/index.test.ts +554 -0
  98. package/src/trace/index.ts +258 -0
  99. package/src/trace/style.scss +210 -0
  100. package/src/trace/types.d.ts +47 -0
  101. package/src/util.test.ts +135 -0
  102. package/templates/_top.html +23 -22
  103. package/types/jsdom.d.ts +9 -0
@@ -0,0 +1,416 @@
1
+ import com.sun.jdi.*;
2
+ import com.sun.jdi.connect.*;
3
+ import com.sun.jdi.event.*;
4
+ import com.sun.jdi.request.*;
5
+ import java.io.*;
6
+ import java.util.*;
7
+
8
+ /**
9
+ * Runs a Java class under JDI, stepping through execution line by line
10
+ * and emitting a JSONL trace to stdout. Each line is a self-contained
11
+ * JSON object representing one execution step with stack frames,
12
+ * local variables, and reachable heap objects.
13
+ *
14
+ * Usage: java TraceRunner <className> <classPath>
15
+ */
16
+ public class TraceRunner {
17
+
18
+ private static final Map<Long, String> objectIdMap = new HashMap<>();
19
+ private static long nextObjId = 1;
20
+ private static Map<String, ObjectReference> previousReachable = new LinkedHashMap<>();
21
+
22
+ public static void main(String[] args) throws Exception {
23
+ if (args.length < 2) {
24
+ System.err.println("Usage: java TraceRunner <className> <classPath>");
25
+ System.exit(1);
26
+ }
27
+
28
+ String className = args[0];
29
+ String classPath = args[1];
30
+
31
+ LaunchingConnector connector =
32
+ Bootstrap.virtualMachineManager().defaultConnector();
33
+ Map<String, Connector.Argument> connArgs = connector.defaultArguments();
34
+ connArgs.get("main").setValue(className);
35
+ connArgs.get("options").setValue("-cp " + classPath);
36
+
37
+ VirtualMachine vm = connector.launch(connArgs);
38
+ Process process = vm.process();
39
+ InputStream targetStdout = process.getInputStream();
40
+ InputStream targetStderr = process.getErrorStream();
41
+
42
+ Thread stderrThread = new Thread(() -> {
43
+ try {
44
+ targetStderr.transferTo(System.err);
45
+ } catch (IOException ignored) {
46
+ }
47
+ });
48
+ stderrThread.setDaemon(true);
49
+ stderrThread.start();
50
+
51
+ try {
52
+ ClassPrepareRequest cpr =
53
+ vm.eventRequestManager().createClassPrepareRequest();
54
+ cpr.addClassFilter(className);
55
+ cpr.setSuspendPolicy(EventRequest.SUSPEND_ALL);
56
+ cpr.enable();
57
+
58
+ vm.eventQueue().remove().resume(); // consume VMStartEvent
59
+
60
+ boolean running = true;
61
+ while (running) {
62
+ EventSet eventSet = vm.eventQueue().remove();
63
+ for (Event event : eventSet) {
64
+ if (event instanceof ClassPrepareEvent cpe) {
65
+ Method mainMethod = findMainMethod(cpe.referenceType());
66
+ if (mainMethod != null) {
67
+ var locs = mainMethod.allLineLocations();
68
+ if (!locs.isEmpty()) {
69
+ var bp = vm.eventRequestManager()
70
+ .createBreakpointRequest(locs.get(0));
71
+ bp.setSuspendPolicy(EventRequest.SUSPEND_ALL);
72
+ bp.enable();
73
+ }
74
+ }
75
+ cpr.disable();
76
+
77
+ } else if (event instanceof BreakpointEvent bpe) {
78
+ emitState(bpe.thread(), targetStdout);
79
+
80
+ var step = vm.eventRequestManager().createStepRequest(
81
+ bpe.thread(),
82
+ StepRequest.STEP_LINE,
83
+ StepRequest.STEP_INTO);
84
+ step.addClassExclusionFilter("java.*");
85
+ step.addClassExclusionFilter("javax.*");
86
+ step.addClassExclusionFilter("jdk.*");
87
+ step.addClassExclusionFilter("sun.*");
88
+ step.addClassExclusionFilter("com.sun.*");
89
+ step.setSuspendPolicy(EventRequest.SUSPEND_ALL);
90
+ step.enable();
91
+ bpe.request().disable();
92
+
93
+ } else if (event instanceof StepEvent se) {
94
+ emitState(se.thread(), targetStdout);
95
+
96
+ } else if (event instanceof VMDeathEvent
97
+ || event instanceof VMDisconnectEvent) {
98
+ running = false;
99
+ }
100
+ }
101
+ if (running) {
102
+ eventSet.resume();
103
+ }
104
+ }
105
+ } catch (VMDisconnectedException ignored) {
106
+ } finally {
107
+ try {
108
+ vm.dispose();
109
+ } catch (VMDisconnectedException ignored) {
110
+ }
111
+ stderrThread.join(1000);
112
+ }
113
+ }
114
+
115
+ private static Method findMainMethod(ReferenceType type) {
116
+ for (Method m : type.methods()) {
117
+ if (m.name().equals("main") && m.returnTypeName().equals("void")) {
118
+ var argTypes = m.argumentTypeNames();
119
+ if (argTypes.isEmpty()
120
+ || (argTypes.size() == 1
121
+ && argTypes.get(0).equals("java.lang.String[]"))) {
122
+ return m;
123
+ }
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+
129
+ private static void emitState(ThreadReference thread,
130
+ InputStream targetStdout) throws Exception {
131
+ Thread.sleep(1);
132
+ String newStdout = drainStream(targetStdout);
133
+
134
+ Location loc = thread.frame(0).location();
135
+ int line = loc.lineNumber();
136
+ String file = loc.sourceName();
137
+
138
+ var reachable = new LinkedHashMap<String, ObjectReference>();
139
+ String stackJson = serializeStack(thread, reachable);
140
+
141
+ // Carry forward objects from the previous step that are still alive.
142
+ // This keeps in-flight objects (e.g., on the operand stack during
143
+ // chained constructor calls) visible in the diagram.
144
+ for (var entry : previousReachable.entrySet()) {
145
+ if (!reachable.containsKey(entry.getKey())) {
146
+ try {
147
+ if (!entry.getValue().isCollected()) {
148
+ reachable.put(entry.getKey(), entry.getValue());
149
+ }
150
+ } catch (Exception ignored) {
151
+ }
152
+ }
153
+ }
154
+
155
+ String heapJson = serializeHeap(reachable);
156
+ previousReachable = new LinkedHashMap<>(reachable);
157
+
158
+ System.out.println(
159
+ "{\"line\":" + line
160
+ + ",\"file\":\"" + jsonEscape(file)
161
+ + "\",\"stack\":" + stackJson
162
+ + ",\"heap\":" + heapJson
163
+ + ",\"stdout\":\"" + jsonEscape(newStdout) + "\"}");
164
+ System.out.flush();
165
+ }
166
+
167
+ private static String serializeStack(ThreadReference thread,
168
+ Map<String, ObjectReference> reachable) throws Exception {
169
+ var sb = new StringBuilder("[");
170
+ boolean first = true;
171
+ for (StackFrame frame : thread.frames()) {
172
+ ReferenceType declType = frame.location().declaringType();
173
+ if (!isUserClass(declType)) {
174
+ continue;
175
+ }
176
+ if (!first) {
177
+ sb.append(',');
178
+ }
179
+ first = false;
180
+ sb.append("{\"method\":\"")
181
+ .append(jsonEscape(frame.location().method().name()))
182
+ .append("\",\"class\":\"")
183
+ .append(jsonEscape(declType.name()))
184
+ .append("\",\"line\":")
185
+ .append(frame.location().lineNumber())
186
+ .append(",\"locals\":{");
187
+
188
+ boolean firstVar = true;
189
+
190
+ // Emit 'this' for instance methods
191
+ if (!frame.location().method().isStatic()) {
192
+ try {
193
+ ObjectReference thisObj = frame.thisObject();
194
+ if (thisObj != null) {
195
+ sb.append("\"this\":")
196
+ .append(serializeValue(thisObj, reachable));
197
+ firstVar = false;
198
+ }
199
+ } catch (Exception ignored) {
200
+ }
201
+ }
202
+
203
+ try {
204
+ // Get ALL locals declared in the method
205
+ var allVars = frame.location().method().variables();
206
+ // Get the set of currently visible (initialized) variables
207
+ var visibleVars = new HashSet<LocalVariable>();
208
+ try {
209
+ visibleVars.addAll(frame.visibleVariables());
210
+ } catch (AbsentInformationException ignored) {
211
+ }
212
+ // Track names we've already emitted (avoid duplicates from scopes)
213
+ var emittedNames = new HashSet<String>();
214
+ for (LocalVariable lv : allVars) {
215
+ if (emittedNames.contains(lv.name())) {
216
+ continue;
217
+ }
218
+ emittedNames.add(lv.name());
219
+ if (!firstVar) {
220
+ sb.append(',');
221
+ }
222
+ firstVar = false;
223
+ sb.append("\"").append(jsonEscape(lv.name())).append("\":");
224
+ if (visibleVars.contains(lv)) {
225
+ Value val = frame.getValue(lv);
226
+ sb.append(serializeValue(val, reachable));
227
+ } else {
228
+ // Variable declared but not yet initialized
229
+ sb.append("{\"type\":\"uninitialized\"}");
230
+ }
231
+ }
232
+ } catch (AbsentInformationException ignored) {
233
+ }
234
+
235
+ sb.append("}}");
236
+ }
237
+ sb.append(']');
238
+ return sb.toString();
239
+ }
240
+
241
+ private static String serializeValue(Value val,
242
+ Map<String, ObjectReference> reachable) {
243
+ if (val == null) {
244
+ return "{\"type\":\"null\"}";
245
+ }
246
+ if (val instanceof BooleanValue v) {
247
+ return "{\"type\":\"boolean\",\"value\":" + v.value() + "}";
248
+ }
249
+ if (val instanceof ByteValue v) {
250
+ return "{\"type\":\"byte\",\"value\":" + v.value() + "}";
251
+ }
252
+ if (val instanceof CharValue v) {
253
+ return "{\"type\":\"char\",\"value\":\""
254
+ + jsonEscape(String.valueOf(v.value())) + "\"}";
255
+ }
256
+ if (val instanceof DoubleValue v) {
257
+ return "{\"type\":\"double\",\"value\":" + v.value() + "}";
258
+ }
259
+ if (val instanceof FloatValue v) {
260
+ return "{\"type\":\"float\",\"value\":" + v.value() + "}";
261
+ }
262
+ if (val instanceof IntegerValue v) {
263
+ return "{\"type\":\"int\",\"value\":" + v.value() + "}";
264
+ }
265
+ if (val instanceof LongValue v) {
266
+ return "{\"type\":\"long\",\"value\":" + v.value() + "}";
267
+ }
268
+ if (val instanceof ShortValue v) {
269
+ return "{\"type\":\"short\",\"value\":" + v.value() + "}";
270
+ }
271
+ if (val instanceof ObjectReference obj) {
272
+ String id = getObjectId(obj);
273
+ reachable.putIfAbsent(id, obj);
274
+ return "{\"type\":\"ref\",\"id\":\"" + id + "\"}";
275
+ }
276
+ return "{\"type\":\"unknown\"}";
277
+ }
278
+
279
+ private static String serializeHeap(
280
+ Map<String, ObjectReference> reachable) {
281
+ var sb = new StringBuilder("{");
282
+ var processed = new HashSet<String>();
283
+ var queue = new ArrayList<>(reachable.keySet());
284
+ boolean first = true;
285
+ int idx = 0;
286
+
287
+ while (idx < queue.size()) {
288
+ String id = queue.get(idx++);
289
+ if (processed.contains(id)) {
290
+ continue;
291
+ }
292
+ processed.add(id);
293
+
294
+ ObjectReference obj = reachable.get(id);
295
+ if (!first) {
296
+ sb.append(',');
297
+ }
298
+ first = false;
299
+ sb.append("\"").append(id).append("\":");
300
+ sb.append(serializeHeapObject(obj, reachable));
301
+
302
+ // Discover newly reachable objects
303
+ for (String newId : reachable.keySet()) {
304
+ if (!processed.contains(newId) && !queue.contains(newId)) {
305
+ queue.add(newId);
306
+ }
307
+ }
308
+ }
309
+ sb.append('}');
310
+ return sb.toString();
311
+ }
312
+
313
+ private static String serializeHeapObject(ObjectReference obj,
314
+ Map<String, ObjectReference> reachable) {
315
+ if (obj instanceof StringReference str) {
316
+ return "{\"type\":\"String\",\"value\":\""
317
+ + jsonEscape(str.value()) + "\"}";
318
+ }
319
+ if (obj instanceof ArrayReference arr) {
320
+ var sb = new StringBuilder("{\"type\":\"")
321
+ .append(jsonEscape(arr.referenceType().name()))
322
+ .append("\",\"elements\":[");
323
+ int limit = Math.min(arr.length(), 100);
324
+ for (int i = 0; i < limit; i++) {
325
+ if (i > 0) {
326
+ sb.append(',');
327
+ }
328
+ sb.append(serializeValue(arr.getValue(i), reachable));
329
+ }
330
+ if (arr.length() > limit) {
331
+ sb.append(",{\"type\":\"truncated\",\"remaining\":")
332
+ .append(arr.length() - limit).append('}');
333
+ }
334
+ sb.append("]}");
335
+ return sb.toString();
336
+ }
337
+
338
+ ReferenceType type = obj.referenceType();
339
+ if (!isUserClass(type)) {
340
+ // JDK object: show type name and toString value
341
+ try {
342
+ return "{\"type\":\"" + jsonEscape(type.name())
343
+ + "\",\"value\":\"" + jsonEscape(obj.toString()) + "\"}";
344
+ } catch (Exception e) {
345
+ return "{\"type\":\"" + jsonEscape(type.name()) + "\"}";
346
+ }
347
+ }
348
+
349
+ // User object: show fields
350
+ var sb = new StringBuilder("{\"type\":\"")
351
+ .append(jsonEscape(type.name()))
352
+ .append("\",\"fields\":{");
353
+ boolean first = true;
354
+ try {
355
+ for (Field field : type.visibleFields()) {
356
+ if (field.isStatic()) {
357
+ continue;
358
+ }
359
+ if (!first) {
360
+ sb.append(',');
361
+ }
362
+ first = false;
363
+ sb.append("\"").append(jsonEscape(field.name())).append("\":");
364
+ sb.append(serializeValue(obj.getValue(field), reachable));
365
+ }
366
+ } catch (Exception ignored) {
367
+ }
368
+ sb.append("}}");
369
+ return sb.toString();
370
+ }
371
+
372
+ private static String getObjectId(ObjectReference obj) {
373
+ return objectIdMap.computeIfAbsent(
374
+ obj.uniqueID(), k -> "obj_" + nextObjId++);
375
+ }
376
+
377
+ private static boolean isUserClass(ReferenceType type) {
378
+ String name = type.name();
379
+ return !name.startsWith("java.")
380
+ && !name.startsWith("javax.")
381
+ && !name.startsWith("jdk.")
382
+ && !name.startsWith("sun.")
383
+ && !name.startsWith("com.sun.");
384
+ }
385
+
386
+ private static String drainStream(InputStream stream) throws IOException {
387
+ var sb = new StringBuilder();
388
+ while (stream.available() > 0) {
389
+ sb.append((char) stream.read());
390
+ }
391
+ return sb.toString();
392
+ }
393
+
394
+ private static String jsonEscape(String s) {
395
+ var sb = new StringBuilder();
396
+ for (int i = 0; i < s.length(); i++) {
397
+ char c = s.charAt(i);
398
+ switch (c) {
399
+ case '"' -> sb.append("\\\"");
400
+ case '\\' -> sb.append("\\\\");
401
+ case '\n' -> sb.append("\\n");
402
+ case '\r' -> sb.append("\\r");
403
+ case '\t' -> sb.append("\\t");
404
+ default -> {
405
+ if (c < 0x20) {
406
+ sb.append("\\u")
407
+ .append(String.format("%04x", (int) c));
408
+ } else {
409
+ sb.append(c);
410
+ }
411
+ }
412
+ }
413
+ }
414
+ return sb.toString();
415
+ }
416
+ }
@@ -12,7 +12,7 @@ import type {
12
12
  LiterateRunnerEntry,
13
13
  } from '../types';
14
14
 
15
- const log = makeLogger(__filename);
15
+ const log = makeLogger(import.meta.url);
16
16
 
17
17
  let runnerClassDir: string | null = null;
18
18
 
@@ -121,7 +121,7 @@ function ensureRunnerCompiled(): string {
121
121
  return runnerClassDir;
122
122
  }
123
123
 
124
- const sourceDir = path.join(__dirname, 'jdi-runner');
124
+ const sourceDir = path.join(import.meta.dir, 'jdi-runner');
125
125
  const sourceFile = path.join(sourceDir, 'LiterateRunner.java');
126
126
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-runner-'));
127
127
 
@@ -15,6 +15,7 @@ import externalLinksPlugin from '../external-links-plugin';
15
15
  import validateInternalLinksPlugin from '../validate-internal-links-plugin';
16
16
  import applyBasePathPlugin from '../apply-base-path-plugin';
17
17
  import { tocPlugin } from '../toc-plugin';
18
+ import columnsPlugin from '../columns-plugin';
18
19
  import markdownItKatex from '@vscode/markdown-it-katex';
19
20
  import katex from 'katex';
20
21
  import renderA11yString from 'katex/contrib/render-a11y-string';
@@ -22,6 +23,8 @@ import type { SiteVariables } from '../types';
22
23
 
23
24
  interface CreateMarkdownOptions {
24
25
  validatorOptions?: Record<string, unknown>;
26
+ literateJavaOutputPaths?: Set<string>;
27
+ sourceUrlPath?: string;
25
28
  }
26
29
 
27
30
  function capitalize(str: string): string {
@@ -36,7 +39,11 @@ export function createMarkdown(
36
39
  siteVariables: SiteVariables,
37
40
  options: CreateMarkdownOptions = {},
38
41
  ): MarkdownIt {
39
- const { validatorOptions = {} } = options;
42
+ const {
43
+ validatorOptions = {},
44
+ literateJavaOutputPaths,
45
+ sourceUrlPath,
46
+ } = options;
40
47
  const markdown = new MarkdownIt({ html: true, typographer: true })
41
48
  .use(headingSubtitlePlugin)
42
49
  .use(markdownItAnchor, { tabIndex: false })
@@ -45,8 +52,12 @@ export function createMarkdown(
45
52
  .use(deflistIdPlugin)
46
53
  .use(externalLinksPlugin, siteVariables)
47
54
  .use(validateInternalLinksPlugin, validatorOptions)
48
- .use(applyBasePathPlugin, siteVariables)
55
+ .use(applyBasePathPlugin, siteVariables, {
56
+ literateJavaOutputPaths,
57
+ sourceUrlPath,
58
+ })
49
59
  .use(tocPlugin)
60
+ .use(columnsPlugin)
50
61
  .use(markdownItKatex)
51
62
  .use(markdownItContainer, 'details', {
52
63
  marker: '<',
@@ -116,7 +127,12 @@ export function createMarkdown(
116
127
  validate: function (params: string) {
117
128
  return !!params.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
118
129
  },
119
- render: function (tokens: Token[], idx: number) {
130
+ render: function (
131
+ tokens: Token[],
132
+ idx: number,
133
+ _options: unknown,
134
+ env: Record<string, unknown>,
135
+ ) {
120
136
  const matches = tokens[idx].info
121
137
  .trim()
122
138
  .match(/^(note|warning)\s*"?(.+)?"?$/);
@@ -129,19 +145,21 @@ export function createMarkdown(
129
145
  }
130
146
 
131
147
  const title = matches && matches[2]?.trim();
148
+ const displayTitle = title
149
+ ? markdown.utils.escapeHtml(curlyQuote(title))
150
+ : capitalize(type || '');
151
+ const baseId = textToId(title || type || '');
152
+ const count = (usedIds.get(baseId) ?? 0) + 1;
153
+ usedIds.set(baseId, count);
154
+ const titleId = count === 1 ? baseId : `${baseId}-${count}`;
132
155
 
133
- let html = `<div class="${classNames.join(' ')}">`;
134
- if (title) {
135
- const baseId = textToId(title);
136
- const count = (usedIds.get(baseId) ?? 0) + 1;
137
- usedIds.set(baseId, count);
138
- const titleId = count === 1 ? baseId : `${baseId}-${count}`;
139
- const renderedTitle = markdown.utils.escapeHtml(curlyQuote(title));
140
- html += `<p class="title" id="${titleId}">${renderedTitle}</p>\n`;
141
- } else {
142
- const defaultTitle = capitalize(type || '');
143
- html += `<p class="title">${defaultTitle}</p>\n`;
156
+ if (!env.alertIds) {
157
+ env.alertIds = [];
144
158
  }
159
+ (env.alertIds as string[]).push(titleId);
160
+
161
+ let html = `<div class="${classNames.join(' ')}">`;
162
+ html += `<p class="title" id="${titleId}">${displayTitle}</p>\n`;
145
163
  html += '<div class="content">\n';
146
164
  return html;
147
165
  } else {
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import type { SiteVariables } from '../types';
3
3
 
4
4
  export function getPackageDir(): string {
5
- return path.resolve(__dirname, '..', '..');
5
+ return path.resolve(import.meta.dir, '..', '..');
6
6
  }
7
7
 
8
8
  export function getProjectDir(): string {