viewfit 0.0.1 → 0.1.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/LICENSE +21 -0
- package/README.md +161 -3
- package/dist/cli.js +1451 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +1333 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -6
- package/index.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/browser/inspector.ts","../src/config/devices.ts","../src/browser/collector.ts","../src/core/compress.ts","../src/core/encode.ts","../src/core/pages.ts","../src/utils/url.ts","../src/core/metadata.ts","../src/core/recommend.ts","../src/core/site.ts","../src/core/tiers.ts","../src/report/prompts.ts","../src/utils/bytes.ts","../src/report/table.ts","../src/server/static.ts","../src/config/presets.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { dirname, relative, resolve, sep } from \"node:path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport { inspectPages } from \"./browser/inspector.js\";\nimport { getDevicePreset } from \"./config/devices.js\";\nimport { compressImage } from \"./core/compress.js\";\nimport { findHtmlFiles } from \"./core/pages.js\";\nimport { analyzeImages } from \"./core/recommend.js\";\nimport { copyStaticSite } from \"./core/site.js\";\nimport { resolveTierScale } from \"./core/tiers.js\";\nimport { chooseInteractiveOptions, confirmProceed } from \"./report/prompts.js\";\nimport { renderReport } from \"./report/table.js\";\nimport { startStaticServer, type StaticServer } from \"./server/static.js\";\nimport type { OutputFormat, RunOptions } from \"./types.js\";\nimport { formatBytes } from \"./utils/bytes.js\";\n\nexport * from \"./types.js\";\nexport { analyzeImages, requiredScale } from \"./core/recommend.js\";\nexport { resolveTierScale, parseTier, TIER_PRESETS } from \"./core/tiers.js\";\nexport { DEVICE_PRESETS, getDevicePreset, parseViewport } from \"./config/devices.js\";\nexport { PRESETS, getPreset } from \"./config/presets.js\";\n\nexport async function run(options: RunOptions): Promise<void> {\n const isUrl = /^https?:\\/\\//i.test(options.target);\n let rootDir = options.root ? resolve(options.root) : null;\n let server: StaticServer | null = null;\n let pageUrl = options.target;\n let pageUrls: string[] = [options.target];\n const outputDir = resolve(options.output);\n\n try {\n if (isUrl) {\n if (rootDir && !isDirectory(rootDir)) {\n throw new Error(`Root directory not found: ${rootDir}`);\n }\n } else {\n const targetPath = resolve(options.target);\n if (!existsSync(targetPath)) {\n throw new Error(`Target not found: ${targetPath}`);\n }\n\n const stats = statSync(targetPath);\n\n if (stats.isDirectory()) {\n rootDir = targetPath;\n server = await startStaticServer(targetPath);\n\n const exclude =\n outputDir !== rootDir && outputDir.startsWith(rootDir + sep)\n ? [outputDir]\n : [];\n const htmlFiles = await findHtmlFiles(targetPath, { exclude });\n\n if (htmlFiles.length === 0) {\n throw new Error(`No HTML files found under ${targetPath}.`);\n }\n\n pageUrls = htmlFiles.map((filePath) =>\n toPageUrl(server!.url, rootDir!, filePath),\n );\n pageUrl = pageUrls[0]!;\n } else if (stats.isFile()) {\n let serveDir = resolve(options.root ?? dirname(targetPath));\n let relativeFile = relative(serveDir, targetPath);\n\n if (relativeFile.startsWith(\"..\") || relativeFile === \"\") {\n serveDir = dirname(targetPath);\n relativeFile = relative(serveDir, targetPath);\n }\n\n rootDir = serveDir;\n server = await startStaticServer(serveDir);\n pageUrl = new URL(\n relativeFile.split(sep).join(\"/\"),\n `${server.url}/`,\n ).href;\n pageUrls = [pageUrl];\n } else {\n throw new Error(`Target is not a file or directory: ${targetPath}`);\n }\n }\n\n const preset = getDevicePreset(\n options.device,\n options.dpr,\n options.viewport,\n );\n\n const pageCount = pageUrls.length;\n const inspectLabel =\n pageCount === 1 ? pageUrls[0] : `${pageCount} pages`;\n const inspectSpinner = ora(\n `Inspecting ${inspectLabel} as ${preset.name} ${preset.viewport.width}×${preset.viewport.height} @${preset.deviceScaleFactor}x`,\n ).start();\n\n let inspection;\n try {\n inspection = await inspectPages(pageUrls, {\n device: options.device,\n dpr: preset.deviceScaleFactor,\n viewport: preset.viewport,\n timeout: options.timeout,\n browser: options.browser,\n });\n inspectSpinner.succeed(\n pageCount === 1\n ? `Found ${inspection.images.length} rendered image references`\n : `Found ${inspection.images.length} rendered image references across ${pageCount} pages`,\n );\n\n if (inspection.failures.length > 0) {\n console.log(\n chalk.yellow(\n `${inspection.failures.length} page(s) could not be inspected:`,\n ),\n );\n for (const failure of inspection.failures) {\n console.log(\n chalk.yellow(` - ${failure.pageUrl}: ${failure.message}`),\n );\n }\n }\n } catch (error) {\n inspectSpinner.fail(\"Inspection failed\");\n throw error;\n }\n\n if (inspection.images.length === 0) {\n console.log(chalk.yellow(\"No rendered images found on the page(s).\"));\n return;\n }\n\n let tier = options.tier;\n let format: OutputFormat = options.format;\n let quality = options.quality;\n\n if (options.interactive) {\n const chosen = await chooseInteractiveOptions(\n { tier, format, quality },\n inspection.images.length,\n );\n tier = chosen.tier;\n format = chosen.format;\n quality = chosen.quality;\n }\n\n if (options.write && format !== \"original\") {\n throw new Error(\n \"--in-place keeps the original file names, so it requires --format original. Use --out to convert formats.\",\n );\n }\n\n const tierScale = resolveTierScale(tier);\n\n const analyzeSpinner = ora(\"Analyzing image dimensions and sizes...\").start();\n\n let analyses;\n try {\n analyses = await analyzeImages(inspection.images, {\n dpr: inspection.dpr,\n tierScale,\n quality,\n format,\n pageUrl,\n rootDir,\n estimate: true,\n });\n analyzeSpinner.succeed(`Analyzed ${analyses.length} unique images`);\n } catch (error) {\n analyzeSpinner.fail(\"Analysis failed\");\n throw error;\n }\n\n if (options.json) {\n console.log(JSON.stringify(analyses, null, 2));\n return;\n }\n\n console.log(\"\");\n console.log(\n renderReport(analyses, {\n rootDir,\n deviceName: inspection.deviceName,\n viewport: inspection.viewport,\n dpr: inspection.dpr,\n tierScale,\n format,\n quality,\n pageCount: inspection.pages.length,\n }),\n );\n console.log(\"\");\n\n if (options.dryRun) {\n return;\n }\n\n const proceed = await confirmProceed(analyses, {\n write: options.write,\n output: options.output,\n yes: options.yes,\n });\n\n if (!proceed) {\n console.log(chalk.yellow(\"Nothing to do.\"));\n return;\n }\n\n const selected = analyses.filter((analysis) => analysis.recommended);\n\n if (!options.write && rootDir && outputDir === rootDir) {\n throw new Error(\n \"--out cannot be the project root. Choose a subfolder or use --in-place.\",\n );\n }\n\n const compressSpinner = ora(`Compressing ${selected.length} images...`).start();\n\n let originalBytes = 0;\n let outputBytes = 0;\n const failures: string[] = [];\n\n for (const analysis of selected) {\n try {\n const result = await compressImage(analysis, {\n format,\n quality,\n outputDir,\n write: options.write,\n rootDir,\n });\n originalBytes += analysis.originalBytes;\n outputBytes += result.bytes;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n failures.push(`${analysis.filePath ?? analysis.url}: ${message}`);\n }\n }\n\n const saved = originalBytes - outputBytes;\n compressSpinner.succeed(\n `Compressed ${selected.length - failures.length} images · ${formatBytes(\n originalBytes,\n )} → ${formatBytes(outputBytes)} · saved ${formatBytes(Math.max(saved, 0))}`,\n );\n\n if (failures.length > 0) {\n console.log(chalk.red(`\\n${failures.length} image(s) failed:`));\n for (const failure of failures) {\n console.log(chalk.red(` - ${failure}`));\n }\n process.exitCode = 1;\n }\n\n if (options.site) {\n if (!rootDir) {\n console.log(\n chalk.yellow(\"--site requires a local root; site copy was skipped.\"),\n );\n } else {\n const siteSpinner = ora(\"Copying site files...\").start();\n\n try {\n const analyzed = new Set(\n analyses\n .map((analysis) => analysis.filePath)\n .filter((filePath): filePath is string => Boolean(filePath)),\n );\n const optimized = new Set(\n selected\n .map((analysis) => analysis.filePath)\n .filter((filePath): filePath is string => Boolean(filePath)),\n );\n\n const siteResult = await copyStaticSite({\n rootDir,\n outputDir,\n analyzed,\n optimized,\n });\n\n siteSpinner.succeed(\n `Copied ${siteResult.copiedFiles} files · rewrote ${siteResult.rewrittenReferences} image references`,\n );\n } catch (error) {\n siteSpinner.fail(\"Site copy failed\");\n throw error;\n }\n }\n }\n\n if (options.write) {\n console.log(chalk.dim(\"Original files were overwritten.\"));\n } else {\n console.log(chalk.dim(`Output written to ${outputDir}`));\n }\n } finally {\n if (server) {\n await server.close();\n }\n }\n}\n\nfunction isDirectory(path: string): boolean {\n return existsSync(path) && statSync(path).isDirectory();\n}\n\nfunction toPageUrl(\n serverUrl: string,\n rootDir: string,\n filePath: string,\n): string {\n const relativePath = relative(rootDir, filePath).split(sep).join(\"/\");\n return new URL(relativePath, `${serverUrl}/`).href;\n}\n","import { chromium, type Browser, type Page } from \"playwright\";\nimport { getDevicePreset } from \"../config/devices.js\";\nimport type { CollectedImage, InspectOptions } from \"../types.js\";\nimport { collectImagesInPage } from \"./collector.js\";\n\nexport interface InspectionFailure {\n pageUrl: string;\n message: string;\n}\n\nexport interface InspectionResult {\n images: CollectedImage[];\n pageUrl: string;\n pages: string[];\n failures: InspectionFailure[];\n deviceName: string;\n viewport: { width: number; height: number };\n dpr: number;\n}\n\nasync function launchBrowser(channel: string): Promise<Browser> {\n const requested = channel === \"chromium\" ? undefined : channel;\n\n try {\n return await chromium.launch({ channel: requested, headless: true });\n } catch (error) {\n if (requested) {\n throw error;\n }\n\n try {\n return await chromium.launch({ channel: \"chrome\", headless: true });\n } catch {\n throw new Error(\n \"Unable to launch Chromium. Install it with `npx playwright install chromium` or use `--browser chrome` if Google Chrome is installed.\",\n );\n }\n }\n}\n\nasync function autoScroll(page: Page): Promise<void> {\n await page.evaluate(async () => {\n const delay = (ms: number) =>\n new Promise<void>((resolvePromise) => setTimeout(resolvePromise, ms));\n\n const distance = Math.max(window.innerHeight, 400);\n const height = document.body?.scrollHeight ?? document.documentElement.scrollHeight;\n\n for (let y = 0; y < height; y += distance) {\n window.scrollTo(0, y);\n await delay(60);\n }\n\n window.scrollTo(0, 0);\n await delay(100);\n });\n}\n\nasync function decodeImages(page: Page): Promise<void> {\n await page.evaluate(async () => {\n await Promise.all(\n Array.from(document.images).map((image) =>\n image.decode().catch(() => undefined),\n ),\n );\n });\n}\n\nasync function inspectPageInContext(\n page: Page,\n pageUrl: string,\n timeout: number,\n): Promise<CollectedImage[]> {\n await page.goto(pageUrl, {\n waitUntil: \"load\",\n timeout,\n });\n\n await page\n .waitForLoadState(\"networkidle\", { timeout: 5000 })\n .catch(() => undefined);\n\n await autoScroll(page);\n await decodeImages(page);\n\n return (await page.evaluate(collectImagesInPage)) as CollectedImage[];\n}\n\nexport async function inspectPages(\n pageUrls: string[],\n options: InspectOptions,\n): Promise<InspectionResult> {\n const preset = getDevicePreset(options.device, options.dpr, options.viewport);\n const browser = await launchBrowser(options.browser);\n\n try {\n const context = await browser.newContext({\n viewport: preset.viewport,\n deviceScaleFactor: preset.deviceScaleFactor,\n isMobile: preset.isMobile,\n hasTouch: preset.isMobile,\n });\n\n const images: CollectedImage[] = [];\n const failures: InspectionFailure[] = [];\n\n try {\n for (const pageUrl of pageUrls) {\n const page = await context.newPage();\n\n try {\n images.push(...(await inspectPageInContext(page, pageUrl, options.timeout)));\n } catch (error) {\n failures.push({\n pageUrl,\n message: error instanceof Error ? error.message : String(error),\n });\n } finally {\n await page.close();\n }\n }\n } finally {\n await context.close();\n }\n\n return {\n images,\n pageUrl: pageUrls[0] ?? \"\",\n pages: pageUrls,\n failures,\n deviceName: preset.name,\n viewport: preset.viewport,\n dpr: preset.deviceScaleFactor,\n };\n } finally {\n await browser.close();\n }\n}\n\nexport async function inspectPage(\n pageUrl: string,\n options: InspectOptions,\n): Promise<InspectionResult> {\n return inspectPages([pageUrl], options);\n}\n","import type { DeviceMode, DevicePreset, Viewport } from \"../types.js\";\n\nexport const DEVICE_PRESETS: Record<DeviceMode, DevicePreset> = {\n desktop: {\n name: \"Desktop\",\n viewport: { width: 1440, height: 900 },\n deviceScaleFactor: 1,\n isMobile: false,\n },\n mobile: {\n name: \"Mobile\",\n viewport: { width: 390, height: 844 },\n deviceScaleFactor: 3,\n isMobile: true,\n },\n};\n\nexport function isDeviceMode(value: string): value is DeviceMode {\n return value === \"desktop\" || value === \"mobile\";\n}\n\nexport function parseViewport(value: string): Viewport {\n const match = /^(\\d+)x(\\d+)$/i.exec(value.trim());\n if (!match) {\n throw new Error(`Invalid viewport \"${value}\". Expected a value like 1440x900.`);\n }\n\n const width = Number(match[1]);\n const height = Number(match[2]);\n\n if (width <= 0 || height <= 0) {\n throw new Error(\"Viewport dimensions must be positive.\");\n }\n\n return { width, height };\n}\n\nexport function getDevicePreset(\n mode: DeviceMode,\n dpr?: number,\n viewport?: Viewport,\n): DevicePreset {\n const base = DEVICE_PRESETS[mode];\n return {\n ...base,\n viewport: viewport ?? base.viewport,\n deviceScaleFactor: dpr ?? base.deviceScaleFactor,\n };\n}\n","import type { CollectedImage, ImageFit } from \"../types.js\";\n\ninterface BrowserImage {\n src: string;\n kind: \"img\" | \"background\";\n cssWidth: number;\n cssHeight: number;\n fit: ImageFit;\n selector: string;\n}\n\nexport function collectImagesInPage(): BrowserImage[] {\n const results: BrowserImage[] = [];\n const absolute = (value: string): string => {\n try {\n return new URL(value, document.baseURI).href;\n } catch {\n return value;\n }\n };\n\n const selectorFor = (element: Element): string => {\n const tag = element.tagName.toLowerCase();\n if (element.id) {\n return `#${CSS.escape(element.id)}`;\n }\n const className = element.getAttribute(\"class\");\n if (className) {\n const classes = className\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map((name) => `.${CSS.escape(name)}`)\n .join(\"\");\n if (classes) {\n return `${tag}${classes}`;\n }\n }\n return tag;\n };\n\n const normalizeFit = (value: string): ImageFit => {\n switch (value) {\n case \"cover\":\n case \"contain\":\n case \"fill\":\n case \"none\":\n case \"scale-down\":\n return value;\n default:\n return \"fill\";\n }\n };\n\n const normalizeBackgroundFit = (value: string): ImageFit => {\n const normalized = value.trim().toLowerCase();\n if (normalized === \"cover\" || normalized === \"contain\") {\n return normalized;\n }\n if (normalized === \"auto\" || normalized === \"\") {\n return \"auto\";\n }\n return \"fill\";\n };\n\n const backgroundUrls = (value: string): string[] => {\n const urls: string[] = [];\n const pattern = /url\\((['\"]?)(.*?)\\1\\)/g;\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(value)) !== null) {\n const url = match[2];\n if (url && !url.startsWith(\"data:\")) {\n urls.push(url);\n }\n }\n\n return urls;\n };\n\n for (const image of Array.from(document.images)) {\n const rect = image.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) {\n continue;\n }\n\n const src = image.currentSrc || image.src;\n if (!src || src.startsWith(\"data:\")) {\n continue;\n }\n\n results.push({\n src: absolute(src),\n kind: \"img\",\n cssWidth: rect.width,\n cssHeight: rect.height,\n fit: normalizeFit(getComputedStyle(image).objectFit),\n selector: selectorFor(image),\n });\n }\n\n const elements = Array.from(document.querySelectorAll<HTMLElement>(\"*\"));\n\n const addBackground = (\n element: HTMLElement,\n pseudo: string,\n style: CSSStyleDeclaration,\n ): void => {\n if (!style.backgroundImage || style.backgroundImage === \"none\") {\n return;\n }\n\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) {\n return;\n }\n\n const fit = normalizeBackgroundFit(style.backgroundSize);\n const selector = `${selectorFor(element)}${pseudo}`;\n\n for (const url of backgroundUrls(style.backgroundImage)) {\n results.push({\n src: absolute(url),\n kind: \"background\",\n cssWidth: rect.width,\n cssHeight: rect.height,\n fit,\n selector,\n });\n }\n };\n\n for (const element of elements) {\n addBackground(element, \"\", getComputedStyle(element));\n addBackground(element, \"::before\", getComputedStyle(element, \"::before\"));\n addBackground(element, \"::after\", getComputedStyle(element, \"::after\"));\n }\n\n return results;\n}\n\nexport type { CollectedImage };\n","import { mkdir, rename, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, relative } from \"node:path\";\nimport type { ImageAnalysis, OutputFormat } from \"../types.js\";\nimport { encodeImage, extensionFor } from \"./encode.js\";\n\nexport interface CompressOptions {\n format: OutputFormat;\n quality: number;\n outputDir: string;\n write: boolean;\n rootDir: string | null;\n}\n\nexport interface CompressResult {\n input: string;\n output: string;\n bytes: number;\n mode: \"overwrite\" | \"output\" | \"skipped\";\n}\n\nfunction outputPathFor(\n analysis: ImageAnalysis,\n options: CompressOptions,\n): string {\n const source = analysis.filePath as string;\n const relativePath = options.rootDir\n ? relative(options.rootDir, source)\n : basename(source);\n const extension = extensionFor(options.format, analysis.originalFormat);\n const withoutExtension = relativePath.replace(/\\.[^./\\\\]+$/, \"\");\n return join(options.outputDir, `${withoutExtension}.${extension}`);\n}\n\nexport async function compressImage(\n analysis: ImageAnalysis,\n options: CompressOptions,\n): Promise<CompressResult> {\n if (!analysis.filePath || !analysis.recommended) {\n return {\n input: analysis.filePath ?? analysis.url,\n output: \"\",\n bytes: 0,\n mode: \"skipped\",\n };\n }\n\n const buffer = await encodeImage(\n analysis.filePath,\n analysis.targetWidth,\n analysis.targetHeight,\n {\n format: options.format,\n quality: options.quality,\n originalFormat: analysis.originalFormat,\n },\n );\n\n if (options.write) {\n const temporaryPath = `${analysis.filePath}.viewfit-${process.pid}.tmp`;\n await writeFile(temporaryPath, buffer);\n await rename(temporaryPath, analysis.filePath);\n\n return {\n input: analysis.filePath,\n output: analysis.filePath,\n bytes: buffer.length,\n mode: \"overwrite\",\n };\n }\n\n const outputPath = outputPathFor(analysis, options);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, buffer);\n\n return {\n input: analysis.filePath,\n output: outputPath,\n bytes: buffer.length,\n mode: \"output\",\n };\n}\n","import sharp, { type Sharp } from \"sharp\";\nimport type { OutputFormat } from \"../types.js\";\n\nexport interface EncodeOptions {\n format: OutputFormat;\n quality: number;\n originalFormat: string | null;\n}\n\nexport function extensionFor(\n format: OutputFormat,\n originalFormat: string | null,\n): string {\n const target = format === \"original\" ? originalFormat ?? \"jpg\" : format;\n return target === \"jpeg\" ? \"jpg\" : target;\n}\n\nfunction applyFormat(pipeline: Sharp, options: EncodeOptions): Sharp {\n const target =\n options.format === \"original\" ? options.originalFormat : options.format;\n const quality = options.quality;\n\n switch (target) {\n case \"jpeg\":\n case \"jpg\":\n return pipeline.jpeg({ quality, mozjpeg: true });\n case \"png\":\n return pipeline.png({ compressionLevel: 9 });\n case \"webp\":\n return pipeline.webp({ quality });\n case \"avif\":\n return pipeline.avif({ quality });\n case \"gif\":\n return pipeline.gif();\n default:\n return pipeline;\n }\n}\n\nexport async function encodeImage(\n input: string,\n width: number,\n height: number,\n options: EncodeOptions,\n): Promise<Buffer> {\n const pipeline = sharp(input)\n .rotate()\n .resize({\n width,\n height,\n fit: \"inside\",\n withoutEnlargement: true,\n });\n\n return applyFormat(pipeline, options).toBuffer();\n}\n\nexport async function estimateEncodedSize(\n input: string,\n width: number,\n height: number,\n options: EncodeOptions,\n): Promise<number | null> {\n try {\n const buffer = await encodeImage(input, width, height, options);\n return buffer.length;\n } catch {\n return null;\n }\n}\n","import { readdir } from \"node:fs/promises\";\nimport { extname, join, resolve, sep } from \"node:path\";\n\nconst HTML_EXTENSIONS = new Set([\".html\", \".htm\"]);\nconst IGNORED_DIRECTORIES = new Set([\"node_modules\", \".git\"]);\n\nexport interface FindHtmlOptions {\n exclude?: string[];\n}\n\nexport async function findHtmlFiles(\n rootDir: string,\n options: FindHtmlOptions = {},\n): Promise<string[]> {\n const root = resolve(rootDir);\n const excluded = (options.exclude ?? [])\n .map((path) => resolve(path))\n .filter((path) => path !== root);\n const results: string[] = [];\n\n async function walk(directory: string): Promise<void> {\n const entries = await readdir(directory, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(directory, entry.name);\n\n if (entry.isSymbolicLink()) {\n continue;\n }\n\n if (entry.isDirectory()) {\n if (IGNORED_DIRECTORIES.has(entry.name)) {\n continue;\n }\n\n const resolved = resolve(fullPath);\n if (\n excluded.some(\n (path) => resolved === path || resolved.startsWith(path + sep),\n )\n ) {\n continue;\n }\n\n await walk(fullPath);\n continue;\n }\n\n if (entry.isFile() && HTML_EXTENSIONS.has(extname(entry.name).toLowerCase())) {\n results.push(fullPath);\n }\n }\n }\n\n await walk(root);\n return results.sort();\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve, sep } from \"node:path\";\n\nexport function urlToFilePath(\n url: string,\n rootDir: string,\n pageUrl: string,\n): string | null {\n let parsed: URL;\n let base: URL;\n\n try {\n parsed = new URL(url);\n base = new URL(pageUrl);\n } catch {\n return null;\n }\n\n if (parsed.origin !== base.origin) {\n return null;\n }\n\n const root = resolve(rootDir);\n const relativePath = decodeURIComponent(parsed.pathname).replace(/^\\/+/, \"\");\n\n if (relativePath === \"\" || relativePath.includes(\"\\0\")) {\n return null;\n }\n\n const filePath = resolve(root, relativePath);\n\n if (filePath !== root && !filePath.startsWith(root + sep)) {\n return null;\n }\n\n return existsSync(filePath) ? filePath : null;\n}\n","import { stat } from \"node:fs/promises\";\nimport sharp from \"sharp\";\n\nexport interface ImageMetadata {\n width: number;\n height: number;\n format: string | null;\n bytes: number;\n}\n\nexport async function readImageMetadata(\n filePath: string,\n): Promise<ImageMetadata> {\n const [metadata, fileInfo] = await Promise.all([\n sharp(filePath).metadata(),\n stat(filePath),\n ]);\n\n if (!metadata.width || !metadata.height) {\n throw new Error(\"Unable to read image dimensions.\");\n }\n\n const swapped =\n typeof metadata.orientation === \"number\" && metadata.orientation >= 5;\n\n return {\n width: swapped ? metadata.height : metadata.width,\n height: swapped ? metadata.width : metadata.height,\n format: metadata.format ?? null,\n bytes: fileInfo.size,\n };\n}\n","import type {\n AnalyzeOptions,\n CollectedImage,\n ImageAnalysis,\n ImageContext,\n ImageFit,\n} from \"../types.js\";\nimport { urlToFilePath } from \"../utils/url.js\";\nimport { estimateEncodedSize } from \"./encode.js\";\nimport { readImageMetadata } from \"./metadata.js\";\n\nexport function requiredScale(\n fit: ImageFit,\n cssWidth: number,\n cssHeight: number,\n naturalWidth: number,\n naturalHeight: number,\n dpr: number,\n): number {\n const widthRatio = (cssWidth * dpr) / naturalWidth;\n const heightRatio = (cssHeight * dpr) / naturalHeight;\n\n switch (fit) {\n case \"contain\":\n case \"scale-down\":\n return Math.min(widthRatio, heightRatio, 1);\n case \"none\":\n case \"auto\":\n return 1;\n case \"cover\":\n case \"fill\":\n default:\n return Math.min(Math.max(widthRatio, heightRatio), 1);\n }\n}\n\nfunction groupByUrl(images: CollectedImage[]): Map<string, ImageContext[]> {\n const groups = new Map<string, ImageContext[]>();\n\n for (const image of images) {\n const contexts = groups.get(image.src) ?? [];\n contexts.push({\n kind: image.kind,\n cssWidth: image.cssWidth,\n cssHeight: image.cssHeight,\n fit: image.fit,\n selector: image.selector,\n });\n groups.set(image.src, contexts);\n }\n\n return groups;\n}\n\nfunction emptyAnalysis(\n url: string,\n contexts: ImageContext[],\n options: AnalyzeOptions,\n reason: string,\n): ImageAnalysis {\n const renderedWidth = Math.max(...contexts.map((context) => context.cssWidth));\n const renderedHeight = Math.max(\n ...contexts.map((context) => context.cssHeight),\n );\n\n return {\n url,\n filePath: null,\n originalWidth: 0,\n originalHeight: 0,\n originalBytes: 0,\n originalFormat: null,\n renderedWidth,\n renderedHeight,\n contexts,\n targetWidth: Math.max(\n 1,\n Math.round(renderedWidth * options.dpr * options.tierScale),\n ),\n targetHeight: Math.max(\n 1,\n Math.round(renderedHeight * options.dpr * options.tierScale),\n ),\n estimatedBytes: null,\n savingsBytes: null,\n savingsPercent: null,\n transformable: false,\n recommended: false,\n reason,\n };\n}\n\nexport async function analyzeImages(\n images: CollectedImage[],\n options: AnalyzeOptions,\n): Promise<ImageAnalysis[]> {\n const groups = groupByUrl(images);\n const analyses: ImageAnalysis[] = [];\n\n for (const [url, contexts] of groups) {\n const filePath = options.rootDir\n ? urlToFilePath(url, options.rootDir, options.pageUrl)\n : null;\n\n if (!filePath) {\n analyses.push(\n emptyAnalysis(\n url,\n contexts,\n options,\n options.rootDir\n ? \"File not found under the project root (remote or generated asset).\"\n : \"No local root provided; reporting only.\",\n ),\n );\n continue;\n }\n\n let metadata;\n try {\n metadata = await readImageMetadata(filePath);\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Unable to read image.\";\n analyses.push(emptyAnalysis(url, contexts, options, message));\n continue;\n }\n\n const scale = contexts.reduce(\n (current, context) =>\n Math.max(\n current,\n requiredScale(\n context.fit,\n context.cssWidth,\n context.cssHeight,\n metadata.width,\n metadata.height,\n options.dpr,\n ),\n ),\n 0,\n );\n\n const targetWidth = Math.max(\n 1,\n Math.min(\n metadata.width,\n Math.round(metadata.width * scale * options.tierScale),\n ),\n );\n const targetHeight = Math.max(\n 1,\n Math.min(\n metadata.height,\n Math.round(metadata.height * scale * options.tierScale),\n ),\n );\n\n const renderedWidth = Math.max(\n ...contexts.map((context) => context.cssWidth),\n );\n const renderedHeight = Math.max(\n ...contexts.map((context) => context.cssHeight),\n );\n\n const resizeNeeded =\n targetWidth < metadata.width || targetHeight < metadata.height;\n const formatChanges =\n options.format !== \"original\" &&\n options.format !== (metadata.format ?? undefined);\n\n let estimatedBytes: number | null = null;\n const willTransform = resizeNeeded || formatChanges;\n\n if (willTransform && options.estimate) {\n estimatedBytes = await estimateEncodedSize(\n filePath,\n targetWidth,\n targetHeight,\n {\n format: options.format,\n quality: options.quality,\n originalFormat: metadata.format,\n },\n );\n }\n\n const savingsBytes =\n estimatedBytes === null ? null : metadata.bytes - estimatedBytes;\n const savingsPercent =\n savingsBytes === null || metadata.bytes === 0\n ? null\n : (savingsBytes / metadata.bytes) * 100;\n\n analyses.push({\n url,\n filePath,\n originalWidth: metadata.width,\n originalHeight: metadata.height,\n originalBytes: metadata.bytes,\n originalFormat: metadata.format,\n renderedWidth,\n renderedHeight,\n contexts,\n targetWidth,\n targetHeight,\n estimatedBytes,\n savingsBytes,\n savingsPercent,\n transformable: willTransform,\n recommended:\n willTransform && estimatedBytes !== null && estimatedBytes < metadata.bytes,\n reason: willTransform\n ? undefined\n : \"Already at or below the rendered size.\",\n });\n }\n\n return analyses;\n}\n","import { existsSync } from \"node:fs\";\nimport {\n copyFile,\n mkdir,\n readdir,\n readFile,\n writeFile,\n} from \"node:fs/promises\";\nimport { dirname, extname, join, relative, resolve, sep } from \"node:path\";\n\nconst TEXT_EXTENSIONS = new Set([\n \".html\",\n \".htm\",\n \".css\",\n \".js\",\n \".mjs\",\n \".cjs\",\n \".jsx\",\n \".ts\",\n \".tsx\",\n \".json\",\n \".svg\",\n \".txt\",\n \".xml\",\n \".map\",\n]);\n\nconst IMAGE_EXTENSIONS = new Set([\n \".jpg\",\n \".jpeg\",\n \".png\",\n \".gif\",\n \".webp\",\n \".avif\",\n \".tif\",\n \".tiff\",\n \".bmp\",\n]);\n\nconst IGNORED_DIRECTORIES = new Set([\"node_modules\", \".git\"]);\n\nconst REFERENCE_EXTENSIONS = [\"webp\", \"avif\", \"png\", \"jpg\", \"jpeg\"];\n\nconst IMAGE_REFERENCE =\n /([A-Za-z0-9_@%+~.,/\\-]+)\\.(jpe?g|png|gif|tiff?|bmp)(?=([\"')\\s?#>]|$))/gi;\n\nexport interface CopySiteOptions {\n rootDir: string;\n outputDir: string;\n analyzed: Set<string>;\n optimized: Set<string>;\n}\n\nexport interface CopySiteResult {\n copiedFiles: number;\n rewrittenFiles: number;\n rewrittenReferences: number;\n}\n\ninterface RewriteResult {\n content: string;\n replacements: number;\n}\n\nexport function rewriteImageReferences(\n content: string,\n textRelativePath: string,\n outputDir: string,\n): RewriteResult {\n const textDirectory = dirname(textRelativePath);\n let replacements = 0;\n\n const rewritten = content.replace(\n IMAGE_REFERENCE,\n (full: string, rawPath: string, extension: string) => {\n if (\n rawPath.includes(\"://\") ||\n rawPath.startsWith(\"//\") ||\n rawPath.startsWith(\"data:\")\n ) {\n return full;\n }\n\n const referenceDirectory = rawPath.startsWith(\"/\")\n ? \"\"\n : textDirectory;\n const relativeReference = rawPath.startsWith(\"/\")\n ? rawPath.slice(1)\n : join(referenceDirectory, rawPath);\n const outputReference = resolve(outputDir, relativeReference);\n const withoutExtension = outputReference.slice(\n 0,\n outputReference.length - extname(outputReference).length,\n );\n const current = extension.toLowerCase();\n\n for (const candidate of REFERENCE_EXTENSIONS) {\n const sameFormat =\n candidate === current ||\n (candidate === \"jpg\" && current === \"jpeg\") ||\n (candidate === \"jpeg\" && current === \"jpg\");\n\n if (sameFormat) {\n continue;\n }\n\n if (existsSync(`${withoutExtension}.${candidate}`)) {\n replacements += 1;\n return `${rawPath}.${candidate}`;\n }\n }\n\n return full;\n },\n );\n\n return { content: rewritten, replacements };\n}\n\nasync function walk(\n currentDirectory: string,\n options: CopySiteOptions,\n result: CopySiteResult,\n): Promise<void> {\n const entries = await readdir(currentDirectory, { withFileTypes: true });\n\n for (const entry of entries) {\n const sourcePath = join(currentDirectory, entry.name);\n\n if (entry.isSymbolicLink()) {\n continue;\n }\n\n if (entry.isDirectory()) {\n if (IGNORED_DIRECTORIES.has(entry.name)) {\n continue;\n }\n\n const resolved = resolve(sourcePath);\n if (\n resolved === options.outputDir ||\n resolved.startsWith(options.outputDir + sep)\n ) {\n continue;\n }\n\n await walk(sourcePath, options, result);\n continue;\n }\n\n if (!entry.isFile()) {\n continue;\n }\n\n const absoluteSource = resolve(sourcePath);\n if (options.optimized.has(absoluteSource)) {\n continue;\n }\n\n const relativePath = relative(options.rootDir, sourcePath);\n const destinationPath = join(options.outputDir, relativePath);\n const extension = extname(sourcePath).toLowerCase();\n\n if (TEXT_EXTENSIONS.has(extension)) {\n const original = await readFile(sourcePath, \"utf8\");\n const { content, replacements } = rewriteImageReferences(\n original,\n relativePath,\n options.outputDir,\n );\n\n await mkdir(dirname(destinationPath), { recursive: true });\n await writeFile(destinationPath, content);\n result.copiedFiles += 1;\n\n if (replacements > 0) {\n result.rewrittenFiles += 1;\n result.rewrittenReferences += replacements;\n }\n\n continue;\n }\n\n if (IMAGE_EXTENSIONS.has(extension)) {\n if (!options.analyzed.has(absoluteSource)) {\n continue;\n }\n\n if (existsSync(destinationPath)) {\n continue;\n }\n\n await mkdir(dirname(destinationPath), { recursive: true });\n await copyFile(sourcePath, destinationPath);\n result.copiedFiles += 1;\n continue;\n }\n\n if (existsSync(destinationPath)) {\n continue;\n }\n\n await mkdir(dirname(destinationPath), { recursive: true });\n await copyFile(sourcePath, destinationPath);\n result.copiedFiles += 1;\n }\n}\n\nexport async function copyStaticSite(\n options: CopySiteOptions,\n): Promise<CopySiteResult> {\n const result: CopySiteResult = {\n copiedFiles: 0,\n rewrittenFiles: 0,\n rewrittenReferences: 0,\n };\n\n await walk(resolve(options.rootDir), {\n ...options,\n rootDir: resolve(options.rootDir),\n outputDir: resolve(options.outputDir),\n }, result);\n\n return result;\n}\n","import type { Tier, TierName } from \"../types.js\";\n\nexport const TIER_PRESETS: Record<TierName, number> = {\n exact: 1,\n safe: 0.9,\n aggressive: 0.75,\n extreme: 0.5,\n};\n\nexport const TIER_DESCRIPTIONS: Record<TierName, string> = {\n exact: \"Exact rendered size (100%)\",\n safe: \"Slightly below rendered size (90%)\",\n aggressive: \"Clearly below rendered size (75%)\",\n extreme: \"Half of the rendered size (50%)\",\n};\n\nexport function isTierName(value: string): value is TierName {\n return Object.prototype.hasOwnProperty.call(TIER_PRESETS, value);\n}\n\nexport function resolveTierScale(tier: Tier): number {\n if (typeof tier === \"number\") {\n const scale = tier > 1 ? tier / 100 : tier;\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new Error(\"Tier number must be a positive percentage or ratio.\");\n }\n return Math.min(scale, 1);\n }\n\n const preset = TIER_PRESETS[tier];\n if (preset === undefined) {\n throw new Error(`Unknown tier \"${tier}\".`);\n }\n return preset;\n}\n\nexport function parseTier(value: string): Tier {\n const normalized = value.trim().toLowerCase();\n\n if (isTierName(normalized)) {\n return normalized;\n }\n\n const numeric = Number(normalized);\n if (Number.isFinite(numeric) && numeric > 0) {\n return numeric > 1 ? numeric / 100 : numeric;\n }\n\n throw new Error(\n `Invalid tier \"${value}\". Use exact, safe, aggressive, extreme or a number between 1 and 100.`,\n );\n}\n","import prompts from \"prompts\";\nimport type {\n ImageAnalysis,\n OutputFormat,\n Tier,\n TierName,\n} from \"../types.js\";\nimport { formatBytes } from \"../utils/bytes.js\";\nimport { TIER_DESCRIPTIONS } from \"../core/tiers.js\";\n\nexport interface InteractiveOptions {\n tier: Tier;\n format: OutputFormat;\n quality: number;\n}\n\nexport async function chooseInteractiveOptions(\n defaults: InteractiveOptions,\n imageCount: number,\n): Promise<InteractiveOptions> {\n const tierChoices: Array<{ title: string; value: TierName | \"custom\" }> = (\n Object.entries(TIER_DESCRIPTIONS) as Array<[TierName, string]>\n ).map(([value, title]) => ({ title, value }));\n\n tierChoices.push({ title: \"Custom percentage\", value: \"custom\" });\n\n const tierResponse = await prompts({\n type: \"select\",\n name: \"tier\",\n message: `Compression tier for ${imageCount} images`,\n choices: tierChoices,\n initial: Math.max(\n 0,\n tierChoices.findIndex((choice) => choice.value === defaults.tier),\n ),\n });\n\n let tier: Tier = defaults.tier;\n\n if (tierResponse.tier === \"custom\") {\n const customResponse = await prompts({\n type: \"number\",\n name: \"percentage\",\n message: \"Target scale as a percentage of the exact rendered size\",\n initial: 80,\n min: 1,\n max: 100,\n });\n tier = Number(customResponse.percentage ?? 80);\n } else if (tierResponse.tier) {\n tier = tierResponse.tier as TierName;\n }\n\n const formatResponse = await prompts({\n type: \"select\",\n name: \"format\",\n message: \"Output format\",\n choices: [\n { title: \"Keep original format\", value: \"original\" },\n { title: \"WebP\", value: \"webp\" },\n { title: \"AVIF\", value: \"avif\" },\n ],\n initial: [\"original\", \"webp\", \"avif\"].indexOf(defaults.format),\n });\n\n const qualityResponse = await prompts({\n type: \"number\",\n name: \"quality\",\n message: \"Encoder quality (1-100)\",\n initial: defaults.quality,\n min: 1,\n max: 100,\n });\n\n return {\n tier,\n format: (formatResponse.format as OutputFormat) ?? defaults.format,\n quality: Number(qualityResponse.quality ?? defaults.quality),\n };\n}\n\nexport async function confirmProceed(\n analyses: ImageAnalysis[],\n options: { write: boolean; output: string; yes: boolean },\n): Promise<boolean> {\n const selected = analyses.filter((analysis) => analysis.recommended);\n\n if (selected.length === 0) {\n return false;\n }\n\n if (options.yes) {\n return true;\n }\n\n const totalOriginal = selected.reduce(\n (total, analysis) => total + analysis.originalBytes,\n 0,\n );\n const totalEstimated = selected.reduce(\n (total, analysis) => total + (analysis.estimatedBytes ?? analysis.originalBytes),\n 0,\n );\n\n const destination = options.write\n ? \"overwrite the original files\"\n : `write to ${options.output} (original files are kept)`;\n\n const response = await prompts({\n type: \"confirm\",\n name: \"proceed\",\n message: `Compress ${selected.length} images (${formatBytes(\n totalOriginal,\n )} → ${formatBytes(totalEstimated)}) and ${destination}?`,\n initial: true,\n });\n\n return Boolean(response.proceed);\n}\n","const UNITS = [\"B\", \"KB\", \"MB\", \"GB\"] as const;\n\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) {\n return \"-\";\n }\n\n let value = bytes;\n let unit = 0;\n\n while (value >= 1024 && unit < UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n\n const digits = unit === 0 ? 0 : value >= 100 ? 0 : value >= 10 ? 1 : 2;\n return `${value.toFixed(digits)} ${UNITS[unit]}`;\n}\n","import chalk from \"chalk\";\nimport type { ImageAnalysis, OutputFormat, Viewport } from \"../types.js\";\nimport { formatBytes } from \"../utils/bytes.js\";\n\nexport interface ReportOptions {\n rootDir: string | null;\n deviceName: string;\n viewport: Viewport;\n dpr: number;\n tierScale: number;\n format: OutputFormat;\n quality: number;\n pageCount?: number;\n}\n\ninterface Row {\n file: string;\n kind: string;\n rendered: string;\n target: string;\n original: string;\n estimated: string;\n savings: string;\n recommended: boolean;\n reason: string;\n}\n\nconst MAX_FILE_WIDTH = 44;\n\nfunction displayPath(analysis: ImageAnalysis, rootDir: string | null): string {\n if (analysis.filePath && rootDir) {\n const normalizedRoot = rootDir.endsWith(\"/\") ? rootDir : `${rootDir}/`;\n if (analysis.filePath.startsWith(normalizedRoot)) {\n return analysis.filePath.slice(normalizedRoot.length);\n }\n }\n\n if (analysis.filePath) {\n return analysis.filePath;\n }\n\n try {\n return new URL(analysis.url).pathname;\n } catch {\n return analysis.url;\n }\n}\n\nfunction truncate(value: string, maxWidth: number): string {\n if (value.length <= maxWidth) {\n return value;\n }\n return `${value.slice(0, maxWidth - 1)}…`;\n}\n\nfunction buildRows(\n analyses: ImageAnalysis[],\n options: ReportOptions,\n): Row[] {\n return analyses.map((analysis) => {\n const kinds = new Set(analysis.contexts.map((context) => context.kind));\n const magnitude =\n analysis.savingsPercent === null\n ? null\n : Math.abs(analysis.savingsPercent);\n const savings =\n magnitude === null\n ? \"-\"\n : `${analysis.savingsPercent! >= 0 ? \"-\" : \"+\"}${\n magnitude >= 99.5 ? magnitude.toFixed(1) : magnitude.toFixed(0)\n }%`;\n\n return {\n file: truncate(displayPath(analysis, options.rootDir), MAX_FILE_WIDTH),\n kind: kinds.size === 2 ? \"mixed\" : (analysis.contexts[0]?.kind ?? \"img\"),\n rendered: `${Math.round(analysis.renderedWidth)}×${Math.round(\n analysis.renderedHeight,\n )}`,\n target: `${analysis.targetWidth}×${analysis.targetHeight}`,\n original:\n analysis.originalBytes > 0 ? formatBytes(analysis.originalBytes) : \"-\",\n estimated:\n analysis.estimatedBytes !== null\n ? `~${formatBytes(analysis.estimatedBytes)}`\n : \"-\",\n savings,\n recommended: analysis.recommended,\n reason: analysis.reason ?? \"\",\n };\n });\n}\n\nfunction pad(value: string, width: number): string {\n return value.padEnd(width);\n}\n\nexport function renderReport(\n analyses: ImageAnalysis[],\n options: ReportOptions,\n): string {\n const rows = buildRows(analyses, options);\n const headers = [\n \"FILE\",\n \"KIND\",\n \"RENDERED\",\n \"TARGET\",\n \"ORIGINAL\",\n \"EST. SIZE\",\n \"SAVE\",\n ];\n\n const widths = headers.map((header, index) => {\n const key = [\n \"file\",\n \"kind\",\n \"rendered\",\n \"target\",\n \"original\",\n \"estimated\",\n \"savings\",\n ][index] as keyof Row;\n const values = rows.map((row) => String(row[key]).length);\n return Math.max(header.length, ...values);\n });\n\n const lines: string[] = [];\n const headerLine = headers\n .map((header, index) => pad(header, widths[index] ?? header.length))\n .join(\" \");\n\n const pages =\n options.pageCount && options.pageCount > 1\n ? ` · ${options.pageCount} pages`\n : \"\";\n\n lines.push(\n chalk.bold(\n `${options.deviceName} ${options.viewport.width}×${options.viewport.height} @${options.dpr}x · tier ${Math.round(\n options.tierScale * 100,\n )}% · ${options.format} q${options.quality}${pages}`,\n ),\n );\n lines.push(chalk.dim(headerLine));\n lines.push(chalk.dim(widths.map((width) => \"─\".repeat(width)).join(\" \")));\n\n let totalOriginal = 0;\n let totalEstimated = 0;\n let recommendedCount = 0;\n\n for (const row of rows) {\n const cells = [\n row.file,\n row.kind,\n row.rendered,\n row.target,\n row.original,\n row.estimated,\n row.savings,\n ].map((value, index) => pad(value, widths[index] ?? value.length));\n\n const line = cells.join(\" \");\n\n if (row.recommended) {\n recommendedCount += 1;\n lines.push(chalk.green(line));\n } else {\n lines.push(chalk.dim(line));\n }\n\n if (row.reason) {\n lines.push(chalk.dim(` ${row.reason}`));\n }\n }\n\n for (const analysis of analyses) {\n if (!analysis.recommended) {\n continue;\n }\n totalOriginal += analysis.originalBytes;\n totalEstimated += analysis.estimatedBytes ?? analysis.originalBytes;\n }\n\n const saved = totalOriginal - totalEstimated;\n const percent = totalOriginal > 0 ? (saved / totalOriginal) * 100 : 0;\n\n lines.push(\"\");\n lines.push(\n `${recommendedCount} of ${analyses.length} images optimizable · ${chalk.bold(\n formatBytes(totalOriginal),\n )} → ${chalk.bold(formatBytes(totalEstimated))} · save ${chalk.green(\n `${formatBytes(saved)} (${percent.toFixed(0)}%)`,\n )}`,\n );\n\n return lines.join(\"\\n\");\n}\n","import http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport sirv from \"sirv\";\n\nexport interface StaticServer {\n url: string;\n close: () => Promise<void>;\n}\n\nexport async function startStaticServer(root: string): Promise<StaticServer> {\n const serve = sirv(root, {\n dev: true,\n etag: true,\n single: false,\n ignores: false,\n });\n\n const server = http.createServer((request, response) => {\n serve(request, response, () => {\n response.statusCode = 404;\n response.end(\"Not found\");\n });\n });\n\n await new Promise<void>((resolvePromise, rejectPromise) => {\n server.once(\"error\", rejectPromise);\n server.listen(0, \"127.0.0.1\", () => resolvePromise());\n });\n\n const address = server.address() as AddressInfo;\n\n return {\n url: `http://127.0.0.1:${address.port}`,\n close: () =>\n new Promise<void>((resolvePromise, rejectPromise) => {\n server.close((error) =>\n error ? rejectPromise(error) : resolvePromise(),\n );\n }),\n };\n}\n","import type { DeviceMode, OutputFormat, TierName } from \"../types.js\";\n\nexport interface Preset {\n description: string;\n device: DeviceMode;\n tier: TierName;\n format: OutputFormat;\n quality: number;\n}\n\nexport const PRESETS: Record<string, Preset> = {\n web: {\n description: \"Mobile · WebP · 90% tier · quality 80\",\n device: \"mobile\",\n tier: \"safe\",\n format: \"webp\",\n quality: 80,\n },\n hq: {\n description: \"Desktop · AVIF · exact tier · quality 85\",\n device: \"desktop\",\n tier: \"exact\",\n format: \"avif\",\n quality: 85,\n },\n fast: {\n description: \"Mobile · WebP · 75% tier · quality 70\",\n device: \"mobile\",\n tier: \"aggressive\",\n format: \"webp\",\n quality: 70,\n },\n};\n\nexport function getPreset(name: string): Preset {\n const preset = PRESETS[name.toLowerCase()];\n if (!preset) {\n throw new Error(\n `Unknown preset \"${name}\". Use ${Object.keys(PRESETS).join(\", \")}.`,\n );\n }\n return preset;\n}\n"],"mappings":"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","names":["existsSync","dirname","relative","resolve","sep","chalk","join","walk","resolve","sep","sharp","existsSync","mkdir","readdir","writeFile","dirname","extname","join","relative","resolve","sep","IGNORED_DIRECTORIES","resolve","existsSync","sep","dirname","relative","chalk"]}
|
package/package.json
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viewfit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Smart image fitting and compression based on actual rendered dimensions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"viewfit": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
6
19
|
"scripts": {
|
|
7
|
-
"
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsx src/cli.ts",
|
|
22
|
+
"start": "node dist/cli.js",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"prepack": "npm run build"
|
|
8
27
|
},
|
|
9
|
-
"
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/andrea-bonacci/viewfit.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/andrea-bonacci/viewfit/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/andrea-bonacci/viewfit#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"images",
|
|
38
|
+
"image-optimization",
|
|
39
|
+
"responsive-images",
|
|
40
|
+
"playwright",
|
|
41
|
+
"sharp",
|
|
42
|
+
"cli"
|
|
43
|
+
],
|
|
10
44
|
"author": "Andrea Bonacci",
|
|
11
|
-
"license": "MIT"
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"chalk": "^6.0.1",
|
|
48
|
+
"commander": "^15.0.0",
|
|
49
|
+
"ora": "^10.0.0",
|
|
50
|
+
"playwright": "^1.65.0",
|
|
51
|
+
"prompts": "^2.4.2",
|
|
52
|
+
"sharp": "^0.35.5",
|
|
53
|
+
"sirv": "^3.0.2"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^26.6.6",
|
|
57
|
+
"@types/prompts": "^2.4.9",
|
|
58
|
+
"tsup": "^9.0.0",
|
|
59
|
+
"tsx": "^4.23.15",
|
|
60
|
+
"typescript": "^7.0.2",
|
|
61
|
+
"vitest": "^5.1.0"
|
|
62
|
+
}
|
|
12
63
|
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log("Work in progress")
|