@abreen/tada 1.17.0 → 1.18.0

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 (107) hide show
  1. package/README.md +41 -7
  2. package/bin/tada.ts +23 -9
  3. package/bin/validators.ts +34 -1
  4. package/build/branding.ts +41 -0
  5. package/build/build-manifest.ts +10 -5
  6. package/build/{watch/compiler-types.d.ts → build-types.d.ts} +8 -5
  7. package/build/build-validation.ts +176 -0
  8. package/build/bundle.ts +75 -65
  9. package/build/columns-plugin.ts +27 -1
  10. package/build/config-loader.ts +3 -3
  11. package/build/custom-fonts.ts +8 -5
  12. package/build/deflist-id-plugin.ts +2 -2
  13. package/build/generate-favicon.ts +7 -6
  14. package/build/generate-fonts.ts +8 -15
  15. package/build/generate-katex-assets.ts +19 -17
  16. package/build/generate-web-app-manifest.ts +3 -11
  17. package/build/lodash-template.ts +14 -0
  18. package/build/output-publication.ts +238 -0
  19. package/build/pagefind.ts +49 -21
  20. package/build/pdf-text.ts +41 -1
  21. package/build/pipeline.ts +95 -101
  22. package/build/site-assets.ts +46 -0
  23. package/build/site-build.ts +267 -0
  24. package/build/source-model.ts +223 -229
  25. package/build/source-records.ts +69 -81
  26. package/build/template-globals.ts +3 -0
  27. package/build/templates.ts +12 -8
  28. package/build/toc-plugin.ts +21 -21
  29. package/build/types.d.ts +38 -38
  30. package/build/util.ts +0 -4
  31. package/build/utils/code.ts +51 -117
  32. package/build/utils/final-html.ts +171 -47
  33. package/build/utils/jdi-runner/LiterateRunner.java +9 -1
  34. package/build/utils/literate-java.ts +11 -7
  35. package/build/utils/markdown-partials.ts +4 -3
  36. package/build/utils/markdown.ts +6 -17
  37. package/build/utils/paths.ts +3 -0
  38. package/build/utils/plain-text.ts +18 -0
  39. package/build/utils/render.ts +197 -154
  40. package/build/utils/shiki-highlighter.ts +11 -0
  41. package/build/utils/source-template.ts +2 -2
  42. package/build/utils/trace-core.ts +8 -83
  43. package/build/utils/trace.ts +38 -95
  44. package/build/validate-config-links.ts +39 -6
  45. package/build/watch/build-incremental.ts +46 -103
  46. package/build/watch/compiler.ts +158 -37
  47. package/build/watch/engine.ts +207 -162
  48. package/build/watch/index.ts +9 -2
  49. package/build/watch/planner.ts +88 -144
  50. package/build/watch/runtime.ts +21 -34
  51. package/build/watch/types.d.ts +32 -64
  52. package/package.json +4 -5
  53. package/schema/site.schema.json +18 -2
  54. package/src/_content.scss +447 -0
  55. package/src/_fonts.scss +90 -0
  56. package/src/_layout.scss +50 -35
  57. package/src/appearance-picker/{style.scss → _index.scss} +1 -1
  58. package/src/code/{style.scss → _index.scss} +61 -0
  59. package/src/header/{_base.scss → _index.scss} +98 -6
  60. package/src/index.ts +1 -17
  61. package/src/navigate/{style.scss → _index.scss} +20 -0
  62. package/src/navigate/eligible.ts +0 -40
  63. package/src/navigate/index.ts +50 -5
  64. package/src/navigate/lifecycle.ts +19 -2
  65. package/src/navigate/runtime.ts +20 -4
  66. package/src/page-update/index.ts +33 -20
  67. package/src/print/{style.scss → _index.scss} +3 -1
  68. package/src/search/index.ts +30 -2
  69. package/src/slides/{style.scss → _index.scss} +5 -0
  70. package/src/style.scss +24 -536
  71. package/src/timezone/index.ts +29 -3
  72. package/src/timezone/time-format.ts +4 -0
  73. package/src/toc/index.ts +6 -5
  74. package/src/trace/index.ts +11 -1
  75. package/templates/_author.html +2 -2
  76. package/templates/_download.html +1 -1
  77. package/templates/_footer.html +1 -1
  78. package/templates/_heading.html +3 -3
  79. package/templates/_nav.html +2 -2
  80. package/templates/_page-bottom.html +6 -0
  81. package/templates/_top.html +18 -14
  82. package/templates/code.html +22 -21
  83. package/templates/default.html +12 -11
  84. package/templates/literate.html +6 -5
  85. package/build/copy.ts +0 -82
  86. package/build/generate-content-assets.ts +0 -121
  87. package/build/utils/content-files.ts +0 -170
  88. package/build/watch/assets.ts +0 -103
  89. package/build/watch/build-full.ts +0 -97
  90. package/build/watch/build-helpers.ts +0 -66
  91. package/build/watch/build-result.ts +0 -19
  92. package/build/watch/fs-commit.ts +0 -153
  93. package/build/watch/mutations.ts +0 -73
  94. package/build/watch/snapshot.ts +0 -201
  95. package/build/watch/validation.ts +0 -50
  96. package/src/code.scss +0 -60
  97. package/src/critical.scss +0 -5
  98. package/src/header/style.scss +0 -91
  99. /package/src/{literate.scss → _literate.scss} +0 -0
  100. /package/src/{material-symbols.scss → _material-symbols.scss} +0 -0
  101. /package/src/anchor/{style.scss → _index.scss} +0 -0
  102. /package/src/page-update/{style.scss → _index.scss} +0 -0
  103. /package/src/question/{style.scss → _index.scss} +0 -0
  104. /package/src/search/{style.scss → _index.scss} +0 -0
  105. /package/src/timezone/{style.scss → _index.scss} +0 -0
  106. /package/src/toc/{style.scss → _index.scss} +0 -0
  107. /package/src/trace/{style.scss → _index.scss} +0 -0
