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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@ Thumbs.db

test-output/

test-img.png

.cursor/
55 changes: 33 additions & 22 deletions src/cli/utils/progress-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cliProgress from 'cli-progress';
import { promises as fs } from 'fs';
import path from 'path';
import { setProgressRecorder } from '../../core/progress-events';
import { GenerateOptions } from '../commands/generate-orchestrator';
import type { GenerateOptions } from '../commands/generate-orchestrator';

export interface ProgressConfig {
total: number;
Expand Down Expand Up @@ -30,6 +30,14 @@ export class ProgressTracker {
'.json', '.xml', '.html'
];

// Type-safe check for optional transparent flag on options
private static hasTransparentFlag(o: unknown): boolean {
if (!o || typeof o !== "object") return false;
const rec = o as Record<string, unknown>;
const t = rec.transparent;
return typeof t === "boolean" && t;
}

/**
* Estimate total files based on generation options (2024 optimized counts)
*/
Expand Down Expand Up @@ -63,7 +71,7 @@ export class ProgressTracker {

// Transparent background generation (single output when used alone)
const hasOtherGenerators = !!(options.all || options.web || options.favicon || options.pwa || options.seo || options.social);
if ((options as any).transparent && !hasOtherGenerators) {
if (ProgressTracker.hasTransparentFlag(options) && !hasOtherGenerators) {
totalFiles += 1; // one transparent image
}

Expand Down Expand Up @@ -94,33 +102,36 @@ export class ProgressTracker {
return files.filter(file =>
this.assetExtensions.some(ext => file.toLowerCase().endsWith(ext))
).length;
} catch (error) {
} catch (_error) {
// Directory might not exist yet
return 0;
}
}

/**
* Start polling the directory for file changes
* Note: not async by design; the interval body handles awaits.
*/
private async startPolling(): Promise<void> {
private startPolling(): void {
if (this.isPolling || !this.outputDirectory || !this.bar) return;

this.isPolling = true;

this.pollingInterval = setInterval(async () => {
try {
const currentFileCount = await this.countAssetFiles(this.outputDirectory);
const newFiles = currentFileCount - this.initialFileCount;

// Update progress based on actual file count
if (newFiles !== this.currentProgress) {
this.currentProgress = Math.min(newFiles, this.totalFiles);
this.bar?.update(this.currentProgress);

this.pollingInterval = setInterval(() => {
void (async () => {
try {
const currentFileCount = await this.countAssetFiles(this.outputDirectory);
const newFiles = currentFileCount - this.initialFileCount;

// Update progress based on actual file count
if (newFiles !== this.currentProgress) {
this.currentProgress = Math.min(newFiles, this.totalFiles);
this.bar?.update(this.currentProgress);
}
} catch (_error) {
// Ignore polling errors to prevent breaking the main functionality
}
} catch (error) {
// Ignore polling errors to prevent breaking the main functionality
}
})();
}, 150); // Poll every 150ms for smooth updates
}

Expand Down Expand Up @@ -180,7 +191,7 @@ export class ProgressTracker {

// If events aren't available for some reason, fallback to polling
if (!this.usingEvents) {
await this.startPolling();
this.startPolling();
}
}

Expand Down Expand Up @@ -226,15 +237,15 @@ export class ProgressTracker {
}

// Deprecated methods - kept for compatibility but no longer needed
increment(amount: number = 1): void {
increment(_amount: number = 1): void {
// No-op - progress is now tracked by file polling
}

update(value: number): void {
update(_value: number): void {
// No-op - progress is now tracked by file polling
}

addFiles(fileCount: number): void {
addFiles(_fileCount: number): void {
// No-op - progress is now tracked by file polling
}

Expand Down
23 changes: 15 additions & 8 deletions src/core/image-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,9 +718,11 @@ export class ImageProcessor {
'info:'
]);

const [wStr, hStr] = sizeOut.trim().split(/\s+/);
const width = parseInt(wStr, 10);
const height = parseInt(hStr, 10);
const sizeParts = sizeOut.toString().trim().split(/\s+/);
const wStr = sizeParts[0] ?? "0";
const hStr = sizeParts[1] ?? "0";
const width = Number.parseInt(wStr, 10);
const height = Number.parseInt(hStr, 10);

// 2) Get raw RGBA for the resized image
const { stdout } = await execFileAsync(magickCmd, [
Expand Down Expand Up @@ -802,11 +804,16 @@ export class ImageProcessor {
if (!onBorder) continue;

const idx = (y * width + x) * c;
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
const a = data[idx + 3];

// Guard against out-of-bounds access when sampling edge pixels
if (idx + 3 >= data.length) {
continue;
}
// Provide safe defaults to satisfy noUncheckedIndexedAccess
const r = data[idx] ?? 0;
const g = data[idx + 1] ?? 0;
const b = data[idx + 2] ?? 0;
const a = data[idx + 3] ?? 0;

totalBorder++;
if (a < alphaThreshold) {
transparentCount++;
Expand Down