@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 CHANGED
@@ -9,10 +9,20 @@ import {
9
9
  getDistDir,
10
10
  toPosix,
11
11
  } from './utils/paths';
12
- import { deriveTheme, deriveLinkHue } from './utils/derive-theme';
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: '[name].bundle.[ext]',
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, 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,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
- this.pruneRemovedSources(
188
- buildFileSet,
189
- removedOutputRelPaths,
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.javacAvailable === undefined && dirtySourceFiles.size > 0) {
89
+ if (!this.traceToolAvailability && buildContentFiles.length > 0) {
199
90
  this.traceToolAvailability = checkTraceToolAvailability();
200
- this.javacAvailable = this.traceToolAvailability.java;
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 dirtySourceFiles) {
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.javacAvailable,
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 '../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() */
@@ -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) {
@@ -432,6 +432,7 @@ function renderPlainTextContent(
432
432
  string,
433
433
  {
434
434
  manifestUrl: string;
435
+ artifactId: string;
435
436
  highlightedSource: string;
436
437
  totalSteps: number;
437
438
  mtime: number;
@@ -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
- ): TraceManifest {
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
- fs.writeFileSync(
56
- path.join(traceOutputDir, `chunk-${chunkIndex}.json`),
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
- fs.writeFileSync(
66
- path.join(traceOutputDir, `chunk-${chunkIndex}.json`),
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
- fs.writeFileSync(
80
- path.join(traceOutputDir, 'manifest.json'),
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
- return manifest;
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 baseDir = [relDir, '_traces', traceName].filter(Boolean).join('/');
102
- const outputPaths = [`${baseDir}/manifest.json`];
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(`${baseDir}/chunk-${chunkIndex}.json`);
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
- [relDir, '_traces', traceName, 'manifest.json']
161
- .filter(Boolean)
162
- .join('/'),
215
+ buildTraceOutputPath(relDir, traceName, artifactId, 'manifest.json'),
163
216
  ),
164
217
  );
165
218
  }