@abreen/tada 1.13.7 → 1.14.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 (49) hide show
  1. package/README.md +46 -0
  2. package/build/generate-content-assets.ts +39 -185
  3. package/build/pipeline.ts +1 -1
  4. package/build/source-model.ts +2 -14
  5. package/build/types.d.ts +18 -18
  6. package/build/utils/html-comments.ts +3 -0
  7. package/build/utils/jdi-runner/TraceRunner.java +227 -38
  8. package/build/utils/markdown-partials.ts +235 -0
  9. package/build/utils/markdown.ts +12 -1
  10. package/build/utils/python-runner/trace_runner.py +58 -13
  11. package/build/utils/render.ts +14 -12
  12. package/build/utils/trace-core.ts +136 -14
  13. package/build/utils/trace-heap.ts +34 -0
  14. package/build/utils/trace-java.ts +60 -7
  15. package/build/utils/trace-layout.ts +58 -13
  16. package/build/utils/trace-python.ts +6 -2
  17. package/build/utils/trace-svg.ts +54 -60
  18. package/build/utils/trace.ts +193 -78
  19. package/build/watch/build-full.ts +26 -52
  20. package/build/watch/build-helpers.ts +66 -0
  21. package/build/watch/build-incremental.ts +32 -60
  22. package/build/watch/build-result.ts +19 -0
  23. package/build/watch/compiler-types.d.ts +2 -2
  24. package/build/watch/compiler.ts +30 -62
  25. package/build/watch/config-paths.ts +56 -0
  26. package/build/watch/engine.ts +199 -0
  27. package/{watch → build/watch}/fs-commit.ts +1 -3
  28. package/build/watch/index.ts +1 -1
  29. package/build/watch/mutations.ts +23 -25
  30. package/build/watch/planner.ts +14 -78
  31. package/build/watch/runtime.ts +26 -38
  32. package/build/watch/snapshot.ts +26 -47
  33. package/build/watch/types.d.ts +84 -0
  34. package/build/watch/validation.ts +1 -1
  35. package/init/content/lectures/02/_pr1.md +1 -1
  36. package/init/content/lectures/02/index.md +2 -2
  37. package/init/content/lectures/02/subdir/_foobar.md +3 -0
  38. package/init/content/lectures/02/subdir/_pr2.md +3 -3
  39. package/package.json +1 -2
  40. package/src/slides/index.ts +6 -2
  41. package/src/trace/index.ts +49 -12
  42. package/src/trace/style.scss +10 -0
  43. package/src/trace/types.d.ts +16 -4
  44. package/build/utils/include.ts +0 -57
  45. package/build/watch-reachability-state.ts +0 -305
  46. package/init/content/lectures/02/subdir/_foobar.html +0 -3
  47. package/watch/engine.ts +0 -296
  48. package/watch/events.ts +0 -10
  49. package/watch/types.d.ts +0 -103
package/README.md CHANGED
@@ -40,6 +40,7 @@ A static site generator. The successor to Presto.
40
40
  * `??? question ... ???` renders a Q & A section; answers hidden until click
41
41
  * `+++ ... +++ ... +++` renders a two-column layout
42
42
  * Special heading subtitles with `## Heading # A subtitle here`
43
+ * `{{{ _partial.md }}}` syntax for including partials
43
44
  - Automatically generated favicon
44
45
  * Text, color, font and font weight taken from config file
45
46
 
@@ -333,6 +334,51 @@ templates][lodash].
333
334
  - Custom variables from the `"vars"` property of the config are available
334
335
  under `vars` (e.g., `<%= vars.staffEmail %>`)
335
336
 
337
+ ### Markdown partials
338
+
339
+ Use the `{{{ _partial.md }}}` syntax to include a partial (a fragment of
340
+ Markdown in a separate file whose name must start with `_`) at that particular
341
+ location in a Markdown file.
342
+
343
+ This is useful for reducing duplication across multiple documents which use the
344
+ exact same content, or for breaking up a large document into building blocks
345
+ which are easier to edit.
346
+
347
+ Partials are never built into their own pages, and they don't have front matter.
348
+
349
+ The `{{{ ... }}}` syntax is not a simple string replacement; instead, the
350
+ Markdown syntax tree of the partial is *added* to the syntax tree of the
351
+ Markdown file that includes it. This is a powerful feature because it allows you
352
+ to include content as a subtree.
353
+
354
+ For example, this partial `_groceries.md`
355
+
356
+ ```
357
+ * apples
358
+ * oranges
359
+ * pears
360
+ ```
361
+
362
+ can be inserted as a sub-list of a larger list in a Markdown page
363
+
364
+ ```
365
+ title: To do list
366
+
367
+ * Grocery shopping
368
+ {{{ _groceries.md }}}
369
+ * Car wash
370
+ ```
371
+
372
+ which results in:
373
+
374
+ ```
375
+ * Grocery shopping
376
+ * apples
377
+ * oranges
378
+ * pears
379
+ * Car wash
380
+ ```
381
+
336
382
 
