{"version":3,"file":"watcher.cjs","names":[],"sources":["../src/watcher.ts"],"sourcesContent":["import { watch, type FSWatcher } from \"node:fs\";\nimport type { Fixture } from \"./types.js\";\nimport type { Logger } from \"./logger.js\";\nimport type { ValidationResult } from \"./fixture-loader.js\";\n\nconst DEBOUNCE_MS = 500;\n\nexport function watchFixtures(\n  fixturePath: string,\n  fixtures: Fixture[],\n  loadFn: () => Fixture[],\n  opts: {\n    logger: Logger;\n    validate?: boolean;\n    validateFn?: (fixtures: Fixture[]) => ValidationResult[];\n  },\n): { close: () => void } {\n  const { logger, validate, validateFn } = opts;\n  let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n  function reload() {\n    logger.info(`File changed — reloading fixtures from ${fixturePath}...`);\n\n    let newFixtures: Fixture[];\n    try {\n      newFixtures = loadFn();\n    } catch (err) {\n      logger.error(\"Failed to reload fixtures:\", err);\n      logger.error(\"Previous fixtures remain active. Fix the error and save again to retry.\");\n      return;\n    }\n\n    if (newFixtures.length === 0 && fixtures.length > 0) {\n      logger.warn(\n        \"Reload produced 0 fixtures — keeping previous fixtures. Check fixture file for errors.\",\n      );\n      return;\n    }\n\n    if (validate && validateFn) {\n      const results = validateFn(newFixtures);\n      const errors = results.filter((r) => r.severity === \"error\");\n      const warnings = results.filter((r) => r.severity === \"warning\");\n\n      for (const w of warnings) {\n        logger.warn(`Fixture ${w.fixtureIndex}: ${w.message}`);\n      }\n\n      if (errors.length > 0) {\n        for (const e of errors) {\n          logger.error(`Fixture ${e.fixtureIndex}: ${e.message}`);\n        }\n        logger.error(`${errors.length} validation error(s) — keeping previous fixtures`);\n        return;\n      }\n    }\n\n    // Replace in-place to preserve array reference identity\n    fixtures.length = 0;\n    fixtures.push(...newFixtures);\n    logger.info(`Reloaded ${newFixtures.length} fixture(s)`);\n  }\n\n  const watcher: FSWatcher = watch(fixturePath, { recursive: true }, () => {\n    if (debounceTimer) clearTimeout(debounceTimer);\n    debounceTimer = setTimeout(reload, DEBOUNCE_MS);\n  });\n\n  watcher.on(\"error\", (err: Error) => {\n    if (debounceTimer) clearTimeout(debounceTimer);\n    debounceTimer = null;\n    try {\n      watcher.close();\n    } catch {\n      /* already dead */\n    }\n    logger.error(`File watcher error on ${fixturePath}: ${err.message}`);\n    logger.error(\"Fixture auto-reload is no longer active. Restart the server to resume watching.\");\n  });\n\n  return {\n    close() {\n      if (debounceTimer) clearTimeout(debounceTimer);\n      watcher.close();\n    },\n  };\n}\n"],"mappings":";;;;AAKA,MAAM,cAAc;AAEpB,SAAgB,cACd,aACA,UACA,QACA,MAKuB;CACvB,MAAM,EAAE,QAAQ,UAAU,eAAe;CACzC,IAAI,gBAAsD;CAE1D,SAAS,SAAS;AAChB,SAAO,KAAK,0CAA0C,YAAY,KAAK;EAEvE,IAAI;AACJ,MAAI;AACF,iBAAc,QAAQ;WACf,KAAK;AACZ,UAAO,MAAM,8BAA8B,IAAI;AAC/C,UAAO,MAAM,0EAA0E;AACvF;;AAGF,MAAI,YAAY,WAAW,KAAK,SAAS,SAAS,GAAG;AACnD,UAAO,KACL,yFACD;AACD;;AAGF,MAAI,YAAY,YAAY;GAC1B,MAAM,UAAU,WAAW,YAAY;GACvC,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,aAAa,QAAQ;GAC5D,MAAM,WAAW,QAAQ,QAAQ,MAAM,EAAE,aAAa,UAAU;AAEhE,QAAK,MAAM,KAAK,SACd,QAAO,KAAK,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAGxD,OAAI,OAAO,SAAS,GAAG;AACrB,SAAK,MAAM,KAAK,OACd,QAAO,MAAM,WAAW,EAAE,aAAa,IAAI,EAAE,UAAU;AAEzD,WAAO,MAAM,GAAG,OAAO,OAAO,kDAAkD;AAChF;;;AAKJ,WAAS,SAAS;AAClB,WAAS,KAAK,GAAG,YAAY;AAC7B,SAAO,KAAK,YAAY,YAAY,OAAO,aAAa;;CAG1D,MAAM,6BAA2B,aAAa,EAAE,WAAW,MAAM,QAAQ;AACvE,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB,WAAW,QAAQ,YAAY;GAC/C;AAEF,SAAQ,GAAG,UAAU,QAAe;AAClC,MAAI,cAAe,cAAa,cAAc;AAC9C,kBAAgB;AAChB,MAAI;AACF,WAAQ,OAAO;UACT;AAGR,SAAO,MAAM,yBAAyB,YAAY,IAAI,IAAI,UAAU;AACpE,SAAO,MAAM,kFAAkF;GAC/F;AAEF,QAAO,EACL,QAAQ;AACN,MAAI,cAAe,cAAa,cAAc;AAC9C,UAAQ,OAAO;IAElB"}