@@ -1,103 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { bundle, getBundleNaming } from '../bundle';
4
- import { copyFonts, DEFAULT_FONT_PRELOAD_FILES } from '../generate-fonts';
5
- import { copyKatexAssets } from '../generate-katex-assets';
6
- import { generateFavicons } from '../generate-favicon';
7
- import { generateWebAppManifest } from '../generate-web-app-manifest';
8
- import { getRuntimeBundledShikiLanguages } from '../site-variables';
9
- import { getPackageDir, toPosix } from '../util';
10
- import { initHighlighter } from '../utils/shiki-highlighter';
11
- import type { SiteVariables } from '../types';
12
-
13
- const RELOAD_CLIENT_PATH = path.resolve(
14
- getPackageDir(),
15
- 'build/watch-reload-client.ts',
16
- );
17
-
18
- export function makeTempBuildDir(distDir: string): string {
19
- const parentDir = path.dirname(distDir);
20
- const prefix = `${path.basename(distDir)}-watch-`;
21
- return fs.mkdtempSync(path.join(parentDir, prefix));
22
- }
23
-
24
- export function removeDirIfExists(dir: string): void {
25
- fs.rmSync(dir, { recursive: true, force: true });
26
- }
27
-
28
- export async function ensureHighlighter(
29
- siteVariables: SiteVariables,
30
- ): Promise<void> {
31
- await initHighlighter(getRuntimeBundledShikiLanguages(siteVariables));
32
- }
33
-
34
- export async function bundleWatchAssets(
35
- outputDir: string,
36
- siteVariables: SiteVariables,
37
- ): Promise<string[]> {
38
- const appAssets = await bundle(siteVariables, {
39
- mode: 'development',
40
- distDir: outputDir,
41
- });
42
- const reloadAssets = await Bun.build({
43
- entrypoints: [RELOAD_CLIENT_PATH],
44
- outdir: outputDir,
45
- naming: getBundleNaming(),
46
- sourcemap: 'inline',
47
- });
48
- return [
49
- ...appAssets,
50
- ...reloadAssets.outputs.map(output =>
51
- toPosix(path.relative(outputDir, output.path)),
52
- ),
53
- ];
54
- }
55
-
56
- export async function populateStaticAssets(
57
- outputDir: string,
58
- siteVariables: SiteVariables,
59
- ): Promise<void> {
60
- copyFonts(outputDir);
61
- copyKatexAssets(outputDir);
62
- if (siteVariables.features.favicon !== false) {
63
- await generateFavicons(siteVariables, outputDir);
64
- generateWebAppManifest(siteVariables, outputDir);
65
- }
66
- }
67
-
68
- export function copyExistingBuildAssets(
69
- distDir: string,
70
- outputDir: string,
71
- assetFiles: string[],
72
- ): void {
73
- const relPaths = [
74
- ...assetFiles,
75
- ...DEFAULT_FONT_PRELOAD_FILES.sans,
76
- ...DEFAULT_FONT_PRELOAD_FILES.serif,
77
- ];
78
-
79
- for (const relPath of relPaths) {
80
- const sourcePath = path.join(distDir, relPath);
81
- if (!fs.existsSync(sourcePath)) {
82
- continue;
83
- }
84
- const destinationPath = path.join(outputDir, relPath);
85
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
86
- try {
87
- fs.linkSync(sourcePath, destinationPath);
88
- } catch {
89
- fs.copyFileSync(sourcePath, destinationPath);
90
- }
91
- }
92
- }
93
-
94
- export function writeAssets(
95
- outputDir: string,
96
- assets: Map<string, string | Buffer>,
97
- ): void {
98
- for (const [assetPath, content] of assets) {
99
- const outputPath = path.join(outputDir, assetPath);
100
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
101
- fs.writeFileSync(outputPath, content);
102
- }
103
- }
@@ -1,97 +0,0 @@
1
- import { compileTemplates, config } from '../templates';
2
- import { getDevSiteVariables } from '../site-variables';
3
- import { getDistDir } from '../util';
4
- import type { CompilerBuildResult } from './types';
5
- import { createSnapshot, type TadaSourceRecord } from './snapshot';
6
- import { scanProject } from '../source-model';
7
- import type { TraceCache, WatchTraceOptions } from './compiler-types';
8
- import {
9
- bundleWatchAssets,
10
- ensureHighlighter,
11
- makeTempBuildDir,
12
- populateStaticAssets,
13
- removeDirIfExists,
14
- } from './assets';
15
- import { validateConfig, validateProjectConfigLinks } from './validation';
16
- import { buildFailedFromError, buildSucceeded } from './build-result';
17
- import {
18
- buildFailedWithDiagnostics,
19
- renderContentRecord,
20
- renderPublicRecord,
21
- } from './build-helpers';
22
-
23
- export async function buildFull({
24
- traceCache,
25
- traceOptions,
26
- }: {
27
- traceCache: TraceCache;
28
- traceOptions: WatchTraceOptions;
29
- }): Promise<CompilerBuildResult> {
30
- const distDir = getDistDir();
31
- const outputDir = makeTempBuildDir(distDir);
32
- try {
33
- const siteVariables = getDevSiteVariables();
34
- const scan = scanProject(siteVariables);
35
- const configDiagnostics = validateConfig(scan, siteVariables);
36
- if (configDiagnostics.length > 0) {
37
- return buildFailedWithDiagnostics(outputDir, configDiagnostics);
38
- }
39
-
40
- compileTemplates(siteVariables);
41
- await ensureHighlighter(siteVariables);
42
-
43
- const assetFiles = await bundleWatchAssets(outputDir, siteVariables);
44
- await populateStaticAssets(outputDir, siteVariables);
45
-
46
- const contentRecords = new Map<string, TadaSourceRecord>();
47
- for (const filePath of scan.contentFiles) {
48
- const record = renderContentRecord({
49
- filePath,
50
- siteVariables,
51
- scan,
52
- assetFiles,
53
- outputDir,
54
- traceCache,
55
- traceOptions,
56
- cachedTraceSourceDir: distDir,
57
- });
58
- if (record) {
59
- contentRecords.set(filePath, record);
60
- }
61
- }
62
-
63
- const publicRecords = new Map<string, TadaSourceRecord>();
64
- for (const filePath of scan.publicFiles) {
65
- const record = renderPublicRecord({
66
- filePath,
67
- publicDir: scan.publicDir,
68
- outputDir,
69
- });
70
- publicRecords.set(filePath, record);
71
- }
72
-
73
- const linkDiagnostics = validateProjectConfigLinks(scan.validTargets);
74
- if (linkDiagnostics.length > 0) {
75
- return buildFailedWithDiagnostics(outputDir, linkDiagnostics);
76
- }
77
-
78
- const nextSnapshot = createSnapshot({
79
- siteVariables,
80
- assetFiles,
81
- navData: config('nav'),
82
- authorsData: config('authors'),
83
- scan,
84
- contentRecords,
85
- publicRecords,
86
- });
87
-
88
- return buildSucceeded(nextSnapshot, {
89
- kind: 'replace-root',
90
- stagedPath: outputDir,
91
- targetPath: distDir,
92
- });
93
- } catch (error) {
94
- removeDirIfExists(outputDir);
95
- return buildFailedFromError(error);
96
- }
97
- }
@@ -1,66 +0,0 @@
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
- }
@@ -1,19 +0,0 @@
1
- import type { CommitPlan, CompilerBuildResult } from './types';
2
- import { createBuildMeta, type TadaSnapshot } from './snapshot';
3
- import { diagnosticsFromMessages } from './validation';
4
-
5
- export function buildSucceeded(
6
- snapshot: TadaSnapshot,
7
- commit: CommitPlan,
8
- ): CompilerBuildResult {
9
- return { ok: true, snapshot, commit, meta: createBuildMeta(snapshot) };
10
- }
11
-
12
- export function buildFailedFromError(error: unknown): CompilerBuildResult {
13
- return {
14
- ok: false,
15
- diagnostics: diagnosticsFromMessages([
16
- error instanceof Error ? error.message : String(error),
17
- ]),
18
- };
19
- }
@@ -1,153 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { globals, type Globals } from '../globals';
4
- import type {
5
- ApplyMutationsCommitPlan,
6
- CommitPlan,
7
- FileMutation,
8
- ReplaceRootCommitPlan,
9
- } from './types';
10
-
11
- type CommitPlanGlobals = Pick<Globals, 'now' | 'pid' | 'sleepSync'>;
12
-
13
- const TRANSIENT_RENAME_ERROR_CODES = new Set([
14
- 'EACCES',
15
- 'EBUSY',
16
- 'ENOTEMPTY',
17
- 'EPERM',
18
- ]);
19
- const RENAME_RETRY_DELAY_MS = 25;
20
- const RENAME_RETRY_COUNT = 8;
21
-
22
- function removeDirIfExists(dir: string): void {
23
- fs.rmSync(dir, { recursive: true, force: true });
24
- }
25
-
26
- function ensureParentDir(filePath: string): void {
27
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
28
- }
29
-
30
- function isTransientRenameError(
31
- error: unknown,
32
- sourcePath: string,
33
- ): error is NodeJS.ErrnoException {
34
- const code = (error as NodeJS.ErrnoException | undefined)?.code;
35
- return (
36
- typeof code === 'string' &&
37
- TRANSIENT_RENAME_ERROR_CODES.has(code) &&
38
- fs.existsSync(sourcePath)
39
- );
40
- }
41
-
42
- function renameWithRetry(
43
- sourcePath: string,
44
- targetPath: string,
45
- globals: CommitPlanGlobals,
46
- ): void {
47
- for (let attempt = 0; attempt <= RENAME_RETRY_COUNT; attempt++) {
48
- try {
49
- fs.renameSync(sourcePath, targetPath);
50
- return;
51
- } catch (error) {
52
- if (
53
- attempt === RENAME_RETRY_COUNT ||
54
- !isTransientRenameError(error, sourcePath)
55
- ) {
56
- throw error;
57
- }
58
- globals.sleepSync(RENAME_RETRY_DELAY_MS);
59
- }
60
- }
61
- }
62
-
63
- function pruneEmptyDirs(rootDir: string, filePath: string): void {
64
- let currentDir = path.dirname(filePath);
65
- const root = path.resolve(rootDir);
66
- while (currentDir !== root) {
67
- try {
68
- fs.rmdirSync(currentDir);
69
- } catch (err) {
70
- const code = (err as NodeJS.ErrnoException).code;
71
- if (code === 'ENOTEMPTY' || code === 'ENOENT') {
72
- break;
73
- }
74
- throw err;
75
- }
76
- currentDir = path.dirname(currentDir);
77
- }
78
- }
79
-
80
- function applyFileMutation(
81
- rootDir: string,
82
- mutation: FileMutation,
83
- stagedRoot: string,
84
- globals: CommitPlanGlobals,
85
- ): void {
86
- const targetPath = path.join(rootDir, mutation.path);
87
- const stagedPath = path.join(stagedRoot, mutation.path);
88
-
89
- if (mutation.kind === 'write') {
90
- ensureParentDir(targetPath);
91
- renameWithRetry(stagedPath, targetPath, globals);
92
- return;
93
- }
94
-
95
- fs.rmSync(targetPath, { force: true });
96
- pruneEmptyDirs(rootDir, targetPath);
97
- }
98
-
99
- function applyMutations(
100
- plan: ApplyMutationsCommitPlan,
101
- globals: CommitPlanGlobals,
102
- ): void {
103
- const stagedRoot = fs.mkdtempSync(path.join(plan.rootDir, '.watch-stage-'));
104
- try {
105
- for (const mutation of plan.mutations) {
106
- if (mutation.kind === 'write') {
107
- const stagedPath = path.join(stagedRoot, mutation.path);
108
- ensureParentDir(stagedPath);
109
- fs.writeFileSync(stagedPath, mutation.content);
110
- }
111
- }
112
-
113
- for (const mutation of plan.mutations) {
114
- applyFileMutation(plan.rootDir, mutation, stagedRoot, globals);
115
- }
116
- } finally {
117
- removeDirIfExists(stagedRoot);
118
- }
119
- }
120
-
121
- function replaceRoot(
122
- plan: ReplaceRootCommitPlan,
123
- globals: CommitPlanGlobals,
124
- ): void {
125
- const { targetPath, stagedPath } = plan;
126
- const backupDir = `${targetPath}.bak-${globals.pid()}-${globals.now()}`;
127
- const targetExists = fs.existsSync(targetPath);
128
-
129
- try {
130
- if (targetExists) {
131
- renameWithRetry(targetPath, backupDir, globals);
132
- }
133
- renameWithRetry(stagedPath, targetPath, globals);
134
- if (targetExists) {
135
- removeDirIfExists(backupDir);
136
- }
137
- } catch (err) {
138
- if (!fs.existsSync(targetPath) && fs.existsSync(backupDir)) {
139
- renameWithRetry(backupDir, targetPath, globals);
140
- }
141
- throw err;
142
- }
143
- }
144
-
145
- export function applyCommitPlan(plan: CommitPlan): void {
146
- const runtimeGlobals: CommitPlanGlobals = globals;
147
- if (plan.kind === 'replace-root') {
148
- replaceRoot(plan, runtimeGlobals);
149
- return;
150
- }
151
-
152
- applyMutations(plan, runtimeGlobals);
153
- }
@@ -1,73 +0,0 @@
1
- import type { FileMutation } from './types';
2
- import type { TadaOutputOwner, TadaSnapshot } from './snapshot';
3
-
4
- function outputsEqual(
5
- left: string | Buffer | undefined,
6
- right: string | Buffer | undefined,
7
- ): boolean {
8
- if (left === undefined || right === undefined) {
9
- return left === right;
10
- }
11
- if (typeof left === 'string' && typeof right === 'string') {
12
- return left === right;
13
- }
14
- const leftBuffer = typeof left === 'string' ? Buffer.from(left) : left;
15
- const rightBuffer = typeof right === 'string' ? Buffer.from(right) : right;
16
- return Buffer.compare(leftBuffer, rightBuffer) === 0;
17
- }
18
-
19
- function getOutputContent(
20
- snapshot: TadaSnapshot,
21
- owner: TadaOutputOwner | undefined,
22
- outputPath: string,
23
- ): string | Buffer | undefined {
24
- if (!owner) {
25
- return undefined;
26
- }
27
- const records =
28
- owner.kind === 'content' ? snapshot.contentRecords : snapshot.publicRecords;
29
- return records.get(owner.sourcePath)?.outputs.get(outputPath);
30
- }
31
-
32
- export function computeMutations(
33
- previous: TadaSnapshot,
34
- next: TadaSnapshot,
35
- forceSourcePaths: Set<string> = new Set(),
36
- ): FileMutation[] {
37
- const mutations: FileMutation[] = [];
38
- const allPaths = new Set([
39
- ...previous.outputOwners.keys(),
40
- ...next.outputOwners.keys(),
41
- ]);
42
-
43
- for (const outputPath of [...allPaths].sort()) {
44
- const previousOwner = previous.outputOwners.get(outputPath);
45
- const nextOwner = next.outputOwners.get(outputPath);
46
-
47
- const previousContent = getOutputContent(
48
- previous,
49
- previousOwner,
50
- outputPath,
51
- );
52
- const nextContent = getOutputContent(next, nextOwner, outputPath);
53
-
54
- if (!nextOwner) {
55
- mutations.push({ path: outputPath, kind: 'delete' });
56
- continue;
57
- }
58
-
59
- if (
60
- !previousOwner ||
61
- !outputsEqual(previousContent, nextContent) ||
62
- forceSourcePaths.has(nextOwner.sourcePath)
63
- ) {
64
- mutations.push({
65
- path: outputPath,
66
- kind: 'write',
67
- content: nextContent!,
68
- });
69
- }
70
- }
71
-
72
- return mutations;
73
- }
@@ -1,201 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import {
4
- type TadaProjectScan,
5
- assertNoOutputPathConflicts,
6
- } from '../source-model';
7
- import type { Asset, HtmlOutputAnalysis, SiteVariables } from '../types';
8
- import type { TadaBuildMeta } from './compiler-types';
9
-
10
- export interface TadaSourceRecord {
11
- sourcePath: string;
12
- kind: 'content' | 'public';
13
- outputs: Map<string, string | Buffer>;
14
- htmlAnalysisByOutputPath?: Map<string, HtmlOutputAnalysis>;
15
- partialDeps: Set<string>;
16
- traceDeps: Set<string>;
17
- internalTargets: Set<string>;
18
- generatedOutputPaths: Set<string>;
19
- authorKey?: string;
20
- }
21
-
22
- export interface TadaOutputOwner {
23
- kind: 'content' | 'public';
24
- sourcePath: string;
25
- }
26
-
27
- export interface TadaSnapshot {
28
- siteVariables: SiteVariables;
29
- assetFiles: string[];
30
- navData: unknown;
31
- authorsData: unknown;
32
- contentRecords: Map<string, TadaSourceRecord>;
33
- publicRecords: Map<string, TadaSourceRecord>;
34
- outputOwners: Map<string, TadaOutputOwner>;
35
- reversePartialDeps: Map<string, Set<string>>;
36
- reverseTraceDeps: Map<string, Set<string>>;
37
- reverseInternalTargetDeps: Map<string, Set<string>>;
38
- reverseAuthorDeps: Map<string, Set<string>>;
39
- scan: TadaProjectScan;
40
- }
41
-
42
- function buildReverseMap(
43
- records: Iterable<TadaSourceRecord>,
44
- selector: (record: TadaSourceRecord) => Iterable<string>,
45
- ): Map<string, Set<string>> {
46
- const reverse = new Map<string, Set<string>>();
47
-
48
- for (const record of records) {
49
- for (const key of selector(record)) {
50
- if (!reverse.has(key)) {
51
- reverse.set(key, new Set());
52
- }
53
- reverse.get(key)!.add(record.sourcePath);
54
- }
55
- }
56
-
57
- return reverse;
58
- }
59
-
60
- function collectOutputOwners(snapshot: {
61
- contentRecords: Map<string, TadaSourceRecord>;
62
- publicRecords: Map<string, TadaSourceRecord>;
63
- }): Map<string, TadaOutputOwner> {
64
- const owners = new Map<string, TadaOutputOwner>();
65
-
66
- for (const record of snapshot.contentRecords.values()) {
67
- for (const outputPath of record.outputs.keys()) {
68
- owners.set(outputPath, {
69
- kind: 'content',
70
- sourcePath: record.sourcePath,
71
- });
72
- }
73
- }
74
-
75
- for (const record of snapshot.publicRecords.values()) {
76
- for (const outputPath of record.outputs.keys()) {
77
- owners.set(outputPath, { kind: 'public', sourcePath: record.sourcePath });
78
- }
79
- }
80
-
81
- return owners;
82
- }
83
-
84
- export function createSnapshot({
85
- siteVariables,
86
- assetFiles,
87
- navData,
88
- authorsData,
89
- scan,
90
- contentRecords,
91
- publicRecords,
92
- }: {
93
- siteVariables: SiteVariables;
94
- assetFiles: string[];
95
- navData: unknown;
96
- authorsData: unknown;
97
- scan: TadaProjectScan;
98
- contentRecords: Map<string, TadaSourceRecord>;
99
- publicRecords: Map<string, TadaSourceRecord>;
100
- }): TadaSnapshot {
101
- const allRecords = [...contentRecords.values()];
102
-
103
- return {
104
- siteVariables,
105
- assetFiles,
106
- navData,
107
- authorsData,
108
- contentRecords,
109
- publicRecords,
110
- outputOwners: collectOutputOwners({ contentRecords, publicRecords }),
111
- reversePartialDeps: buildReverseMap(
112
- allRecords,
113
- record => record.partialDeps,
114
- ),
115
- reverseTraceDeps: buildReverseMap(allRecords, record => record.traceDeps),
116
- reverseInternalTargetDeps: buildReverseMap(
117
- allRecords,
118
- record => record.internalTargets,
119
- ),
120
- reverseAuthorDeps: buildReverseMap(allRecords, record =>
121
- record.authorKey ? [record.authorKey] : [],
122
- ),
123
- scan,
124
- };
125
- }
126
-
127
- export function collectSourceOutputs(
128
- assets: Asset[],
129
- generatedOutputPaths: Set<string>,
130
- stageDir: string,
131
- ): Map<string, string | Buffer> {
132
- const outputs = new Map<string, string | Buffer>();
133
- for (const asset of assets) {
134
- outputs.set(asset.assetPath, asset.content);
135
- }
136
- for (const outputPath of generatedOutputPaths) {
137
- outputs.set(outputPath, fs.readFileSync(path.join(stageDir, outputPath)));
138
- }
139
- return outputs;
140
- }
141
-
142
- function cloneHtmlOutputAnalysis(
143
- analysis: HtmlOutputAnalysis,
144
- ): HtmlOutputAnalysis {
145
- return { outgoingTargets: new Set(analysis.outgoingTargets) };
146
- }
147
-
148
- export function collectSourceHtmlAnalysis(
149
- assets: Asset[],
150
- ): Map<string, HtmlOutputAnalysis> {
151
- const htmlAnalysisByOutputPath = new Map<string, HtmlOutputAnalysis>();
152
- for (const asset of assets) {
153
- if (!asset.assetPath.endsWith('.html') || !asset.htmlAnalysis) {
154
- continue;
155
- }
156
- htmlAnalysisByOutputPath.set(
157
- asset.assetPath,
158
- cloneHtmlOutputAnalysis(asset.htmlAnalysis),
159
- );
160
- }
161
- return htmlAnalysisByOutputPath;
162
- }
163
-
164
- function collectHtmlAssetsByPath(
165
- contentRecords: Map<string, TadaSourceRecord>,
166
- ): Map<string, string> {
167
- const htmlAssetsByPath = new Map<string, string>();
168
- for (const record of contentRecords.values()) {
169
- for (const [outputPath, content] of record.outputs) {
170
- if (outputPath.endsWith('.html') && typeof content === 'string') {
171
- htmlAssetsByPath.set(outputPath, content);
172
- }
173
- }
174
- }
175
- return htmlAssetsByPath;
176
- }
177
-
178
- function collectHtmlAnalysisByPath(
179
- contentRecords: Map<string, TadaSourceRecord>,
180
- ): Map<string, HtmlOutputAnalysis> {
181
- const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
182
- for (const record of contentRecords.values()) {
183
- if (!record.htmlAnalysisByOutputPath) {
184
- continue;
185
- }
186
- for (const [outputPath, analysis] of record.htmlAnalysisByOutputPath) {
187
- htmlAnalysisByPath.set(outputPath, cloneHtmlOutputAnalysis(analysis));
188
- }
189
- }
190
- return htmlAnalysisByPath;
191
- }
192
-
193
- export function createBuildMeta(snapshot: TadaSnapshot): TadaBuildMeta {
194
- return {
195
- htmlAssetsByPath: collectHtmlAssetsByPath(snapshot.contentRecords),
196
- htmlAnalysisByPath: collectHtmlAnalysisByPath(snapshot.contentRecords),
197
- siteVariables: snapshot.siteVariables,
198
- };
199
- }
200
-
201
- export { assertNoOutputPathConflicts, type TadaProjectScan };