{"version":3,"file":"service.cjs","names":[],"sources":["../../../src/image/service.ts"],"sourcesContent":["import { readFile, mkdir, writeFile, rename, rm, stat } from \"node:fs/promises\";\nimport { join, dirname, extname, basename, resolve, sep } from \"node:path\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport type { ImageFormat } from \"./index.js\";\n\n// --- ImageService: metadata-driven image processing and manifest ---\n//\n//   * Reads real image dimensions from the source file (sharp metadata).\n//   * Generates hashed variant filenames from a SHA-256 transform key that\n//     incorporates: content digest + normalized transform options +\n//     encoder/service version + output naming version (§4.2).\n//   * Applies path containment for sources and outputs (no traversal, no NUL,\n//     no symlink escape, no Unicode/separator tricks).\n//   * Writes outputs atomically (temp + rename) with single-flight per\n//     transform key and a bounded concurrency pool.\n//   * Supports `strict` mode: fails the build on missing sources or failed\n//     transforms instead of emitting a partially-written variant.\n//   * Falls back gracefully when sharp is not installed.\n\nconst ENCODER_VERSION = \"sharp-1\";\nconst NAMING_VERSION = \"v1\";\nconst HASH_LENGTH = 12;\nconst DEFAULT_QUALITY = 80;\nconst DEFAULT_CONCURRENCY = 4;\n\nexport interface ImageVariant {\n  /** URL path relative to the site root, e.g. \"/images/hero.abc123def456.800w.webp\". */\n  url: string;\n  /** Width in pixels. */\n  width: number;\n  /** Height in pixels (preserves aspect ratio). */\n  height: number;\n  /** Format of the variant. */\n  format: ImageFormat;\n  /** File size in bytes. */\n  size: number;\n}\n\nexport interface ImageEntry {\n  /** Original source URL, e.g. \"/images/hero.jpg\". */\n  src: string;\n  /** Intrinsic width of the source. */\n  width: number;\n  /** Intrinsic height of the source. */\n  height: number;\n  /** All generated variants. */\n  variants: ImageVariant[];\n  /** Content hash of the source file. */\n  hash: string;\n}\n\nexport interface ImageManifest {\n  version: 1;\n  entries: Record<string, ImageEntry>;\n}\n\nexport interface ProcessOptions {\n  /** Absolute path to the public directory (source images). */\n  publicDir: string;\n  /** Absolute path to the output directory. */\n  outDir: string;\n  /** Formats to generate. Defaults to [\"webp\", \"avif\"]. */\n  formats?: ImageFormat[];\n  /** Quality (1-100). Defaults to 80. */\n  quality?: number;\n  /** Path to write the manifest JSON. */\n  manifestPath?: string;\n  /** When true, missing sources or failed transforms fail the build. */\n  strict?: boolean;\n  /** Max concurrent sharp transforms. Defaults to 4. */\n  concurrency?: number;\n  /** Optional URL base prefix applied to variant URLs. */\n  base?: string;\n}\n\nexport interface ProcessResult {\n  manifest: ImageManifest;\n  /** Number of variants generated. */\n  count: number;\n  /** Whether sharp was available. */\n  optimized: boolean;\n}\n\nlet sharpLoader: (() => Promise<any>) | null | undefined;\n\nasync function loadSharp(): Promise<any | null> {\n  if (sharpLoader === null) return null;\n  if (sharpLoader) return sharpLoader();\n  try {\n    // @ts-ignore — `sharp` is an optional peer dependency.\n    const mod = await import(\"sharp\");\n    const sharp = mod.default;\n    if (typeof sharp !== \"function\") {\n      sharpLoader = null;\n      return null;\n    }\n    sharpLoader = async () => sharp;\n    return sharp;\n  } catch {\n    sharpLoader = null;\n    return null;\n  }\n}\n\nexport async function isSharpAvailable(): Promise<boolean> {\n  const sharp = await loadSharp();\n  return sharp !== null;\n}\n\n// --- Transform identity (§4.2) ---\n\n/**\n * SHA-256 transform key. Stable for identical content+options and invalidated\n * whenever the source bytes, the effective transform options, the encoder\n * version or the naming scheme change.\n */\nexport function transformHash(sourceBuffer: Buffer, width: number, format: ImageFormat, quality: number): string {\n  const contentDigest = createHash(\"sha256\").update(sourceBuffer).digest(\"hex\");\n  const normalizedOptions = JSON.stringify({\n    width,\n    format,\n    quality,\n    withoutEnlargement: true,\n  });\n  return createHash(\"sha256\")\n    .update(`${contentDigest}|${normalizedOptions}|${ENCODER_VERSION}|${NAMING_VERSION}`)\n    .digest(\"hex\")\n    .slice(0, HASH_LENGTH);\n}\n\n// --- Path containment (§9.5) ---\n\nfunction isSafeRelativePath(value: string): boolean {\n  if (value.includes(\"\\0\") || value.includes(\"\\\\\")) return false;\n  if (/%[0-9a-f]{2}/i.test(value)) return false;\n  const segments = value.replace(/^\\/+/, \"\").split(\"/\");\n  return !segments.some((segment) => segment === \"..\" || segment === \".\" || segment === \"\");\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n  return candidate === root || candidate.startsWith(`${root}${sep}`);\n}\n\nfunction assertInside(root: string, candidate: string, label: string): void {\n  const resolvedRoot = resolve(root);\n  const resolvedCandidate = resolve(candidate);\n  if (!isInside(resolvedRoot, resolvedCandidate)) {\n    throw new Error(`[nix-js-kit] Image ${label} escapes its allowed root (${resolvedCandidate}).`);\n  }\n}\n\n// --- Concurrency: bounded pool + single-flight ---\n\nfunction createPool(limit: number) {\n  let active = 0;\n  const waiters: Array<() => void> = [];\n  const acquire = () =>\n    new Promise<void>((resolve) => {\n      if (active < limit) {\n        active++;\n        resolve();\n      } else {\n        waiters.push(() => {\n          active++;\n          resolve();\n        });\n      }\n    });\n  const release = () => {\n    active--;\n    const next = waiters.shift();\n    if (next) next();\n    else if (active < 0) active = 0;\n  };\n  return {\n    async run<T>(fn: () => Promise<T>): Promise<T> {\n      await acquire();\n      try {\n        return await fn();\n      } finally {\n        release();\n      }\n    },\n  };\n}\n\n// --- Atomic writes (§9.6) ---\n\nasync function atomicWriteFile(path: string, data: Buffer | string): Promise<void> {\n  const temp = `${path}.${process.pid}.${randomBytes(6).toString(\"hex\")}.tmp`;\n  try {\n    await writeFile(temp, data);\n    await rename(temp, path);\n  } catch (error) {\n    await rm(temp, { force: true }).catch(() => { });\n    throw error;\n  }\n}\n\nasync function fileExists(path: string): Promise<boolean> {\n  try {\n    await stat(path);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n// --- Public programmatic API (§3.1 / §3.3) ---\n\nexport interface ImageRequest {\n  src: string;\n  alt: string;\n  widths?: readonly number[];\n  formats?: readonly ImageFormat[];\n  sizes?: string;\n  width?: number;\n  height?: number;\n  priority?: boolean;\n  loading?: \"lazy\" | \"eager\";\n  decoding?: \"async\" | \"sync\" | \"auto\";\n  quality?: number;\n  fit?: string;\n  class?: string;\n  attributes?: Record<string, unknown>;\n}\n\nexport interface GeneratedImage {\n  url: string;\n  width: number;\n  height: number;\n  format: ImageFormat;\n  size: number;\n}\n\nexport interface ImageMetadata {\n  src: string;\n  width?: number;\n  height?: number;\n  sources: Array<{ type: string; srcset: string }>;\n  attributes: Record<string, string | number | boolean | undefined>;\n  generated: readonly GeneratedImage[];\n}\n\nexport interface ImageServiceContext {\n  publicDir: string;\n  outDir: string;\n  manifest: ImageManifest;\n}\n\nexport interface ImageServiceCapabilities {\n  /** Whether the encoder (sharp) is available. */\n  encoding: boolean;\n  /** Whether remote images can be fetched. */\n  remote: boolean;\n  /** Whether a runtime image endpoint exists. */\n  runtimeEndpoint: boolean;\n  /** Whether the host exposes a writable filesystem. */\n  filesystem: boolean;\n}\n\nexport interface ImageService {\n  resolve(request: ImageRequest, context: ImageServiceContext): Promise<ImageMetadata>;\n  capabilities: ImageServiceCapabilities;\n}\n\n/**\n * Creates a build-time ImageService bound to a public/output directory pair.\n */\nexport function createImageService(options: ProcessOptions): ImageService {\n  return {\n    capabilities: {\n      encoding: false,\n      remote: false,\n      runtimeEndpoint: false,\n      filesystem: true,\n    },\n    async resolve(request, context) {\n      return getImage(request, { ...options, publicDir: context.publicDir, outDir: context.outDir });\n    },\n  };\n}\n\n/**\n * Programmatic async image API (§3.1). Ensures the requested variants exist on\n * disk (build-time), then returns deterministic metadata (not opaque markup).\n */\nexport async function getImage(\n  request: ImageRequest,\n  options: ProcessOptions,\n): Promise<ImageMetadata> {\n  const { src, alt, widths, formats, quality, priority, loading, decoding, class: className, attributes = {} } = request;\n  const targetWidths = widths?.length ? [...widths] : [request.width ?? 0];\n  const targetFormats = formats?.length ? [...formats] : options.formats ?? [\"webp\", \"avif\"];\n  const result = await processImageBatch(\n    [{ src, widths: targetWidths, formats: targetFormats }],\n    { ...options, quality: quality ?? options.quality },\n  );\n  const entry = result.manifest.entries[src];\n  const generated: GeneratedImage[] = entry\n    ? entry.variants.map((v) => ({ url: v.url, width: v.width, height: v.height, format: v.format, size: v.size }))\n    : [];\n\n  const sources: ImageMetadata[\"sources\"] = [];\n  for (const format of targetFormats) {\n    const srcset = entry ? buildSrcset(entry, format) : \"\";\n    if (srcset) sources.push({ type: format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`, srcset });\n  }\n\n  return {\n    src,\n    width: entry?.width,\n    height: entry?.height,\n    sources,\n    attributes: {\n      alt,\n      width: entry?.width ?? request.width,\n      height: entry?.height ?? request.height,\n      loading: priority ? \"eager\" : (loading ?? \"lazy\"),\n      decoding: decoding ?? \"async\",\n      ...(priority ? { fetchpriority: \"high\" } : {}),\n      ...(className ? { class: className } : {}),\n      ...attributes,\n    },\n    generated,\n  };\n}\n\n// --- Batch processing ---\n\nexport async function processImageBatch(\n  images: { src: string; widths: number[]; formats?: ImageFormat[] }[],\n  options: ProcessOptions,\n): Promise<ProcessResult> {\n  const sharp = await loadSharp();\n  const {\n    publicDir,\n    outDir,\n    formats = [\"webp\", \"avif\"],\n    quality = DEFAULT_QUALITY,\n    strict = false,\n    concurrency = DEFAULT_CONCURRENCY,\n    base = \"\",\n  } = options;\n  const entries: Record<string, ImageEntry> = {};\n  let count = 0;\n  const pool = createPool(concurrency);\n  const inFlight = new Map<string, Promise<void>>();\n\n  const warned = new Set<string>();\n  const warnOnce = (key: string, message: string): void => {\n    if (warned.has(key)) return;\n    warned.add(key);\n    console.warn(`[nix-js-kit] ${message}`);\n  };\n\n  if (!sharp) {\n    // Without sharp, build a manifest with only the original source entries.\n    for (const { src } of images) {\n      if (entries[src]) continue;\n      if (!isSafeRelativePath(src)) {\n        if (strict) throw new Error(`[nix-js-kit] Invalid image source path: ${src}`);\n        warnOnce(`path:${src}`, `Skipping invalid image source path: ${src}`);\n        continue;\n      }\n      const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n      assertInside(publicDir, sourcePath, `source \"${src}\"`);\n      try {\n        const buffer = await readFile(sourcePath);\n        entries[src] = {\n          src,\n          width: 0,\n          height: 0,\n          variants: [],\n          hash: createHash(\"sha256\").update(buffer).digest(\"hex\").slice(0, 8),\n        };\n      } catch (error) {\n        if (strict) throw new Error(`[nix-js-kit] Image source not found: ${src}`);\n        warnOnce(`missing:${src}`, `Image source not found: ${src}. Skipping.`);\n      }\n    }\n    const manifest: ImageManifest = { version: 1, entries };\n    if (options.manifestPath) await writeManifest(options.manifestPath, manifest);\n    return { manifest, count: 0, optimized: false };\n  }\n\n  for (const { src, widths, formats: imgFormats } of images) {\n    if (entries[src]) continue;\n\n    if (!isSafeRelativePath(src)) {\n      if (strict) throw new Error(`[nix-js-kit] Invalid image source path: ${src}`);\n      warnOnce(`path:${src}`, `Skipping invalid image source path: ${src}`);\n      continue;\n    }\n\n    const sourcePath = join(publicDir, src.replace(/^\\//, \"\"));\n    assertInside(publicDir, sourcePath, `source \"${src}\"`);\n\n    let sourceBuffer: Buffer;\n    try {\n      sourceBuffer = await readFile(sourcePath);\n    } catch (error) {\n      if (strict) throw new Error(`[nix-js-kit] Image source not found: ${src}`);\n      warnOnce(`missing:${src}`, `Image not found: ${src}. Skipping.`);\n      continue;\n    }\n\n    const ext = extname(src);\n    const safeBase = basename(src, ext).replace(/[^a-zA-Z0-9._-]+/g, \"-\");\n    const dir = dirname(src);\n    const targetFormats = imgFormats?.length ? imgFormats : formats;\n\n    // Read real metadata from the source.\n    let sourceWidth = 0;\n    let sourceHeight = 0;\n    try {\n      const meta = await sharp(sourceBuffer).metadata();\n      sourceWidth = meta.width ?? 0;\n      sourceHeight = meta.height ?? 0;\n    } catch {\n      // Fallback: no metadata.\n    }\n\n    const variants: ImageVariant[] = [];\n\n    const processVariant = async (width: number, format: ImageFormat): Promise<void> => {\n      // Never upscale: skip widths larger than the source.\n      if (sourceWidth > 0 && width > sourceWidth) return;\n\n      const hash = transformHash(sourceBuffer, width, format, quality);\n      const variantName = `${safeBase}.${hash}.${width}w.${format}`;\n      const variantRelPath = join(dir, variantName);\n      const variantAbsPath = join(outDir, variantRelPath.replace(/^\\//, \"\"));\n      assertInside(outDir, variantAbsPath, `variant \"${variantRelPath}\"`);\n      const variantUrl = `${base.replace(/\\/$/, \"\")}/${variantRelPath.replace(/\\\\/g, \"/\").replace(/^\\//, \"\")}`;\n\n      // Reuse an existing, valid output file (validated, not guessed).\n      if (await fileExists(variantAbsPath)) {\n        try {\n          const info = await sharp(variantAbsPath).metadata();\n          variants.push({\n            url: variantUrl,\n            width: info.width ?? width,\n            height: info.height ?? Math.round((info.height ?? 0) || (sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0)),\n            format,\n            size: (await stat(variantAbsPath)).size,\n          });\n          count++;\n          return;\n        } catch {\n          // Existing file invalid — regenerate below.\n        }\n      }\n\n      const key = variantAbsPath;\n      if (inFlight.has(key)) {\n        await inFlight.get(key);\n        variants.push({\n          url: variantUrl,\n          width,\n          height: Math.round(sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0),\n          format,\n          size: (await stat(variantAbsPath)).size,\n        });\n        count++;\n        return;\n      }\n\n      const task = (async () => {\n        try {\n          const buffer = await sharp(sourceBuffer)\n            .resize({ width, withoutEnlargement: true })\n            .toFormat(format, { quality })\n            .toBuffer();\n          await mkdir(dirname(variantAbsPath), { recursive: true });\n          await atomicWriteFile(variantAbsPath, buffer);\n        } catch (error) {\n          if (strict) throw new Error(`[nix-js-kit] Failed to generate ${variantName}: ${error instanceof Error ? error.message : String(error)}`);\n          warnOnce(`fail:${variantName}`, `Failed to generate ${variantName}.`);\n          return;\n        }\n        variants.push({\n          url: variantUrl,\n          width,\n          height: Math.round(sourceHeight && sourceWidth ? (width * sourceHeight) / sourceWidth : 0),\n          format,\n          size: (await stat(variantAbsPath)).size,\n        });\n        count++;\n      })().finally(() => inFlight.delete(key));\n\n      inFlight.set(key, task);\n      await pool.run(() => task);\n    };\n\n    const tasks: Promise<void>[] = [];\n    for (const width of widths) {\n      for (const format of targetFormats) {\n        tasks.push(processVariant(width, format));\n      }\n    }\n    await Promise.all(tasks);\n\n    entries[src] = {\n      src,\n      width: sourceWidth,\n      height: sourceHeight,\n      variants,\n      hash: createHash(\"sha256\").update(sourceBuffer).digest(\"hex\").slice(0, 8),\n    };\n  }\n\n  const manifest: ImageManifest = { version: 1, entries };\n  if (options.manifestPath) await writeManifest(options.manifestPath, manifest);\n  return { manifest, count, optimized: true };\n}\n\n/**\n * Read a manifest from disk, or return an empty one if it doesn't exist.\n */\nexport async function readManifest(path: string): Promise<ImageManifest> {\n  try {\n    const data = await readFile(path, \"utf8\");\n    return JSON.parse(data) as ImageManifest;\n  } catch {\n    return { version: 1, entries: {} };\n  }\n}\n\n/**\n * Write a manifest to disk atomically.\n */\nexport async function writeManifest(path: string, manifest: ImageManifest): Promise<void> {\n  await mkdir(dirname(path), { recursive: true });\n  await atomicWriteFile(path, JSON.stringify(manifest, null, 2));\n}\n\n/**\n * Look up an image entry in the manifest by its source URL.\n */\nexport function getManifestEntry(manifest: ImageManifest, src: string): ImageEntry | undefined {\n  return manifest.entries[src];\n}\n\n/**\n * Build a srcset string from manifest variants of a given format.\n * Returns e.g. \"/images/hero.abc.400w.webp 400w, /images/hero.abc.800w.webp 800w\".\n */\nexport function buildSrcset(entry: ImageEntry, format: ImageFormat): string {\n  return entry.variants\n    .filter((v) => v.format === format)\n    .map((v) => `${v.url} ${v.width}w`)\n    .join(\", \");\n}\n\n/**\n * Build the full <picture> markup for an image entry, with <source> per format\n * and a fallback <img>.\n */\nexport function buildPictureMarkup(entry: ImageEntry, opts: {\n  alt: string;\n  sizes?: string;\n  priority?: boolean;\n  class?: string;\n  attributes?: Record<string, string>;\n  fallbackSrc?: string;\n  fallbackWidth?: number;\n  fallbackHeight?: number;\n}): string {\n  const {\n    alt,\n    sizes,\n    priority = false,\n    class: className,\n    attributes = {},\n    fallbackSrc = entry.src,\n    fallbackWidth = entry.width,\n    fallbackHeight = entry.height,\n  } = opts;\n\n  const formats = [...new Set(entry.variants.map((v) => v.format))];\n  const loadingAttr = priority ? \"\" : ' loading=\"lazy\"';\n  const fetchPriorityAttr = priority ? ' fetchpriority=\"high\"' : \"\";\n  const sizesAttr = sizes ? ` sizes=\"${escapeAttr(sizes)}\"` : \"\";\n  const classAttr = className ? ` class=\"${escapeAttr(className)}\"` : \"\";\n  const extraAttrs = Object.entries(attributes)\n    .map(([key, value]) => ` ${escapeAttr(key)}=\"${escapeAttr(String(value))}\"`)\n    .join(\"\");\n\n  const sources = formats\n    .map((format) => {\n      const srcset = buildSrcset(entry, format);\n      if (!srcset) return \"\";\n      const type = format === \"jpeg\" ? \"image/jpeg\" : `image/${format}`;\n      return `<source srcset=\"${srcset}\"${sizesAttr} type=\"${type}\" />`;\n    })\n    .filter(Boolean)\n    .join(\"\");\n\n  const img = `<img src=\"${escapeAttr(fallbackSrc)}\" alt=\"${escapeAttr(alt)}\" width=\"${fallbackWidth}\" height=\"${fallbackHeight}\"${loadingAttr} decoding=\"async\"${fetchPriorityAttr}${classAttr}${extraAttrs} />`;\n\n  return sources ? `<picture>${sources}${img}</picture>` : img;\n}\n\n/**\n * Validate that every variant URL in the manifest corresponds to a real file\n * in the output directory. Returns a list of missing URLs.\n */\nexport async function validateManifestUrls(\n  manifest: ImageManifest,\n  outDir: string,\n): Promise<string[]> {\n  const missing: string[] = [];\n  for (const entry of Object.values(manifest.entries)) {\n    for (const variant of entry.variants) {\n      const relative = variant.url.replace(/^\\/+/, \"\");\n      const resolved = resolve(outDir, relative);\n      if (!isInside(resolve(outDir), resolved)) {\n        missing.push(variant.url);\n        continue;\n      }\n      try {\n        await stat(resolved);\n      } catch {\n        missing.push(variant.url);\n      }\n    }\n  }\n  return missing;\n}\n\nfunction escapeAttr(value: string): string {\n  return value\n    .replace(/&/g, \"&amp;\")\n    .replace(/\"/g, \"&quot;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\");\n}\n"],"mappings":"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"}