Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions toolkit-docs-generator/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1714,11 +1714,17 @@ program
}
}

const mergeFailures = allResults.filter((result) => result.error);
// Error results can still carry a last-known-good toolkit fallback from
// the merger. Keep them in the batch output so one failed merge cannot
// silently remove that toolkit from index.json.
const writableResults = allResults;

// Generate output files (batch mode if not incremental)
if (!useIncremental && allResults.length > 0) {
if (!useIncremental && writableResults.length > 0) {
spinner.start("Writing output files...");

const toolkits = allResults.map((r) => r.toolkit);
const toolkits = writableResults.map((result) => result.toolkit);
const genResult = await generator.generateAll(toolkits);

filesWritten.push(...genResult.filesWritten);
Expand All @@ -1731,13 +1737,15 @@ program
}
} else if (useIncremental) {
// Generate index file for incremental mode
if (allResults.length > 0 || skipToolkitIds.size > 0) {
if (writableResults.length > 0 || skipToolkitIds.size > 0) {
spinner.start("Generating index file...");
try {
const existingToolkits = await generator.getCompletedToolkitIds();
const allToolkitIds = new Set([
...existingToolkits,
...allResults.map((r) => r.toolkit.id.toLowerCase()),
...writableResults.map((result) =>
result.toolkit.id.toLowerCase()
),
Comment thread
cursor[bot] marked this conversation as resolved.
]);

// Load all toolkits for index
Expand Down Expand Up @@ -1797,7 +1805,11 @@ program
}

// Print summary
if (filesWritten.length > 0 || writeErrors.length > 0) {
if (
filesWritten.length > 0 ||
writeErrors.length > 0 ||
mergeFailures.length > 0
) {
console.log(chalk.green("\n✓ Generation complete\n"));
if (options.verbose) {
console.log(chalk.dim("Files:"));
Expand All @@ -1814,6 +1826,14 @@ program
console.log(chalk.yellow(` ${error}`));
}
}
if (mergeFailures.length > 0) {
console.log(chalk.red("\nFailed toolkits:"));
for (const failure of mergeFailures) {
console.log(
chalk.red(` ${failure.toolkit.id}: ${failure.error}`)
);
}
}
}

if (options.verifyOutput) {
Expand Down Expand Up @@ -1846,13 +1866,7 @@ program
(count, result) => count + result.warnings.length,
0
);
const failedToolkits = allResults
.filter((result) =>
result.warnings.some((warning) =>
warning.startsWith("Error processing toolkit")
)
)
.map((result) => result.toolkit.id);
const failedToolkits = mergeFailures.map((result) => result.toolkit.id);
const failedTools = allResults.flatMap((result) => result.failedTools);
const failedToolkitsFromTools = Array.from(
new Set(failedTools.map((tool) => tool.toolkitId))
Expand Down Expand Up @@ -1905,6 +1919,9 @@ program
title: "generate",
details: runDetails,
});
if (mergeFailures.length > 0 || writeErrors.length > 0) {
process.exitCode = 1;
}
} catch (error) {
spinner.fail(
`Error: ${error instanceof Error ? error.message : String(error)}`
Expand Down Expand Up @@ -2415,6 +2432,11 @@ program

spinner.start(progressTracker.getProgressString());
const results = await merger.mergeAllToolkits();
const mergeFailures = results.filter((result) => result.error);
// Error results can still carry a last-known-good toolkit fallback.
// Preserve it in batch output rather than letting --clear-output
// remove the prior JSON artifact.
const writableResults = results;
const summary = progressTracker.getSummary();
spinner.succeed(
`Processed ${summary.completed} toolkit(s) with ${summary.totalTools} tools in ${summary.elapsed}`
Expand All @@ -2435,11 +2457,20 @@ program
console.log(chalk.dim(` - ${warning}`));
}
}
if (mergeFailures.length > 0) {
console.log(chalk.red("\nFailed toolkits:"));
for (const failure of mergeFailures) {
console.log(
chalk.red(` ${failure.toolkit.id}: ${failure.error}`)
);
}
process.exitCode = 1;
}

// Generate output (batch mode if not incremental)
if (!useIncremental && results.length > 0) {
if (!useIncremental && writableResults.length > 0) {
spinner.start("Writing output files...");
const toolkits = results.map((r) => r.toolkit);
const toolkits = writableResults.map((result) => result.toolkit);
const genResult = await generator.generateAll(toolkits);

filesWritten.push(...genResult.filesWritten);
Expand All @@ -2461,7 +2492,9 @@ program
const existingToolkits = await generator.getCompletedToolkitIds();
const allToolkitIds = new Set([
...existingToolkits,
...results.map((r) => r.toolkit.id.toLowerCase()),
...writableResults.map((result) =>
result.toolkit.id.toLowerCase()
),
]);

const toolkitsForIndex: MergedToolkit[] = [];
Expand Down Expand Up @@ -2553,6 +2586,7 @@ program
for (const warning of writeErrors) {
console.log(chalk.yellow(` ${warning}`));
}
process.exitCode = 1;
}
} catch (error) {
spinner.fail(
Expand Down
32 changes: 28 additions & 4 deletions toolkit-docs-generator/src/merger/data-merger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface MergeResult {
toolkit: MergedToolkit;
warnings: string[];
failedTools: FailedTool[];
error?: string;
}

export interface ToolExampleResult {
Expand Down Expand Up @@ -987,6 +988,15 @@ export class DataMerger {
message: string,
previousToolkit?: MergedToolkit
): MergeResult {
if (previousToolkit) {
return {
toolkit: previousToolkit,
warnings: [`Error processing toolkit: ${message}`],
failedTools: [],
error: message,
};
}

return {
toolkit: {
id: toolkitId,
Expand All @@ -1005,13 +1015,14 @@ export class DataMerger {
},
auth: null,
tools: [],
documentationChunks: previousToolkit?.documentationChunks ?? [],
customImports: previousToolkit?.customImports ?? [],
subPages: previousToolkit?.subPages ?? [],
documentationChunks: [],
customImports: [],
subPages: [],
generatedAt: new Date().toISOString(),
},
warnings: [`Error processing toolkit: ${message}`],
failedTools: [],
error: message,
};
}

Expand Down Expand Up @@ -1049,8 +1060,21 @@ export class DataMerger {
return result;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (this.requireCompleteData) {
throw new Error(`Failed to process ${toolkitId}: ${message}`, {
cause: error,
});
}
const previousToolkit = this.getPreviousToolkit(toolkitId);
return this.buildMergeErrorResult(toolkitId, message, previousToolkit);
const result = this.buildMergeErrorResult(
toolkitId,
message,
previousToolkit
);
if (this.onToolkitComplete && previousToolkit) {
await this.onToolkitComplete(result);
}
Comment thread
cursor[bot] marked this conversation as resolved.
return result;
}
}

Expand Down
35 changes: 34 additions & 1 deletion toolkit-docs-generator/tests/merger/data-merger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1917,18 +1917,23 @@ describe("DataMerger", () => {
}),
createStubGenerator()
);
const completedToolkitIds: string[] = [];

const merger = new DataMerger({
toolkitDataSource,
customSectionsSource: makeFailingCustomSectionsSource(),
toolExampleGenerator: createStubGenerator(),
previousToolkits: new Map([["github", previousResult.toolkit]]),
onToolkitComplete: async (result) => {
completedToolkitIds.push(result.toolkit.id);
},
});

const results = await merger.mergeAllToolkits();
const result = results[0];

// Error result must preserve previous custom sections, not return empty arrays
expect(result?.error).toBe("Custom sections source unavailable");
expect(result?.toolkit).toEqual(previousResult.toolkit);
expect(result?.toolkit.documentationChunks).toHaveLength(1);
expect(result?.toolkit.documentationChunks[0]?.content).toBe(
"Critical: GitHub Apps only."
Expand All @@ -1938,29 +1943,36 @@ describe("DataMerger", () => {
expect(result?.warnings[0]).toContain(
"Custom sections source unavailable"
);
expect(completedToolkitIds).toEqual(["Github"]);
});

it("returns empty custom sections in error result when no previous toolkit exists", async () => {
const toolkitDataSource = createCombinedToolkitDataSource({
toolSource: new InMemoryToolDataSource([githubTool1]),
metadataSource: new InMemoryMetadataSource([githubMetadata]),
});
const completedToolkitIds: string[] = [];

const merger = new DataMerger({
toolkitDataSource,
customSectionsSource: makeFailingCustomSectionsSource(),
toolExampleGenerator: createStubGenerator(),
onToolkitComplete: async (result) => {
completedToolkitIds.push(result.toolkit.id);
},
});

const results = await merger.mergeAllToolkits();
const result = results[0];

expect(result?.error).toBe("Custom sections source unavailable");
expect(result?.toolkit.documentationChunks).toHaveLength(0);
expect(result?.toolkit.customImports).toHaveLength(0);
expect(result?.toolkit.subPages).toHaveLength(0);
expect(result?.warnings[0]).toContain(
"Custom sections source unavailable"
);
expect(completedToolkitIds).toEqual([]);
});
});

Expand Down Expand Up @@ -2053,6 +2065,27 @@ describe("DataMerger", () => {
expect(results[0]?.toolkit.id).toBe("Github");
});

it("fails strict runs when a complete toolkit cannot be merged", async () => {
const toolkitDataSource = createCombinedToolkitDataSource({
toolSource: new InMemoryToolDataSource([githubTool1]),
metadataSource: new InMemoryMetadataSource([githubMetadata]),
});
const merger = new DataMerger({
toolkitDataSource,
customSectionsSource: {
getCustomSections: async () => {
throw new Error("Custom sections source unavailable");
},
},
toolExampleGenerator: createStubGenerator(),
requireCompleteData: true,
});

await expect(merger.mergeAllToolkits()).rejects.toThrow(
"Failed to process Github: Custom sections source unavailable"
);
});

it("should return empty array when no tools", async () => {
const toolkitDataSource = createCombinedToolkitDataSource({
toolSource: new InMemoryToolDataSource([]),
Expand Down
Loading