@abreen/tada 1.13.4 → 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.
- package/build/bundle.ts +17 -2
- package/build/generate-content-assets.ts +38 -175
- package/build/pipeline.ts +1 -1
- package/build/source-model.ts +2 -14
- package/build/types.d.ts +1 -12
- package/build/utils/derive-theme.ts +10 -0
- package/build/utils/render.ts +1 -0
- package/build/utils/trace-core.ts +75 -22
- package/build/utils/trace.ts +27 -10
- package/build/watch/assets.ts +2 -2
- package/build/watch/build-full.ts +26 -52
- package/build/watch/build-helpers.ts +66 -0
- package/build/watch/build-incremental.ts +32 -60
- package/build/watch/build-result.ts +19 -0
- package/build/watch/compiler-types.d.ts +1 -0
- package/build/watch/compiler.ts +25 -61
- package/build/watch/config-paths.ts +56 -0
- package/build/watch/engine.ts +199 -0
- package/{watch → build/watch}/fs-commit.ts +1 -3
- package/build/watch/index.ts +1 -1
- package/build/watch/mutations.ts +23 -25
- package/build/watch/planner.ts +14 -78
- package/build/watch/runtime.ts +26 -38
- package/build/watch/snapshot.ts +26 -47
- package/build/watch/types.d.ts +84 -0
- package/build/watch/validation.ts +1 -1
- package/package.json +1 -2
- package/src/_base.scss +6 -5
- package/src/slides/index.ts +34 -0
- package/src/style.scss +1 -1
- package/src/trace/index.ts +14 -7
- package/src/trace/style.scss +11 -10
- package/templates/_theme.scss +2 -0
- package/templates/default.html +1 -1
- package/build/watch-reachability-state.ts +0 -305
- package/watch/engine.ts +0 -296
- package/watch/events.ts +0 -10
- package/watch/types.d.ts +0 -103
package/build/bundle.ts
CHANGED
|
@@ -9,10 +9,20 @@ import {
|
|
|
9
9
|
getDistDir,
|
|
10
10
|
toPosix,
|
|
11
11
|
} from './utils/paths';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
deriveTheme,
|
|
14
|
+
deriveLinkHue,
|
|
15
|
+
deriveTraceLineActiveHue,
|
|
16
|
+
} from './utils/derive-theme';
|
|
13
17
|
import type { PluginBuilder } from 'bun';
|
|
14
18
|
import type { SiteVariables } from './types';
|
|
15
19
|
import timezones from '../src/timezone/timezones.json' with { type: 'json' };
|
|
20
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
21
|
+
|
|
22
|
+
export function getBundleNaming(): string {
|
|
23
|
+
const version = pkg.version.replace(/[^a-zA-Z0-9.-]/g, '-');
|
|
24
|
+
return `[name].bundle.tada-${version}.[ext]`;
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
function formatCssNumber(value: number, precision = 4): string {
|
|
18
28
|
if (Number.isInteger(value)) {
|
|
@@ -40,6 +50,9 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
40
50
|
const linkColorHover = `hsl(${linkHue}deg 34% 60%)`;
|
|
41
51
|
const linkColorDark = `hsl(${linkHue}deg 50% 72%)`;
|
|
42
52
|
const linkColorHoverDark = `hsl(${linkHue}deg 40% 80%)`;
|
|
53
|
+
const traceLineActiveHue = formatCssNumber(deriveTraceLineActiveHue(tintHue));
|
|
54
|
+
const bgTraceLineActive = `hsl(${traceLineActiveHue}deg 100% 86%)`;
|
|
55
|
+
const bgTraceLineActiveDark = `hsl(${traceLineActiveHue}deg 90% 18%)`;
|
|
43
56
|
|
|
44
57
|
const rendered = _.template(template)({
|
|
45
58
|
...theme,
|
|
@@ -54,6 +67,8 @@ function renderThemeScss(siteVariables: SiteVariables): string {
|
|
|
54
67
|
linkColorHover,
|
|
55
68
|
linkColorDark,
|
|
56
69
|
linkColorHoverDark,
|
|
70
|
+
bgTraceLineActive,
|
|
71
|
+
bgTraceLineActiveDark,
|
|
57
72
|
});
|
|
58
73
|
|
|
59
74
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-'));
|
|
@@ -109,7 +124,7 @@ export async function bundle(
|
|
|
109
124
|
const result = await Bun.build({
|
|
110
125
|
entrypoints,
|
|
111
126
|
outdir: resolvedDistDir,
|
|
112
|
-
naming:
|
|
127
|
+
naming: getBundleNaming(),
|
|
113
128
|
minify: mode === 'production',
|
|
114
129
|
sourcemap: isDev ? 'inline' : 'none',
|
|
115
130
|
define: createDefine(siteVariables, isDev),
|
|
@@ -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
|
|
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,16 +23,25 @@ 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,
|
|
36
42
|
{
|
|
37
43
|
manifestUrl: string;
|
|
44
|
+
artifactId: string;
|
|
38
45
|
highlightedSource: string;
|
|
39
46
|
totalSteps: number;
|
|
40
47
|
mtime: number;
|
|
@@ -43,133 +50,23 @@ export class ContentRenderer {
|
|
|
43
50
|
|
|
44
51
|
constructor(siteVariables: SiteVariables) {
|
|
45
52
|
this.siteVariables = siteVariables;
|
|
46
|
-
this.sourceFileCache = new Map();
|
|
47
|
-
this.lastBuildFiles = new Set();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private getCachedRecord(filePath: string): TadaSourceRecord | undefined {
|
|
51
|
-
return this.sourceFileCache.get(filePath);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
async initHighlighter(): Promise<void> {
|
|
55
56
|
await initHighlighter(getRuntimeBundledShikiLanguages(this.siteVariables));
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
getDirtySourceFiles(
|
|
59
|
-
buildContentFiles: string[],
|
|
60
|
-
{ changedContentFiles, jsonDataChanged, partialsChanged }: WatchState = {},
|
|
61
|
-
): Set<string> {
|
|
62
|
-
if (
|
|
63
|
-
this.lastBuildFiles.size === 0 ||
|
|
64
|
-
!changedContentFiles ||
|
|
65
|
-
jsonDataChanged ||
|
|
66
|
-
partialsChanged
|
|
67
|
-
) {
|
|
68
|
-
return new Set(buildContentFiles);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// If any trace source changed, the trace cache will be stale, so rebuild
|
|
72
|
-
// all content pages to pick up the new trace output.
|
|
73
|
-
const traceFileChanged = [...changedContentFiles].some(isTraceSourceFile);
|
|
74
|
-
if (traceFileChanged) {
|
|
75
|
-
return new Set(buildContentFiles);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const buildFileSet = new Set(buildContentFiles);
|
|
79
|
-
const dirtySourceFiles = new Set<string>();
|
|
80
|
-
for (const filePath of changedContentFiles) {
|
|
81
|
-
if (buildFileSet.has(filePath)) {
|
|
82
|
-
dirtySourceFiles.add(filePath);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return dirtySourceFiles;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
updateSourceCache(
|
|
89
|
-
filePath: string,
|
|
90
|
-
record: TadaSourceRecord,
|
|
91
|
-
changedOutputRelPaths: Set<string>,
|
|
92
|
-
removedOutputRelPaths: Set<string>,
|
|
93
|
-
changedHtmlAssetPaths: Set<string>,
|
|
94
|
-
removedHtmlAssetPaths: Set<string>,
|
|
95
|
-
): void {
|
|
96
|
-
const previousRecord = this.getCachedRecord(filePath);
|
|
97
|
-
const nextOutputPaths = new Set(record.outputs.keys());
|
|
98
|
-
|
|
99
|
-
for (const outputPath of previousRecord?.outputs.keys() ?? []) {
|
|
100
|
-
if (nextOutputPaths.has(outputPath)) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
removedOutputRelPaths.add(outputPath);
|
|
104
|
-
if (outputPath.endsWith('.html')) {
|
|
105
|
-
removedHtmlAssetPaths.add(outputPath);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
for (const outputPath of record.outputs.keys()) {
|
|
110
|
-
changedOutputRelPaths.add(outputPath);
|
|
111
|
-
if (outputPath.endsWith('.html')) {
|
|
112
|
-
changedHtmlAssetPaths.add(outputPath);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.sourceFileCache.set(filePath, record);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
writeCachedAssets(distDir: string, buildContentFiles: string[]): void {
|
|
120
|
-
for (const filePath of buildContentFiles) {
|
|
121
|
-
const record = this.getCachedRecord(filePath);
|
|
122
|
-
if (!record) {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
for (const [outputPath, content] of record.outputs) {
|
|
126
|
-
const outPath = path.join(distDir, outputPath);
|
|
127
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
128
|
-
fs.writeFileSync(outPath, content);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
pruneRemovedSources(
|
|
134
|
-
buildFileSet: Set<string>,
|
|
135
|
-
removedOutputRelPaths: Set<string>,
|
|
136
|
-
removedHtmlAssetPaths: Set<string>,
|
|
137
|
-
): void {
|
|
138
|
-
for (const filePath of [...this.lastBuildFiles]) {
|
|
139
|
-
if (buildFileSet.has(filePath)) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const previousRecord = this.getCachedRecord(filePath);
|
|
144
|
-
for (const outputPath of previousRecord?.outputs.keys() ?? []) {
|
|
145
|
-
removedOutputRelPaths.add(outputPath);
|
|
146
|
-
if (outputPath.endsWith('.html')) {
|
|
147
|
-
removedHtmlAssetPaths.add(outputPath);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
this.sourceFileCache.delete(filePath);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
59
|
processContent({
|
|
155
60
|
distDir,
|
|
156
61
|
assetFiles,
|
|
157
62
|
scan,
|
|
158
|
-
watchState,
|
|
159
63
|
}: ContentRenderOptions): ContentRenderResult {
|
|
160
64
|
const buildContentFiles = [...scan.buildContentFiles];
|
|
161
|
-
const buildFileSet = new Set<string>(buildContentFiles);
|
|
162
|
-
const dirtySourceFiles = this.getDirtySourceFiles(
|
|
163
|
-
buildContentFiles,
|
|
164
|
-
watchState,
|
|
165
|
-
);
|
|
166
|
-
const changedOutputRelPaths = new Set<string>();
|
|
167
|
-
const removedOutputRelPaths = new Set<string>();
|
|
168
|
-
const changedHtmlAssetPaths = new Set<string>();
|
|
169
|
-
const removedHtmlAssetPaths = new Set<string>();
|
|
170
65
|
const validInternalTargets = scan.validTargets;
|
|
171
66
|
|
|
172
67
|
const errors: Error[] = [];
|
|
68
|
+
const htmlAssetsByPath = new Map<string, string>();
|
|
69
|
+
const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
|
|
173
70
|
|
|
174
71
|
const configLinkErrors = validateConfigLinks(
|
|
175
72
|
validInternalTargets,
|
|
@@ -184,29 +81,21 @@ export class ContentRenderer {
|
|
|
184
81
|
errors.push(new Error(msg));
|
|
185
82
|
}
|
|
186
83
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
removedHtmlAssetPaths,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
if (dirtySourceFiles.size > 0) {
|
|
194
|
-
const noun = dirtySourceFiles.size === 1 ? 'file' : 'files';
|
|
195
|
-
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}`;
|
|
196
87
|
}
|
|
197
88
|
|
|
198
|
-
if (this.
|
|
89
|
+
if (!this.traceToolAvailability && buildContentFiles.length > 0) {
|
|
199
90
|
this.traceToolAvailability = checkTraceToolAvailability();
|
|
200
|
-
|
|
201
|
-
if (!this.javacAvailable) {
|
|
91
|
+
if (!this.traceToolAvailability.java) {
|
|
202
92
|
log.warn`javac was not found; literate Java pages will not include execution output`;
|
|
203
93
|
}
|
|
204
94
|
}
|
|
205
95
|
|
|
206
|
-
for (const filePath of
|
|
207
|
-
let record: TadaSourceRecord;
|
|
96
|
+
for (const filePath of buildContentFiles) {
|
|
208
97
|
try {
|
|
209
|
-
record = createContentRecord({
|
|
98
|
+
const record = createContentRecord({
|
|
210
99
|
filePath,
|
|
211
100
|
siteVariables: this.siteVariables,
|
|
212
101
|
scan,
|
|
@@ -214,53 +103,27 @@ export class ContentRenderer {
|
|
|
214
103
|
outputDir: distDir,
|
|
215
104
|
traceCache: this.traceCache,
|
|
216
105
|
traceToolAvailability: this.traceToolAvailability,
|
|
217
|
-
skipLiterateJavaExecution: !this.
|
|
106
|
+
skipLiterateJavaExecution: !this.traceToolAvailability?.java,
|
|
218
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
|
+
}
|
|
219
122
|
} catch (err) {
|
|
220
123
|
errors.push(err as Error);
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
this.updateSourceCache(
|
|
224
|
-
filePath,
|
|
225
|
-
record,
|
|
226
|
-
changedOutputRelPaths,
|
|
227
|
-
removedOutputRelPaths,
|
|
228
|
-
changedHtmlAssetPaths,
|
|
229
|
-
removedHtmlAssetPaths,
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
this.writeCachedAssets(distDir, buildContentFiles);
|
|
234
|
-
this.lastBuildFiles = buildFileSet;
|
|
235
|
-
|
|
236
|
-
// Collect HTML asset content and analysis for Pagefind
|
|
237
|
-
const htmlAssetsByPath = new Map<string, string>();
|
|
238
|
-
const htmlAnalysisByPath = new Map<string, HtmlOutputAnalysis>();
|
|
239
|
-
for (const filePath of buildContentFiles) {
|
|
240
|
-
const record = this.getCachedRecord(filePath);
|
|
241
|
-
if (!record) {
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
for (const [outputPath, content] of record.outputs) {
|
|
245
|
-
if (!outputPath.endsWith('.html') || typeof content !== 'string') {
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
htmlAssetsByPath.set(outputPath, content);
|
|
249
|
-
const analysis = record.htmlAnalysisByOutputPath?.get(outputPath);
|
|
250
|
-
if (analysis) {
|
|
251
|
-
htmlAnalysisByPath.set(outputPath, cloneHtmlOutputAnalysis(analysis));
|
|
252
|
-
}
|
|
253
124
|
}
|
|
254
125
|
}
|
|
255
126
|
|
|
256
|
-
return {
|
|
257
|
-
errors,
|
|
258
|
-
changedHtmlAssetPaths,
|
|
259
|
-
removedHtmlAssetPaths,
|
|
260
|
-
removedOutputRelPaths,
|
|
261
|
-
htmlAssetsByPath,
|
|
262
|
-
htmlAnalysisByPath,
|
|
263
|
-
buildContentFiles,
|
|
264
|
-
};
|
|
127
|
+
return { errors, htmlAssetsByPath, htmlAnalysisByPath };
|
|
265
128
|
}
|
|
266
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 '
|
|
18
|
+
import { applyCommitPlan } from './watch/fs-commit';
|
|
19
19
|
import path from 'path';
|
|
20
20
|
|
|
21
21
|
const log = makeLogger(import.meta.url);
|
package/build/source-model.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
normalizeOutputPath,
|
|
16
16
|
toPosix,
|
|
17
17
|
} from './utils/paths';
|
|
18
|
-
import type { ChangeBatch } from '
|
|
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:
|
|
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() */
|
|
@@ -97,6 +85,7 @@ export interface RenderPlainTextOptions {
|
|
|
97
85
|
string,
|
|
98
86
|
{
|
|
99
87
|
manifestUrl: string;
|
|
88
|
+
artifactId: string;
|
|
100
89
|
highlightedSource: string;
|
|
101
90
|
totalSteps: number;
|
|
102
91
|
mtime: number;
|
|
@@ -16,6 +16,8 @@ const DARK_TEXT_L_MAX = 0.82;
|
|
|
16
16
|
|
|
17
17
|
const LINK_ANCHOR_HUE = 212;
|
|
18
18
|
const LINK_TINT_BLEND = 0.05;
|
|
19
|
+
const TRACE_LINE_ACTIVE_ANCHOR_HUE = 52;
|
|
20
|
+
const TRACE_LINE_ACTIVE_TINT_BLEND = 0.1;
|
|
19
21
|
|
|
20
22
|
function clamp(v: number, min: number, max: number): number {
|
|
21
23
|
return Math.min(Math.max(v, min), max);
|
|
@@ -83,6 +85,14 @@ export function deriveLinkHue(tintHue: number): number {
|
|
|
83
85
|
return (LINK_ANCHOR_HUE + diff * LINK_TINT_BLEND + 360) % 360;
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
export function deriveTraceLineActiveHue(tintHue: number): number {
|
|
89
|
+
const diff = ((tintHue - TRACE_LINE_ACTIVE_ANCHOR_HUE + 540) % 360) - 180;
|
|
90
|
+
return (
|
|
91
|
+
(TRACE_LINE_ACTIVE_ANCHOR_HUE + diff * TRACE_LINE_ACTIVE_TINT_BLEND + 360) %
|
|
92
|
+
360
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
export function getTextOnColor(cssColor: string): '#fff' | '#000' {
|
|
87
97
|
const parsed = parse(cssColor);
|
|
88
98
|
if (!parsed) {
|
package/build/utils/render.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
3
4
|
import { renderCodeSegment } from './code';
|
|
4
5
|
import { splitLines } from './literate-java';
|
|
5
6
|
import { normalizeOutputPath } from './paths';
|
|
@@ -14,18 +15,46 @@ export interface ChunkTraceOutputOptions {
|
|
|
14
15
|
ignoreFields?: Record<string, string[]>;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
export interface ChunkTraceOutputResult {
|
|
19
|
+
manifest: TraceManifest;
|
|
20
|
+
artifactId: string;
|
|
21
|
+
outputPaths: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hashTraceFiles(files: { name: string; content: string }[]): string {
|
|
25
|
+
const hasher = createHash('sha256');
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
hasher.update(file.name, 'utf8');
|
|
28
|
+
hasher.update('\0', 'utf8');
|
|
29
|
+
hasher.update(file.content, 'utf8');
|
|
30
|
+
hasher.update('\0', 'utf8');
|
|
31
|
+
}
|
|
32
|
+
return `sha256-${hasher.digest('hex').slice(0, 16)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildTraceOutputPath(
|
|
36
|
+
relDir: string,
|
|
37
|
+
traceName: string,
|
|
38
|
+
artifactId: string,
|
|
39
|
+
fileName: string,
|
|
40
|
+
): string {
|
|
41
|
+
return [relDir, '_traces', traceName, artifactId, fileName]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join('/');
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
export function chunkTraceOutput(
|
|
18
47
|
output: string,
|
|
19
48
|
traceOutputDir: string,
|
|
49
|
+
relDir: string,
|
|
50
|
+
traceName: string,
|
|
20
51
|
sourceFile: string,
|
|
21
52
|
source: string,
|
|
22
53
|
options: ChunkTraceOutputOptions = {},
|
|
23
|
-
):
|
|
54
|
+
): ChunkTraceOutputResult {
|
|
24
55
|
const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;
|
|
25
56
|
const ignoreFields = options.ignoreFields ?? {};
|
|
26
57
|
|
|
27
|
-
fs.mkdirSync(traceOutputDir, { recursive: true });
|
|
28
|
-
|
|
29
58
|
const lines = output
|
|
30
59
|
.trim()
|
|
31
60
|
.split('\n')
|
|
@@ -46,26 +75,27 @@ export function chunkTraceOutput(
|
|
|
46
75
|
|
|
47
76
|
let chunkEntries: TraceChunkEntry[] = [];
|
|
48
77
|
let chunkIndex = 0;
|
|
78
|
+
const chunkFiles: { name: string; content: string }[] = [];
|
|
49
79
|
|
|
50
80
|
for (const step of allSteps) {
|
|
51
81
|
const svg = generateStepSvg(step, layout);
|
|
52
82
|
chunkEntries.push({ line: step.line, stdout: step.stdout, svg });
|
|
53
83
|
|
|
54
84
|
if (chunkEntries.length >= chunkSize) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
JSON.stringify(chunkEntries),
|
|
58
|
-
);
|
|
85
|
+
chunkFiles.push({
|
|
86
|
+
name: `chunk-${chunkIndex}.json`,
|
|
87
|
+
content: JSON.stringify(chunkEntries),
|
|
88
|
+
});
|
|
59
89
|
chunkEntries = [];
|
|
60
90
|
chunkIndex++;
|
|
61
91
|
}
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
if (chunkEntries.length > 0) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
JSON.stringify(chunkEntries),
|
|
68
|
-
);
|
|
95
|
+
chunkFiles.push({
|
|
96
|
+
name: `chunk-${chunkIndex}.json`,
|
|
97
|
+
content: JSON.stringify(chunkEntries),
|
|
98
|
+
});
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
const manifest: TraceManifest = {
|
|
@@ -76,12 +106,26 @@ export function chunkTraceOutput(
|
|
|
76
106
|
lineToSteps,
|
|
77
107
|
};
|
|
78
108
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
JSON.stringify(manifest),
|
|
82
|
-
|
|
109
|
+
const manifestFile = {
|
|
110
|
+
name: 'manifest.json',
|
|
111
|
+
content: JSON.stringify(manifest),
|
|
112
|
+
};
|
|
113
|
+
const files = [manifestFile, ...chunkFiles];
|
|
114
|
+
const artifactId = hashTraceFiles(files);
|
|
115
|
+
const artifactDir = path.join(traceOutputDir, artifactId);
|
|
83
116
|
|
|
84
|
-
|
|
117
|
+
fs.mkdirSync(artifactDir, { recursive: true });
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
fs.writeFileSync(path.join(artifactDir, file.name), file.content);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
manifest,
|
|
124
|
+
artifactId,
|
|
125
|
+
outputPaths: files.map(file =>
|
|
126
|
+
buildTraceOutputPath(relDir, traceName, artifactId, file.name),
|
|
127
|
+
),
|
|
128
|
+
};
|
|
85
129
|
}
|
|
86
130
|
|
|
87
131
|
export function highlightTraceSource(
|
|
@@ -96,16 +140,25 @@ export function highlightTraceSource(
|
|
|
96
140
|
export function getTraceOutputPaths(
|
|
97
141
|
relDir: string,
|
|
98
142
|
traceName: string,
|
|
143
|
+
artifactId: string,
|
|
99
144
|
totalSteps: number,
|
|
100
145
|
): string[] {
|
|
101
|
-
const
|
|
102
|
-
|
|
146
|
+
const outputPaths = [
|
|
147
|
+
buildTraceOutputPath(relDir, traceName, artifactId, 'manifest.json'),
|
|
148
|
+
];
|
|
103
149
|
for (
|
|
104
150
|
let chunkIndex = 0;
|
|
105
151
|
chunkIndex * DEFAULT_CHUNK_SIZE < totalSteps;
|
|
106
152
|
chunkIndex++
|
|
107
153
|
) {
|
|
108
|
-
outputPaths.push(
|
|
154
|
+
outputPaths.push(
|
|
155
|
+
buildTraceOutputPath(
|
|
156
|
+
relDir,
|
|
157
|
+
traceName,
|
|
158
|
+
artifactId,
|
|
159
|
+
`chunk-${chunkIndex}.json`,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
109
162
|
}
|
|
110
163
|
return outputPaths;
|
|
111
164
|
}
|
|
@@ -148,18 +201,18 @@ export function materializeTraceOutputs({
|
|
|
148
201
|
export function buildManifestUrl({
|
|
149
202
|
relDir,
|
|
150
203
|
traceName,
|
|
204
|
+
artifactId,
|
|
151
205
|
applyBasePath,
|
|
152
206
|
}: {
|
|
153
207
|
relDir: string;
|
|
154
208
|
traceName: string;
|
|
209
|
+
artifactId: string;
|
|
155
210
|
applyBasePath: (subPath: string) => string;
|
|
156
211
|
}): string {
|
|
157
212
|
return applyBasePath(
|
|
158
213
|
normalizeOutputPath(
|
|
159
214
|
'/' +
|
|
160
|
-
|
|
161
|
-
.filter(Boolean)
|
|
162
|
-
.join('/'),
|
|
215
|
+
buildTraceOutputPath(relDir, traceName, artifactId, 'manifest.json'),
|
|
163
216
|
),
|
|
164
217
|
);
|
|
165
218
|
}
|