@djangocfg/seo 2.1.50 → 2.1.52

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/scanner.ts","../../src/routes/analyzer.ts"],"names":[],"mappings":";;;;;AAiDA,IAAM,UAAA,GAAa,CAAC,UAAA,EAAY,SAAA,EAAW,YAAY,SAAS,CAAA;AAChE,IAAM,WAAA,GAAc,CAAC,WAAA,EAAa,UAAA,EAAY,aAAa,UAAU,CAAA;AACrE,IAAM,gBAAgB,CAAC,QAAA,EAAU,SAAA,EAAW,OAAA,EAAS,aAAa,UAAU,CAAA;AAKrE,SAAS,UAAA,CAAW,QAAA,GAAmB,OAAA,CAAQ,GAAA,EAAI,EAAkB;AAC1E,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,IAAA,CAAK,UAAU,KAAK,CAAA;AAAA,IACpB,IAAA,CAAK,QAAA,EAAU,KAAA,EAAO,KAAK;AAAA,GAC7B;AAEA,EAAA,KAAA,MAAW,OAAO,UAAA,EAAY;AAC5B,IAAA,IAAI,WAAW,GAAG,CAAA,IAAK,SAAS,GAAG,CAAA,CAAE,aAAY,EAAG;AAClD,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAKO,SAAS,UAAA,CAAW,OAAA,GAAuB,EAAC,EAAe;AAChE,EAAA,MAAM;AAAA,IACJ,MAAA,GAAS,YAAW,IAAK,OAAA;AAAA,IACzB,UAAA,GAAa,IAAA;AAAA,IACb,cAAA,GAAiB;AAAA,GACnB,GAAI,OAAA;AAEJ,EAAA,IAAI,CAAC,UAAA,CAAW,MAAM,CAAA,EAAG;AACvB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,MAAM,CAAA,CAAE,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,SAAsB,EAAC;AAE7B,EAAA,aAAA,CAAc,QAAQ,EAAA,EAAI,MAAA,EAAQ,EAAE,UAAA,EAAY,gBAAgB,CAAA;AAEhE,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,YAAA,EAAc,OAAO,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,SAAA,IAAa,CAAA,CAAE,IAAA,KAAS,MAAM,CAAA;AAAA,IAClE,aAAA,EAAe,OAAO,MAAA,CAAO,CAAA,CAAA,KAAK,EAAE,SAAA,IAAa,CAAA,CAAE,SAAS,MAAM,CAAA;AAAA,IAClE,WAAW,MAAA,CAAO,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,SAAS,KAAK,CAAA;AAAA,IAC9C;AAAA,GACF;AACF;AAEA,SAAS,aAAA,CACP,GAAA,EACA,SAAA,EACA,MAAA,EACA,OAAA,EACM;AACN,EAAA,IAAI,OAAA;AAEJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAU,YAAY,GAAG,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAK,KAAK,CAAA;AAEhC,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI;AACF,MAAA,IAAA,GAAO,SAAS,QAAQ,CAAA;AAAA,IAC1B,CAAA,CAAA,MAAQ;AACN,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,CAAK,aAAY,EAAG;AAEtB,MAAA,IAAI,MAAM,UAAA,CAAW,GAAG,KAAK,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,EAAG;AAGpD,MAAA,IAAI,MAAM,UAAA,CAAW,GAAG,KAAK,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,EAAG;AAEhD,QAAA,aAAA,CAAc,QAAA,EAAU,SAAA,EAAW,MAAA,EAAQ,OAAO,CAAA;AAClD,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,EAAG;AAEzB,QAAA,aAAA,CAAc,QAAA,EAAU,SAAA,EAAW,MAAA,EAAQ,OAAO,CAAA;AAClD,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,KAAA,CAAM,WAAW,GAAG,CAAA,IAAK,CAAC,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA,EAAG;AACjD,QAAA;AAAA,MACF;AAGA,MAAA,MAAM,OAAA,GAAU,eAAe,KAAK,CAAA;AACpC,MAAA,MAAM,YAAA,GAAe,SAAA,GAAY,GAAA,GAAM,OAAA,CAAQ,UAAA;AAE/C,MAAA,aAAA,CAAc,QAAA,EAAU,YAAA,EAAc,MAAA,EAAQ,OAAO,CAAA;AAAA,IACvD,CAAA,MAAA,IAAW,IAAA,CAAK,MAAA,EAAO,EAAG;AAExB,MAAA,IAAI,UAAA,CAAW,QAAA,CAAS,KAAK,CAAA,EAAG;AAC9B,QAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,SAAA,IAAa,GAAA,EAAK,KAAK,MAAM,CAAA;AAC3D,QAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,MACnB;AAGA,MAAA,IAAI,OAAA,CAAQ,UAAA,IAAc,WAAA,CAAY,QAAA,CAAS,KAAK,CAAA,EAAG;AACrD,QAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,SAAA,IAAa,GAAA,EAAK,KAAK,KAAK,CAAA;AAC1D,QAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,MACnB;AAGA,MAAA,IAAI,QAAQ,cAAA,EAAgB;AAC1B,QAAA,MAAM,QAAA,GAAW,KAAA,CAAM,OAAA,CAAQ,mBAAA,EAAqB,EAAE,CAAA;AACtD,QAAA,IAAI,aAAA,CAAc,QAAA,CAAS,QAAQ,CAAA,EAAG;AACpC,UAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,SAAA,IAAa,GAAA,EAAK,KAAK,QAA6B,CAAA;AAClF,UAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAUA,SAAS,eAAe,OAAA,EAA8B;AAEpD,EAAA,IAAI,QAAQ,UAAA,CAAW,OAAO,KAAK,OAAA,CAAQ,QAAA,CAAS,IAAI,CAAA,EAAG;AACzD,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACrC,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,OAAA;AAAA,MACZ,SAAA,EAAW,IAAA;AAAA,MACX,SAAA;AAAA,MACA,UAAA,EAAY,IAAA;AAAA,MACZ,kBAAA,EAAoB;AAAA,KACtB;AAAA,EACF;AAGA,EAAA,IAAI,QAAQ,UAAA,CAAW,MAAM,KAAK,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,EAAG;AACvD,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACrC,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,OAAA;AAAA,MACZ,SAAA,EAAW,IAAA;AAAA,MACX,SAAA;AAAA,MACA,UAAA,EAAY,IAAA;AAAA,MACZ,kBAAA,EAAoB;AAAA,KACtB;AAAA,EACF;AAGA,EAAA,IAAI,QAAQ,UAAA,CAAW,GAAG,KAAK,OAAA,CAAQ,QAAA,CAAS,GAAG,CAAA,EAAG;AACpD,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACrC,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,OAAA;AAAA,MACZ,SAAA,EAAW,IAAA;AAAA,MACX,SAAA;AAAA,MACA,UAAA,EAAY,KAAA;AAAA,MACZ,kBAAA,EAAoB;AAAA,KACtB;AAAA,EACF;AAGA,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,OAAA;AAAA,IACZ,SAAA,EAAW,KAAA;AAAA,IACX,UAAA,EAAY,KAAA;AAAA,IACZ,kBAAA,EAAoB;AAAA,GACtB;AACF;AAEA,SAAS,eAAA,CAAgB,IAAA,EAAc,QAAA,EAAkB,IAAA,EAAoC;AAC3F,EAAA,MAAM,WAAW,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,OAAO,OAAO,CAAA;AAC/C,EAAA,MAAM,kBAA4B,EAAC;AACnC,EAAA,IAAI,SAAA,GAAY,KAAA;AAChB,EAAA,IAAI,UAAA,GAAa,KAAA;AACjB,EAAA,IAAI,kBAAA,GAAqB,KAAA;AAEzB,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,MAAM,IAAA,GAAO,eAAe,OAAO,CAAA;AACnC,IAAA,IAAI,KAAK,SAAA,EAAW;AAClB,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,IAAI,KAAK,SAAA,EAAW;AAClB,QAAA,eAAA,CAAgB,IAAA,CAAK,KAAK,SAAS,CAAA;AAAA,MACrC;AACA,MAAA,IAAI,IAAA,CAAK,YAAY,UAAA,GAAa,IAAA;AAClC,MAAA,IAAI,IAAA,CAAK,oBAAoB,kBAAA,GAAqB,IAAA;AAAA,IACpD;AAAA,EACF;AAGA,EAAA,IAAI,UAAA;AACJ,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,aAAa,CAAA;AAC/C,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,UAAA,GAAa,WAAW,CAAC,CAAA;AAAA,EAC3B;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,IAAA,IAAQ,GAAA;AAAA,IACd,QAAA;AAAA,IACA,IAAA;AAAA,IACA,SAAA;AAAA,IACA,eAAA;AAAA,IACA,UAAA;AAAA,IACA,kBAAA;AAAA,IACA;AAAA,GACF;AACF;AAKO,SAAS,UAAA,CAAW,OAAkB,MAAA,EAAyC;AACpF,EAAA,IAAI,MAAM,KAAA,CAAM,IAAA;AAEhB,EAAA,KAAA,MAAW,OAAA,IAAW,MAAM,eAAA,EAAiB;AAC3C,IAAA,MAAM,KAAA,GAAQ,MAAA,GAAS,OAAO,CAAA,IAAK,IAAI,OAAO,CAAA,CAAA,CAAA;AAG9C,IAAA,IAAI,MAAM,kBAAA,EAAoB;AAC5B,MAAA,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,CAAA,KAAA,EAAQ,OAAO,MAAM,KAAK,CAAA;AAAA,IAC9C,CAAA,MAAA,IAAW,MAAM,UAAA,EAAY;AAC3B,MAAA,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,CAAA,IAAA,EAAO,OAAO,KAAK,KAAK,CAAA;AAAA,IAC5C,CAAA,MAAO;AACL,MAAA,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,CAAA,CAAA,EAAI,OAAO,KAAK,KAAK,CAAA;AAAA,IACzC;AAAA,EACF;AAEA,EAAA,OAAO,GAAA;AACT;AAKO,SAAS,aAAA,CAAc,YAAwB,OAAA,EAA2B;AAC/E,EAAA,OAAO,UAAA,CAAW,YAAA,CAAa,GAAA,CAAI,CAAA,KAAA,KAAS;AAC1C,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,KAAA,CAAM,MAAM,OAAO,CAAA;AACvC,IAAA,OAAO,GAAA,CAAI,IAAA;AAAA,EACb,CAAC,CAAA;AACH;AC/OO,SAAS,kBAAA,CACd,UAAA,EACA,WAAA,EACA,OAAA,EACuB;AACvB,EAAA,MAAM,YAAY,UAAA,CAAW,MAAA,CAAO,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,SAAS,MAAM,CAAA;AAGjE,EAAA,MAAM,eAAe,IAAI,GAAA;AAAA,IACvB,WAAA,CAAY,IAAI,CAAA,GAAA,KAAO;AACrB,MAAA,IAAI;AACF,QAAA,OAAO,IAAI,GAAA,CAAI,GAAG,CAAA,CAAE,QAAA;AAAA,MACtB,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,GAAA;AAAA,MACT;AAAA,IACF,CAAC;AAAA,GACH;AAGA,EAAA,MAAM,qBAAkC,EAAC;AACzC,EAAA,MAAM,WAAwB,EAAC;AAE/B,EAAA,KAAA,MAAW,KAAA,IAAS,WAAW,YAAA,EAAc;AAC3C,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,KAAS,GAAA,GAAM,MAAM,KAAA,CAAM,IAAA;AAE9C,IAAA,IAAI,aAAa,GAAA,CAAI,IAAI,CAAA,IAAK,YAAA,CAAa,IAAI,IAAA,GAAO,GAAG,CAAA,IAAK,YAAA,CAAa,IAAI,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAG;AACvG,MAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AAAA,IACrB,CAAA,MAAO;AACL,MAAA,kBAAA,CAAmB,KAAK,KAAK,CAAA;AAAA,IAC/B;AAAA,EACF;AAGA,EAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,UAAA,CAAW,aAAa,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,IAAI,CAAC,CAAA;AACpE,EAAA,MAAM,eAAA,GAAkB,WAAW,aAAA,CAAc,GAAA,CAAI,OAAK,YAAA,CAAa,CAAA,CAAE,IAAI,CAAC,CAAA;AAE9E,EAAA,MAAM,iBAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,QAAQ,YAAA,EAAc;AAE/B,IAAA,IAAI,YAAY,GAAA,CAAI,IAAI,CAAA,IAAK,WAAA,CAAY,IAAI,IAAA,GAAO,GAAG,CAAA,IAAK,WAAA,CAAY,IAAI,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,EAAG;AACpG,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,iBAAiB,eAAA,CAAgB,IAAA,CAAK,WAAS,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA;AACrE,IAAA,IAAI,cAAA,EAAgB;AAEpB,IAAA,cAAA,CAAe,KAAK,IAAI,CAAA;AAAA,EAC1B;AAEA,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,WAAA;AAAA,IACA,kBAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACF;AACF;AAKA,SAAS,aAAa,SAAA,EAA2B;AAC/C,EAAA,IAAI,UAAU,SAAA,CAEX,OAAA,CAAQ,iBAAA,EAAmB,MAAM,EAEjC,OAAA,CAAQ,yBAAA,EAA2B,UAAU,CAAA,CAE7C,QAAQ,qBAAA,EAAuB,KAAK,CAAA,CAEpC,OAAA,CAAQ,iBAAiB,QAAQ,CAAA;AAGpC,EAAA,OAAA,GAAU,IAAI,OAAO,CAAA,GAAA,CAAA;AAErB,EAAA,OAAO,IAAI,OAAO,OAAO,CAAA;AAC3B;AAKA,eAAsB,YAAA,CACpB,YACA,OAAA,EACoC;AACpC,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,OAAA,GAAU,GAAA;AAAA,IACV,WAAA,GAAc,CAAA;AAAA,IACd,UAAA,GAAa;AAAA,GACf,GAAI,OAAA;AAEJ,EAAA,MAAM,MAAA,GAAS,UAAA,GAAa,UAAA,CAAW,YAAA,GAAe,UAAA,CAAW,OAAO,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,IAAA,KAAS,MAAM,CAAA;AACrG,EAAA,MAAM,KAAA,GAAQ,OAAO,WAAW,CAAA;AAEhC,EAAA,MAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,GAAA;AAAA,IAC5B,MAAA,CAAO,GAAA;AAAA,MAAI,CAAA,KAAA,KACT,MAAM,YAAY;AAChB,QAAA,MAAM,MAAM,IAAI,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,OAAO,CAAA,CAAE,IAAA;AACzC,QAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAE3B,QAAA,IAAI;AACF,UAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,UAAA,MAAM,YAAY,UAAA,CAAW,MAAM,UAAA,CAAW,KAAA,IAAS,OAAO,CAAA;AAE9D,UAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,YAChC,MAAA,EAAQ,MAAA;AAAA,YACR,QAAQ,UAAA,CAAW,MAAA;AAAA,YACnB,QAAA,EAAU;AAAA,WACX,CAAA;AAED,UAAA,YAAA,CAAa,SAAS,CAAA;AAEtB,UAAA,OAAO;AAAA,YACL,KAAA;AAAA,YACA,GAAA;AAAA,YACA,YAAY,QAAA,CAAS,MAAA;AAAA,YACrB,YAAA,EAAc,QAAA,CAAS,MAAA,IAAU,GAAA,IAAO,SAAS,MAAA,GAAS,GAAA;AAAA,YAC1D,YAAA,EAAc,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,WAC7B;AAAA,QACF,SAAS,KAAA,EAAO;AACd,UAAA,OAAO;AAAA,YACL,KAAA;AAAA,YACA,GAAA;AAAA,YACA,UAAA,EAAY,CAAA;AAAA,YACZ,YAAA,EAAc,KAAA;AAAA,YACd,OAAQ,KAAA,CAAgB,OAAA;AAAA,YACxB,YAAA,EAAc,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,WAC7B;AAAA,QACF;AAAA,MACF,CAAC;AAAA;AACH,GACF;AAEA,EAAA,OAAO,OAAA;AACT;AAKO,SAAS,aAAA,CACd,UAAA,EACA,UAAA,EACA,YAAA,EACY;AACZ,EAAA,MAAM,SAAqB,EAAC;AAC5B,EAAA,MAAM,GAAA,GAAA,iBAAM,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAGnC,EAAA,IAAI,UAAA,IAAc,UAAA,CAAW,kBAAA,CAAmB,MAAA,GAAS,CAAA,EAAG;AAC1D,IAAA,KAAA,MAAW,KAAA,IAAS,WAAW,kBAAA,EAAoB;AACjD,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,EAAA,EAAI,CAAA,sBAAA,EAAyB,KAAA,CAAM,IAAI,CAAA,CAAA;AAAA,QACvC,QAAA,EAAU,UAAA;AAAA,QACV,QAAA,EAAU,SAAA;AAAA,QACV,KAAA,EAAO,4BAAA;AAAA,QACP,WAAA,EAAa,CAAA,aAAA,EAAgB,KAAA,CAAM,IAAI,CAAA,sBAAA,CAAA;AAAA,QACvC,KAAK,KAAA,CAAM,IAAA;AAAA,QACX,cAAA,EAAgB,oDAAA;AAAA,QAChB,UAAA,EAAY;AAAA,OACb,CAAA;AAAA,IACH;AAAA,EACF;AAGA,EAAA,IAAI,UAAA,IAAc,UAAA,CAAW,cAAA,CAAe,MAAA,GAAS,CAAA,EAAG;AACtD,IAAA,KAAA,MAAW,QAAQ,UAAA,CAAW,cAAA,CAAe,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG;AACzD,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,EAAA,EAAI,kBAAkB,IAAI,CAAA,CAAA;AAAA,QAC1B,QAAA,EAAU,UAAA;AAAA,QACV,QAAA,EAAU,MAAA;AAAA,QACV,KAAA,EAAO,oCAAA;AAAA,QACP,WAAA,EAAa,OAAO,IAAI,CAAA,wCAAA,CAAA;AAAA,QACxB,GAAA,EAAK,IAAA;AAAA,QACL,cAAA,EAAgB,uDAAA;AAAA,QAChB,UAAA,EAAY;AAAA,OACb,CAAA;AAAA,IACH;AAAA,EACF;AAGA,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,MAAA,IAAI,CAAC,OAAO,YAAA,EAAc;AACxB,QAAA,MAAA,CAAO,IAAA,CAAK;AAAA,UACV,EAAA,EAAI,CAAA,mBAAA,EAAsB,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA,CAAA;AAAA,UAC3C,QAAA,EAAU,WAAA;AAAA,UACV,QAAA,EAAU,MAAA,CAAO,UAAA,KAAe,GAAA,GAAM,OAAA,GAAU,SAAA;AAAA,UAChD,KAAA,EAAO,CAAA,cAAA,EAAiB,MAAA,CAAO,UAAA,IAAc,OAAO,CAAA,CAAA;AAAA,UACpD,WAAA,EAAa,OAAO,KAAA,IAAS,CAAA,MAAA,EAAS,OAAO,KAAA,CAAM,IAAI,CAAA,iBAAA,EAAoB,MAAA,CAAO,UAAU,CAAA,CAAA;AAAA,UAC5F,KAAK,MAAA,CAAO,GAAA;AAAA,UACZ,cAAA,EAAgB,sCAAA;AAAA,UAChB,UAAA,EAAY;AAAA,SACb,CAAA;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI,WAAW,aAAA,CAAc,MAAA,GAAS,UAAA,CAAW,YAAA,CAAa,SAAS,CAAA,EAAG;AACxE,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,EAAA,EAAI,yBAAA;AAAA,MACJ,QAAA,EAAU,SAAA;AAAA,MACV,QAAA,EAAU,MAAA;AAAA,MACV,KAAA,EAAO,8BAAA;AAAA,MACP,WAAA,EAAa,GAAG,UAAA,CAAW,aAAA,CAAc,MAAM,CAAA,YAAA,EAAe,UAAA,CAAW,aAAa,MAAM,CAAA,cAAA,CAAA;AAAA,MAC5F,GAAA,EAAK,GAAA;AAAA,MACL,cAAA,EAAgB,gEAAA;AAAA,MAChB,UAAA,EAAY;AAAA,KACb,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,qBAAA,CACd,UAAA,EACA,UAAA,EACA,YAAA,EACQ;AACR,EAAA,MAAM,QAAkB,EAAC;AAEzB,EAAA,KAAA,CAAM,KAAK,mBAAmB,CAAA;AAC9B,EAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AACb,EAAA,KAAA,CAAM,IAAA,CAAK,CAAA,cAAA,EAAiB,UAAA,CAAW,MAAA,CAAO,MAAM,CAAA,CAAE,CAAA;AACtD,EAAA,KAAA,CAAM,IAAA,CAAK,CAAA,2BAAA,EAAe,UAAA,CAAW,YAAA,CAAa,MAAM,CAAA,CAAE,CAAA;AAC1D,EAAA,KAAA,CAAM,IAAA,CAAK,CAAA,4BAAA,EAAgB,UAAA,CAAW,aAAA,CAAc,MAAM,CAAA,CAAE,CAAA;AAC5D,EAAA,KAAA,CAAM,IAAA,CAAK,CAAA,wBAAA,EAAY,UAAA,CAAW,SAAA,CAAU,MAAM,CAAA,CAAE,CAAA;AACpD,EAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AAEb,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,KAAA,CAAM,KAAK,wBAAwB,CAAA;AACnC,IAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AACb,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,cAAA,EAAiB,UAAA,CAAW,WAAA,CAAY,MAAM,CAAA,CAAE,CAAA;AAC3D,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,6BAAA,EAAiB,UAAA,CAAW,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AACxD,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,yCAAA,EAA6B,UAAA,CAAW,kBAAA,CAAmB,MAAM,CAAA,CAAE,CAAA;AAC9E,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,qCAAA,EAAyB,UAAA,CAAW,cAAA,CAAe,MAAM,CAAA,CAAE,CAAA;AACtE,IAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AAAA,EACf;AAEA,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,MAAM,aAAa,YAAA,CAAa,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,YAAY,CAAA,CAAE,MAAA;AAC5D,IAAA,MAAM,SAAS,YAAA,CAAa,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,EAAE,YAAY,CAAA;AAEvD,IAAA,KAAA,CAAM,KAAK,wBAAwB,CAAA;AACnC,IAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AACb,IAAA,KAAA,CAAM,KAAK,CAAA,YAAA,EAAe,UAAU,CAAA,CAAA,EAAI,YAAA,CAAa,MAAM,CAAA,CAAE,CAAA;AAE7D,IAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,MAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AACb,MAAA,KAAA,CAAM,KAAK,gBAAgB,CAAA;AAC3B,MAAA,KAAA,MAAW,CAAA,IAAK,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG;AACnC,QAAA,KAAA,CAAM,IAAA,CAAK,CAAA,IAAA,EAAO,CAAA,CAAE,KAAA,CAAM,IAAI,WAAM,CAAA,CAAE,UAAA,IAAc,CAAA,CAAE,KAAK,CAAA,CAAE,CAAA;AAAA,MAC/D;AACA,MAAA,IAAI,MAAA,CAAO,SAAS,EAAA,EAAI;AACtB,QAAA,KAAA,CAAM,IAAA,CAAK,CAAA,SAAA,EAAY,MAAA,CAAO,MAAA,GAAS,EAAE,CAAA,KAAA,CAAO,CAAA;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB","file":"index.mjs","sourcesContent":["/**\n * @djangocfg/seo - Next.js App Router Scanner\n * Scans app/ directory to extract all routes\n */\n\nimport { readdirSync, statSync, existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\n\nexport interface RouteInfo {\n /** Route path (e.g., /blog/[slug]) */\n path: string;\n /** File path relative to app/ */\n filePath: string;\n /** Route type */\n type: 'page' | 'api' | 'layout' | 'loading' | 'error';\n /** Is dynamic route */\n isDynamic: boolean;\n /** Dynamic segments (e.g., ['slug', '...path']) */\n dynamicSegments: string[];\n /** Is catch-all route */\n isCatchAll: boolean;\n /** Is optional catch-all route */\n isOptionalCatchAll: boolean;\n /** Route group (if any) */\n routeGroup?: string;\n}\n\nexport interface ScanResult {\n /** All discovered routes */\n routes: RouteInfo[];\n /** Static routes (no dynamic segments) */\n staticRoutes: RouteInfo[];\n /** Dynamic routes (with [param]) */\n dynamicRoutes: RouteInfo[];\n /** API routes */\n apiRoutes: RouteInfo[];\n /** App directory path */\n appDir: string;\n}\n\nexport interface ScanOptions {\n /** Path to app directory */\n appDir?: string;\n /** Include API routes */\n includeApi?: boolean;\n /** Include layouts, loading, error pages */\n includeSpecial?: boolean;\n}\n\nconst PAGE_FILES = ['page.tsx', 'page.ts', 'page.jsx', 'page.js'];\nconst ROUTE_FILES = ['route.tsx', 'route.ts', 'route.jsx', 'route.js'];\nconst SPECIAL_FILES = ['layout', 'loading', 'error', 'not-found', 'template'];\n\n/**\n * Find app directory by searching common locations\n */\nexport function findAppDir(startDir: string = process.cwd()): string | null {\n const candidates = [\n join(startDir, 'app'),\n join(startDir, 'src', 'app'),\n ];\n\n for (const dir of candidates) {\n if (existsSync(dir) && statSync(dir).isDirectory()) {\n return dir;\n }\n }\n\n return null;\n}\n\n/**\n * Scan Next.js app directory for routes\n */\nexport function scanRoutes(options: ScanOptions = {}): ScanResult {\n const {\n appDir = findAppDir() || './app',\n includeApi = true,\n includeSpecial = false,\n } = options;\n\n if (!existsSync(appDir)) {\n throw new Error(`App directory not found: ${appDir}`);\n }\n\n const routes: RouteInfo[] = [];\n\n scanDirectory(appDir, '', routes, { includeApi, includeSpecial });\n\n return {\n routes,\n staticRoutes: routes.filter(r => !r.isDynamic && r.type === 'page'),\n dynamicRoutes: routes.filter(r => r.isDynamic && r.type === 'page'),\n apiRoutes: routes.filter(r => r.type === 'api'),\n appDir,\n };\n}\n\nfunction scanDirectory(\n dir: string,\n routePath: string,\n routes: RouteInfo[],\n options: { includeApi: boolean; includeSpecial: boolean }\n): void {\n let entries: string[];\n\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n\n let stat;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n // Skip private folders\n if (entry.startsWith('_') || entry.startsWith('.')) continue;\n\n // Handle route groups (folder)\n if (entry.startsWith('(') && entry.endsWith(')')) {\n // Route groups don't add to URL path\n scanDirectory(fullPath, routePath, routes, options);\n continue;\n }\n\n // Handle parallel routes @folder\n if (entry.startsWith('@')) {\n // Parallel routes don't add to URL path\n scanDirectory(fullPath, routePath, routes, options);\n continue;\n }\n\n // Handle intercepting routes (.)folder, (..)folder, (...)folder\n if (entry.startsWith('(') && !entry.endsWith(')')) {\n continue; // Skip intercepting routes for now\n }\n\n // Build new route path\n const segment = processSegment(entry);\n const newRoutePath = routePath + '/' + segment.urlSegment;\n\n scanDirectory(fullPath, newRoutePath, routes, options);\n } else if (stat.isFile()) {\n // Check for page files\n if (PAGE_FILES.includes(entry)) {\n const route = createRouteInfo(routePath || '/', dir, 'page');\n routes.push(route);\n }\n\n // Check for API route files\n if (options.includeApi && ROUTE_FILES.includes(entry)) {\n const route = createRouteInfo(routePath || '/', dir, 'api');\n routes.push(route);\n }\n\n // Check for special files\n if (options.includeSpecial) {\n const baseName = entry.replace(/\\.(tsx?|jsx?|js)$/, '');\n if (SPECIAL_FILES.includes(baseName)) {\n const route = createRouteInfo(routePath || '/', dir, baseName as RouteInfo['type']);\n routes.push(route);\n }\n }\n }\n }\n}\n\ninterface SegmentInfo {\n urlSegment: string;\n isDynamic: boolean;\n paramName?: string;\n isCatchAll: boolean;\n isOptionalCatchAll: boolean;\n}\n\nfunction processSegment(segment: string): SegmentInfo {\n // Optional catch-all: [[...slug]]\n if (segment.startsWith('[[...') && segment.endsWith(']]')) {\n const paramName = segment.slice(5, -2);\n return {\n urlSegment: segment,\n isDynamic: true,\n paramName,\n isCatchAll: true,\n isOptionalCatchAll: true,\n };\n }\n\n // Catch-all: [...slug]\n if (segment.startsWith('[...') && segment.endsWith(']')) {\n const paramName = segment.slice(4, -1);\n return {\n urlSegment: segment,\n isDynamic: true,\n paramName,\n isCatchAll: true,\n isOptionalCatchAll: false,\n };\n }\n\n // Dynamic: [slug]\n if (segment.startsWith('[') && segment.endsWith(']')) {\n const paramName = segment.slice(1, -1);\n return {\n urlSegment: segment,\n isDynamic: true,\n paramName,\n isCatchAll: false,\n isOptionalCatchAll: false,\n };\n }\n\n // Static segment\n return {\n urlSegment: segment,\n isDynamic: false,\n isCatchAll: false,\n isOptionalCatchAll: false,\n };\n}\n\nfunction createRouteInfo(path: string, filePath: string, type: RouteInfo['type']): RouteInfo {\n const segments = path.split('/').filter(Boolean);\n const dynamicSegments: string[] = [];\n let isDynamic = false;\n let isCatchAll = false;\n let isOptionalCatchAll = false;\n\n for (const segment of segments) {\n const info = processSegment(segment);\n if (info.isDynamic) {\n isDynamic = true;\n if (info.paramName) {\n dynamicSegments.push(info.paramName);\n }\n if (info.isCatchAll) isCatchAll = true;\n if (info.isOptionalCatchAll) isOptionalCatchAll = true;\n }\n }\n\n // Extract route group from path\n let routeGroup: string | undefined;\n const groupMatch = filePath.match(/\\(([^)]+)\\)/);\n if (groupMatch) {\n routeGroup = groupMatch[1];\n }\n\n return {\n path: path || '/',\n filePath,\n type,\n isDynamic,\n dynamicSegments,\n isCatchAll,\n isOptionalCatchAll,\n routeGroup,\n };\n}\n\n/**\n * Convert route path to URL (replace dynamic segments with example values)\n */\nexport function routeToUrl(route: RouteInfo, params?: Record<string, string>): string {\n let url = route.path;\n\n for (const segment of route.dynamicSegments) {\n const value = params?.[segment] || `{${segment}}`;\n\n // Handle different dynamic segment types\n if (route.isOptionalCatchAll) {\n url = url.replace(`[[...${segment}]]`, value);\n } else if (route.isCatchAll) {\n url = url.replace(`[...${segment}]`, value);\n } else {\n url = url.replace(`[${segment}]`, value);\n }\n }\n\n return url;\n}\n\n/**\n * Get static routes as URLs (ready to check)\n */\nexport function getStaticUrls(scanResult: ScanResult, baseUrl: string): string[] {\n return scanResult.staticRoutes.map(route => {\n const url = new URL(route.path, baseUrl);\n return url.href;\n });\n}\n","/**\n * @djangocfg/seo - Routes Analyzer\n * Compare routes with sitemap and verify accessibility\n */\n\nimport pLimit from 'p-limit';\nimport type { ScanResult, RouteInfo } from './scanner.js';\nimport type { SeoIssue } from '../types/index.js';\n\nexport interface SitemapUrl {\n loc: string;\n lastmod?: string;\n changefreq?: string;\n priority?: string;\n}\n\nexport interface RouteComparisonResult {\n /** Routes found in app/ */\n appRoutes: RouteInfo[];\n /** URLs found in sitemap */\n sitemapUrls: string[];\n /** Static routes missing from sitemap */\n missingFromSitemap: RouteInfo[];\n /** Sitemap URLs that don't match any route */\n extraInSitemap: string[];\n /** Matching routes */\n matching: RouteInfo[];\n}\n\nexport interface RouteVerificationResult {\n /** Route being verified */\n route: RouteInfo;\n /** Full URL */\n url: string;\n /** HTTP status code */\n statusCode: number;\n /** Is accessible (2xx) */\n isAccessible: boolean;\n /** Error message if failed */\n error?: string;\n /** Response time in ms */\n responseTime: number;\n}\n\nexport interface VerifyOptions {\n /** Base URL (e.g., http://localhost:3000) */\n baseUrl: string;\n /** Request timeout in ms */\n timeout?: number;\n /** Max concurrent requests */\n concurrency?: number;\n /** Only verify static routes */\n staticOnly?: boolean;\n}\n\n/**\n * Compare routes with sitemap URLs\n */\nexport function compareWithSitemap(\n scanResult: ScanResult,\n sitemapUrls: string[],\n baseUrl: string\n): RouteComparisonResult {\n const appRoutes = scanResult.routes.filter(r => r.type === 'page');\n\n // Normalize sitemap URLs to paths\n const sitemapPaths = new Set(\n sitemapUrls.map(url => {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n })\n );\n\n // Find static routes missing from sitemap\n const missingFromSitemap: RouteInfo[] = [];\n const matching: RouteInfo[] = [];\n\n for (const route of scanResult.staticRoutes) {\n const path = route.path === '/' ? '/' : route.path;\n // Check with and without trailing slash\n if (sitemapPaths.has(path) || sitemapPaths.has(path + '/') || sitemapPaths.has(path.replace(/\\/$/, ''))) {\n matching.push(route);\n } else {\n missingFromSitemap.push(route);\n }\n }\n\n // Find sitemap URLs that don't match any static route\n const staticPaths = new Set(scanResult.staticRoutes.map(r => r.path));\n const dynamicPatterns = scanResult.dynamicRoutes.map(r => routeToRegex(r.path));\n\n const extraInSitemap: string[] = [];\n for (const path of sitemapPaths) {\n // Skip if matches static route\n if (staticPaths.has(path) || staticPaths.has(path + '/') || staticPaths.has(path.replace(/\\/$/, ''))) {\n continue;\n }\n\n // Skip if matches dynamic route pattern\n const matchesDynamic = dynamicPatterns.some(regex => regex.test(path));\n if (matchesDynamic) continue;\n\n extraInSitemap.push(path);\n }\n\n return {\n appRoutes,\n sitemapUrls,\n missingFromSitemap,\n extraInSitemap,\n matching,\n };\n}\n\n/**\n * Convert route path to regex for matching\n */\nfunction routeToRegex(routePath: string): RegExp {\n let pattern = routePath\n // Escape special regex chars except [ ]\n .replace(/[.+?^${}()|\\\\]/g, '\\\\$&')\n // Optional catch-all: [[...slug]] matches zero or more segments\n .replace(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g, '(?:/.*)?')\n // Catch-all: [...slug] matches one or more segments\n .replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, '/.+')\n // Dynamic: [slug] matches one segment\n .replace(/\\[([^\\]]+)\\]/g, '/[^/]+');\n\n // Handle trailing slash optionally\n pattern = `^${pattern}/?$`;\n\n return new RegExp(pattern);\n}\n\n/**\n * Verify routes are accessible\n */\nexport async function verifyRoutes(\n scanResult: ScanResult,\n options: VerifyOptions\n): Promise<RouteVerificationResult[]> {\n const {\n baseUrl,\n timeout = 10000,\n concurrency = 5,\n staticOnly = true,\n } = options;\n\n const routes = staticOnly ? scanResult.staticRoutes : scanResult.routes.filter(r => r.type === 'page');\n const limit = pLimit(concurrency);\n\n const results = await Promise.all(\n routes.map(route =>\n limit(async () => {\n const url = new URL(route.path, baseUrl).href;\n const startTime = Date.now();\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal,\n redirect: 'follow',\n });\n\n clearTimeout(timeoutId);\n\n return {\n route,\n url,\n statusCode: response.status,\n isAccessible: response.status >= 200 && response.status < 400,\n responseTime: Date.now() - startTime,\n };\n } catch (error) {\n return {\n route,\n url,\n statusCode: 0,\n isAccessible: false,\n error: (error as Error).message,\n responseTime: Date.now() - startTime,\n };\n }\n })\n )\n );\n\n return results;\n}\n\n/**\n * Analyze routes and generate SEO issues\n */\nexport function analyzeRoutes(\n scanResult: ScanResult,\n comparison?: RouteComparisonResult,\n verification?: RouteVerificationResult[]\n): SeoIssue[] {\n const issues: SeoIssue[] = [];\n const now = new Date().toISOString();\n\n // Issue: Static routes missing from sitemap\n if (comparison && comparison.missingFromSitemap.length > 0) {\n for (const route of comparison.missingFromSitemap) {\n issues.push({\n id: `route-missing-sitemap-${route.path}`,\n category: 'indexing',\n severity: 'warning',\n title: 'Route missing from sitemap',\n description: `Static route ${route.path} is not in sitemap.xml`,\n url: route.path,\n recommendation: 'Add this route to your sitemap for better indexing',\n detectedAt: now,\n });\n }\n }\n\n // Issue: Sitemap URLs that don't match routes (potential dead links)\n if (comparison && comparison.extraInSitemap.length > 0) {\n for (const path of comparison.extraInSitemap.slice(0, 20)) {\n issues.push({\n id: `sitemap-orphan-${path}`,\n category: 'indexing',\n severity: 'info',\n title: 'Sitemap URL without matching route',\n description: `URL ${path} in sitemap doesn't match any app/ route`,\n url: path,\n recommendation: 'Verify this URL is still valid or remove from sitemap',\n detectedAt: now,\n });\n }\n }\n\n // Issue: Inaccessible routes\n if (verification) {\n for (const result of verification) {\n if (!result.isAccessible) {\n issues.push({\n id: `route-inaccessible-${result.route.path}`,\n category: 'technical',\n severity: result.statusCode === 404 ? 'error' : 'warning',\n title: `Route returns ${result.statusCode || 'error'}`,\n description: result.error || `Route ${result.route.path} returned status ${result.statusCode}`,\n url: result.url,\n recommendation: 'Fix the route or remove it from app/',\n detectedAt: now,\n });\n }\n }\n }\n\n // Issue: Too many dynamic routes (SEO concern)\n if (scanResult.dynamicRoutes.length > scanResult.staticRoutes.length * 2) {\n issues.push({\n id: 'too-many-dynamic-routes',\n category: 'content',\n severity: 'info',\n title: 'High ratio of dynamic routes',\n description: `${scanResult.dynamicRoutes.length} dynamic vs ${scanResult.staticRoutes.length} static routes`,\n url: '/',\n recommendation: 'Ensure dynamic routes have proper generateStaticParams for SSG',\n detectedAt: now,\n });\n }\n\n return issues;\n}\n\n/**\n * Generate routes summary\n */\nexport function generateRoutesSummary(\n scanResult: ScanResult,\n comparison?: RouteComparisonResult,\n verification?: RouteVerificationResult[]\n): string {\n const lines: string[] = [];\n\n lines.push('## Routes Summary');\n lines.push('');\n lines.push(`Total routes: ${scanResult.routes.length}`);\n lines.push(`├── Static: ${scanResult.staticRoutes.length}`);\n lines.push(`├── Dynamic: ${scanResult.dynamicRoutes.length}`);\n lines.push(`└── API: ${scanResult.apiRoutes.length}`);\n lines.push('');\n\n if (comparison) {\n lines.push('### Sitemap Comparison');\n lines.push('');\n lines.push(`Sitemap URLs: ${comparison.sitemapUrls.length}`);\n lines.push(`├── Matching: ${comparison.matching.length}`);\n lines.push(`├── Missing from sitemap: ${comparison.missingFromSitemap.length}`);\n lines.push(`└── Extra in sitemap: ${comparison.extraInSitemap.length}`);\n lines.push('');\n }\n\n if (verification) {\n const accessible = verification.filter(r => r.isAccessible).length;\n const broken = verification.filter(r => !r.isAccessible);\n\n lines.push('### Route Verification');\n lines.push('');\n lines.push(`Accessible: ${accessible}/${verification.length}`);\n\n if (broken.length > 0) {\n lines.push('');\n lines.push('Broken routes:');\n for (const r of broken.slice(0, 10)) {\n lines.push(` - ${r.route.path} → ${r.statusCode || r.error}`);\n }\n if (broken.length > 10) {\n lines.push(` - ... +${broken.length - 10} more`);\n }\n }\n }\n\n return lines.join('\\n');\n}\n"]}
1
+ {"version":3,"sources":["../../src/routes/scanner.ts","../../src/routes/analyzer.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @djangocfg/seo - Next.js App Router Scanner\n * Scans app/ directory to extract all routes\n */\n\nimport { readdirSync, statSync, existsSync } from 'node:fs';\nimport { join, relative } from 'node:path';\n\nexport interface RouteInfo {\n /** Route path (e.g., /blog/[slug]) */\n path: string;\n /** File path relative to app/ */\n filePath: string;\n /** Route type */\n type: 'page' | 'api' | 'layout' | 'loading' | 'error';\n /** Is dynamic route */\n isDynamic: boolean;\n /** Dynamic segments (e.g., ['slug', '...path']) */\n dynamicSegments: string[];\n /** Is catch-all route */\n isCatchAll: boolean;\n /** Is optional catch-all route */\n isOptionalCatchAll: boolean;\n /** Route group (if any) */\n routeGroup?: string;\n}\n\nexport interface ScanResult {\n /** All discovered routes */\n routes: RouteInfo[];\n /** Static routes (no dynamic segments) */\n staticRoutes: RouteInfo[];\n /** Dynamic routes (with [param]) */\n dynamicRoutes: RouteInfo[];\n /** API routes */\n apiRoutes: RouteInfo[];\n /** App directory path */\n appDir: string;\n}\n\nexport interface ScanOptions {\n /** Path to app directory */\n appDir?: string;\n /** Include API routes */\n includeApi?: boolean;\n /** Include layouts, loading, error pages */\n includeSpecial?: boolean;\n}\n\nconst PAGE_FILES = ['page.tsx', 'page.ts', 'page.jsx', 'page.js'];\nconst ROUTE_FILES = ['route.tsx', 'route.ts', 'route.jsx', 'route.js'];\nconst SPECIAL_FILES = ['layout', 'loading', 'error', 'not-found', 'template'];\n\n/**\n * Find app directory by searching common locations\n */\nexport function findAppDir(startDir: string = process.cwd()): string | null {\n const candidates = [\n join(startDir, 'app'),\n join(startDir, 'src', 'app'),\n ];\n\n for (const dir of candidates) {\n if (existsSync(dir) && statSync(dir).isDirectory()) {\n return dir;\n }\n }\n\n return null;\n}\n\n/**\n * Scan Next.js app directory for routes\n */\nexport function scanRoutes(options: ScanOptions = {}): ScanResult {\n const {\n appDir = findAppDir() || './app',\n includeApi = true,\n includeSpecial = false,\n } = options;\n\n if (!existsSync(appDir)) {\n throw new Error(`App directory not found: ${appDir}`);\n }\n\n const routes: RouteInfo[] = [];\n\n scanDirectory(appDir, '', routes, { includeApi, includeSpecial });\n\n return {\n routes,\n staticRoutes: routes.filter(r => !r.isDynamic && r.type === 'page'),\n dynamicRoutes: routes.filter(r => r.isDynamic && r.type === 'page'),\n apiRoutes: routes.filter(r => r.type === 'api'),\n appDir,\n };\n}\n\nfunction scanDirectory(\n dir: string,\n routePath: string,\n routes: RouteInfo[],\n options: { includeApi: boolean; includeSpecial: boolean }\n): void {\n let entries: string[];\n\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n\n let stat;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n // Skip private folders\n if (entry.startsWith('_') || entry.startsWith('.')) continue;\n\n // Handle route groups (folder)\n if (entry.startsWith('(') && entry.endsWith(')')) {\n // Route groups don't add to URL path\n scanDirectory(fullPath, routePath, routes, options);\n continue;\n }\n\n // Handle parallel routes @folder\n if (entry.startsWith('@')) {\n // Parallel routes don't add to URL path\n scanDirectory(fullPath, routePath, routes, options);\n continue;\n }\n\n // Handle intercepting routes (.)folder, (..)folder, (...)folder\n if (entry.startsWith('(') && !entry.endsWith(')')) {\n continue; // Skip intercepting routes for now\n }\n\n // Build new route path\n const segment = processSegment(entry);\n const newRoutePath = routePath + '/' + segment.urlSegment;\n\n scanDirectory(fullPath, newRoutePath, routes, options);\n } else if (stat.isFile()) {\n // Check for page files\n if (PAGE_FILES.includes(entry)) {\n const route = createRouteInfo(routePath || '/', dir, 'page');\n routes.push(route);\n }\n\n // Check for API route files\n if (options.includeApi && ROUTE_FILES.includes(entry)) {\n const route = createRouteInfo(routePath || '/', dir, 'api');\n routes.push(route);\n }\n\n // Check for special files\n if (options.includeSpecial) {\n const baseName = entry.replace(/\\.(tsx?|jsx?|js)$/, '');\n if (SPECIAL_FILES.includes(baseName)) {\n const route = createRouteInfo(routePath || '/', dir, baseName as RouteInfo['type']);\n routes.push(route);\n }\n }\n }\n }\n}\n\ninterface SegmentInfo {\n urlSegment: string;\n isDynamic: boolean;\n paramName?: string;\n isCatchAll: boolean;\n isOptionalCatchAll: boolean;\n}\n\nfunction processSegment(segment: string): SegmentInfo {\n // Optional catch-all: [[...slug]]\n if (segment.startsWith('[[...') && segment.endsWith(']]')) {\n const paramName = segment.slice(5, -2);\n return {\n urlSegment: segment,\n isDynamic: true,\n paramName,\n isCatchAll: true,\n isOptionalCatchAll: true,\n };\n }\n\n // Catch-all: [...slug]\n if (segment.startsWith('[...') && segment.endsWith(']')) {\n const paramName = segment.slice(4, -1);\n return {\n urlSegment: segment,\n isDynamic: true,\n paramName,\n isCatchAll: true,\n isOptionalCatchAll: false,\n };\n }\n\n // Dynamic: [slug]\n if (segment.startsWith('[') && segment.endsWith(']')) {\n const paramName = segment.slice(1, -1);\n return {\n urlSegment: segment,\n isDynamic: true,\n paramName,\n isCatchAll: false,\n isOptionalCatchAll: false,\n };\n }\n\n // Static segment\n return {\n urlSegment: segment,\n isDynamic: false,\n isCatchAll: false,\n isOptionalCatchAll: false,\n };\n}\n\nfunction createRouteInfo(path: string, filePath: string, type: RouteInfo['type']): RouteInfo {\n const segments = path.split('/').filter(Boolean);\n const dynamicSegments: string[] = [];\n let isDynamic = false;\n let isCatchAll = false;\n let isOptionalCatchAll = false;\n\n for (const segment of segments) {\n const info = processSegment(segment);\n if (info.isDynamic) {\n isDynamic = true;\n if (info.paramName) {\n dynamicSegments.push(info.paramName);\n }\n if (info.isCatchAll) isCatchAll = true;\n if (info.isOptionalCatchAll) isOptionalCatchAll = true;\n }\n }\n\n // Extract route group from path\n let routeGroup: string | undefined;\n const groupMatch = filePath.match(/\\(([^)]+)\\)/);\n if (groupMatch) {\n routeGroup = groupMatch[1];\n }\n\n return {\n path: path || '/',\n filePath,\n type,\n isDynamic,\n dynamicSegments,\n isCatchAll,\n isOptionalCatchAll,\n routeGroup,\n };\n}\n\n/**\n * Convert route path to URL (replace dynamic segments with example values)\n */\nexport function routeToUrl(route: RouteInfo, params?: Record<string, string>): string {\n let url = route.path;\n\n for (const segment of route.dynamicSegments) {\n const value = params?.[segment] || `{${segment}}`;\n\n // Handle different dynamic segment types\n if (route.isOptionalCatchAll) {\n url = url.replace(`[[...${segment}]]`, value);\n } else if (route.isCatchAll) {\n url = url.replace(`[...${segment}]`, value);\n } else {\n url = url.replace(`[${segment}]`, value);\n }\n }\n\n return url;\n}\n\n/**\n * Get static routes as URLs (ready to check)\n */\nexport function getStaticUrls(scanResult: ScanResult, baseUrl: string): string[] {\n return scanResult.staticRoutes.map(route => {\n const url = new URL(route.path, baseUrl);\n return url.href;\n });\n}\n","/**\n * @djangocfg/seo - Routes Analyzer\n * Compare routes with sitemap and verify accessibility\n */\n\nimport pLimit from 'p-limit';\nimport type { ScanResult, RouteInfo } from './scanner.js';\nimport type { SeoIssue } from '../types/index.js';\n\nexport interface SitemapUrl {\n loc: string;\n lastmod?: string;\n changefreq?: string;\n priority?: string;\n}\n\nexport interface RouteComparisonResult {\n /** Routes found in app/ */\n appRoutes: RouteInfo[];\n /** URLs found in sitemap */\n sitemapUrls: string[];\n /** Static routes missing from sitemap */\n missingFromSitemap: RouteInfo[];\n /** Sitemap URLs that don't match any route */\n extraInSitemap: string[];\n /** Matching routes */\n matching: RouteInfo[];\n}\n\nexport interface RouteVerificationResult {\n /** Route being verified */\n route: RouteInfo;\n /** Full URL */\n url: string;\n /** HTTP status code */\n statusCode: number;\n /** Is accessible (2xx) */\n isAccessible: boolean;\n /** Error message if failed */\n error?: string;\n /** Response time in ms */\n responseTime: number;\n}\n\nexport interface VerifyOptions {\n /** Base URL (e.g., http://localhost:3000) */\n baseUrl: string;\n /** Request timeout in ms */\n timeout?: number;\n /** Max concurrent requests */\n concurrency?: number;\n /** Only verify static routes */\n staticOnly?: boolean;\n}\n\n/**\n * Compare routes with sitemap URLs\n */\nexport function compareWithSitemap(\n scanResult: ScanResult,\n sitemapUrls: string[],\n baseUrl: string\n): RouteComparisonResult {\n const appRoutes = scanResult.routes.filter(r => r.type === 'page');\n\n // Normalize sitemap URLs to paths\n const sitemapPaths = new Set(\n sitemapUrls\n .filter(url => url)\n .map(url => {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n })\n .filter(Boolean)\n );\n\n // Find static routes missing from sitemap\n const missingFromSitemap: RouteInfo[] = [];\n const matching: RouteInfo[] = [];\n\n for (const route of scanResult.staticRoutes) {\n const path = route.path || '/';\n // Check with and without trailing slash\n if (sitemapPaths.has(path) || sitemapPaths.has(path + '/') || sitemapPaths.has(path.replace(/\\/$/, ''))) {\n matching.push(route);\n } else {\n missingFromSitemap.push(route);\n }\n }\n\n // Find sitemap URLs that don't match any static route\n const staticPaths = new Set(scanResult.staticRoutes.map(r => r.path).filter(Boolean));\n const dynamicPatterns = scanResult.dynamicRoutes\n .filter(r => r.path)\n .map(r => routeToRegex(r.path));\n\n const extraInSitemap: string[] = [];\n for (const path of sitemapPaths) {\n if (!path) continue;\n // Skip if matches static route\n if (staticPaths.has(path) || staticPaths.has(path + '/') || staticPaths.has(path.replace(/\\/$/, ''))) {\n continue;\n }\n\n // Skip if matches dynamic route pattern\n const matchesDynamic = dynamicPatterns.some(regex => regex.test(path));\n if (matchesDynamic) continue;\n\n extraInSitemap.push(path);\n }\n\n return {\n appRoutes,\n sitemapUrls,\n missingFromSitemap,\n extraInSitemap,\n matching,\n };\n}\n\n/**\n * Convert route path to regex for matching\n */\nfunction routeToRegex(routePath: string): RegExp {\n if (!routePath) return /^$/;\n let pattern = routePath\n // Escape special regex chars except [ ]\n .replace(/[.+?^${}()|\\\\]/g, '\\\\$&')\n // Optional catch-all: [[...slug]] matches zero or more segments\n .replace(/\\[\\[\\.\\.\\.([^\\]]+)\\]\\]/g, '(?:/.*)?')\n // Catch-all: [...slug] matches one or more segments\n .replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, '/.+')\n // Dynamic: [slug] matches one segment\n .replace(/\\[([^\\]]+)\\]/g, '/[^/]+');\n\n // Handle trailing slash optionally\n pattern = `^${pattern}/?$`;\n\n return new RegExp(pattern);\n}\n\n/**\n * Verify routes are accessible\n */\nexport async function verifyRoutes(\n scanResult: ScanResult,\n options: VerifyOptions\n): Promise<RouteVerificationResult[]> {\n const {\n baseUrl,\n timeout = 10000,\n concurrency = 5,\n staticOnly = true,\n } = options;\n\n const routes = staticOnly ? scanResult.staticRoutes : scanResult.routes.filter(r => r.type === 'page');\n const limit = pLimit(concurrency);\n\n const results = await Promise.all(\n routes.map(route =>\n limit(async () => {\n const url = new URL(route.path, baseUrl).href;\n const startTime = Date.now();\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const response = await fetch(url, {\n method: 'HEAD',\n signal: controller.signal,\n redirect: 'follow',\n });\n\n clearTimeout(timeoutId);\n\n return {\n route,\n url,\n statusCode: response.status,\n isAccessible: response.status >= 200 && response.status < 400,\n responseTime: Date.now() - startTime,\n };\n } catch (error) {\n return {\n route,\n url,\n statusCode: 0,\n isAccessible: false,\n error: (error as Error).message,\n responseTime: Date.now() - startTime,\n };\n }\n })\n )\n );\n\n return results;\n}\n\n/**\n * Analyze routes and generate SEO issues\n */\nexport function analyzeRoutes(\n scanResult: ScanResult,\n comparison?: RouteComparisonResult,\n verification?: RouteVerificationResult[]\n): SeoIssue[] {\n const issues: SeoIssue[] = [];\n const now = new Date().toISOString();\n\n // Issue: Static routes missing from sitemap\n if (comparison && comparison.missingFromSitemap.length > 0) {\n for (const route of comparison.missingFromSitemap) {\n issues.push({\n id: `route-missing-sitemap-${route.path}`,\n category: 'indexing',\n severity: 'warning',\n title: 'Route missing from sitemap',\n description: `Static route ${route.path} is not in sitemap.xml`,\n url: route.path,\n recommendation: 'Add this route to your sitemap for better indexing',\n detectedAt: now,\n });\n }\n }\n\n // Issue: Sitemap URLs that don't match routes (potential dead links)\n if (comparison && comparison.extraInSitemap.length > 0) {\n for (const path of comparison.extraInSitemap.slice(0, 20)) {\n issues.push({\n id: `sitemap-orphan-${path}`,\n category: 'indexing',\n severity: 'info',\n title: 'Sitemap URL without matching route',\n description: `URL ${path} in sitemap doesn't match any app/ route`,\n url: path,\n recommendation: 'Verify this URL is still valid or remove from sitemap',\n detectedAt: now,\n });\n }\n }\n\n // Issue: Inaccessible routes\n if (verification) {\n for (const result of verification) {\n if (!result.isAccessible) {\n issues.push({\n id: `route-inaccessible-${result.route.path}`,\n category: 'technical',\n severity: result.statusCode === 404 ? 'error' : 'warning',\n title: `Route returns ${result.statusCode || 'error'}`,\n description: result.error || `Route ${result.route.path} returned status ${result.statusCode}`,\n url: result.url,\n recommendation: 'Fix the route or remove it from app/',\n detectedAt: now,\n });\n }\n }\n }\n\n // Issue: Too many dynamic routes (SEO concern)\n if (scanResult.dynamicRoutes.length > scanResult.staticRoutes.length * 2) {\n issues.push({\n id: 'too-many-dynamic-routes',\n category: 'content',\n severity: 'info',\n title: 'High ratio of dynamic routes',\n description: `${scanResult.dynamicRoutes.length} dynamic vs ${scanResult.staticRoutes.length} static routes`,\n url: '/',\n recommendation: 'Ensure dynamic routes have proper generateStaticParams for SSG',\n detectedAt: now,\n });\n }\n\n return issues;\n}\n\n/**\n * Generate routes summary\n */\nexport function generateRoutesSummary(\n scanResult: ScanResult,\n comparison?: RouteComparisonResult,\n verification?: RouteVerificationResult[]\n): string {\n const lines: string[] = [];\n\n lines.push('## Routes Summary');\n lines.push('');\n lines.push(`Total routes: ${scanResult.routes.length}`);\n lines.push(`├── Static: ${scanResult.staticRoutes.length}`);\n lines.push(`├── Dynamic: ${scanResult.dynamicRoutes.length}`);\n lines.push(`└── API: ${scanResult.apiRoutes.length}`);\n lines.push('');\n\n if (comparison) {\n lines.push('### Sitemap Comparison');\n lines.push('');\n lines.push(`Sitemap URLs: ${comparison.sitemapUrls.length}`);\n lines.push(`├── Matching: ${comparison.matching.length}`);\n lines.push(`├── Missing from sitemap: ${comparison.missingFromSitemap.length}`);\n lines.push(`└── Extra in sitemap: ${comparison.extraInSitemap.length}`);\n lines.push('');\n }\n\n if (verification) {\n const accessible = verification.filter(r => r.isAccessible).length;\n const broken = verification.filter(r => !r.isAccessible);\n\n lines.push('### Route Verification');\n lines.push('');\n lines.push(`Accessible: ${accessible}/${verification.length}`);\n\n if (broken.length > 0) {\n lines.push('');\n lines.push('Broken routes:');\n for (const r of broken.slice(0, 10)) {\n lines.push(` - ${r.route.path} → ${r.statusCode || r.error}`);\n }\n if (broken.length > 10) {\n lines.push(` - ... +${broken.length - 10} more`);\n }\n }\n }\n\n return lines.join('\\n');\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/seo",
3
- "version": "2.1.50",
3
+ "version": "2.1.52",
4
4
  "description": "SEO analytics and indexing diagnostics module with Google Search Console integration and AI-ready reports",
5
5
  "keywords": [
6
6
  "seo",
@@ -101,7 +101,7 @@
101
101
  "robots-parser": "^3.0.1"
102
102
  },
103
103
  "devDependencies": {
104
- "@djangocfg/typescript-config": "^2.1.50",
104
+ "@djangocfg/typescript-config": "^2.1.52",
105
105
  "@types/node": "^24.7.2",
106
106
  "tsup": "^8.5.0",
107
107
  "tsx": "^4.19.2",
@@ -51,6 +51,32 @@ export async function analyzeRobotsTxt(siteUrl: string): Promise<RobotsAnalysis>
51
51
  analysis.exists = true;
52
52
  analysis.content = await response.text();
53
53
 
54
+ // Check for Cloudflare managed robots.txt override
55
+ if (
56
+ analysis.content.includes('content-signal') ||
57
+ analysis.content.includes('Content-Signal') ||
58
+ analysis.content.includes('ai-input') ||
59
+ analysis.content.includes('ai-train')
60
+ ) {
61
+ analysis.issues.push({
62
+ id: 'cloudflare-managed-robots',
63
+ url: robotsUrl,
64
+ category: 'technical',
65
+ severity: 'warning',
66
+ title: 'Cloudflare managed robots.txt detected',
67
+ description:
68
+ 'Your robots.txt is being overwritten by Cloudflare\'s "Content Signals Policy". ' +
69
+ 'Your app/robots.ts file is not being served.',
70
+ recommendation:
71
+ 'Disable in Cloudflare Dashboard: Security → Settings → "Manage your robots.txt" → Set to "Off".',
72
+ detectedAt: new Date().toISOString(),
73
+ metadata: {
74
+ cloudflareFeature: 'Managed robots.txt',
75
+ docsUrl: 'https://developers.cloudflare.com/bots/additional-configurations/managed-robots-txt/',
76
+ },
77
+ });
78
+ }
79
+
54
80
  // Parse robots.txt
55
81
  const robots = robotsParser(robotsUrl, analysis.content);
56
82
 
@@ -78,6 +78,67 @@ export function generateClaudeContext(report: SeoReport): string {
78
78
  lines.push('- Private `_folder` - skipped');
79
79
  lines.push('');
80
80
 
81
+ // SEO files setup
82
+ lines.push('## SEO Files (Next.js App Router)');
83
+ lines.push('');
84
+ lines.push('**Required files in `app/`:**');
85
+ lines.push('');
86
+ lines.push('### sitemap.xml/route.ts');
87
+ lines.push('```typescript');
88
+ lines.push("import { createSitemapHandler } from '@djangocfg/nextjs/sitemap';");
89
+ lines.push('');
90
+ lines.push('export const dynamic = "force-static";');
91
+ lines.push('export const { GET } = createSitemapHandler({');
92
+ lines.push(' siteUrl,');
93
+ lines.push(' staticPages: [');
94
+ lines.push(' { loc: "/", priority: 1.0, changefreq: "daily" },');
95
+ lines.push(' { loc: "/about", priority: 0.8 },');
96
+ lines.push(' ],');
97
+ lines.push(' dynamicPages: async () => fetchPagesFromAPI(),');
98
+ lines.push('});');
99
+ lines.push('```');
100
+ lines.push('');
101
+ lines.push('### robots.ts');
102
+ lines.push('```typescript');
103
+ lines.push("import type { MetadataRoute } from 'next';");
104
+ lines.push('');
105
+ lines.push('export default function robots(): MetadataRoute.Robots {');
106
+ lines.push(' return {');
107
+ lines.push(' rules: { userAgent: "*", allow: "/" },');
108
+ lines.push(' sitemap: `${siteUrl}/sitemap.xml`,');
109
+ lines.push(' };');
110
+ lines.push('}');
111
+ lines.push('```');
112
+ lines.push('');
113
+ lines.push('### Cloudflare Override');
114
+ lines.push('');
115
+ lines.push('If robots.txt shows "Content-Signal" or "ai-train" — Cloudflare is overriding your file.');
116
+ lines.push('**Fix:** Dashboard → Security → Settings → "Manage your robots.txt" → Set to "Off"');
117
+ lines.push('');
118
+ lines.push('### Declarative Routes with SEO');
119
+ lines.push('');
120
+ lines.push('Create `app/_routes/` with SEO metadata for sitemap:');
121
+ lines.push('```typescript');
122
+ lines.push("import { defineRoute } from '@djangocfg/nextjs/navigation';");
123
+ lines.push('');
124
+ lines.push("export const home = defineRoute('/', {");
125
+ lines.push(" label: 'Home',");
126
+ lines.push(' protected: false,');
127
+ lines.push(' priority: 1.0, // Sitemap priority 0.0-1.0');
128
+ lines.push(" changefreq: 'daily', // always|hourly|daily|weekly|monthly|yearly|never");
129
+ lines.push(' noindex: false, // Exclude from sitemap');
130
+ lines.push('});');
131
+ lines.push('');
132
+ lines.push('export const staticRoutes = [home, about, contact];');
133
+ lines.push('```');
134
+ lines.push('');
135
+ lines.push('Then in `sitemap.xml/route.ts`:');
136
+ lines.push('```typescript');
137
+ lines.push("import { routes } from '@/app/_routes';");
138
+ lines.push('routes.getAllStaticRoutes().filter(r => !r.metadata.noindex)');
139
+ lines.push('```');
140
+ lines.push('');
141
+
81
142
  // Link guidelines
82
143
  lines.push('## Link Guidelines');
83
144
  lines.push('');
@@ -65,13 +65,16 @@ export function compareWithSitemap(
65
65
 
66
66
  // Normalize sitemap URLs to paths
67
67
  const sitemapPaths = new Set(
68
- sitemapUrls.map(url => {
69
- try {
70
- return new URL(url).pathname;
71
- } catch {
72
- return url;
73
- }
74
- })
68
+ sitemapUrls
69
+ .filter(url => url)
70
+ .map(url => {
71
+ try {
72
+ return new URL(url).pathname;
73
+ } catch {
74
+ return url;
75
+ }
76
+ })
77
+ .filter(Boolean)
75
78
  );
76
79
 
77
80
  // Find static routes missing from sitemap
@@ -79,7 +82,7 @@ export function compareWithSitemap(
79
82
  const matching: RouteInfo[] = [];
80
83
 
81
84
  for (const route of scanResult.staticRoutes) {
82
- const path = route.path === '/' ? '/' : route.path;
85
+ const path = route.path || '/';
83
86
  // Check with and without trailing slash
84
87
  if (sitemapPaths.has(path) || sitemapPaths.has(path + '/') || sitemapPaths.has(path.replace(/\/$/, ''))) {
85
88
  matching.push(route);
@@ -89,11 +92,14 @@ export function compareWithSitemap(
89
92
  }
90
93
 
91
94
  // Find sitemap URLs that don't match any static route
92
- const staticPaths = new Set(scanResult.staticRoutes.map(r => r.path));
93
- const dynamicPatterns = scanResult.dynamicRoutes.map(r => routeToRegex(r.path));
95
+ const staticPaths = new Set(scanResult.staticRoutes.map(r => r.path).filter(Boolean));
96
+ const dynamicPatterns = scanResult.dynamicRoutes
97
+ .filter(r => r.path)
98
+ .map(r => routeToRegex(r.path));
94
99
 
95
100
  const extraInSitemap: string[] = [];
96
101
  for (const path of sitemapPaths) {
102
+ if (!path) continue;
97
103
  // Skip if matches static route
98
104
  if (staticPaths.has(path) || staticPaths.has(path + '/') || staticPaths.has(path.replace(/\/$/, ''))) {
99
105
  continue;
@@ -119,6 +125,7 @@ export function compareWithSitemap(
119
125
  * Convert route path to regex for matching
120
126
  */
121
127
  function routeToRegex(routePath: string): RegExp {
128
+ if (!routePath) return /^$/;
122
129
  let pattern = routePath
123
130
  // Escape special regex chars except [ ]
124
131
  .replace(/[.+?^${}()|\\]/g, '\\$&')