@abreen/tada 1.8.1 → 1.10.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.
- package/README.md +24 -34
- package/bin/tada.ts +1 -1
- package/bin/validators.ts +1 -1
- package/build/apply-base-path-plugin.ts +79 -17
- package/build/bundle.ts +9 -1
- package/build/content-watch.ts +3 -3
- package/build/copy.ts +30 -0
- package/build/generate-content-assets.ts +45 -59
- package/build/pagefind.ts +0 -7
- package/build/serve.ts +4 -0
- package/build/templates.ts +0 -2
- package/build/types.d.ts +19 -1
- package/build/util.ts +3 -0
- package/build/utils/content-files.ts +98 -0
- package/build/utils/include.ts +4 -0
- package/build/utils/python-runner/trace_runner.py +272 -0
- package/build/utils/render.ts +47 -15
- package/build/utils/source-template.ts +27 -0
- package/build/utils/trace-core.ts +203 -0
- package/build/utils/trace-java.ts +132 -0
- package/build/utils/trace-layout.ts +31 -3
- package/build/utils/trace-python.ts +26 -0
- package/build/utils/trace.ts +174 -294
- package/build/validate-internal-links-plugin.ts +16 -4
- package/build/watch/assets.ts +111 -0
- package/build/watch/build-full.ts +124 -0
- package/build/watch/build-incremental.ts +139 -0
- package/build/watch/compiler-types.d.ts +19 -0
- package/build/watch/compiler.ts +112 -0
- package/build/watch/index.ts +22 -0
- package/build/watch/mutations.ts +75 -0
- package/build/watch/planner.ts +230 -0
- package/build/watch/records.ts +190 -0
- package/build/watch/runtime.ts +143 -0
- package/build/watch/snapshot.ts +602 -0
- package/build/watch/validation.ts +36 -0
- package/build/watch-reload-client.ts +10 -5
- package/init/content/labs/01/index.md +6 -0
- package/init/content/labs/01/trace_demo.py +16 -0
- package/init/content/lectures/01/Rectangle.java +2 -0
- package/init/content/lectures/01/index.md +6 -0
- package/init/content/lectures/01/rectangle.py +5 -0
- package/package.json +42 -23
- package/python/command.ts +95 -0
- package/python/module.ts +18 -0
- package/src/_alerts.scss +11 -8
- package/src/_base.scss +25 -29
- package/src/_layout.scss +17 -14
- package/src/_mixins.scss +10 -9
- package/src/anchor/style.scss +4 -2
- package/src/code/index.ts +9 -2
- package/src/code/style.scss +14 -13
- package/src/code.scss +14 -13
- package/src/header/_base.scss +21 -24
- package/src/header/style.scss +16 -12
- package/src/index.ts +3 -0
- package/src/literate/style.scss +4 -4
- package/src/navigate/index.ts +43 -279
- package/src/navigate/lifecycle.ts +1 -1
- package/src/navigate/runtime.ts +341 -0
- package/src/page-update/README.md +4 -0
- package/src/page-update/index.ts +280 -0
- package/src/page-update/style.scss +53 -0
- package/src/print/style.scss +4 -4
- package/src/question/style.scss +10 -6
- package/src/search/index.ts +9 -12
- package/src/search/style.scss +44 -42
- package/src/style.scss +63 -58
- package/src/timezone/style.scss +5 -5
- package/src/toc/style.scss +17 -16
- package/src/trace/style.scss +27 -27
- package/src/validators.ts +45 -0
- package/templates/_footer.html +1 -1
- package/templates/_theme.scss +8 -19
- package/watch/engine.ts +296 -0
- package/watch/events.ts +10 -0
- package/watch/fs-commit.ts +102 -0
- package/watch/types.d.ts +103 -0
- package/build/watch.ts +0 -493
package/build/utils/trace.ts
CHANGED
|
@@ -1,193 +1,33 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
2
|
import path from 'path';
|
|
4
|
-
import { execFileSync } from 'child_process';
|
|
5
3
|
import { makeLogger } from '../log';
|
|
6
4
|
import { B } from '../colors';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
import { toPosix } from './paths';
|
|
6
|
+
import { checkJavac } from './literate-java';
|
|
7
|
+
import {
|
|
8
|
+
buildManifestUrl,
|
|
9
|
+
chunkTraceOutput,
|
|
10
|
+
DEFAULT_CHUNK_SIZE,
|
|
11
|
+
getTraceOutputPaths,
|
|
12
|
+
highlightTraceSource,
|
|
13
|
+
materializeTraceOutputs,
|
|
14
|
+
renderTraceWidgetHtml,
|
|
15
|
+
} from './trace-core';
|
|
16
|
+
import {
|
|
17
|
+
parseIgnoreFields,
|
|
18
|
+
runJavaTrace,
|
|
19
|
+
validateJavaTraceTarget,
|
|
20
|
+
} from './trace-java';
|
|
21
|
+
import { resolvePythonCommand } from '../../python/command';
|
|
22
|
+
import { runPythonTrace } from './trace-python';
|
|
23
|
+
import type {
|
|
24
|
+
RenderDependencyCollector,
|
|
25
|
+
TraceToolAvailability,
|
|
26
|
+
} from '../types';
|
|
13
27
|
|
|
14
28
|
const log = makeLogger(import.meta.url);
|
|
15
29
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Parse // @trace-ignore comments from Java source code.
|
|
20
|
-
* Returns a map of className -> fieldName[] for fields that should be
|
|
21
|
-
* excluded from layout consideration.
|
|
22
|
-
*/
|
|
23
|
-
export function parseIgnoreFields(source: string): Record<string, string[]> {
|
|
24
|
-
const result: Record<string, string[]> = {};
|
|
25
|
-
const lines = source.split('\n');
|
|
26
|
-
// Track nesting: Java inner classes are reported as Outer$Inner by the tracer.
|
|
27
|
-
// Each entry is [className, braceDepthWhenOpened].
|
|
28
|
-
const classStack: { name: string; depth: number }[] = [];
|
|
29
|
-
let braceDepth = 0;
|
|
30
|
-
|
|
31
|
-
for (const line of lines) {
|
|
32
|
-
// Strip strings and comments (except @trace-ignore) to avoid false braces
|
|
33
|
-
const stripped = line
|
|
34
|
-
.replace(/"(?:[^"\\]|\\.)*"/g, '')
|
|
35
|
-
.replace(/\/\/(?!.*@trace-ignore).*/g, '');
|
|
36
|
-
|
|
37
|
-
const classMatch = stripped.match(/\bclass\s+(\w+)/);
|
|
38
|
-
if (classMatch) {
|
|
39
|
-
classStack.push({ name: classMatch[1], depth: braceDepth });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (const ch of stripped) {
|
|
43
|
-
if (ch === '{') {
|
|
44
|
-
braceDepth++;
|
|
45
|
-
}
|
|
46
|
-
if (ch === '}') {
|
|
47
|
-
braceDepth--;
|
|
48
|
-
// Pop classes whose scope has ended
|
|
49
|
-
while (
|
|
50
|
-
classStack.length > 0 &&
|
|
51
|
-
braceDepth <= classStack[classStack.length - 1].depth
|
|
52
|
-
) {
|
|
53
|
-
classStack.pop();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const currentClass =
|
|
59
|
-
classStack.length > 0 ? classStack.map(c => c.name).join('$') : null;
|
|
60
|
-
|
|
61
|
-
if (currentClass && line.includes('// @trace-ignore')) {
|
|
62
|
-
const before = line.split('//')[0].trim();
|
|
63
|
-
const withoutSemicolon = before.endsWith(';')
|
|
64
|
-
? before.slice(0, -1).trim()
|
|
65
|
-
: before;
|
|
66
|
-
const parts = withoutSemicolon.split(/\s+/);
|
|
67
|
-
if (parts.length >= 2) {
|
|
68
|
-
const fieldName = parts[parts.length - 1];
|
|
69
|
-
if (!result[currentClass]) {
|
|
70
|
-
result[currentClass] = [];
|
|
71
|
-
}
|
|
72
|
-
result[currentClass].push(fieldName);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let tracerClassDir: string | null = null;
|
|
81
|
-
|
|
82
|
-
function ensureTracerCompiled(): string {
|
|
83
|
-
if (tracerClassDir) {
|
|
84
|
-
return tracerClassDir;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const sourceDir = path.join(import.meta.dir, 'jdi-runner');
|
|
88
|
-
const sourceFile = path.join(sourceDir, 'TraceRunner.java');
|
|
89
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-tracer-'));
|
|
90
|
-
|
|
91
|
-
log.debug`Compiling TraceRunner.java into ${tempDir}`;
|
|
92
|
-
|
|
93
|
-
runJavac(['-d', tempDir, sourceFile], {
|
|
94
|
-
tempDir,
|
|
95
|
-
label: 'Failed to compile TraceRunner.java',
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
tracerClassDir = tempDir;
|
|
99
|
-
return tempDir;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function compileTargetFile(javaFilePath: string): string {
|
|
103
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-trace-target-'));
|
|
104
|
-
|
|
105
|
-
runJavac(
|
|
106
|
-
[
|
|
107
|
-
'-g',
|
|
108
|
-
'-d',
|
|
109
|
-
tempDir,
|
|
110
|
-
'-sourcepath',
|
|
111
|
-
path.dirname(javaFilePath),
|
|
112
|
-
javaFilePath,
|
|
113
|
-
],
|
|
114
|
-
{ tempDir, label: `Compilation failed for ${javaFilePath}` },
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
return tempDir;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function chunkTraceOutput(
|
|
121
|
-
output: string,
|
|
122
|
-
traceOutputDir: string,
|
|
123
|
-
sourceFile: string,
|
|
124
|
-
source: string,
|
|
125
|
-
chunkSize: number = DEFAULT_CHUNK_SIZE,
|
|
126
|
-
): TraceManifest {
|
|
127
|
-
fs.mkdirSync(traceOutputDir, { recursive: true });
|
|
128
|
-
|
|
129
|
-
const lines = output
|
|
130
|
-
.trim()
|
|
131
|
-
.split('\n')
|
|
132
|
-
.filter(l => l.length > 0);
|
|
133
|
-
|
|
134
|
-
// Parse all steps first (needed for layout pass)
|
|
135
|
-
const allSteps: TraceStep[] = lines.map(l => JSON.parse(l));
|
|
136
|
-
|
|
137
|
-
const lineToSteps: Record<number, number[]> = {};
|
|
138
|
-
for (let i = 0; i < allSteps.length; i++) {
|
|
139
|
-
const step = allSteps[i];
|
|
140
|
-
if (!lineToSteps[step.line]) {
|
|
141
|
-
lineToSteps[step.line] = [];
|
|
142
|
-
}
|
|
143
|
-
lineToSteps[step.line].push(i);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Parse @trace-ignore comments from source
|
|
147
|
-
const ignoreFields = parseIgnoreFields(source);
|
|
148
|
-
|
|
149
|
-
// Compute stable layout across all steps, then generate SVG per step
|
|
150
|
-
const layout = computeLayout(allSteps, ignoreFields);
|
|
151
|
-
|
|
152
|
-
let chunkEntries: TraceChunkEntry[] = [];
|
|
153
|
-
let chunkIndex = 0;
|
|
154
|
-
|
|
155
|
-
for (const step of allSteps) {
|
|
156
|
-
const svg = generateStepSvg(step, layout);
|
|
157
|
-
chunkEntries.push({ line: step.line, stdout: step.stdout, svg });
|
|
158
|
-
|
|
159
|
-
if (chunkEntries.length >= chunkSize) {
|
|
160
|
-
fs.writeFileSync(
|
|
161
|
-
path.join(traceOutputDir, `chunk-${chunkIndex}.json`),
|
|
162
|
-
JSON.stringify(chunkEntries),
|
|
163
|
-
);
|
|
164
|
-
chunkEntries = [];
|
|
165
|
-
chunkIndex++;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (chunkEntries.length > 0) {
|
|
170
|
-
fs.writeFileSync(
|
|
171
|
-
path.join(traceOutputDir, `chunk-${chunkIndex}.json`),
|
|
172
|
-
JSON.stringify(chunkEntries),
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const manifest: TraceManifest = {
|
|
177
|
-
totalSteps: allSteps.length,
|
|
178
|
-
chunkSize,
|
|
179
|
-
sourceFile,
|
|
180
|
-
source,
|
|
181
|
-
lineToSteps,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
fs.writeFileSync(
|
|
185
|
-
path.join(traceOutputDir, 'manifest.json'),
|
|
186
|
-
JSON.stringify(manifest),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
return manifest;
|
|
190
|
-
}
|
|
30
|
+
export const TRACEABLE_EXTENSIONS = new Set(['.java', '.py']);
|
|
191
31
|
|
|
192
32
|
export interface TraceContext {
|
|
193
33
|
filePath: string;
|
|
@@ -195,7 +35,9 @@ export interface TraceContext {
|
|
|
195
35
|
distDir: string;
|
|
196
36
|
applyBasePath: (subPath: string) => string;
|
|
197
37
|
cache: Map<string, TraceResult>;
|
|
198
|
-
|
|
38
|
+
toolAvailability?: TraceToolAvailability;
|
|
39
|
+
dependencyCollector?: RenderDependencyCollector;
|
|
40
|
+
cachedTraceSourceDir?: string;
|
|
199
41
|
}
|
|
200
42
|
|
|
201
43
|
export interface TraceResult {
|
|
@@ -205,52 +47,72 @@ export interface TraceResult {
|
|
|
205
47
|
mtime: number;
|
|
206
48
|
}
|
|
207
49
|
|
|
208
|
-
function
|
|
209
|
-
return
|
|
210
|
-
linkLineNumbers: false,
|
|
211
|
-
});
|
|
50
|
+
export function isTraceSourceFile(filePath: string): boolean {
|
|
51
|
+
return TRACEABLE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
212
52
|
}
|
|
213
53
|
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
54
|
+
export function checkTraceToolAvailability(): TraceToolAvailability {
|
|
55
|
+
return { java: checkJavac(), python: resolvePythonCommand() !== null };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function traceLanguageForFile(filePath: string): 'java' | 'python' {
|
|
59
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
60
|
+
if (ext === '.java') {
|
|
61
|
+
return 'java';
|
|
62
|
+
}
|
|
63
|
+
if (ext === '.py') {
|
|
64
|
+
return 'python';
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Unsupported trace source: ${filePath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function availabilityForFile(
|
|
70
|
+
filePath: string,
|
|
71
|
+
toolAvailability?: TraceToolAvailability,
|
|
72
|
+
): boolean | undefined {
|
|
73
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
74
|
+
if (ext === '.java') {
|
|
75
|
+
return toolAvailability?.java;
|
|
76
|
+
}
|
|
77
|
+
if (ext === '.py') {
|
|
78
|
+
return toolAvailability?.python;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function disabledTraceMessage(fileName: string): string {
|
|
84
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
85
|
+
if (ext === '.java') {
|
|
86
|
+
return `javac was not found; trace for ${B`${fileName}`} will be disabled`;
|
|
87
|
+
}
|
|
88
|
+
return `Python was not found; trace for ${B`${fileName}`} will be disabled`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function sourceFileNameForTrace(fileName: string): string {
|
|
92
|
+
const { name, ext } = path.parse(fileName);
|
|
93
|
+
return `${name}${ext}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runTraceForFile(
|
|
97
|
+
filePath: string,
|
|
98
|
+
source: string,
|
|
99
|
+
): { output: string; ignoreFields?: Record<string, string[]> } {
|
|
100
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
101
|
+
if (ext === '.java') {
|
|
102
|
+
validateJavaTraceTarget(filePath, source);
|
|
103
|
+
return {
|
|
104
|
+
output: runJavaTrace(filePath, path.parse(filePath).name),
|
|
105
|
+
ignoreFields: parseIgnoreFields(source),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (ext === '.py') {
|
|
109
|
+
return { output: runPythonTrace(filePath) };
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`Unsupported trace source: ${filePath}`);
|
|
250
112
|
}
|
|
251
113
|
|
|
252
114
|
export function createTraceHelpers(context: TraceContext): {
|
|
253
|
-
renderTrace: (
|
|
115
|
+
renderTrace: (sourceFile: string) => string;
|
|
254
116
|
} {
|
|
255
117
|
const {
|
|
256
118
|
filePath,
|
|
@@ -258,97 +120,115 @@ export function createTraceHelpers(context: TraceContext): {
|
|
|
258
120
|
distDir,
|
|
259
121
|
applyBasePath,
|
|
260
122
|
cache,
|
|
261
|
-
|
|
123
|
+
toolAvailability,
|
|
124
|
+
dependencyCollector,
|
|
125
|
+
cachedTraceSourceDir,
|
|
262
126
|
} = context;
|
|
263
127
|
const pageDir = path.dirname(filePath);
|
|
264
128
|
|
|
265
|
-
function getOrRunTrace(
|
|
266
|
-
const
|
|
129
|
+
function getOrRunTrace(sourceFile: string): TraceResult {
|
|
130
|
+
const sourceFilePath = path.resolve(pageDir, sourceFile);
|
|
131
|
+
dependencyCollector?.traceFiles?.add(sourceFilePath);
|
|
132
|
+
const traceName = path.parse(sourceFile).name;
|
|
133
|
+
const relDir = toPosix(path.relative(contentDir, pageDir));
|
|
267
134
|
|
|
268
|
-
const cached = cache.get(
|
|
135
|
+
const cached = cache.get(sourceFilePath);
|
|
269
136
|
if (cached) {
|
|
270
|
-
const mtime = fs.statSync(
|
|
137
|
+
const mtime = fs.statSync(sourceFilePath, {
|
|
271
138
|
throwIfNoEntry: false,
|
|
272
139
|
})?.mtimeMs;
|
|
273
140
|
if (mtime !== undefined && mtime === cached.mtime) {
|
|
274
|
-
|
|
141
|
+
const outputPaths = getTraceOutputPaths(
|
|
142
|
+
relDir,
|
|
143
|
+
traceName,
|
|
144
|
+
cached.totalSteps,
|
|
145
|
+
);
|
|
146
|
+
if (
|
|
147
|
+
materializeTraceOutputs({
|
|
148
|
+
outputPaths,
|
|
149
|
+
targetDistDir: distDir,
|
|
150
|
+
sourceDistDir: cachedTraceSourceDir || distDir,
|
|
151
|
+
})
|
|
152
|
+
) {
|
|
153
|
+
for (const outputPath of outputPaths) {
|
|
154
|
+
dependencyCollector?.generatedOutputPaths?.add(outputPath);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
...cached,
|
|
158
|
+
manifestUrl: buildManifestUrl({ relDir, traceName, applyBasePath }),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
275
161
|
}
|
|
276
162
|
}
|
|
277
163
|
|
|
278
|
-
if (!fs.existsSync(
|
|
279
|
-
throw new Error(`Trace target not found: ${
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const source = fs.readFileSync(javaFilePath, 'utf-8');
|
|
283
|
-
if (!hasMainMethod(source)) {
|
|
284
|
-
throw new Error(
|
|
285
|
-
`${javaFilePath}: no main() method found (required for tracing)`,
|
|
286
|
-
);
|
|
164
|
+
if (!fs.existsSync(sourceFilePath)) {
|
|
165
|
+
throw new Error(`Trace target not found: ${sourceFilePath}`);
|
|
287
166
|
}
|
|
288
167
|
|
|
289
|
-
const
|
|
290
|
-
log.info`Tracing ${B`${javaFile}`}`;
|
|
168
|
+
const source = fs.readFileSync(sourceFilePath, 'utf-8');
|
|
291
169
|
|
|
292
|
-
|
|
170
|
+
log.info`Tracing ${B`${sourceFile}`}`;
|
|
293
171
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
const manifestUrl = applyBasePath(
|
|
316
|
-
normalizeOutputPath(
|
|
317
|
-
'/' +
|
|
318
|
-
[relDir, '_traces', className, 'manifest.json']
|
|
319
|
-
.filter(Boolean)
|
|
320
|
-
.join('/'),
|
|
321
|
-
),
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
const totalSteps = manifest.totalSteps;
|
|
325
|
-
const mtime = fs.statSync(javaFilePath).mtimeMs;
|
|
326
|
-
cache.set(javaFilePath, {
|
|
327
|
-
manifestUrl,
|
|
328
|
-
highlightedSource,
|
|
329
|
-
totalSteps,
|
|
330
|
-
mtime,
|
|
331
|
-
});
|
|
332
|
-
return { manifestUrl, highlightedSource, totalSteps, mtime };
|
|
333
|
-
} finally {
|
|
334
|
-
fs.rmSync(targetClassDir, { recursive: true, force: true });
|
|
172
|
+
const { output, ignoreFields } = runTraceForFile(sourceFilePath, source);
|
|
173
|
+
const highlightedSource = highlightTraceSource(
|
|
174
|
+
source,
|
|
175
|
+
traceLanguageForFile(sourceFilePath),
|
|
176
|
+
);
|
|
177
|
+
const traceOutputDir = path.join(distDir, relDir, '_traces', traceName);
|
|
178
|
+
|
|
179
|
+
const manifest = chunkTraceOutput(
|
|
180
|
+
output,
|
|
181
|
+
traceOutputDir,
|
|
182
|
+
sourceFileNameForTrace(sourceFile),
|
|
183
|
+
source,
|
|
184
|
+
{ chunkSize: DEFAULT_CHUNK_SIZE, ignoreFields },
|
|
185
|
+
);
|
|
186
|
+
for (const outputPath of getTraceOutputPaths(
|
|
187
|
+
relDir,
|
|
188
|
+
traceName,
|
|
189
|
+
manifest.totalSteps,
|
|
190
|
+
)) {
|
|
191
|
+
dependencyCollector?.generatedOutputPaths?.add(outputPath);
|
|
335
192
|
}
|
|
193
|
+
|
|
194
|
+
const manifestUrl = buildManifestUrl({ relDir, traceName, applyBasePath });
|
|
195
|
+
|
|
196
|
+
const totalSteps = manifest.totalSteps;
|
|
197
|
+
const mtime = fs.statSync(sourceFilePath).mtimeMs;
|
|
198
|
+
cache.set(sourceFilePath, {
|
|
199
|
+
manifestUrl,
|
|
200
|
+
highlightedSource,
|
|
201
|
+
totalSteps,
|
|
202
|
+
mtime,
|
|
203
|
+
});
|
|
204
|
+
return { manifestUrl, highlightedSource, totalSteps, mtime };
|
|
336
205
|
}
|
|
337
206
|
|
|
338
207
|
return {
|
|
339
|
-
renderTrace: (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
208
|
+
renderTrace: (sourceFile: string): string => {
|
|
209
|
+
const sourceFilePath = path.resolve(pageDir, sourceFile);
|
|
210
|
+
const available = availabilityForFile(sourceFilePath, toolAvailability);
|
|
211
|
+
if (available === false) {
|
|
212
|
+
log.warn`${disabledTraceMessage(sourceFile)}`;
|
|
213
|
+
if (!fs.existsSync(sourceFilePath)) {
|
|
214
|
+
throw new Error(`Trace target not found: ${sourceFilePath}`);
|
|
345
215
|
}
|
|
346
|
-
const source = fs.readFileSync(
|
|
347
|
-
return
|
|
216
|
+
const source = fs.readFileSync(sourceFilePath, 'utf-8');
|
|
217
|
+
return renderTraceWidgetHtml({
|
|
218
|
+
highlightedSource: highlightTraceSource(
|
|
219
|
+
source,
|
|
220
|
+
traceLanguageForFile(sourceFilePath),
|
|
221
|
+
),
|
|
222
|
+
});
|
|
348
223
|
}
|
|
224
|
+
|
|
349
225
|
const { manifestUrl, highlightedSource, totalSteps } =
|
|
350
|
-
getOrRunTrace(
|
|
351
|
-
return
|
|
226
|
+
getOrRunTrace(sourceFile);
|
|
227
|
+
return renderTraceWidgetHtml({
|
|
228
|
+
highlightedSource,
|
|
229
|
+
manifestUrl,
|
|
230
|
+
totalSteps,
|
|
231
|
+
});
|
|
352
232
|
},
|
|
353
233
|
};
|
|
354
234
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type MarkdownIt from 'markdown-it';
|
|
2
2
|
import type Token from 'markdown-it/lib/token.mjs';
|
|
3
|
+
import { JSDOM } from 'jsdom';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import { isInternalLink } from './utils/link';
|
|
5
6
|
import { makeLogger } from './log';
|
|
@@ -12,6 +13,7 @@ interface ValidateInternalLinksOptions {
|
|
|
12
13
|
sourceUrlPath?: string;
|
|
13
14
|
validTargets?: Set<string>;
|
|
14
15
|
codeExtensions?: string[];
|
|
16
|
+
onValidLink?: (targetPath: string) => void;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
function stripQueryAndHash(href: string): string {
|
|
@@ -90,6 +92,7 @@ export default function validateInternalLinks(
|
|
|
90
92
|
sourceUrlPath,
|
|
91
93
|
validTargets,
|
|
92
94
|
codeExtensions = [],
|
|
95
|
+
onValidLink,
|
|
93
96
|
} = options;
|
|
94
97
|
|
|
95
98
|
if (!enabled) {
|
|
@@ -160,11 +163,15 @@ export default function validateInternalLinks(
|
|
|
160
163
|
if (codeExtPattern) {
|
|
161
164
|
const unrewritten = resolveLinkPath(sourceUrlPath!, href, null);
|
|
162
165
|
if (unrewritten && validTargets!.has(unrewritten)) {
|
|
166
|
+
onValidLink?.(unrewritten);
|
|
163
167
|
return;
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
reportBrokenLink(rawHref, resolvedPath);
|
|
171
|
+
return;
|
|
167
172
|
}
|
|
173
|
+
|
|
174
|
+
onValidLink?.(resolvedPath);
|
|
168
175
|
}
|
|
169
176
|
|
|
170
177
|
function validateToken(token: Token): void {
|
|
@@ -174,10 +181,15 @@ export default function validateInternalLinks(
|
|
|
174
181
|
validateHref(href);
|
|
175
182
|
}
|
|
176
183
|
} else if (token.type === 'html_block' || token.type === 'html_inline') {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
const dom = new JSDOM(`<body>${token.content}</body>`);
|
|
185
|
+
for (const anchor of dom.window.document.body.querySelectorAll(
|
|
186
|
+
'a[href]',
|
|
187
|
+
)) {
|
|
188
|
+
const href = anchor.getAttribute('href');
|
|
189
|
+
if (href) {
|
|
190
|
+
validateHref(href);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
181
193
|
}
|
|
182
194
|
|
|
183
195
|
token.children?.forEach(validateToken);
|