337
383
  [inter]: https://fonts.google.com/specimen/Inter
338
384
  [lodash]: https://lodash.info/doc/template
@@ -6,16 +6,15 @@ import { createContentRecord } from './source-records';
6
6
  import { validateConfigLinks } from './validate-config-links';
7
7
  import { config, getConfigFileName } from './templates';
8
8
  import { initHighlighter } from './utils/shiki-highlighter';
9
- import { checkTraceToolAvailability, isTraceSourceFile } from './utils/trace';
9
+ import { checkTraceToolAvailability } from './utils/trace';
10
10
  import type {
11
11
  SiteVariables,
12
12
  ContentRenderOptions,
13
13
  ContentRenderResult,
14
14
  HtmlOutputAnalysis,
15
15
  TraceToolAvailability,
16
- WatchState,
17
16
  } from './types';
18
- import type { TadaSourceRecord } from './watch/snapshot';
17
+ import type { TraceCache } from './watch/compiler-types';
19
18
 
20
19
  const log = makeLogger(import.meta.url);
21
20
 
@@ -25,152 +24,41 @@ function cloneHtmlOutputAnalysis(
25
24
  return { outgoingTargets: new Set(analysis.outgoingTargets) };
26
25
  }
27
26
 
27
+ function writeRecordOutputs(
28
+ distDir: string,
29
+ outputs: Map<string, string | Buffer>,
30
+ ): void {
31
+ for (const [outputPath, content] of outputs) {
32
+ const outPath = path.join(distDir, outputPath);
33
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
34
+ fs.writeFileSync(outPath, content);
35
+ }
36
+ }
37
+
28
38
  export class ContentRenderer {
29
39
  private siteVariables: SiteVariables;
30
- private sourceFileCache: Map<string, TadaSourceRecord>;
31
- private lastBuildFiles: Set<string>;
32
- private javacAvailable: boolean | undefined;
33
40
  private traceToolAvailability: TraceToolAvailability | undefined;
34
- private traceCache: Map<
35
- string,
36
- {
37
- manifestUrl: string;
38
- artifactId: string;
39
- highlightedSource: string;
40
- totalSteps: number;
41
- mtime: number;
42
- }
43
- > = new Map();
41
+ private traceCache: TraceCache = new Map();
44
42
 
45
43
  constructor(siteVariables: SiteVariables) {
46
44
  this.siteVariables = siteVariables;
47
- this.sourceFileCache = new Map();
48
- this.lastBuildFiles = new Set();
49
- }
50
-
51
- private getCachedRecord(filePath: string): TadaSourceRecord | undefined {
52
- return this.sourceFileCache.get(filePath);
53
45
  }
54
46
 
55
47
  async initHighlighter(): Promise<void> {
56
48
  await initHighlighter(getRuntimeBundledShikiLanguages(this.siteVariables));
57
49
  }
58
50
 
59
- getDirtySourceFiles(
60
- buildContentFiles: string[],
61
- { changedContentFiles, jsonDataChanged, partialsChanged }: WatchState = {},
62
- ): Set<string> {
63
- if (
64
- this.lastBuildFiles.size === 0 ||
65
- !changedContentFiles ||
66
- jsonDataChanged ||
67
- partialsChanged
68
- ) {
69
- return new Set(buildContentFiles);
70
- }
71
-
72
- // If any trace source changed, the trace cache will be stale, so rebuild
73
- // all content pages to pick up the new trace output.
74
- const traceFileChanged = [...changedContentFiles].some(isTraceSourceFile);
75
- if (traceFileChanged) {
76
- return new Set(buildContentFiles);
77
- }
78
-
79
- const buildFileSet = new Set(buildContentFiles);
80
- const dirtySourceFiles = new Set<string>();
81
- for (const filePath of changedContentFiles) {
82
- if (buildFileSet.has(filePath)) {
83
- dirtySourceFiles.add(filePath);
84
- }
85
- }
86
- return dirtySourceFiles;
87
- }
88
-
89
- updateSourceCache(
90
- filePath: string,
91
- record: TadaSourceRecord,
92
- changedOutputRelPaths: Set<string>,
93
- removedOutputRelPaths: Set<string>,
94
- changedHtmlAssetPaths: Set<string>,
95
- removedHtmlAssetPaths: Set<string>,
96
- ): void {
97
- const previousRecord = this.getCachedRecord(filePath);
98
- const nextOutputPaths = new Set(record.outputs.keys());
99
-
100
- for (const outputPath of previousRecord?.outputs.keys() ?? []) {
101
- if (nextOutputPaths.has(outputPath)) {
102
- continue;
103
- }
104
- removedOutputRelPaths.add(outputPath);
105
- if (outputPath.endsWith('.html')) {
106
- removedHtmlAssetPaths.add(outputPath);
107
- }
108
- }
109
-
110
- for (const outputPath of record.outputs.keys()) {
111
- changedOutputRelPaths.add(outputPath);
112
- if (outputPath.endsWith('.html')) {
113
- changedHtmlAssetPaths.add(outputPath);
114
- }
115
- }
116
-
117
- this.sourceFileCache.set(filePath, record);
118
- }
119
-
120
- writeCachedAssets(distDir: string, buildContentFiles: string[]): void {
121
- for (const filePath of buildContentFiles) {
122
- const record = this.getCachedRecord(filePath);
123
- if (!record) {
124
- continue;
125
- }
126
- for (const [outputPath, content] of record.outputs) {
127
- const outPath = path.join(distDir, outputPath);
128
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
129
- fs.writeFileSync(outPath, content);
130
- }
131
- }
132
- }
133
-
134
- pruneRemovedSources(
135
- buildFileSet: Set<string>,
136
- removedOutputRelPaths: Set<string>,
137
- removedHtmlAssetPaths: Set<string>,
138
- ): void {
139
- for (const filePath of [...this.lastBuildFiles]) {
140
- if (buildFileSet.has(filePath)) {
141
- continue;
142
- }
143
-
144
- const previousRecord = this.getCachedRecord(filePath);
145
- for (const outputPath of previousRecord?.outputs.keys() ?? []) {
146
- removedOutputRelPaths.add(outputPath);
147
- if (outputPath.endsWith('.html')) {
148
- removedHtmlAssetPaths.add(outputPath);
149
- }
150
- }
151
- this.sourceFileCache.delete(filePath);
152
- }
153
- }
154
-
155
51
  processContent({
156
52
  distDir,
157
53
  assetFiles,
158
54
  scan,
159
- watchState,
160
55
  }: ContentRenderOptions): ContentRenderResult {
161
56
  const buildContentFiles = [...scan.buildContentFiles];
162
- const buildFileSet = new Set<string>(buildContentFiles);
163
- const dirtySourceFiles = this.getDirtySourceFiles(
164
- buildContentFiles,
165
- watchState,
166
- );
167
- const changedOutputRelPaths = new Set<string>();
168
- const removedOutputRelPaths = new Set<string>();
169
- const changedHtmlAssetPaths = new Set<string>();
170
- const removedHtmlAssetPaths = new Set<string>();
171
57
  const validInternalTargets = scan.validTargets;
172
58
 
173
59
  const errors: Error[] = [];
60
+ const htmlAssetsByPath = new Map<string, string>();
61
+ const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
174
62
 
175
63
  const configLinkErrors = validateConfigLinks(
176
64
  validInternalTargets,
@@ -185,29 +73,21 @@ export class ContentRenderer {
185
73
  errors.push(new Error(msg));
186
74
  }
187
75
 
188
- this.pruneRemovedSources(
189
- buildFileSet,
190
- removedOutputRelPaths,
191
- removedHtmlAssetPaths,
192
- );
193
-
194
- if (dirtySourceFiles.size > 0) {
195
- const noun = dirtySourceFiles.size === 1 ? 'file' : 'files';
196
- log.info`Processing ${dirtySourceFiles.size} content ${noun}`;
76
+ if (buildContentFiles.length > 0) {
77
+ const noun = buildContentFiles.length === 1 ? 'file' : 'files';
78
+ log.info`Processing ${buildContentFiles.length} content ${noun}`;
197
79
  }
198
80
 
199
- if (this.javacAvailable === undefined && dirtySourceFiles.size > 0) {
81
+ if (!this.traceToolAvailability && buildContentFiles.length > 0) {
200
82
  this.traceToolAvailability = checkTraceToolAvailability();
201
- this.javacAvailable = this.traceToolAvailability.java;
202
- if (!this.javacAvailable) {
83
+ if (!this.traceToolAvailability.java) {
203
84
  log.warn`javac was not found; literate Java pages will not include execution output`;
204
85
  }
205
86
  }
206
87
 
207
- for (const filePath of dirtySourceFiles) {
208
- let record: TadaSourceRecord;
88
+ for (const filePath of buildContentFiles) {
209
89
  try {
210
- record = createContentRecord({
90
+ const record = createContentRecord({
211
91
  filePath,
212
92
  siteVariables: this.siteVariables,
213
93
  scan,
@@ -215,53 +95,27 @@ export class ContentRenderer {
215
95
  outputDir: distDir,
216
96
  traceCache: this.traceCache,
217
97
  traceToolAvailability: this.traceToolAvailability,
218
- skipLiterateJavaExecution: !this.javacAvailable,
98
+ skipLiterateJavaExecution: !this.traceToolAvailability?.java,
219
99
  });
100
+ writeRecordOutputs(distDir, record.outputs);
101
+ for (const [outputPath, content] of record.outputs) {
102
+ if (!outputPath.endsWith('.html') || typeof content !== 'string') {
103
+ continue;
104
+ }
105
+ htmlAssetsByPath.set(outputPath, content);
106
+ const analysis = record.htmlAnalysisByOutputPath?.get(outputPath);
107
+ if (analysis) {
108
+ htmlAnalysisByPath.set(
109
+ outputPath,
110
+ cloneHtmlOutputAnalysis(analysis),
111
+ );
112
+ }
113
+ }
220
114
  } catch (err) {
221
115
  errors.push(err as Error);
222
- continue;
223
- }
224
- this.updateSourceCache(
225
- filePath,
226
- record,
227
- changedOutputRelPaths,
228
- removedOutputRelPaths,
229
- changedHtmlAssetPaths,
230
- removedHtmlAssetPaths,
231
- );
232
- }
233
-
234
- this.writeCachedAssets(distDir, buildContentFiles);
235
- this.lastBuildFiles = buildFileSet;
236
-
237
- // Collect HTML asset content and analysis for Pagefind
238
- const htmlAssetsByPath = new Map<string, string>();
239
- const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
240
- for (const filePath of buildContentFiles) {
241
- const record = this.getCachedRecord(filePath);
242
- if (!record) {
243
- continue;
244
- }
245
- for (const [outputPath, content] of record.outputs) {
246
- if (!outputPath.endsWith('.html') || typeof content !== 'string') {
247
- continue;
248
- }
249
- htmlAssetsByPath.set(outputPath, content);
250
- const analysis = record.htmlAnalysisByOutputPath?.get(outputPath);
251
- if (analysis) {
252
- htmlAnalysisByPath.set(outputPath, cloneHtmlOutputAnalysis(analysis));
253
- }
254
116
  }
255
117
  }
256
118
 
257
- return {
258
- errors,
259
- changedHtmlAssetPaths,
260
- removedHtmlAssetPaths,
261
- removedOutputRelPaths,
262
- htmlAssetsByPath,
263
- htmlAnalysisByPath,
264
- buildContentFiles,
265
- };
119
+ return { errors, htmlAssetsByPath, htmlAnalysisByPath };
266
120
  }
267
121
  }
package/build/pipeline.ts CHANGED
@@ -15,7 +15,7 @@ import { makeLogger, printFlair } from './log';
15
15
  import { generateBuildManifest, getNextVersion } from './build-manifest';
16
16
  import { scanProject } from './source-model';
17
17
  import { validateConfig } from './watch/validation';
18
- import { applyCommitPlan } from '../watch/fs-commit';
18
+ import { applyCommitPlan } from './watch/fs-commit';
19
19
  import path from 'path';
20
20
 
21
21
  const log = makeLogger(import.meta.url);
@@ -15,7 +15,7 @@ import {
15
15
  normalizeOutputPath,
16
16
  toPosix,
17
17
  } from './utils/paths';
18
- import type { ChangeBatch } from '../watch/types';
18
+ import type { ChangeBatch } from './watch/types';
19
19
  import type { SiteVariables } from './types';
20
20
 
21
21
  export interface TadaProjectScan {
@@ -34,18 +34,6 @@ export interface TadaProjectScan {
34
34
  sourceTargetPaths: Map<string, Set<string>>;
35
35
  }
36
36
 
37
- export interface TadaProjectScanSnapshotLike {
38
- processedExts: Set<string>;
39
- contentFiles: Set<string>;
40
- buildContentFiles: Set<string>;
41
- publicFiles: Set<string>;
42
- literateJavaOutputPaths: Set<string>;
43
- contentOwners: Map<string, string>;
44
- publicOwners: Map<string, string>;
45
- sourceOutputPaths: Map<string, Set<string>>;
46
- sourceTargetPaths: Map<string, Set<string>>;
47
- }
48
-
49
37
  export function shouldSkipContentFile(filePath: string): boolean {
50
38
  const ext = path.extname(filePath).toLowerCase();
51
39
  if (!(extensionIsMarkdown(ext) || ext === '.html')) {
@@ -333,7 +321,7 @@ export function scanProject(siteVariables: SiteVariables): TadaProjectScan {
333
321
  }
334
322
 
335
323
  export function updateProjectScan(
336
- snapshot: TadaProjectScanSnapshotLike,
324
+ snapshot: TadaProjectScan,
337
325
  batch: ChangeBatch,
338
326
  ): TadaProjectScan {
339
327
  const scan = createEmptyScan(snapshot.processedExts);
package/build/types.d.ts CHANGED
@@ -49,25 +49,13 @@ export interface ContentRenderOptions {
49
49
  distDir: string;
50
50
  assetFiles: string[];
51
51
  scan: TadaProjectScan;
52
- watchState?: WatchState;
53
- }
54
-
55
- /** State passed from watch mode to control incremental rebuilds */
56
- export interface WatchState {
57
- changedContentFiles?: Set<string>;
58
- jsonDataChanged?: boolean;
59
- partialsChanged?: boolean;
60
52
  }
61
53
 
62
54
  /** Result from ContentRenderer.processContent() */
63
55
  export interface ContentRenderResult {
64
56
  errors: Error[];
65
- changedHtmlAssetPaths: Set<string>;
66
- removedHtmlAssetPaths: Set<string>;
67
- removedOutputRelPaths: Set<string>;
68
57
  htmlAssetsByPath: Map<string, string>;
69
58
  htmlAnalysisByPath: Map<string, HtmlOutputAnalysis>;
70
- buildContentFiles: string[];
71
59
  }
72
60
 
73
61
  /** Logger returned by makeLogger() */
@@ -98,9 +86,9 @@ export interface RenderPlainTextOptions {
98
86
  {
99
87
  manifestUrl: string;
100
88
  artifactId: string;
101
- highlightedSource: string;
89
+ highlightedSources: { file: string; highlightedSource: string }[];
102
90
  totalSteps: number;
103
- mtime: number;
91
+ sourceMtims: Record<string, number>;
104
92
  }
105
93
  >;
106
94
  traceToolAvailability?: TraceToolAvailability;
@@ -218,7 +206,13 @@ export interface TraceStep {
218
206
  file: string;
219
207
  stack: TraceStackFrame[];
220
208
  heap: Record<string, TraceHeapObject>;
221
- stdout: string;
209
+ output?: TraceOutputEvent[];
210
+ transientHeapRoots?: string[];
211
+ }
212
+
213
+ export interface TraceOutputEvent {
214
+ stream: 'stdout' | 'stderr';
215
+ text: string;
222
216
  }
223
217
 
224
218
  export interface TraceStackFrame {
@@ -244,13 +238,18 @@ export type TraceValue =
244
238
  export type TraceHeapObject =
245
239
  | { type: string; elements: TraceValue[] }
246
240
  | { type: string; fields: Record<string, TraceValue> }
247
- | { type: string; value: string };
241
+ | { type: string; value: string | number | boolean };
248
242
 
249
243
  /** Manifest written alongside trace chunks */
250
244
  export interface TraceManifest {
251
245
  totalSteps: number;
252
246
  chunkSize: number;
253
- sourceFile: string;
247
+ primaryFile: string;
248
+ sources: TraceSource[];
249
+ }
250
+
251
+ export interface TraceSource {
252
+ file: string;
254
253
  source: string;
255
254
  lineToSteps: Record<number, number[]>;
256
255
  }
@@ -279,8 +278,9 @@ export interface TraceLayout {
279
278
 
280
279
  /** A single entry in a chunk file (new format with precomputed SVG). */
281
280
  export interface TraceChunkEntry {
281
+ file: string;
282
282
  line: number;
283
- stdout: string;
283
+ output?: TraceOutputEvent[];
284
284
  svg: string;
285
285
  }
286
286
 
@@ -0,0 +1,3 @@
1
+ export function stripHtmlComments(str: string): string {
2
+ return str.replace(/<!---[\s\S]*?-->/g, '');
3
+ }