@abreen/tada 1.13.7 → 1.13.9

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.
@@ -6,16 +6,14 @@ 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';
19
17
 
20
18
  const log = makeLogger(import.meta.url);
21
19
 
@@ -25,11 +23,19 @@ function cloneHtmlOutputAnalysis(
25
23
  return { outgoingTargets: new Set(analysis.outgoingTargets) };
26
24
  }
27
25
 
26
+ function writeRecordOutputs(
27
+ distDir: string,
28
+ outputs: Map<string, string | Buffer>,
29
+ ): void {
30
+ for (const [outputPath, content] of outputs) {
31
+ const outPath = path.join(distDir, outputPath);
32
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
33
+ fs.writeFileSync(outPath, content);
34
+ }
35
+ }
36
+
28
37
  export class ContentRenderer {
29
38
  private siteVariables: SiteVariables;
30
- private sourceFileCache: Map<string, TadaSourceRecord>;
31
- private lastBuildFiles: Set<string>;
32
- private javacAvailable: boolean | undefined;
33
39
  private traceToolAvailability: TraceToolAvailability | undefined;
34
40
  private traceCache: Map<
35
41
  string,
@@ -44,133 +50,23 @@ export class ContentRenderer {
44
50
 
45
51
  constructor(siteVariables: SiteVariables) {
46
52
  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
53
  }
54
54
 
55
55
  async initHighlighter(): Promise<void> {
56
56
  await initHighlighter(getRuntimeBundledShikiLanguages(this.siteVariables));
57
57
  }
58
58
 
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
59
  processContent({
156
60
  distDir,
157
61
  assetFiles,
158
62
  scan,
159
- watchState,
160
63
  }: ContentRenderOptions): ContentRenderResult {
161
64
  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
65
  const validInternalTargets = scan.validTargets;
172
66
 
173
67
  const errors: Error[] = [];
68
+ const htmlAssetsByPath = new Map<string, string>();
69
+ const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
174
70
 
175
71
  const configLinkErrors = validateConfigLinks(
176
72
  validInternalTargets,
@@ -185,29 +81,21 @@ export class ContentRenderer {
185
81
  errors.push(new Error(msg));
186
82
  }
187
83
 
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}`;
84
+ if (buildContentFiles.length > 0) {
85
+ const noun = buildContentFiles.length === 1 ? 'file' : 'files';
86
+ log.info`Processing ${buildContentFiles.length} content ${noun}`;
197
87
  }
198
88
 
199
- if (this.javacAvailable === undefined && dirtySourceFiles.size > 0) {
89
+ if (!this.traceToolAvailability && buildContentFiles.length > 0) {
200
90
  this.traceToolAvailability = checkTraceToolAvailability();
201
- this.javacAvailable = this.traceToolAvailability.java;
202
- if (!this.javacAvailable) {
91
+ if (!this.traceToolAvailability.java) {
203
92
  log.warn`javac was not found; literate Java pages will not include execution output`;
204
93
  }
205
94
  }
206
95
 
207
- for (const filePath of dirtySourceFiles) {
208
- let record: TadaSourceRecord;
96
+ for (const filePath of buildContentFiles) {
209
97
  try {
210
- record = createContentRecord({
98
+ const record = createContentRecord({
211
99
  filePath,
212
100
  siteVariables: this.siteVariables,
213
101
  scan,
@@ -215,53 +103,27 @@ export class ContentRenderer {
215
103
  outputDir: distDir,
216
104
  traceCache: this.traceCache,
217
105
  traceToolAvailability: this.traceToolAvailability,
218
- skipLiterateJavaExecution: !this.javacAvailable,
106
+ skipLiterateJavaExecution: !this.traceToolAvailability?.java,
219
107
  });
108
+ writeRecordOutputs(distDir, record.outputs);
109
+ for (const [outputPath, content] of record.outputs) {
110
+ if (!outputPath.endsWith('.html') || typeof content !== 'string') {
111
+ continue;
112
+ }
113
+ htmlAssetsByPath.set(outputPath, content);
114
+ const analysis = record.htmlAnalysisByOutputPath?.get(outputPath);
115
+ if (analysis) {
116
+ htmlAnalysisByPath.set(
117
+ outputPath,
118
+ cloneHtmlOutputAnalysis(analysis),
119
+ );
120
+ }
121
+ }
220
122
  } catch (err) {
221
123
  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
124
  }
255
125
  }
256
126
 
257
- return {
258
- errors,
259
- changedHtmlAssetPaths,
260
- removedHtmlAssetPaths,
261
- removedOutputRelPaths,
262
- htmlAssetsByPath,
263
- htmlAnalysisByPath,
264
- buildContentFiles,
265
- };
127
+ return { errors, htmlAssetsByPath, htmlAnalysisByPath };
266
128
  }
267
129
  }
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() */
@@ -1,34 +1,24 @@
1
1
  import { compileTemplates, config } from '../templates';
2
2
  import { getDevSiteVariables } from '../site-variables';
3
- import { createContentRecord, createPublicRecord } from '../source-records';
4
3
  import { getDistDir } from '../util';
5
- import type { CompilerBuildResult } from '../../watch/types';
6
- import {
7
- collectHtmlAnalysisByPath,
8
- collectHtmlAssetsByPath,
9
- createSnapshot,
10
- type TadaSourceRecord,
11
- type TadaSnapshot,
12
- } from './snapshot';
4
+ import type { CompilerBuildResult } from './types';
5
+ import { createSnapshot, type TadaSourceRecord } from './snapshot';
13
6
  import { scanProject } from '../source-model';
14
- import type {
15
- TadaBuildMeta,
16
- TraceCache,
17
- WatchTraceOptions,
18
- } from './compiler-types';
7
+ import type { TraceCache, WatchTraceOptions } from './compiler-types';
19
8
  import {
20
9
  bundleWatchAssets,
21
10
  ensureHighlighter,
22
11
  makeTempBuildDir,
23
12
  populateStaticAssets,
24
13
  removeDirIfExists,
25
- writeAssets,
26
14
  } from './assets';
15
+ import { validateConfig, validateProjectConfigLinks } from './validation';
16
+ import { buildFailedFromError, buildSucceeded } from './build-result';
27
17
  import {
28
- diagnosticsFromMessages,
29
- validateConfig,
30
- validateProjectConfigLinks,
31
- } from './validation';
18
+ buildFailedWithDiagnostics,
19
+ renderContentRecord,
20
+ renderPublicRecord,
21
+ } from './build-helpers';
32
22
 
33
23
  export async function buildFull({
34
24
  traceCache,
@@ -36,7 +26,7 @@ export async function buildFull({
36
26
  }: {
37
27
  traceCache: TraceCache;
38
28
  traceOptions: WatchTraceOptions;
39
- }): Promise<CompilerBuildResult<TadaSnapshot, TadaBuildMeta>> {
29
+ }): Promise<CompilerBuildResult> {
40
30
  const distDir = getDistDir();
41
31
  const outputDir = makeTempBuildDir(distDir);
42
32
  try {
@@ -44,8 +34,7 @@ export async function buildFull({
44
34
  const scan = scanProject(siteVariables);
45
35
  const configDiagnostics = validateConfig(scan);
46
36
  if (configDiagnostics.length > 0) {
47
- removeDirIfExists(outputDir);
48
- return { ok: false, diagnostics: configDiagnostics };
37
+ return buildFailedWithDiagnostics(outputDir, configDiagnostics);
49
38
  }
50
39
 
51
40
  compileTemplates(siteVariables);
@@ -56,33 +45,34 @@ export async function buildFull({
56
45
 
57
46
  const contentRecords = new Map<string, TadaSourceRecord>();
58
47
  for (const filePath of scan.contentFiles) {
59
- const record = createContentRecord({
48
+ const record = renderContentRecord({
60
49
  filePath,
61
50
  siteVariables,
62
51
  scan,
63
52
  assetFiles,
64
53
  outputDir,
65
54
  traceCache,
66
- traceToolAvailability: traceOptions.toolAvailability,
55
+ traceOptions,
67
56
  cachedTraceSourceDir: distDir,
68
57
  });
69
- if (record.outputs.size > 0) {
70
- writeAssets(outputDir, record.outputs);
58
+ if (record) {
71
59
  contentRecords.set(filePath, record);
72
60
  }
73
61
  }
74
62
 
75
63
  const publicRecords = new Map<string, TadaSourceRecord>();
76
64
  for (const filePath of scan.publicFiles) {
77
- const record = createPublicRecord(filePath, scan.publicDir);
78
- writeAssets(outputDir, record.outputs);
65
+ const record = renderPublicRecord({
66
+ filePath,
67
+ publicDir: scan.publicDir,
68
+ outputDir,
69
+ });
79
70
  publicRecords.set(filePath, record);
80
71
  }
81
72
 
82
73
  const linkDiagnostics = validateProjectConfigLinks(scan.validTargets);
83
74
  if (linkDiagnostics.length > 0) {
84
- removeDirIfExists(outputDir);
85
- return { ok: false, diagnostics: linkDiagnostics };
75
+ return buildFailedWithDiagnostics(outputDir, linkDiagnostics);
86
76
  }
87
77
 
88
78
  const nextSnapshot = createSnapshot({
@@ -95,29 +85,13 @@ export async function buildFull({
95
85
  publicRecords,
96
86
  });
97
87
 
98
- return {
99
- ok: true,
100
- snapshot: nextSnapshot,
101
- commit: {
102
- kind: 'replace-root',
103
- stagedPath: outputDir,
104
- targetPath: distDir,
105
- },
106
- meta: {
107
- htmlAssetsByPath: collectHtmlAssetsByPath(nextSnapshot.contentRecords),
108
- htmlAnalysisByPath: collectHtmlAnalysisByPath(
109
- nextSnapshot.contentRecords,
110
- ),
111
- siteVariables,
112
- },
113
- };
88
+ return buildSucceeded(nextSnapshot, {
89
+ kind: 'replace-root',
90
+ stagedPath: outputDir,
91
+ targetPath: distDir,
92
+ });
114
93
  } catch (error) {
115
94
  removeDirIfExists(outputDir);
116
- return {
117
- ok: false,
118
- diagnostics: diagnosticsFromMessages([
119
- error instanceof Error ? error.message : String(error),
120
- ]),
121
- };
95
+ return buildFailedFromError(error);
122
96
  }
123
97
  }
@@ -0,0 +1,66 @@
1
+ import { createContentRecord, createPublicRecord } from '../source-records';
2
+ import type { SiteVariables } from '../types';
3
+ import type { TadaProjectScan, TadaSourceRecord } from './snapshot';
4
+ import type { CompilerBuildResult, WatchDiagnostic } from './types';
5
+ import type { TraceCache, WatchTraceOptions } from './compiler-types';
6
+ import { removeDirIfExists, writeAssets } from './assets';
7
+
8
+ export function buildFailedWithDiagnostics(
9
+ outputDir: string,
10
+ diagnostics: WatchDiagnostic[],
11
+ ): CompilerBuildResult {
12
+ removeDirIfExists(outputDir);
13
+ return { ok: false, diagnostics };
14
+ }
15
+
16
+ export function renderContentRecord({
17
+ filePath,
18
+ siteVariables,
19
+ scan,
20
+ assetFiles,
21
+ outputDir,
22
+ traceCache,
23
+ traceOptions,
24
+ cachedTraceSourceDir,
25
+ }: {
26
+ filePath: string;
27
+ siteVariables: SiteVariables;
28
+ scan: TadaProjectScan;
29
+ assetFiles: string[];
30
+ outputDir: string;
31
+ traceCache: TraceCache;
32
+ traceOptions: WatchTraceOptions;
33
+ cachedTraceSourceDir: string;
34
+ }): TadaSourceRecord | undefined {
35
+ const record = createContentRecord({
36
+ filePath,
37
+ siteVariables,
38
+ scan,
39
+ assetFiles,
40
+ outputDir,
41
+ traceCache,
42
+ traceToolAvailability: traceOptions.toolAvailability,
43
+ cachedTraceSourceDir,
44
+ });
45
+ if (record.outputs.size === 0) {
46
+ return undefined;
47
+ }
48
+ writeAssets(outputDir, record.outputs);
49
+ return record;
50
+ }
51
+
52
+ export function renderPublicRecord({
53
+ filePath,
54
+ publicDir,
55
+ outputDir,
56
+ }: {
57
+ filePath: string;
58
+ publicDir: string;
59
+ outputDir?: string;
60
+ }): TadaSourceRecord {
61
+ const record = createPublicRecord(filePath, publicDir);
62
+ if (outputDir) {
63
+ writeAssets(outputDir, record.outputs);
64
+ }
65
+ return record;
66
+ }