feat(cli): add timestamped filenames for insight reports

- Generate date-stamped filenames (insight-YYYY-MM-DD.html)
- Append timestamp for multiple runs same day (insight-YYYY-MM-DD-HHMMSS.html)
- Create insight.html alias pointing to latest report
- Use symlink when possible, fallback to copy for cross-platform compatibility

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-02-26 14:38:12 +08:00
parent eea5daae74
commit 509260ddfc

View file

@ -14,7 +14,9 @@ import type {
InsightProgressCallback,
} from '../types/StaticInsightTypes.js';
import type { Config } from '@qwen-code/qwen-code-core';
import { createDebugLogger, type Config } from '@qwen-code/qwen-code-core';
const logger = createDebugLogger('StaticInsightGenerator');
export class StaticInsightGenerator {
private dataProcessor: DataProcessor;
@ -32,6 +34,62 @@ export class StaticInsightGenerator {
return outputDir;
}
// Generate timestamped filename with collision detection
private async generateOutputPath(outputDir: string): Promise<string> {
const now = new Date();
const date = now.toISOString().split('T')[0]; // YYYY-MM-DD
const time = now.toTimeString().slice(0, 8).replace(/:/g, ''); // HHMMSS
let outputPath = path.join(outputDir, `insight-${date}.html`);
// Check if date-only file exists, if so, add timestamp
try {
await fs.access(outputPath);
// File exists, use timestamped version
outputPath = path.join(outputDir, `insight-${date}-${time}.html`);
} catch {
// File doesn't exist, use date-only name
}
return outputPath;
}
// Create or update the "latest" alias (symlink preferred, copy as fallback)
private async updateLatestAlias(
outputDir: string,
targetPath: string,
): Promise<void> {
const latestPath = path.join(outputDir, 'insight.html');
const relativeTarget = path.relative(outputDir, targetPath);
// Remove existing file/symlink if it exists
try {
await fs.unlink(latestPath);
} catch {
// File doesn't exist, ignore
}
// Try symlink first (preferred - lightweight, always points to latest)
try {
await fs.symlink(relativeTarget, latestPath);
logger.debug('Created insight symlink:', relativeTarget);
return;
} catch (error) {
logger.debug(
'Failed to create insight symlink, falling back to copy:',
error,
);
}
// Fallback: copy file (works everywhere, uses more disk space)
try {
await fs.copyFile(targetPath, latestPath);
logger.debug('Created insight copy:', targetPath);
} catch (error) {
logger.debug('Failed to create insight latest alias:', error);
}
}
// Generate the static insight HTML file
async generateStaticInsight(
baseDir: string,
@ -52,10 +110,15 @@ export class StaticInsightGenerator {
// Render HTML
const html = await this.templateRenderer.renderInsightHTML(insights);
const outputPath = path.join(outputDir, 'insight.html');
// Generate timestamped output path
const outputPath = await this.generateOutputPath(outputDir);
// Write the HTML file
await fs.writeFile(outputPath, html, 'utf-8');
// Update latest alias (symlink preferred, copy as fallback)
await this.updateLatestAlias(outputDir, outputPath);
return outputPath;
}
}