@intlayer/backend 8.12.2 → 8.12.4-canary.0

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.
Files changed (415) hide show
  1. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/bundle_optimization.json +9954 -6953
  2. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/configuration.json +1 -1
  3. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  4. package/dist/esm/controllers/audit.controller.mjs.map +1 -1
  5. package/dist/esm/controllers/bitbucket.controller.mjs.map +1 -1
  6. package/dist/esm/controllers/cliSessionToken.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/demo.controller.mjs +32 -25
  8. package/dist/esm/controllers/demo.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/dictionary.controller.mjs +1 -0
  10. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/environment.controller.mjs.map +1 -1
  12. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  13. package/dist/esm/controllers/github.controller.mjs.map +1 -1
  14. package/dist/esm/controllers/gitlab.controller.mjs.map +1 -1
  15. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  16. package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
  17. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  18. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  19. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  20. package/dist/esm/controllers/projectMemberAccess.controller.mjs.map +1 -1
  21. package/dist/esm/controllers/recursiveAudit.controller.mjs.map +1 -1
  22. package/dist/esm/controllers/reviewer.controller.mjs.map +1 -1
  23. package/dist/esm/controllers/searchDoc.controller.mjs.map +1 -1
  24. package/dist/esm/controllers/showcaseProject.controller.mjs.map +1 -1
  25. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  26. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  27. package/dist/esm/controllers/translation.controller.mjs.map +1 -1
  28. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  29. package/dist/esm/emails/AffiliateActivatedEmail.mjs.map +1 -1
  30. package/dist/esm/emails/AffiliateConversionEmail.mjs.map +1 -1
  31. package/dist/esm/emails/AffiliateInvitationEmail.mjs.map +1 -1
  32. package/dist/esm/emails/AffiliateWelcomeEmail.mjs.map +1 -1
  33. package/dist/esm/emails/InviteUserEmail.mjs.map +1 -1
  34. package/dist/esm/emails/MagicLinkEmail.mjs.map +1 -1
  35. package/dist/esm/emails/MissionRequestedClientEmail.mjs.map +1 -1
  36. package/dist/esm/emails/MissionRequestedReviewerEmail.mjs.map +1 -1
  37. package/dist/esm/emails/OAuthTokenCreatedEmail.mjs.map +1 -1
  38. package/dist/esm/emails/PasswordChangeConfirmation.mjs.map +1 -1
  39. package/dist/esm/emails/ResetUserPassword.mjs.map +1 -1
  40. package/dist/esm/emails/ReviewerApplicationEmail.mjs.map +1 -1
  41. package/dist/esm/emails/ReviewerApprovedEmail.mjs.map +1 -1
  42. package/dist/esm/emails/ReviewerContactEmail.mjs.map +1 -1
  43. package/dist/esm/emails/SubscriptionPaymentCancellation.mjs.map +1 -1
  44. package/dist/esm/emails/SubscriptionPaymentError.mjs.map +1 -1
  45. package/dist/esm/emails/SubscriptionPaymentSuccess.mjs.map +1 -1
  46. package/dist/esm/emails/ValidateUserEmail.mjs.map +1 -1
  47. package/dist/esm/emails/Welcome.mjs.map +1 -1
  48. package/dist/esm/index.mjs +1 -1
  49. package/dist/esm/index.mjs.map +1 -1
  50. package/dist/esm/logger/index.mjs.map +1 -1
  51. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  52. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  53. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  54. package/dist/esm/routes/audit.routes.mjs.map +1 -1
  55. package/dist/esm/routes/bitbucket.routes.mjs.map +1 -1
  56. package/dist/esm/routes/demo.routes.mjs.map +1 -1
  57. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  58. package/dist/esm/routes/environment.routes.mjs.map +1 -1
  59. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  60. package/dist/esm/routes/github.routes.mjs.map +1 -1
  61. package/dist/esm/routes/gitlab.routes.mjs.map +1 -1
  62. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  63. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  64. package/dist/esm/routes/paramsSchemas.mjs.map +1 -1
  65. package/dist/esm/routes/project.routes.mjs.map +1 -1
  66. package/dist/esm/routes/reviewer.routes.mjs.map +1 -1
  67. package/dist/esm/routes/search.routes.mjs.map +1 -1
  68. package/dist/esm/routes/showcaseProject.routes.mjs.map +1 -1
  69. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  70. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  71. package/dist/esm/routes/translate.routes.mjs.map +1 -1
  72. package/dist/esm/routes/user.routes.mjs.map +1 -1
  73. package/dist/esm/schemas/account.schema.mjs.map +1 -1
  74. package/dist/esm/schemas/affiliate.schema.mjs.map +1 -1
  75. package/dist/esm/schemas/affiliateInvitation.schema.mjs.map +1 -1
  76. package/dist/esm/schemas/audit.schema.mjs.map +1 -1
  77. package/dist/esm/schemas/auditJob.schema.mjs.map +1 -1
  78. package/dist/esm/schemas/auditPage.schema.mjs.map +1 -1
  79. package/dist/esm/schemas/cliSessionToken.schema.mjs.map +1 -1
  80. package/dist/esm/schemas/dictionary.schema.mjs.map +1 -1
  81. package/dist/esm/schemas/discussion.schema.mjs.map +1 -1
  82. package/dist/esm/schemas/oAuth2.schema.mjs.map +1 -1
  83. package/dist/esm/schemas/organization.schema.mjs.map +1 -1
  84. package/dist/esm/schemas/plans.schema.mjs.map +1 -1
  85. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  86. package/dist/esm/schemas/promoCode.schema.mjs.map +1 -1
  87. package/dist/esm/schemas/reviewer.schema.mjs.map +1 -1
  88. package/dist/esm/schemas/session.schema.mjs.map +1 -1
  89. package/dist/esm/schemas/showcaseProject.schema.mjs.map +1 -1
  90. package/dist/esm/schemas/tag.schema.mjs.map +1 -1
  91. package/dist/esm/schemas/user.schema.mjs.map +1 -1
  92. package/dist/esm/services/affiliate.service.mjs.map +1 -1
  93. package/dist/esm/services/audit/analysis/analyzeBundleContent.mjs.map +1 -1
  94. package/dist/esm/services/audit/analysis/analyzeLinguisticStructure.mjs.map +1 -1
  95. package/dist/esm/services/audit/analysis/analyzeMetadata.mjs.map +1 -1
  96. package/dist/esm/services/audit/analysis/analyzeRobots.mjs.map +1 -1
  97. package/dist/esm/services/audit/analysis/analyzeSitemap.mjs.map +1 -1
  98. package/dist/esm/services/audit/analysis/analyzeUrlStructure.mjs.map +1 -1
  99. package/dist/esm/services/audit/analysis/calculateScore.mjs.map +1 -1
  100. package/dist/esm/services/audit/checkers/bundleChecker.mjs.map +1 -1
  101. package/dist/esm/services/audit/checkers/linguisticChecker.mjs.map +1 -1
  102. package/dist/esm/services/audit/checkers/metadataChecker.mjs.map +1 -1
  103. package/dist/esm/services/audit/checkers/pageChecker.mjs.map +1 -1
  104. package/dist/esm/services/audit/checkers/robotsChecker.mjs.map +1 -1
  105. package/dist/esm/services/audit/checkers/sitemapChecker.mjs.map +1 -1
  106. package/dist/esm/services/audit/checkers/urlChecker.mjs.map +1 -1
  107. package/dist/esm/services/audit/recursiveAudit.service.mjs.map +1 -1
  108. package/dist/esm/services/audit/seoAudit.service.mjs.map +1 -1
  109. package/dist/esm/services/bitbucket.service.mjs.map +1 -1
  110. package/dist/esm/services/ci.service.mjs.map +1 -1
  111. package/dist/esm/services/cliSessionToken.service.mjs.map +1 -1
  112. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  113. package/dist/esm/services/email.service.mjs.map +1 -1
  114. package/dist/esm/services/environment.service.mjs.map +1 -1
  115. package/dist/esm/services/github.service.mjs.map +1 -1
  116. package/dist/esm/services/gitlab.service.mjs.map +1 -1
  117. package/dist/esm/services/oAuth2.service.mjs.map +1 -1
  118. package/dist/esm/services/organization.service.mjs.map +1 -1
  119. package/dist/esm/services/project/projectScreenshot.service.mjs.map +1 -1
  120. package/dist/esm/services/project.service.mjs.map +1 -1
  121. package/dist/esm/services/projectAccessKey.service.mjs.map +1 -1
  122. package/dist/esm/services/promoCode.service.mjs.map +1 -1
  123. package/dist/esm/services/reviewer/pictureUpload.service.mjs.map +1 -1
  124. package/dist/esm/services/reviewer.service.mjs.map +1 -1
  125. package/dist/esm/services/reviewerMessage.service.mjs.map +1 -1
  126. package/dist/esm/services/reviewerMission.service.mjs.map +1 -1
  127. package/dist/esm/services/session.service.mjs.map +1 -1
  128. package/dist/esm/services/showcase/showcaseProject.service.mjs.map +1 -1
  129. package/dist/esm/services/showcase/showcaseScan.service.mjs.map +1 -1
  130. package/dist/esm/services/showcase/showcaseUploadScreenshot.service.mjs.map +1 -1
  131. package/dist/esm/services/showcase/showcaseVerifyBundle.service.mjs.map +1 -1
  132. package/dist/esm/services/showcase/showcaseVerifyGithub.service.mjs.map +1 -1
  133. package/dist/esm/services/subscription.service.mjs.map +1 -1
  134. package/dist/esm/services/tag.service.mjs.map +1 -1
  135. package/dist/esm/services/translationQueue.service.mjs.map +1 -1
  136. package/dist/esm/services/translationWorker.service.mjs.map +1 -1
  137. package/dist/esm/services/user/avatarUpload.service.mjs.map +1 -1
  138. package/dist/esm/services/user.service.mjs.map +1 -1
  139. package/dist/esm/services/webhook.service.mjs.map +1 -1
  140. package/dist/esm/types/user.types.mjs.map +1 -1
  141. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  142. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/bundle_optimization.json +9954 -6953
  143. package/dist/esm/utils/AI/askDocQuestion/embeddings/docs/en/configuration.json +1 -1
  144. package/dist/esm/utils/AI/askDocQuestion/indexMarkdownFiles.mjs.map +1 -1
  145. package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -1
  146. package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
  147. package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -1
  148. package/dist/esm/utils/AI/auditTag/index.mjs.map +1 -1
  149. package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -1
  150. package/dist/esm/utils/AI/chat/index.mjs.map +1 -1
  151. package/dist/esm/utils/AI/chat/mcpInProcessTools.mjs.map +1 -1
  152. package/dist/esm/utils/AI/chat/sessionTools.mjs.map +1 -1
  153. package/dist/esm/utils/AI/customQuery/index.mjs.map +1 -1
  154. package/dist/esm/utils/AI/getProjectAIOptions.mjs.map +1 -1
  155. package/dist/esm/utils/AI/translateDictionaryDB.mjs.map +1 -1
  156. package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -1
  157. package/dist/esm/utils/accessControl.mjs.map +1 -1
  158. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  159. package/dist/esm/utils/cors.mjs +2 -13
  160. package/dist/esm/utils/cors.mjs.map +1 -1
  161. package/dist/esm/utils/demoDictionaries.mjs.map +1 -1
  162. package/dist/esm/utils/ensureArrayQueryFilter.mjs.map +1 -1
  163. package/dist/esm/utils/ensureMongoDocumentToObject.mjs.map +1 -1
  164. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  165. package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
  166. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  167. package/dist/esm/utils/errors/index.mjs +1 -0
  168. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  169. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  170. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
  171. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  172. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  173. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  174. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  175. package/dist/esm/utils/getFaviconUrl.mjs.map +1 -1
  176. package/dist/esm/utils/github/connectGithub.mjs.map +1 -1
  177. package/dist/esm/utils/httpStatusCodes.mjs.map +1 -1
  178. package/dist/esm/utils/image/resizeImage.mjs.map +1 -1
  179. package/dist/esm/utils/mapper/dictionary.mjs.map +1 -1
  180. package/dist/esm/utils/mapper/organization.mjs.map +1 -1
  181. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  182. package/dist/esm/utils/mapper/session.mjs.map +1 -1
  183. package/dist/esm/utils/mapper/showcaseProject.mjs.map +1 -1
  184. package/dist/esm/utils/mapper/tag.mjs.map +1 -1
  185. package/dist/esm/utils/mapper/user.mjs.map +1 -1
  186. package/dist/esm/utils/mongoDB/connectDB.mjs.map +1 -1
  187. package/dist/esm/utils/oAuth2.mjs.map +1 -1
  188. package/dist/esm/utils/permissions.mjs.map +1 -1
  189. package/dist/esm/utils/plan.mjs.map +1 -1
  190. package/dist/esm/utils/puppeteer/launchBrowser.mjs.map +1 -1
  191. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  192. package/dist/esm/utils/redis/connectRedis.mjs.map +1 -1
  193. package/dist/esm/utils/removeObjectKeys.mjs.map +1 -1
  194. package/dist/esm/utils/responseData.mjs.map +1 -1
  195. package/dist/esm/utils/s3/s3Client.mjs.map +1 -1
  196. package/dist/esm/utils/validation/validateDictionary.mjs.map +1 -1
  197. package/dist/esm/utils/validation/validateOrganization.mjs.map +1 -1
  198. package/dist/esm/utils/validation/validateProject.mjs.map +1 -1
  199. package/dist/esm/utils/validation/validateTag.mjs.map +1 -1
  200. package/dist/esm/utils/validation/validateUser.mjs.map +1 -1
  201. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  202. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  203. package/dist/types/controllers/bitbucket.controller.d.ts.map +1 -1
  204. package/dist/types/controllers/cliSessionToken.controller.d.ts.map +1 -1
  205. package/dist/types/controllers/demo.controller.d.ts.map +1 -1
  206. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  207. package/dist/types/controllers/environment.controller.d.ts.map +1 -1
  208. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  209. package/dist/types/controllers/github.controller.d.ts.map +1 -1
  210. package/dist/types/controllers/gitlab.controller.d.ts.map +1 -1
  211. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  212. package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
  213. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  214. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  215. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  216. package/dist/types/controllers/projectMemberAccess.controller.d.ts.map +1 -1
  217. package/dist/types/controllers/recursiveAudit.controller.d.ts.map +1 -1
  218. package/dist/types/controllers/reviewer.controller.d.ts.map +1 -1
  219. package/dist/types/controllers/searchDoc.controller.d.ts.map +1 -1
  220. package/dist/types/controllers/showcaseProject.controller.d.ts.map +1 -1
  221. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  222. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  223. package/dist/types/controllers/translation.controller.d.ts.map +1 -1
  224. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  225. package/dist/types/emails/AffiliateActivatedEmail.d.ts +20 -18
  226. package/dist/types/emails/AffiliateActivatedEmail.d.ts.map +1 -1
  227. package/dist/types/emails/AffiliateConversionEmail.d.ts +21 -19
  228. package/dist/types/emails/AffiliateConversionEmail.d.ts.map +1 -1
  229. package/dist/types/emails/AffiliateInvitationEmail.d.ts +20 -18
  230. package/dist/types/emails/AffiliateInvitationEmail.d.ts.map +1 -1
  231. package/dist/types/emails/AffiliateWelcomeEmail.d.ts +20 -18
  232. package/dist/types/emails/AffiliateWelcomeEmail.d.ts.map +1 -1
  233. package/dist/types/emails/InviteUserEmail.d.ts +20 -18
  234. package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
  235. package/dist/types/emails/MagicLinkEmail.d.ts +20 -18
  236. package/dist/types/emails/MagicLinkEmail.d.ts.map +1 -1
  237. package/dist/types/emails/MissionRequestedClientEmail.d.ts +20 -18
  238. package/dist/types/emails/MissionRequestedClientEmail.d.ts.map +1 -1
  239. package/dist/types/emails/MissionRequestedReviewerEmail.d.ts +20 -18
  240. package/dist/types/emails/MissionRequestedReviewerEmail.d.ts.map +1 -1
  241. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +20 -18
  242. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts.map +1 -1
  243. package/dist/types/emails/PasswordChangeConfirmation.d.ts +20 -18
  244. package/dist/types/emails/PasswordChangeConfirmation.d.ts.map +1 -1
  245. package/dist/types/emails/ResetUserPassword.d.ts +20 -18
  246. package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
  247. package/dist/types/emails/ReviewerApplicationEmail.d.ts +20 -18
  248. package/dist/types/emails/ReviewerApplicationEmail.d.ts.map +1 -1
  249. package/dist/types/emails/ReviewerApprovedEmail.d.ts +20 -18
  250. package/dist/types/emails/ReviewerApprovedEmail.d.ts.map +1 -1
  251. package/dist/types/emails/ReviewerContactEmail.d.ts +3 -1
  252. package/dist/types/emails/ReviewerContactEmail.d.ts.map +1 -1
  253. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +20 -18
  254. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
  255. package/dist/types/emails/SubscriptionPaymentError.d.ts +20 -18
  256. package/dist/types/emails/SubscriptionPaymentError.d.ts.map +1 -1
  257. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +20 -18
  258. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts.map +1 -1
  259. package/dist/types/emails/ValidateUserEmail.d.ts +20 -18
  260. package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
  261. package/dist/types/emails/Welcome.d.ts +20 -18
  262. package/dist/types/emails/Welcome.d.ts.map +1 -1
  263. package/dist/types/export.d.ts +1 -1
  264. package/dist/types/logger/index.d.ts +3 -1
  265. package/dist/types/logger/index.d.ts.map +1 -1
  266. package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
  267. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  268. package/dist/types/routes/demo.routes.d.ts.map +1 -1
  269. package/dist/types/schemas/account.schema.d.ts +35 -34
  270. package/dist/types/schemas/account.schema.d.ts.map +1 -1
  271. package/dist/types/schemas/affiliate.schema.d.ts +109 -108
  272. package/dist/types/schemas/affiliate.schema.d.ts.map +1 -1
  273. package/dist/types/schemas/affiliateInvitation.schema.d.ts +49 -48
  274. package/dist/types/schemas/affiliateInvitation.schema.d.ts.map +1 -1
  275. package/dist/types/schemas/audit.schema.d.ts.map +1 -1
  276. package/dist/types/schemas/auditJob.schema.d.ts +6 -6
  277. package/dist/types/schemas/auditPage.schema.d.ts +6 -6
  278. package/dist/types/schemas/cliSessionToken.schema.d.ts +14 -13
  279. package/dist/types/schemas/cliSessionToken.schema.d.ts.map +1 -1
  280. package/dist/types/schemas/dictionary.schema.d.ts +60 -59
  281. package/dist/types/schemas/dictionary.schema.d.ts.map +1 -1
  282. package/dist/types/schemas/discussion.schema.d.ts +51 -50
  283. package/dist/types/schemas/discussion.schema.d.ts.map +1 -1
  284. package/dist/types/schemas/oAuth2.schema.d.ts +18 -17
  285. package/dist/types/schemas/oAuth2.schema.d.ts.map +1 -1
  286. package/dist/types/schemas/organization.schema.d.ts +45 -44
  287. package/dist/types/schemas/organization.schema.d.ts.map +1 -1
  288. package/dist/types/schemas/plans.schema.d.ts +45 -44
  289. package/dist/types/schemas/plans.schema.d.ts.map +1 -1
  290. package/dist/types/schemas/project.schema.d.ts +73 -72
  291. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  292. package/dist/types/schemas/promoCode.schema.d.ts +61 -60
  293. package/dist/types/schemas/promoCode.schema.d.ts.map +1 -1
  294. package/dist/types/schemas/reviewer.schema.d.ts +221 -220
  295. package/dist/types/schemas/reviewer.schema.d.ts.map +1 -1
  296. package/dist/types/schemas/session.schema.d.ts +54 -52
  297. package/dist/types/schemas/session.schema.d.ts.map +1 -1
  298. package/dist/types/schemas/showcaseProject.schema.d.ts +61 -60
  299. package/dist/types/schemas/showcaseProject.schema.d.ts.map +1 -1
  300. package/dist/types/schemas/tag.schema.d.ts +45 -44
  301. package/dist/types/schemas/tag.schema.d.ts.map +1 -1
  302. package/dist/types/schemas/user.schema.d.ts +71 -70
  303. package/dist/types/schemas/user.schema.d.ts.map +1 -1
  304. package/dist/types/services/affiliate.service.d.ts.map +1 -1
  305. package/dist/types/services/audit/analysis/analyzeBundleContent.d.ts.map +1 -1
  306. package/dist/types/services/audit/analysis/analyzeLinguisticStructure.d.ts.map +1 -1
  307. package/dist/types/services/audit/analysis/analyzeRobots.d.ts.map +1 -1
  308. package/dist/types/services/audit/analysis/analyzeSitemap.d.ts.map +1 -1
  309. package/dist/types/services/audit/analysis/calculateScore.d.ts.map +1 -1
  310. package/dist/types/services/audit/checkers/bundleChecker.d.ts.map +1 -1
  311. package/dist/types/services/audit/recursiveAudit.service.d.ts +5 -4
  312. package/dist/types/services/audit/recursiveAudit.service.d.ts.map +1 -1
  313. package/dist/types/services/audit/types.d.ts.map +1 -1
  314. package/dist/types/services/bitbucket.service.d.ts.map +1 -1
  315. package/dist/types/services/cliSessionToken.service.d.ts.map +1 -1
  316. package/dist/types/services/dictionary.service.d.ts.map +1 -1
  317. package/dist/types/services/email.service.d.ts.map +1 -1
  318. package/dist/types/services/github.service.d.ts.map +1 -1
  319. package/dist/types/services/gitlab.service.d.ts.map +1 -1
  320. package/dist/types/services/oAuth2.service.d.ts.map +1 -1
  321. package/dist/types/services/organization.service.d.ts.map +1 -1
  322. package/dist/types/services/project/projectScreenshot.service.d.ts.map +1 -1
  323. package/dist/types/services/project.service.d.ts.map +1 -1
  324. package/dist/types/services/promoCode.service.d.ts.map +1 -1
  325. package/dist/types/services/reviewer/pictureUpload.service.d.ts.map +1 -1
  326. package/dist/types/services/reviewer.service.d.ts.map +1 -1
  327. package/dist/types/services/reviewerMessage.service.d.ts.map +1 -1
  328. package/dist/types/services/reviewerMission.service.d.ts.map +1 -1
  329. package/dist/types/services/session.service.d.ts.map +1 -1
  330. package/dist/types/services/showcase/showcaseProject.service.d.ts.map +1 -1
  331. package/dist/types/services/showcase/showcaseScan.service.d.ts.map +1 -1
  332. package/dist/types/services/showcase/showcaseUploadScreenshot.service.d.ts.map +1 -1
  333. package/dist/types/services/showcase/showcaseVerifyBundle.service.d.ts.map +1 -1
  334. package/dist/types/services/showcase/showcaseVerifyGithub.service.d.ts.map +1 -1
  335. package/dist/types/services/subscription.service.d.ts.map +1 -1
  336. package/dist/types/services/tag.service.d.ts.map +1 -1
  337. package/dist/types/services/translationQueue.service.d.ts +2 -1
  338. package/dist/types/services/translationQueue.service.d.ts.map +1 -1
  339. package/dist/types/services/translationWorker.service.d.ts.map +1 -1
  340. package/dist/types/services/user/avatarUpload.service.d.ts.map +1 -1
  341. package/dist/types/services/user.service.d.ts.map +1 -1
  342. package/dist/types/services/webhook.service.d.ts.map +1 -1
  343. package/dist/types/types/Routes.d.ts.map +1 -1
  344. package/dist/types/types/account.types.d.ts.map +1 -1
  345. package/dist/types/types/affiliate.types.d.ts.map +1 -1
  346. package/dist/types/types/affiliateInvitation.types.d.ts.map +1 -1
  347. package/dist/types/types/dictionary.types.d.ts.map +1 -1
  348. package/dist/types/types/discussion.types.d.ts.map +1 -1
  349. package/dist/types/types/oAuth2.types.d.ts.map +1 -1
  350. package/dist/types/types/organization.types.d.ts.map +1 -1
  351. package/dist/types/types/plan.types.d.ts.map +1 -1
  352. package/dist/types/types/project.types.d.ts.map +1 -1
  353. package/dist/types/types/promoCode.types.d.ts.map +1 -1
  354. package/dist/types/types/reviewer.types.d.ts.map +1 -1
  355. package/dist/types/types/session.types.d.ts.map +1 -1
  356. package/dist/types/types/showcaseProject.types.d.ts.map +1 -1
  357. package/dist/types/types/tag.types.d.ts.map +1 -1
  358. package/dist/types/types/user.types.d.ts.map +1 -1
  359. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
  360. package/dist/types/utils/AI/askDocQuestion/indexMarkdownFiles.d.ts.map +1 -1
  361. package/dist/types/utils/AI/auditDictionary/index.d.ts.map +1 -1
  362. package/dist/types/utils/AI/auditDictionaryField/index.d.ts.map +1 -1
  363. package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts.map +1 -1
  364. package/dist/types/utils/AI/auditTag/index.d.ts.map +1 -1
  365. package/dist/types/utils/AI/autocomplete/index.d.ts.map +1 -1
  366. package/dist/types/utils/AI/chat/index.d.ts.map +1 -1
  367. package/dist/types/utils/AI/chat/mcpInProcessTools.d.ts.map +1 -1
  368. package/dist/types/utils/AI/chat/sessionTools.d.ts +25 -24
  369. package/dist/types/utils/AI/chat/sessionTools.d.ts.map +1 -1
  370. package/dist/types/utils/AI/customQuery/index.d.ts.map +1 -1
  371. package/dist/types/utils/AI/translateDictionaryDB.d.ts.map +1 -1
  372. package/dist/types/utils/AI/translateJSON/index.d.ts.map +1 -1
  373. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  374. package/dist/types/utils/cors.d.ts +1 -7
  375. package/dist/types/utils/cors.d.ts.map +1 -1
  376. package/dist/types/utils/demoDictionaries.d.ts.map +1 -1
  377. package/dist/types/utils/ensureArrayQueryFilter.d.ts.map +1 -1
  378. package/dist/types/utils/errors/ErrorHandler.d.ts +8 -6
  379. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  380. package/dist/types/utils/errors/ErrorsClass.d.ts.map +1 -1
  381. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  382. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
  383. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
  384. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
  385. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
  386. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
  387. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +7 -6
  388. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
  389. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
  390. package/dist/types/utils/getFaviconUrl.d.ts.map +1 -1
  391. package/dist/types/utils/github/connectGithub.d.ts.map +1 -1
  392. package/dist/types/utils/httpStatusCodes.d.ts.map +1 -1
  393. package/dist/types/utils/image/resizeImage.d.ts.map +1 -1
  394. package/dist/types/utils/mapper/dictionary.d.ts.map +1 -1
  395. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  396. package/dist/types/utils/mapper/session.d.ts.map +1 -1
  397. package/dist/types/utils/mapper/showcaseProject.d.ts.map +1 -1
  398. package/dist/types/utils/mapper/tag.d.ts.map +1 -1
  399. package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
  400. package/dist/types/utils/mongoDB/connectDB.d.ts.map +1 -1
  401. package/dist/types/utils/mongoDB/types.d.ts.map +1 -1
  402. package/dist/types/utils/oAuth2.d.ts.map +1 -1
  403. package/dist/types/utils/permissions.d.ts +2 -1
  404. package/dist/types/utils/permissions.d.ts.map +1 -1
  405. package/dist/types/utils/plan.d.ts.map +1 -1
  406. package/dist/types/utils/rateLimiter.d.ts.map +1 -1
  407. package/dist/types/utils/redis/connectRedis.d.ts.map +1 -1
  408. package/dist/types/utils/responseData.d.ts.map +1 -1
  409. package/dist/types/utils/s3/s3Client.d.ts.map +1 -1
  410. package/dist/types/utils/validation/validateDictionary.d.ts.map +1 -1
  411. package/dist/types/utils/validation/validateOrganization.d.ts.map +1 -1
  412. package/dist/types/utils/validation/validateProject.d.ts.map +1 -1
  413. package/dist/types/utils/validation/validateTag.d.ts.map +1 -1
  414. package/dist/types/utils/validation/validateUser.d.ts.map +1 -1
  415. package/package.json +17 -17
@@ -1 +1 @@
1
- {"version":3,"file":"bundleChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/bundleChecker.ts"],"sourcesContent":["import {\n analyzeBundleContent,\n type BundleChunkInput,\n} from '../analysis/analyzeBundleContent';\nimport type { AuditEvent } from '../types';\n\nexport const checkBundleContent = (\n chunks: BundleChunkInput[],\n htmlContent: string,\n currentLocale: string | undefined,\n targetUrl: string,\n totalPageSize: number,\n onEvent: (event: AuditEvent) => void\n): void => {\n if (!currentLocale) {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'warning',\n data: {\n warningsDetails:\n 'Cannot analyse bundle content: page locale not detected',\n },\n });\n return;\n }\n\n const analysis = analyzeBundleContent(\n chunks,\n htmlContent,\n currentLocale,\n totalPageSize\n );\n\n const {\n renderedContentSize,\n contentSize,\n totalLocaleSize,\n totalUnusedLocaleSize,\n unusedPercentOfLocale,\n mainBundleChunks,\n lazyBundleChunks,\n totalPageSize: analysisTotalPageSize,\n } = analysis;\n\n const formatSize = (bytes: number): string => {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(2)} KB`;\n return `${bytes} B`;\n };\n const filename = (url: string) => url.split('/').pop()?.split('?')[0] ?? url;\n\n const serializeChunk = (c: (typeof mainBundleChunks)[number]) => ({\n filename: filename(c.url),\n url: c.url,\n fileSize: formatSize(c.fileSize),\n totalLocaleSize: formatSize(c.totalLocaleSize),\n usedLocaleSize: formatSize(c.usedLocaleSize),\n unusedLocaleSize: formatSize(c.unusedLocaleSize),\n dictionariesFound: c.dictionariesFound,\n unusedPercent: `${c.unusedPercent}%`,\n });\n\n const summary = {\n currentLocale,\n totalPageSize: formatSize(analysisTotalPageSize),\n renderedContentSize: formatSize(renderedContentSize),\n contentSize: formatSize(contentSize),\n totalLocaleSize: formatSize(totalLocaleSize),\n totalUnusedLocaleSize: formatSize(totalUnusedLocaleSize),\n unusedPercentOfLocale: `${unusedPercentOfLocale}%`,\n mainBundleChunks: mainBundleChunks.map(serializeChunk),\n lazyBundleChunks: lazyBundleChunks.map(serializeChunk),\n };\n\n // Status is driven by the main bundle — lazy chunks with unused content are expected\n const mainBundleMaxUnused = mainBundleChunks.reduce(\n (max, c) => Math.max(max, c.unusedPercent),\n 0\n );\n\n if (mainBundleMaxUnused === 0) {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'success',\n data: { successDetails: summary },\n });\n } else if (mainBundleMaxUnused <= 30) {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'warning',\n data: { warningsDetails: summary },\n });\n } else {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'error',\n data: { errorsDetails: summary },\n });\n }\n};\n"],"mappings":";;;AAMA,MAAa,sBACX,QACA,aACA,eACA,WACA,eACA,YACS;CACT,IAAI,CAAC,eAAe;EAClB,QAAQ;GACN,MAAM,4BAA4B;GAClC,QAAQ;GACR,MAAM,EACJ,iBACE,0DACJ;EACF,CAAC;EACD;CACF;CASA,MAAM,EACJ,qBACA,aACA,iBACA,uBACA,uBACA,kBACA,kBACA,eAAe,0BAfA,qBACf,QACA,aACA,eACA,aAYS;CAEX,MAAM,cAAc,UAA0B;EAC5C,IAAI,SAAS,OAAO,MAAM,OAAO,IAAI,SAAS,OAAO,MAAK,CAAE,QAAQ,CAAC,EAAE;EACvE,IAAI,SAAS,MAAM,OAAO,IAAI,QAAQ,KAAI,CAAE,QAAQ,CAAC,EAAE;EACvD,OAAO,GAAG,MAAM;CAClB;CACA,MAAM,YAAY,QAAgB,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM;CAEzE,MAAM,kBAAkB,OAA0C;EAChE,UAAU,SAAS,EAAE,GAAG;EACxB,KAAK,EAAE;EACP,UAAU,WAAW,EAAE,QAAQ;EAC/B,iBAAiB,WAAW,EAAE,eAAe;EAC7C,gBAAgB,WAAW,EAAE,cAAc;EAC3C,kBAAkB,WAAW,EAAE,gBAAgB;EAC/C,mBAAmB,EAAE;EACrB,eAAe,GAAG,EAAE,cAAc;CACpC;CAEA,MAAM,UAAU;EACd;EACA,eAAe,WAAW,qBAAqB;EAC/C,qBAAqB,WAAW,mBAAmB;EACnD,aAAa,WAAW,WAAW;EACnC,iBAAiB,WAAW,eAAe;EAC3C,uBAAuB,WAAW,qBAAqB;EACvD,uBAAuB,GAAG,sBAAsB;EAChD,kBAAkB,iBAAiB,IAAI,cAAc;EACrD,kBAAkB,iBAAiB,IAAI,cAAc;CACvD;CAGA,MAAM,sBAAsB,iBAAiB,QAC1C,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,aAAa,GACzC,CACF;CAEA,IAAI,wBAAwB,GAC1B,QAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ;EACR,MAAM,EAAE,gBAAgB,QAAQ;CAClC,CAAC;MACI,IAAI,uBAAuB,IAChC,QAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ;EACR,MAAM,EAAE,iBAAiB,QAAQ;CACnC,CAAC;MAED,QAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ;EACR,MAAM,EAAE,eAAe,QAAQ;CACjC,CAAC;AAEL"}
1
+ {"version":3,"file":"bundleChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/bundleChecker.ts"],"sourcesContent":["import {\n analyzeBundleContent,\n type BundleChunkInput,\n} from '../analysis/analyzeBundleContent';\nimport type { AuditEvent } from '../types';\n\nexport const checkBundleContent = (\n chunks: BundleChunkInput[],\n htmlContent: string,\n currentLocale: string | undefined,\n targetUrl: string,\n totalPageSize: number,\n onEvent: (event: AuditEvent) => void\n): void => {\n if (!currentLocale) {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'warning',\n data: {\n warningsDetails:\n 'Cannot analyse bundle content: page locale not detected',\n },\n });\n return;\n }\n\n const analysis = analyzeBundleContent(\n chunks,\n htmlContent,\n currentLocale,\n totalPageSize\n );\n\n const {\n renderedContentSize,\n contentSize,\n totalLocaleSize,\n totalUnusedLocaleSize,\n unusedPercentOfLocale,\n mainBundleChunks,\n lazyBundleChunks,\n totalPageSize: analysisTotalPageSize,\n } = analysis;\n\n const formatSize = (bytes: number): string => {\n if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(2)} KB`;\n return `${bytes} B`;\n };\n const filename = (url: string) => url.split('/').pop()?.split('?')[0] ?? url;\n\n const serializeChunk = (c: (typeof mainBundleChunks)[number]) => ({\n filename: filename(c.url),\n url: c.url,\n fileSize: formatSize(c.fileSize),\n totalLocaleSize: formatSize(c.totalLocaleSize),\n usedLocaleSize: formatSize(c.usedLocaleSize),\n unusedLocaleSize: formatSize(c.unusedLocaleSize),\n dictionariesFound: c.dictionariesFound,\n unusedPercent: `${c.unusedPercent}%`,\n });\n\n const summary = {\n currentLocale,\n totalPageSize: formatSize(analysisTotalPageSize),\n renderedContentSize: formatSize(renderedContentSize),\n contentSize: formatSize(contentSize),\n totalLocaleSize: formatSize(totalLocaleSize),\n totalUnusedLocaleSize: formatSize(totalUnusedLocaleSize),\n unusedPercentOfLocale: `${unusedPercentOfLocale}%`,\n mainBundleChunks: mainBundleChunks.map(serializeChunk),\n lazyBundleChunks: lazyBundleChunks.map(serializeChunk),\n };\n\n // Status is driven by the main bundle — lazy chunks with unused content are expected\n const mainBundleMaxUnused = mainBundleChunks.reduce(\n (max, c) => Math.max(max, c.unusedPercent),\n 0\n );\n\n if (mainBundleMaxUnused === 0) {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'success',\n data: { successDetails: summary },\n });\n } else if (mainBundleMaxUnused <= 30) {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'warning',\n data: { warningsDetails: summary },\n });\n } else {\n onEvent({\n type: `url_unusedBundleContent\\\\${targetUrl}`,\n status: 'error',\n data: { errorsDetails: summary },\n });\n }\n};\n"],"mappings":";;;AAMA,MAAa,sBACX,QACA,aACA,eACA,WACA,eACA,YACS;AACT,KAAI,CAAC,eAAe;AAClB,UAAQ;GACN,MAAM,4BAA4B;GAClC,QAAQ;GACR,MAAM,EACJ,iBACE,2DACH;GACF,CAAC;AACF;;CAUF,MAAM,EACJ,qBACA,aACA,iBACA,uBACA,uBACA,kBACA,kBACA,eAAe,0BAfA,qBACf,QACA,aACA,eACA,cAYU;CAEZ,MAAM,cAAc,UAA0B;AAC5C,MAAI,SAAS,OAAO,KAAM,QAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,EAAE,CAAC;AACvE,MAAI,SAAS,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;AACvD,SAAO,GAAG,MAAM;;CAElB,MAAM,YAAY,QAAgB,IAAI,MAAM,IAAI,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,MAAM;CAEzE,MAAM,kBAAkB,OAA0C;EAChE,UAAU,SAAS,EAAE,IAAI;EACzB,KAAK,EAAE;EACP,UAAU,WAAW,EAAE,SAAS;EAChC,iBAAiB,WAAW,EAAE,gBAAgB;EAC9C,gBAAgB,WAAW,EAAE,eAAe;EAC5C,kBAAkB,WAAW,EAAE,iBAAiB;EAChD,mBAAmB,EAAE;EACrB,eAAe,GAAG,EAAE,cAAc;EACnC;CAED,MAAM,UAAU;EACd;EACA,eAAe,WAAW,sBAAsB;EAChD,qBAAqB,WAAW,oBAAoB;EACpD,aAAa,WAAW,YAAY;EACpC,iBAAiB,WAAW,gBAAgB;EAC5C,uBAAuB,WAAW,sBAAsB;EACxD,uBAAuB,GAAG,sBAAsB;EAChD,kBAAkB,iBAAiB,IAAI,eAAe;EACtD,kBAAkB,iBAAiB,IAAI,eAAe;EACvD;CAGD,MAAM,sBAAsB,iBAAiB,QAC1C,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,cAAc,EAC1C,EACD;AAED,KAAI,wBAAwB,EAC1B,SAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ;EACR,MAAM,EAAE,gBAAgB,SAAS;EAClC,CAAC;UACO,uBAAuB,GAChC,SAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ;EACR,MAAM,EAAE,iBAAiB,SAAS;EACnC,CAAC;KAEF,SAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ;EACR,MAAM,EAAE,eAAe,SAAS;EACjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"linguisticChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/linguisticChecker.ts"],"sourcesContent":["import { analyzeLinguisticStructure } from '../analysis/analyzeLinguisticStructure';\nimport type { AuditEvent, CheerioAPI } from '../types';\n\nexport const checkLinguisticStructure = async (\n $: CheerioAPI,\n targetUrl: string,\n localesSet: Set<string>,\n onEvent: (event: AuditEvent) => void\n): Promise<string[]> => {\n const linguisticData = analyzeLinguisticStructure($, targetUrl, localesSet);\n const alternatesOnRoot = linguisticData.alternates;\n\n onEvent({\n type: `url_hreflang\\\\${targetUrl}`,\n status: linguisticData.hreflangs.length > 0 ? 'success' : 'warning',\n data: {\n successDetails:\n linguisticData.hreflangs.length > 0\n ? linguisticData.hreflangs\n : undefined,\n warningsDetails:\n linguisticData.hreflangs.length === 0\n ? 'No hreflang tags found'\n : undefined,\n },\n });\n\n onEvent({\n domainData: { discoveredLocales: Array.from(localesSet) },\n });\n\n onEvent({\n type: `url_hasXDefault\\\\${targetUrl}`,\n status: linguisticData.hasXDefault ? 'success' : 'error',\n data: {\n successDetails: linguisticData.hasXDefault ? true : undefined,\n errorsDetails: linguisticData.hasXDefault\n ? undefined\n : 'Missing x-default hreflang link',\n },\n });\n\n return alternatesOnRoot;\n};\n"],"mappings":";;;AAGA,MAAa,2BAA2B,OACtC,GACA,WACA,YACA,YACsB;CACtB,MAAM,iBAAiB,2BAA2B,GAAG,WAAW,UAAU;CAC1E,MAAM,mBAAmB,eAAe;CAExC,QAAQ;EACN,MAAM,iBAAiB;EACvB,QAAQ,eAAe,UAAU,SAAS,IAAI,YAAY;EAC1D,MAAM;GACJ,gBACE,eAAe,UAAU,SAAS,IAC9B,eAAe,YACf;GACN,iBACE,eAAe,UAAU,WAAW,IAChC,2BACA;EACR;CACF,CAAC;CAED,QAAQ,EACN,YAAY,EAAE,mBAAmB,MAAM,KAAK,UAAU,EAAE,EAC1D,CAAC;CAED,QAAQ;EACN,MAAM,oBAAoB;EAC1B,QAAQ,eAAe,cAAc,YAAY;EACjD,MAAM;GACJ,gBAAgB,eAAe,cAAc,OAAO;GACpD,eAAe,eAAe,cAC1B,SACA;EACN;CACF,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"linguisticChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/linguisticChecker.ts"],"sourcesContent":["import { analyzeLinguisticStructure } from '../analysis/analyzeLinguisticStructure';\nimport type { AuditEvent, CheerioAPI } from '../types';\n\nexport const checkLinguisticStructure = async (\n $: CheerioAPI,\n targetUrl: string,\n localesSet: Set<string>,\n onEvent: (event: AuditEvent) => void\n): Promise<string[]> => {\n const linguisticData = analyzeLinguisticStructure($, targetUrl, localesSet);\n const alternatesOnRoot = linguisticData.alternates;\n\n onEvent({\n type: `url_hreflang\\\\${targetUrl}`,\n status: linguisticData.hreflangs.length > 0 ? 'success' : 'warning',\n data: {\n successDetails:\n linguisticData.hreflangs.length > 0\n ? linguisticData.hreflangs\n : undefined,\n warningsDetails:\n linguisticData.hreflangs.length === 0\n ? 'No hreflang tags found'\n : undefined,\n },\n });\n\n onEvent({\n domainData: { discoveredLocales: Array.from(localesSet) },\n });\n\n onEvent({\n type: `url_hasXDefault\\\\${targetUrl}`,\n status: linguisticData.hasXDefault ? 'success' : 'error',\n data: {\n successDetails: linguisticData.hasXDefault ? true : undefined,\n errorsDetails: linguisticData.hasXDefault\n ? undefined\n : 'Missing x-default hreflang link',\n },\n });\n\n return alternatesOnRoot;\n};\n"],"mappings":";;;AAGA,MAAa,2BAA2B,OACtC,GACA,WACA,YACA,YACsB;CACtB,MAAM,iBAAiB,2BAA2B,GAAG,WAAW,WAAW;CAC3E,MAAM,mBAAmB,eAAe;AAExC,SAAQ;EACN,MAAM,iBAAiB;EACvB,QAAQ,eAAe,UAAU,SAAS,IAAI,YAAY;EAC1D,MAAM;GACJ,gBACE,eAAe,UAAU,SAAS,IAC9B,eAAe,YACf;GACN,iBACE,eAAe,UAAU,WAAW,IAChC,2BACA;GACP;EACF,CAAC;AAEF,SAAQ,EACN,YAAY,EAAE,mBAAmB,MAAM,KAAK,WAAW,EAAE,EAC1D,CAAC;AAEF,SAAQ;EACN,MAAM,oBAAoB;EAC1B,QAAQ,eAAe,cAAc,YAAY;EACjD,MAAM;GACJ,gBAAgB,eAAe,cAAc,OAAO;GACpD,eAAe,eAAe,cAC1B,SACA;GACL;EACF,CAAC;AAEF,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"metadataChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/metadataChecker.ts"],"sourcesContent":["import { analyzeMetadata } from '../analysis/analyzeMetadata';\nimport type { AuditEvent, CheerioAPI } from '../types';\n\nexport const checkMetadata = async (\n $: CheerioAPI,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const metadataData = analyzeMetadata($);\n\n onEvent({\n type: `url_hasCanonical\\\\${targetUrl}`,\n status: metadataData.hasCanonicalTag ? 'success' : 'error',\n data: {\n successDetails: metadataData.hasCanonicalTag ? true : undefined,\n errorsDetails: metadataData.hasCanonicalTag\n ? undefined\n : 'Missing canonical tag',\n },\n });\n};\n"],"mappings":";;;AAGA,MAAa,gBAAgB,OAC3B,GACA,WACA,YACkB;CAClB,MAAM,eAAe,gBAAgB,CAAC;CAEtC,QAAQ;EACN,MAAM,qBAAqB;EAC3B,QAAQ,aAAa,kBAAkB,YAAY;EACnD,MAAM;GACJ,gBAAgB,aAAa,kBAAkB,OAAO;GACtD,eAAe,aAAa,kBACxB,SACA;EACN;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"metadataChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/metadataChecker.ts"],"sourcesContent":["import { analyzeMetadata } from '../analysis/analyzeMetadata';\nimport type { AuditEvent, CheerioAPI } from '../types';\n\nexport const checkMetadata = async (\n $: CheerioAPI,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const metadataData = analyzeMetadata($);\n\n onEvent({\n type: `url_hasCanonical\\\\${targetUrl}`,\n status: metadataData.hasCanonicalTag ? 'success' : 'error',\n data: {\n successDetails: metadataData.hasCanonicalTag ? true : undefined,\n errorsDetails: metadataData.hasCanonicalTag\n ? undefined\n : 'Missing canonical tag',\n },\n });\n};\n"],"mappings":";;;AAGA,MAAa,gBAAgB,OAC3B,GACA,WACA,YACkB;CAClB,MAAM,eAAe,gBAAgB,EAAE;AAEvC,SAAQ;EACN,MAAM,qBAAqB;EAC3B,QAAQ,aAAa,kBAAkB,YAAY;EACnD,MAAM;GACJ,gBAAgB,aAAa,kBAAkB,OAAO;GACtD,eAAe,aAAa,kBACxB,SACA;GACL;EACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"pageChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/pageChecker.ts"],"sourcesContent":["import type { AuditEvent, CheerioAPI } from '../types';\n\nexport const checkHtmlAttributes = async (\n $: CheerioAPI,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<{ langTag: string | undefined; dirTag: string | null }> => {\n const langTag = $('html').attr('lang');\n const dirTag = $('html').attr('dir') || null;\n\n const htmlLangPresent = Boolean(langTag);\n const htmlDirPresent = Boolean(dirTag);\n\n onEvent({\n type: `url_htmlLang\\\\${targetUrl}`,\n status: htmlLangPresent ? 'success' : 'error',\n data: {\n successDetails: htmlLangPresent ? langTag : undefined,\n errorsDetails: !htmlLangPresent\n ? 'Missing html lang attribute'\n : undefined,\n },\n });\n\n onEvent({\n type: `url_currentLocale\\\\${targetUrl}`,\n status: langTag ? 'success' : 'warning',\n data: {\n successDetails: langTag,\n warningsDetails: !langTag ? 'No locale detected' : undefined,\n },\n });\n\n onEvent({\n type: `url_htmlDir\\\\${targetUrl}`,\n status: htmlDirPresent ? 'success' : 'warning',\n data: {\n successDetails: htmlDirPresent ? dirTag : undefined,\n warningsDetails: !htmlDirPresent\n ? 'Missing html dir attribute'\n : undefined,\n },\n });\n\n return { langTag, dirTag };\n};\n\nexport const extractPageMetadata = async (\n $: CheerioAPI,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const title = $('title').text();\n const metaDescription = $(\"meta[name='description']\").attr('content') ?? '';\n const metaOgImage = $(\"meta[property='og:image']\").attr('content');\n\n let image = '';\n if (metaOgImage) {\n try {\n image = new URL(metaOgImage, targetUrl).href;\n } catch {\n image = metaOgImage;\n }\n }\n\n onEvent({\n domainData: { title, image, description: metaDescription },\n });\n};\n"],"mappings":";AAEA,MAAa,sBAAsB,OACjC,GACA,WACA,YACoE;CACpE,MAAM,UAAU,EAAE,MAAM,CAAC,CAAC,KAAK,MAAM;CACrC,MAAM,SAAS,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK;CAExC,MAAM,kBAAkB,QAAQ,OAAO;CACvC,MAAM,iBAAiB,QAAQ,MAAM;CAErC,QAAQ;EACN,MAAM,iBAAiB;EACvB,QAAQ,kBAAkB,YAAY;EACtC,MAAM;GACJ,gBAAgB,kBAAkB,UAAU;GAC5C,eAAe,CAAC,kBACZ,gCACA;EACN;CACF,CAAC;CAED,QAAQ;EACN,MAAM,sBAAsB;EAC5B,QAAQ,UAAU,YAAY;EAC9B,MAAM;GACJ,gBAAgB;GAChB,iBAAiB,CAAC,UAAU,uBAAuB;EACrD;CACF,CAAC;CAED,QAAQ;EACN,MAAM,gBAAgB;EACtB,QAAQ,iBAAiB,YAAY;EACrC,MAAM;GACJ,gBAAgB,iBAAiB,SAAS;GAC1C,iBAAiB,CAAC,iBACd,+BACA;EACN;CACF,CAAC;CAED,OAAO;EAAE;EAAS;CAAO;AAC3B;AAEA,MAAa,sBAAsB,OACjC,GACA,WACA,YACkB;CAClB,MAAM,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK;CAC9B,MAAM,kBAAkB,EAAE,0BAA0B,CAAC,CAAC,KAAK,SAAS,KAAK;CACzE,MAAM,cAAc,EAAE,2BAA2B,CAAC,CAAC,KAAK,SAAS;CAEjE,IAAI,QAAQ;CACZ,IAAI,aACF,IAAI;EACF,QAAQ,IAAI,IAAI,aAAa,SAAS,CAAC,CAAC;CAC1C,QAAQ;EACN,QAAQ;CACV;CAGF,QAAQ,EACN,YAAY;EAAE;EAAO;EAAO,aAAa;CAAgB,EAC3D,CAAC;AACH"}
1
+ {"version":3,"file":"pageChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/pageChecker.ts"],"sourcesContent":["import type { AuditEvent, CheerioAPI } from '../types';\n\nexport const checkHtmlAttributes = async (\n $: CheerioAPI,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<{ langTag: string | undefined; dirTag: string | null }> => {\n const langTag = $('html').attr('lang');\n const dirTag = $('html').attr('dir') || null;\n\n const htmlLangPresent = Boolean(langTag);\n const htmlDirPresent = Boolean(dirTag);\n\n onEvent({\n type: `url_htmlLang\\\\${targetUrl}`,\n status: htmlLangPresent ? 'success' : 'error',\n data: {\n successDetails: htmlLangPresent ? langTag : undefined,\n errorsDetails: !htmlLangPresent\n ? 'Missing html lang attribute'\n : undefined,\n },\n });\n\n onEvent({\n type: `url_currentLocale\\\\${targetUrl}`,\n status: langTag ? 'success' : 'warning',\n data: {\n successDetails: langTag,\n warningsDetails: !langTag ? 'No locale detected' : undefined,\n },\n });\n\n onEvent({\n type: `url_htmlDir\\\\${targetUrl}`,\n status: htmlDirPresent ? 'success' : 'warning',\n data: {\n successDetails: htmlDirPresent ? dirTag : undefined,\n warningsDetails: !htmlDirPresent\n ? 'Missing html dir attribute'\n : undefined,\n },\n });\n\n return { langTag, dirTag };\n};\n\nexport const extractPageMetadata = async (\n $: CheerioAPI,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const title = $('title').text();\n const metaDescription = $(\"meta[name='description']\").attr('content') ?? '';\n const metaOgImage = $(\"meta[property='og:image']\").attr('content');\n\n let image = '';\n if (metaOgImage) {\n try {\n image = new URL(metaOgImage, targetUrl).href;\n } catch {\n image = metaOgImage;\n }\n }\n\n onEvent({\n domainData: { title, image, description: metaDescription },\n });\n};\n"],"mappings":";AAEA,MAAa,sBAAsB,OACjC,GACA,WACA,YACoE;CACpE,MAAM,UAAU,EAAE,OAAO,CAAC,KAAK,OAAO;CACtC,MAAM,SAAS,EAAE,OAAO,CAAC,KAAK,MAAM,IAAI;CAExC,MAAM,kBAAkB,QAAQ,QAAQ;CACxC,MAAM,iBAAiB,QAAQ,OAAO;AAEtC,SAAQ;EACN,MAAM,iBAAiB;EACvB,QAAQ,kBAAkB,YAAY;EACtC,MAAM;GACJ,gBAAgB,kBAAkB,UAAU;GAC5C,eAAe,CAAC,kBACZ,gCACA;GACL;EACF,CAAC;AAEF,SAAQ;EACN,MAAM,sBAAsB;EAC5B,QAAQ,UAAU,YAAY;EAC9B,MAAM;GACJ,gBAAgB;GAChB,iBAAiB,CAAC,UAAU,uBAAuB;GACpD;EACF,CAAC;AAEF,SAAQ;EACN,MAAM,gBAAgB;EACtB,QAAQ,iBAAiB,YAAY;EACrC,MAAM;GACJ,gBAAgB,iBAAiB,SAAS;GAC1C,iBAAiB,CAAC,iBACd,+BACA;GACL;EACF,CAAC;AAEF,QAAO;EAAE;EAAS;EAAQ;;AAG5B,MAAa,sBAAsB,OACjC,GACA,WACA,YACkB;CAClB,MAAM,QAAQ,EAAE,QAAQ,CAAC,MAAM;CAC/B,MAAM,kBAAkB,EAAE,2BAA2B,CAAC,KAAK,UAAU,IAAI;CACzE,MAAM,cAAc,EAAE,4BAA4B,CAAC,KAAK,UAAU;CAElE,IAAI,QAAQ;AACZ,KAAI,YACF,KAAI;AACF,UAAQ,IAAI,IAAI,aAAa,UAAU,CAAC;SAClC;AACN,UAAQ;;AAIZ,SAAQ,EACN,YAAY;EAAE;EAAO;EAAO,aAAa;EAAiB,EAC3D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"robotsChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/robotsChecker.ts"],"sourcesContent":["import { analyzeRobots } from '../analysis/analyzeRobots';\nimport type { AuditEvent } from '../types';\n\nexport const checkRobots = async (\n origin: string,\n discoveredLocales: Set<string>,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const robotsData = await analyzeRobots(origin, discoveredLocales);\n\n onEvent({\n type: 'robots_robotsPresent',\n status: robotsData.robotsPresent ? 'success' : 'warning',\n data: {\n successDetails: robotsData.robotsPresent ? true : undefined,\n warningsDetails: robotsData.robotsPresent\n ? undefined\n : 'No robots.txt found',\n errorsDetails:\n robotsData.errors.length > 0 ? robotsData.errors : undefined,\n },\n });\n\n if (robotsData.robotsPresent) {\n onEvent({\n type: 'robots_noLocalizedUrlsForgotten',\n status: robotsData.noLocalizedUrlsForgotten ? 'success' : 'error',\n data: {\n successDetails: robotsData.noLocalizedUrlsForgotten ? true : undefined,\n errorsDetails: robotsData.noLocalizedUrlsForgotten\n ? undefined\n : robotsData.errors,\n },\n });\n }\n};\n"],"mappings":";;;AAGA,MAAa,cAAc,OACzB,QACA,mBACA,YACkB;CAClB,MAAM,aAAa,MAAM,cAAc,QAAQ,iBAAiB;CAEhE,QAAQ;EACN,MAAM;EACN,QAAQ,WAAW,gBAAgB,YAAY;EAC/C,MAAM;GACJ,gBAAgB,WAAW,gBAAgB,OAAO;GAClD,iBAAiB,WAAW,gBACxB,SACA;GACJ,eACE,WAAW,OAAO,SAAS,IAAI,WAAW,SAAS;EACvD;CACF,CAAC;CAED,IAAI,WAAW,eACb,QAAQ;EACN,MAAM;EACN,QAAQ,WAAW,2BAA2B,YAAY;EAC1D,MAAM;GACJ,gBAAgB,WAAW,2BAA2B,OAAO;GAC7D,eAAe,WAAW,2BACtB,SACA,WAAW;EACjB;CACF,CAAC;AAEL"}
1
+ {"version":3,"file":"robotsChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/robotsChecker.ts"],"sourcesContent":["import { analyzeRobots } from '../analysis/analyzeRobots';\nimport type { AuditEvent } from '../types';\n\nexport const checkRobots = async (\n origin: string,\n discoveredLocales: Set<string>,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const robotsData = await analyzeRobots(origin, discoveredLocales);\n\n onEvent({\n type: 'robots_robotsPresent',\n status: robotsData.robotsPresent ? 'success' : 'warning',\n data: {\n successDetails: robotsData.robotsPresent ? true : undefined,\n warningsDetails: robotsData.robotsPresent\n ? undefined\n : 'No robots.txt found',\n errorsDetails:\n robotsData.errors.length > 0 ? robotsData.errors : undefined,\n },\n });\n\n if (robotsData.robotsPresent) {\n onEvent({\n type: 'robots_noLocalizedUrlsForgotten',\n status: robotsData.noLocalizedUrlsForgotten ? 'success' : 'error',\n data: {\n successDetails: robotsData.noLocalizedUrlsForgotten ? true : undefined,\n errorsDetails: robotsData.noLocalizedUrlsForgotten\n ? undefined\n : robotsData.errors,\n },\n });\n }\n};\n"],"mappings":";;;AAGA,MAAa,cAAc,OACzB,QACA,mBACA,YACkB;CAClB,MAAM,aAAa,MAAM,cAAc,QAAQ,kBAAkB;AAEjE,SAAQ;EACN,MAAM;EACN,QAAQ,WAAW,gBAAgB,YAAY;EAC/C,MAAM;GACJ,gBAAgB,WAAW,gBAAgB,OAAO;GAClD,iBAAiB,WAAW,gBACxB,SACA;GACJ,eACE,WAAW,OAAO,SAAS,IAAI,WAAW,SAAS;GACtD;EACF,CAAC;AAEF,KAAI,WAAW,cACb,SAAQ;EACN,MAAM;EACN,QAAQ,WAAW,2BAA2B,YAAY;EAC1D,MAAM;GACJ,gBAAgB,WAAW,2BAA2B,OAAO;GAC7D,eAAe,WAAW,2BACtB,SACA,WAAW;GAChB;EACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sitemapChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/sitemapChecker.ts"],"sourcesContent":["import { analyzeSitemap } from '../analysis/analyzeSitemap';\nimport type { AuditEvent } from '../types';\n\nexport const checkSitemap = async (\n origin: string,\n discoveredLocales: Set<string>,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const sitemapData = await analyzeSitemap(origin, discoveredLocales);\n\n onEvent({\n type: 'sitemap_sitemapPresent',\n status: sitemapData.sitemapPresent ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.sitemapPresent ? true : undefined,\n warningsDetails: sitemapData.sitemapPresent\n ? undefined\n : 'No sitemap.xml found',\n errorsDetails:\n sitemapData.errors.length > 0 ? sitemapData.errors : undefined,\n },\n });\n\n if (sitemapData.sitemapPresent) {\n onEvent({\n type: 'sitemap_noLocalizedUrlsForgotten',\n status: sitemapData.noLocalizedUrlsForgotten ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.noLocalizedUrlsForgotten ? true : undefined,\n warningsDetails: sitemapData.noLocalizedUrlsForgotten\n ? undefined\n : sitemapData.errors,\n },\n });\n\n onEvent({\n type: 'sitemap_hasXDefault',\n status: sitemapData.hasXDefault ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.hasXDefault ? true : undefined,\n warningsDetails: sitemapData.hasXDefault\n ? undefined\n : 'No x-default hreflang in sitemap',\n },\n });\n\n onEvent({\n type: 'sitemap_hasAlternates',\n status: sitemapData.hasAlternates ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.hasAlternates ? true : undefined,\n warningsDetails: sitemapData.hasAlternates\n ? undefined\n : 'No alternate language links found in sitemap',\n },\n });\n }\n};\n"],"mappings":";;;AAGA,MAAa,eAAe,OAC1B,QACA,mBACA,YACkB;CAClB,MAAM,cAAc,MAAM,eAAe,QAAQ,iBAAiB;CAElE,QAAQ;EACN,MAAM;EACN,QAAQ,YAAY,iBAAiB,YAAY;EACjD,MAAM;GACJ,gBAAgB,YAAY,iBAAiB,OAAO;GACpD,iBAAiB,YAAY,iBACzB,SACA;GACJ,eACE,YAAY,OAAO,SAAS,IAAI,YAAY,SAAS;EACzD;CACF,CAAC;CAED,IAAI,YAAY,gBAAgB;EAC9B,QAAQ;GACN,MAAM;GACN,QAAQ,YAAY,2BAA2B,YAAY;GAC3D,MAAM;IACJ,gBAAgB,YAAY,2BAA2B,OAAO;IAC9D,iBAAiB,YAAY,2BACzB,SACA,YAAY;GAClB;EACF,CAAC;EAED,QAAQ;GACN,MAAM;GACN,QAAQ,YAAY,cAAc,YAAY;GAC9C,MAAM;IACJ,gBAAgB,YAAY,cAAc,OAAO;IACjD,iBAAiB,YAAY,cACzB,SACA;GACN;EACF,CAAC;EAED,QAAQ;GACN,MAAM;GACN,QAAQ,YAAY,gBAAgB,YAAY;GAChD,MAAM;IACJ,gBAAgB,YAAY,gBAAgB,OAAO;IACnD,iBAAiB,YAAY,gBACzB,SACA;GACN;EACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"sitemapChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/sitemapChecker.ts"],"sourcesContent":["import { analyzeSitemap } from '../analysis/analyzeSitemap';\nimport type { AuditEvent } from '../types';\n\nexport const checkSitemap = async (\n origin: string,\n discoveredLocales: Set<string>,\n onEvent: (event: AuditEvent) => void\n): Promise<void> => {\n const sitemapData = await analyzeSitemap(origin, discoveredLocales);\n\n onEvent({\n type: 'sitemap_sitemapPresent',\n status: sitemapData.sitemapPresent ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.sitemapPresent ? true : undefined,\n warningsDetails: sitemapData.sitemapPresent\n ? undefined\n : 'No sitemap.xml found',\n errorsDetails:\n sitemapData.errors.length > 0 ? sitemapData.errors : undefined,\n },\n });\n\n if (sitemapData.sitemapPresent) {\n onEvent({\n type: 'sitemap_noLocalizedUrlsForgotten',\n status: sitemapData.noLocalizedUrlsForgotten ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.noLocalizedUrlsForgotten ? true : undefined,\n warningsDetails: sitemapData.noLocalizedUrlsForgotten\n ? undefined\n : sitemapData.errors,\n },\n });\n\n onEvent({\n type: 'sitemap_hasXDefault',\n status: sitemapData.hasXDefault ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.hasXDefault ? true : undefined,\n warningsDetails: sitemapData.hasXDefault\n ? undefined\n : 'No x-default hreflang in sitemap',\n },\n });\n\n onEvent({\n type: 'sitemap_hasAlternates',\n status: sitemapData.hasAlternates ? 'success' : 'warning',\n data: {\n successDetails: sitemapData.hasAlternates ? true : undefined,\n warningsDetails: sitemapData.hasAlternates\n ? undefined\n : 'No alternate language links found in sitemap',\n },\n });\n }\n};\n"],"mappings":";;;AAGA,MAAa,eAAe,OAC1B,QACA,mBACA,YACkB;CAClB,MAAM,cAAc,MAAM,eAAe,QAAQ,kBAAkB;AAEnE,SAAQ;EACN,MAAM;EACN,QAAQ,YAAY,iBAAiB,YAAY;EACjD,MAAM;GACJ,gBAAgB,YAAY,iBAAiB,OAAO;GACpD,iBAAiB,YAAY,iBACzB,SACA;GACJ,eACE,YAAY,OAAO,SAAS,IAAI,YAAY,SAAS;GACxD;EACF,CAAC;AAEF,KAAI,YAAY,gBAAgB;AAC9B,UAAQ;GACN,MAAM;GACN,QAAQ,YAAY,2BAA2B,YAAY;GAC3D,MAAM;IACJ,gBAAgB,YAAY,2BAA2B,OAAO;IAC9D,iBAAiB,YAAY,2BACzB,SACA,YAAY;IACjB;GACF,CAAC;AAEF,UAAQ;GACN,MAAM;GACN,QAAQ,YAAY,cAAc,YAAY;GAC9C,MAAM;IACJ,gBAAgB,YAAY,cAAc,OAAO;IACjD,iBAAiB,YAAY,cACzB,SACA;IACL;GACF,CAAC;AAEF,UAAQ;GACN,MAAM;GACN,QAAQ,YAAY,gBAAgB,YAAY;GAChD,MAAM;IACJ,gBAAgB,YAAY,gBAAgB,OAAO;IACnD,iBAAiB,YAAY,gBACzB,SACA;IACL;GACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"urlChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/urlChecker.ts"],"sourcesContent":["import type { Page } from 'puppeteer';\nimport { analyzeUrlStructure } from '../analysis/analyzeUrlStructure';\nimport type { AuditEvent } from '../types';\n\nexport const checkUrlStructure = async (\n page: Page,\n origin: string,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<string[]> => {\n const urlStructureData = await analyzeUrlStructure(page, origin);\n\n onEvent({\n type: `url_hasLocalizedLinks\\\\${targetUrl}`,\n status: urlStructureData.hasLocalizedLinks ? 'success' : 'warning',\n data: {\n successDetails: urlStructureData.hasLocalizedLinks\n ? {\n message: `${urlStructureData.localizedCount} localized links found out of ${urlStructureData.totalInternalCount} internal links`,\n links: urlStructureData.localizedLinks,\n }\n : undefined,\n warningsDetails: urlStructureData.hasLocalizedLinks\n ? undefined\n : 'No localized links found',\n },\n });\n\n onEvent({\n type: `url_allAnchorsLocalized\\\\${targetUrl}`,\n status: urlStructureData.allAnchorsLocalized ? 'success' : 'warning',\n data: {\n successDetails: urlStructureData.allAnchorsLocalized ? true : undefined,\n warningsDetails: !urlStructureData.allAnchorsLocalized\n ? {\n message: 'Some internal links are not localized',\n links: urlStructureData.nonLocalizedLinks,\n }\n : undefined,\n },\n });\n\n return urlStructureData.internalUrls;\n};\n"],"mappings":";;;AAIA,MAAa,oBAAoB,OAC/B,MACA,QACA,WACA,YACsB;CACtB,MAAM,mBAAmB,MAAM,oBAAoB,MAAM,MAAM;CAE/D,QAAQ;EACN,MAAM,0BAA0B;EAChC,QAAQ,iBAAiB,oBAAoB,YAAY;EACzD,MAAM;GACJ,gBAAgB,iBAAiB,oBAC7B;IACE,SAAS,GAAG,iBAAiB,eAAe,gCAAgC,iBAAiB,mBAAmB;IAChH,OAAO,iBAAiB;GAC1B,IACA;GACJ,iBAAiB,iBAAiB,oBAC9B,SACA;EACN;CACF,CAAC;CAED,QAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ,iBAAiB,sBAAsB,YAAY;EAC3D,MAAM;GACJ,gBAAgB,iBAAiB,sBAAsB,OAAO;GAC9D,iBAAiB,CAAC,iBAAiB,sBAC/B;IACE,SAAS;IACT,OAAO,iBAAiB;GAC1B,IACA;EACN;CACF,CAAC;CAED,OAAO,iBAAiB;AAC1B"}
1
+ {"version":3,"file":"urlChecker.mjs","names":[],"sources":["../../../../../src/services/audit/checkers/urlChecker.ts"],"sourcesContent":["import type { Page } from 'puppeteer';\nimport { analyzeUrlStructure } from '../analysis/analyzeUrlStructure';\nimport type { AuditEvent } from '../types';\n\nexport const checkUrlStructure = async (\n page: Page,\n origin: string,\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<string[]> => {\n const urlStructureData = await analyzeUrlStructure(page, origin);\n\n onEvent({\n type: `url_hasLocalizedLinks\\\\${targetUrl}`,\n status: urlStructureData.hasLocalizedLinks ? 'success' : 'warning',\n data: {\n successDetails: urlStructureData.hasLocalizedLinks\n ? {\n message: `${urlStructureData.localizedCount} localized links found out of ${urlStructureData.totalInternalCount} internal links`,\n links: urlStructureData.localizedLinks,\n }\n : undefined,\n warningsDetails: urlStructureData.hasLocalizedLinks\n ? undefined\n : 'No localized links found',\n },\n });\n\n onEvent({\n type: `url_allAnchorsLocalized\\\\${targetUrl}`,\n status: urlStructureData.allAnchorsLocalized ? 'success' : 'warning',\n data: {\n successDetails: urlStructureData.allAnchorsLocalized ? true : undefined,\n warningsDetails: !urlStructureData.allAnchorsLocalized\n ? {\n message: 'Some internal links are not localized',\n links: urlStructureData.nonLocalizedLinks,\n }\n : undefined,\n },\n });\n\n return urlStructureData.internalUrls;\n};\n"],"mappings":";;;AAIA,MAAa,oBAAoB,OAC/B,MACA,QACA,WACA,YACsB;CACtB,MAAM,mBAAmB,MAAM,oBAAoB,MAAM,OAAO;AAEhE,SAAQ;EACN,MAAM,0BAA0B;EAChC,QAAQ,iBAAiB,oBAAoB,YAAY;EACzD,MAAM;GACJ,gBAAgB,iBAAiB,oBAC7B;IACE,SAAS,GAAG,iBAAiB,eAAe,gCAAgC,iBAAiB,mBAAmB;IAChH,OAAO,iBAAiB;IACzB,GACD;GACJ,iBAAiB,iBAAiB,oBAC9B,SACA;GACL;EACF,CAAC;AAEF,SAAQ;EACN,MAAM,4BAA4B;EAClC,QAAQ,iBAAiB,sBAAsB,YAAY;EAC3D,MAAM;GACJ,gBAAgB,iBAAiB,sBAAsB,OAAO;GAC9D,iBAAiB,CAAC,iBAAiB,sBAC/B;IACE,SAAS;IACT,OAAO,iBAAiB;IACzB,GACD;GACL;EACF,CAAC;AAEF,QAAO,iBAAiB"}
@@ -1 +1 @@
1
- {"version":3,"file":"recursiveAudit.service.mjs","names":[],"sources":["../../../../src/services/audit/recursiveAudit.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { AuditJobModel, AuditJobStatus } from '@schemas/auditJob.schema';\nimport { AuditPageModel, AuditPageStatus } from '@schemas/auditPage.schema';\nimport { load } from 'cheerio';\nimport { mutateScore, type Score } from './analysis/calculateScore';\nimport { runSingleAudit } from './seoAudit.service';\n\nconst SLEEP_TIME = 30000;\nconst MAX_PAGES = 10;\n\nlet isProcessing = false;\n\n/**\n * Fetches sitemap.xml for the given URL and extracts all <loc> entries.\n * Falls back to [targetUrl] if no sitemap is found.\n */\nexport const discoverUrlsFromSitemap = async (\n targetUrl: string\n): Promise<string[]> => {\n try {\n const { origin } = new URL(targetUrl);\n const sitemapUrl = `${origin}/sitemap.xml`;\n\n const response = await fetch(sitemapUrl, {\n method: 'GET',\n headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SEO-Audit-Bot/1.0)' },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!response.ok) return [targetUrl];\n\n const sitemapContent = await response.text();\n const $ = load(sitemapContent, { xmlMode: true });\n\n const urls: string[] = [];\n\n // Primary <loc> entries\n $('loc').each((_, el) => {\n const url = $(el).text().trim();\n if (url) urls.push(url);\n });\n\n // Alternate hreflang URLs from <xhtml:link rel=\"alternate\" href=\"...\">\n // Cheerio in xmlMode parses these as \"xhtml:link\" elements\n $('xhtml\\\\:link[rel=\"alternate\"], link[rel=\"alternate\"]').each((_, el) => {\n const href = $(el).attr('href')?.trim();\n if (href && href !== 'x-default') urls.push(href);\n });\n\n const uniqueUrls = [...new Set(urls)];\n return uniqueUrls.length > 0 ? uniqueUrls : [targetUrl];\n } catch {\n return [targetUrl];\n }\n};\n\nexport const startRecursiveAuditJob = async (\n targetUrl: string,\n userId?: string,\n urls?: string[]\n): Promise<string> => {\n const existingJob = await AuditJobModel.findOne({\n targetUrl: String(targetUrl),\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n });\n\n if (existingJob) {\n return (existingJob._id as any).toString();\n }\n\n const pageUrls =\n urls && urls.length > 0\n ? [...new Set(urls)].slice(0, MAX_PAGES)\n : [targetUrl];\n\n const job = await AuditJobModel.create({\n targetUrl,\n userId,\n status: AuditJobStatus.PENDING,\n totalPageCount: pageUrls.length,\n });\n\n for (const url of pageUrls) {\n await AuditPageModel.create({\n jobId: job._id,\n url,\n status: AuditPageStatus.PENDING,\n }).catch(() => {\n /* ignore duplicate key errors */\n });\n }\n\n processAuditJobs().catch((err) => logger.error(err));\n\n return (job._id as any).toString();\n};\n\nexport const cancelAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.CANCELLED,\n });\n return !!result;\n};\n\nexport const pauseAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.PAUSED,\n });\n return !!result;\n};\n\nexport const resumeAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.RUNNING,\n });\n if (!result) return false;\n processAuditJobs().catch((err) => logger.error(err));\n return true;\n};\n\nexport const processAuditJobs = async (): Promise<void> => {\n if (isProcessing) return;\n isProcessing = true;\n\n try {\n while (true) {\n const job = await AuditJobModel.findOne({\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n }).sort({ createdAt: 1 });\n\n if (!job) break;\n\n if (job.status === AuditJobStatus.PENDING) {\n job.status = AuditJobStatus.RUNNING;\n await job.save();\n }\n\n // Re-fetch to detect external cancellation / pause between pages\n const freshJob = await AuditJobModel.findById(job._id);\n if (\n !freshJob ||\n freshJob.status === AuditJobStatus.CANCELLED ||\n freshJob.status === AuditJobStatus.PAUSED\n ) {\n logger.info(\n `Job ${job._id} is ${freshJob?.status ?? 'missing'} — stopping processor`\n );\n break;\n }\n\n const pendingPage = await AuditPageModel.findOne({\n jobId: job._id,\n status: AuditPageStatus.PENDING,\n });\n\n if (!pendingPage) {\n const hasMorePages = await AuditPageModel.exists({\n jobId: job._id,\n status: { $in: [AuditPageStatus.PENDING, AuditPageStatus.RUNNING] },\n });\n\n if (!hasMorePages) {\n job.status = AuditJobStatus.COMPLETED;\n job.progress = 100;\n await job.save();\n }\n break;\n }\n\n pendingPage.status = AuditPageStatus.RUNNING;\n await pendingPage.save();\n\n try {\n const { events } = await runSingleAudit(pendingPage.url, () => {});\n\n // Compute score the same way the single-page SSE controller does\n let score: Score = { score: 0, totalScore: 0 };\n for (const event of events) {\n score = mutateScore(score, event);\n }\n\n pendingPage.status = AuditPageStatus.COMPLETED;\n pendingPage.results = events;\n pendingPage.score = Math.round(\n score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0\n );\n await pendingPage.save();\n\n const totalPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n });\n const completedPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n status: AuditPageStatus.COMPLETED,\n });\n\n job.totalPageCount = totalPages;\n job.completedPageCount = completedPages;\n job.progress = Math.round((completedPages / totalPages) * 100);\n await job.save();\n } catch (err) {\n logger.error(`Failed to audit page ${pendingPage.url}:`, err);\n pendingPage.status = AuditPageStatus.FAILED;\n pendingPage.error = String(err);\n await pendingPage.save();\n }\n\n await new Promise((resolve) => setTimeout(resolve, SLEEP_TIME));\n }\n } finally {\n isProcessing = false;\n }\n};\n\nexport const getAuditJobStatus = async (jobId: string) => {\n const job = await AuditJobModel.findById(jobId);\n if (!job) return null;\n\n const pages = await AuditPageModel.find({ jobId }).select(\n 'url status score error results'\n );\n\n return { job, pages };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"recursiveAudit.service.mjs","names":[],"sources":["../../../../src/services/audit/recursiveAudit.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { AuditJobModel, AuditJobStatus } from '@schemas/auditJob.schema';\nimport { AuditPageModel, AuditPageStatus } from '@schemas/auditPage.schema';\nimport { load } from 'cheerio';\nimport { mutateScore, type Score } from './analysis/calculateScore';\nimport { runSingleAudit } from './seoAudit.service';\n\nconst SLEEP_TIME = 30000;\nconst MAX_PAGES = 10;\n\nlet isProcessing = false;\n\n/**\n * Fetches sitemap.xml for the given URL and extracts all <loc> entries.\n * Falls back to [targetUrl] if no sitemap is found.\n */\nexport const discoverUrlsFromSitemap = async (\n targetUrl: string\n): Promise<string[]> => {\n try {\n const { origin } = new URL(targetUrl);\n const sitemapUrl = `${origin}/sitemap.xml`;\n\n const response = await fetch(sitemapUrl, {\n method: 'GET',\n headers: { 'User-Agent': 'Mozilla/5.0 (compatible; SEO-Audit-Bot/1.0)' },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!response.ok) return [targetUrl];\n\n const sitemapContent = await response.text();\n const $ = load(sitemapContent, { xmlMode: true });\n\n const urls: string[] = [];\n\n // Primary <loc> entries\n $('loc').each((_, el) => {\n const url = $(el).text().trim();\n if (url) urls.push(url);\n });\n\n // Alternate hreflang URLs from <xhtml:link rel=\"alternate\" href=\"...\">\n // Cheerio in xmlMode parses these as \"xhtml:link\" elements\n $('xhtml\\\\:link[rel=\"alternate\"], link[rel=\"alternate\"]').each((_, el) => {\n const href = $(el).attr('href')?.trim();\n if (href && href !== 'x-default') urls.push(href);\n });\n\n const uniqueUrls = [...new Set(urls)];\n return uniqueUrls.length > 0 ? uniqueUrls : [targetUrl];\n } catch {\n return [targetUrl];\n }\n};\n\nexport const startRecursiveAuditJob = async (\n targetUrl: string,\n userId?: string,\n urls?: string[]\n): Promise<string> => {\n const existingJob = await AuditJobModel.findOne({\n targetUrl: String(targetUrl),\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n });\n\n if (existingJob) {\n return (existingJob._id as any).toString();\n }\n\n const pageUrls =\n urls && urls.length > 0\n ? [...new Set(urls)].slice(0, MAX_PAGES)\n : [targetUrl];\n\n const job = await AuditJobModel.create({\n targetUrl,\n userId,\n status: AuditJobStatus.PENDING,\n totalPageCount: pageUrls.length,\n });\n\n for (const url of pageUrls) {\n await AuditPageModel.create({\n jobId: job._id,\n url,\n status: AuditPageStatus.PENDING,\n }).catch(() => {\n /* ignore duplicate key errors */\n });\n }\n\n processAuditJobs().catch((err) => logger.error(err));\n\n return (job._id as any).toString();\n};\n\nexport const cancelAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.CANCELLED,\n });\n return !!result;\n};\n\nexport const pauseAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.PAUSED,\n });\n return !!result;\n};\n\nexport const resumeAuditJob = async (jobId: string): Promise<boolean> => {\n const result = await AuditJobModel.findByIdAndUpdate(jobId, {\n status: AuditJobStatus.RUNNING,\n });\n if (!result) return false;\n processAuditJobs().catch((err) => logger.error(err));\n return true;\n};\n\nexport const processAuditJobs = async (): Promise<void> => {\n if (isProcessing) return;\n isProcessing = true;\n\n try {\n while (true) {\n const job = await AuditJobModel.findOne({\n status: { $in: [AuditJobStatus.PENDING, AuditJobStatus.RUNNING] },\n }).sort({ createdAt: 1 });\n\n if (!job) break;\n\n if (job.status === AuditJobStatus.PENDING) {\n job.status = AuditJobStatus.RUNNING;\n await job.save();\n }\n\n // Re-fetch to detect external cancellation / pause between pages\n const freshJob = await AuditJobModel.findById(job._id);\n if (\n !freshJob ||\n freshJob.status === AuditJobStatus.CANCELLED ||\n freshJob.status === AuditJobStatus.PAUSED\n ) {\n logger.info(\n `Job ${job._id} is ${freshJob?.status ?? 'missing'} — stopping processor`\n );\n break;\n }\n\n const pendingPage = await AuditPageModel.findOne({\n jobId: job._id,\n status: AuditPageStatus.PENDING,\n });\n\n if (!pendingPage) {\n const hasMorePages = await AuditPageModel.exists({\n jobId: job._id,\n status: { $in: [AuditPageStatus.PENDING, AuditPageStatus.RUNNING] },\n });\n\n if (!hasMorePages) {\n job.status = AuditJobStatus.COMPLETED;\n job.progress = 100;\n await job.save();\n }\n break;\n }\n\n pendingPage.status = AuditPageStatus.RUNNING;\n await pendingPage.save();\n\n try {\n const { events } = await runSingleAudit(pendingPage.url, () => {});\n\n // Compute score the same way the single-page SSE controller does\n let score: Score = { score: 0, totalScore: 0 };\n for (const event of events) {\n score = mutateScore(score, event);\n }\n\n pendingPage.status = AuditPageStatus.COMPLETED;\n pendingPage.results = events;\n pendingPage.score = Math.round(\n score.totalScore > 0 ? (score.score / score.totalScore) * 100 : 0\n );\n await pendingPage.save();\n\n const totalPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n });\n const completedPages = await AuditPageModel.countDocuments({\n jobId: job._id,\n status: AuditPageStatus.COMPLETED,\n });\n\n job.totalPageCount = totalPages;\n job.completedPageCount = completedPages;\n job.progress = Math.round((completedPages / totalPages) * 100);\n await job.save();\n } catch (err) {\n logger.error(`Failed to audit page ${pendingPage.url}:`, err);\n pendingPage.status = AuditPageStatus.FAILED;\n pendingPage.error = String(err);\n await pendingPage.save();\n }\n\n await new Promise((resolve) => setTimeout(resolve, SLEEP_TIME));\n }\n } finally {\n isProcessing = false;\n }\n};\n\nexport const getAuditJobStatus = async (jobId: string) => {\n const job = await AuditJobModel.findById(jobId);\n if (!job) return null;\n\n const pages = await AuditPageModel.find({ jobId }).select(\n 'url status score error results'\n );\n\n return { job, pages };\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"seoAudit.service.mjs","names":[],"sources":["../../../../src/services/audit/seoAudit.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { launchBrowser } from '@utils/puppeteer/launchBrowser';\nimport { load } from 'cheerio';\nimport type { Browser } from 'puppeteer';\nimport { checkBundleContent } from './checkers/bundleChecker';\nimport { checkLinguisticStructure } from './checkers/linguisticChecker';\nimport { checkMetadata } from './checkers/metadataChecker';\nimport {\n checkHtmlAttributes,\n extractPageMetadata,\n} from './checkers/pageChecker';\nimport { checkRobots } from './checkers/robotsChecker';\nimport { checkSitemap } from './checkers/sitemapChecker';\nimport { checkUrlStructure } from './checkers/urlChecker';\nimport type { AuditEvent } from './types';\n\nconst gotoWithRetries = async (\n page: import('puppeteer').Page,\n url: string,\n attempts = 3\n) => {\n let lastErr: unknown;\n for (let i = 1; i <= attempts; i++) {\n try {\n const resp = await page.goto(url, {\n waitUntil: 'domcontentloaded',\n timeout: 45000,\n });\n\n if (!resp) {\n throw new Error(`Failed to get a response from ${url}`);\n }\n\n const status = resp.status();\n logger.info(`[gotoWithRetries] Status: ${status} for ${url}`);\n if (status >= 400) throw new Error(`HTTP ${status} on ${url}`);\n\n await page.waitForSelector('body', { timeout: 10000 });\n\n await page\n .waitForNetworkIdle({ idleTime: 1000, timeout: 10000 })\n .catch(() => {\n /* ok if it doesn't fully idle */\n });\n\n return resp;\n } catch (err) {\n lastErr = err;\n if (i < attempts) {\n await new Promise((r) => setTimeout(r, 500 * i));\n continue;\n }\n throw lastErr;\n }\n }\n};\n\nexport const runSingleAudit = async (\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<{ events: AuditEvent[]; internalUrls: string[] }> => {\n let browser: Browser | undefined;\n const events: AuditEvent[] = [];\n\n const handleEvent = (event: AuditEvent) => {\n events.push(event);\n onEvent(event);\n };\n\n try {\n const origin = new URL(targetUrl).origin;\n const localesSet = new Set<string>();\n\n handleEvent({\n progress: 10,\n message: 'Checking domain and parsing root page...',\n });\n\n browser = await launchBrowser();\n const page = await browser.newPage();\n\n await page.setUserAgent({\n userAgent:\n 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119 Safari/537.36',\n platform: 'Linux',\n userAgentMetadata: {\n brands: [{ brand: 'Google Chrome', version: '119' }],\n platform: 'Linux',\n mobile: false,\n platformVersion: '119',\n architecture: 'x86',\n model: 'Linux',\n },\n });\n\n await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9' });\n\n await page.setRequestInterception(true);\n page.on('request', (req) => {\n const reqUrl = req.url();\n const type = req.resourceType();\n if (\n type === 'image' ||\n type === 'media' ||\n type === 'font' ||\n type === 'websocket' ||\n reqUrl.startsWith('file://') ||\n reqUrl.startsWith('data:') ||\n reqUrl.startsWith('chrome://') ||\n reqUrl.startsWith('view-source:')\n ) {\n req.abort();\n } else {\n req.continue();\n }\n });\n\n const jsResponseMap = new Map<string, string>(); // URL -> content\n let totalPageSize = 0;\n const pendingResponses: Promise<void>[] = [];\n\n page.on('response', (response) => {\n const responsePromise = (async () => {\n const responseUrl = response.url();\n if (response.status() !== 200) return;\n const contentType = response.headers()['content-type'] ?? '';\n const isJavaScript =\n contentType.includes('javascript') ||\n /\\.(js|mjs|cjs)(\\?|$)/.test(responseUrl);\n // Only scan same-origin scripts — third-party analytics/CDN scripts\n // (GTM, Intercom, etc.) contain locale-like keys that cause false positives.\n const isSameOrigin = responseUrl.startsWith(origin);\n\n try {\n const bodyBuffer = await response.buffer();\n totalPageSize += bodyBuffer.length;\n\n if (!isJavaScript || !isSameOrigin) return;\n jsResponseMap.set(responseUrl, bodyBuffer.toString('utf-8'));\n } catch {\n /* response already consumed or aborted */\n }\n })();\n pendingResponses.push(responsePromise);\n });\n\n await page.setViewport({ width: 1280, height: 800 });\n\n page.on('requestfailed', (request) =>\n logger.warn(\n `[requestfailed] ${request.url()} ${request.failure()?.errorText}`\n )\n );\n page.on('console', (message) =>\n logger.info(`[console] ${message.type()} ${message.text()}`)\n );\n page.on('pageerror', (error) => logger.error(`[pageerror] ${error}`));\n\n await gotoWithRetries(page, targetUrl);\n\n await Promise.allSettled(pendingResponses);\n\n const html = await page.content();\n logger.info(`[runSingleAudit] Page loaded. Content length: ${html.length}`);\n const cheerioApi = load(html);\n logger.info(\n `[runSingleAudit] Cheerio loaded. Body found: ${cheerioApi('body').length > 0}`\n );\n\n // Identify main bundle scripts — scripts that are eagerly loaded on initial page load.\n // Covers:\n // <script src=\"...\"> — classic and module entry points\n // <link rel=\"modulepreload\"> — Vite preloads critical chunks this way\n // <link rel=\"preload\" as=\"script\"> — generic preload\n const mainBundleUrls = new Set<string>();\n const addMainUrl = (raw: string | undefined) => {\n if (!raw) return;\n try {\n mainBundleUrls.add(new URL(raw, targetUrl).href);\n } catch {\n /* ignore */\n }\n };\n cheerioApi('script[src]').each((_, el) =>\n addMainUrl(cheerioApi(el).attr('src'))\n );\n cheerioApi('link[rel=\"modulepreload\"][href]').each((_, el) =>\n addMainUrl(cheerioApi(el).attr('href'))\n );\n cheerioApi('link[rel=\"preload\"][as=\"script\"][href]').each((_, el) =>\n addMainUrl(cheerioApi(el).attr('href'))\n );\n\n const bundleChunks = Array.from(jsResponseMap.entries()).map(\n ([url, content]) => ({\n url,\n isMainBundle: mainBundleUrls.has(url),\n content,\n })\n );\n\n await extractPageMetadata(cheerioApi, targetUrl, handleEvent);\n\n handleEvent({ progress: 15 });\n\n const { langTag } = await checkHtmlAttributes(\n cheerioApi,\n targetUrl,\n handleEvent\n );\n\n handleEvent({\n progress: 30,\n message: 'Analyzing linguistic structure...',\n });\n\n await checkLinguisticStructure(\n cheerioApi,\n targetUrl,\n localesSet,\n handleEvent\n );\n\n handleEvent({\n progress: 40,\n message: 'Analysing bundle content & leakage...',\n });\n\n checkBundleContent(\n bundleChunks,\n html,\n langTag,\n targetUrl,\n totalPageSize,\n handleEvent\n );\n\n handleEvent({\n progress: 50,\n message: 'Checking metadata structure...',\n });\n\n await checkMetadata(cheerioApi, targetUrl, handleEvent);\n\n handleEvent({\n progress: 60,\n message: 'Analyzing URL structure...',\n });\n\n const internalUrls = await checkUrlStructure(\n page,\n origin,\n targetUrl,\n handleEvent\n );\n\n handleEvent({\n progress: 70,\n message: 'Checking robots.txt...',\n });\n\n await checkRobots(origin, localesSet, handleEvent);\n\n handleEvent({\n progress: 80,\n message: 'Checking sitemap.xml...',\n });\n\n await checkSitemap(origin, localesSet, handleEvent);\n\n handleEvent({\n progress: 100,\n message: 'Audit completed',\n });\n\n return { events, internalUrls };\n } catch (err: unknown) {\n handleEvent({\n globalError: (err as Error).message,\n });\n throw err;\n } finally {\n if (browser) await browser.close();\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"seoAudit.service.mjs","names":[],"sources":["../../../../src/services/audit/seoAudit.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { launchBrowser } from '@utils/puppeteer/launchBrowser';\nimport { load } from 'cheerio';\nimport type { Browser } from 'puppeteer';\nimport { checkBundleContent } from './checkers/bundleChecker';\nimport { checkLinguisticStructure } from './checkers/linguisticChecker';\nimport { checkMetadata } from './checkers/metadataChecker';\nimport {\n checkHtmlAttributes,\n extractPageMetadata,\n} from './checkers/pageChecker';\nimport { checkRobots } from './checkers/robotsChecker';\nimport { checkSitemap } from './checkers/sitemapChecker';\nimport { checkUrlStructure } from './checkers/urlChecker';\nimport type { AuditEvent } from './types';\n\nconst gotoWithRetries = async (\n page: import('puppeteer').Page,\n url: string,\n attempts = 3\n) => {\n let lastErr: unknown;\n for (let i = 1; i <= attempts; i++) {\n try {\n const resp = await page.goto(url, {\n waitUntil: 'domcontentloaded',\n timeout: 45000,\n });\n\n if (!resp) {\n throw new Error(`Failed to get a response from ${url}`);\n }\n\n const status = resp.status();\n logger.info(`[gotoWithRetries] Status: ${status} for ${url}`);\n if (status >= 400) throw new Error(`HTTP ${status} on ${url}`);\n\n await page.waitForSelector('body', { timeout: 10000 });\n\n await page\n .waitForNetworkIdle({ idleTime: 1000, timeout: 10000 })\n .catch(() => {\n /* ok if it doesn't fully idle */\n });\n\n return resp;\n } catch (err) {\n lastErr = err;\n if (i < attempts) {\n await new Promise((r) => setTimeout(r, 500 * i));\n continue;\n }\n throw lastErr;\n }\n }\n};\n\nexport const runSingleAudit = async (\n targetUrl: string,\n onEvent: (event: AuditEvent) => void\n): Promise<{ events: AuditEvent[]; internalUrls: string[] }> => {\n let browser: Browser | undefined;\n const events: AuditEvent[] = [];\n\n const handleEvent = (event: AuditEvent) => {\n events.push(event);\n onEvent(event);\n };\n\n try {\n const origin = new URL(targetUrl).origin;\n const localesSet = new Set<string>();\n\n handleEvent({\n progress: 10,\n message: 'Checking domain and parsing root page...',\n });\n\n browser = await launchBrowser();\n const page = await browser.newPage();\n\n await page.setUserAgent({\n userAgent:\n 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119 Safari/537.36',\n platform: 'Linux',\n userAgentMetadata: {\n brands: [{ brand: 'Google Chrome', version: '119' }],\n platform: 'Linux',\n mobile: false,\n platformVersion: '119',\n architecture: 'x86',\n model: 'Linux',\n },\n });\n\n await page.setExtraHTTPHeaders({ 'Accept-Language': 'en-US,en;q=0.9' });\n\n await page.setRequestInterception(true);\n page.on('request', (req) => {\n const reqUrl = req.url();\n const type = req.resourceType();\n if (\n type === 'image' ||\n type === 'media' ||\n type === 'font' ||\n type === 'websocket' ||\n reqUrl.startsWith('file://') ||\n reqUrl.startsWith('data:') ||\n reqUrl.startsWith('chrome://') ||\n reqUrl.startsWith('view-source:')\n ) {\n req.abort();\n } else {\n req.continue();\n }\n });\n\n const jsResponseMap = new Map<string, string>(); // URL -> content\n let totalPageSize = 0;\n const pendingResponses: Promise<void>[] = [];\n\n page.on('response', (response) => {\n const responsePromise = (async () => {\n const responseUrl = response.url();\n if (response.status() !== 200) return;\n const contentType = response.headers()['content-type'] ?? '';\n const isJavaScript =\n contentType.includes('javascript') ||\n /\\.(js|mjs|cjs)(\\?|$)/.test(responseUrl);\n // Only scan same-origin scripts — third-party analytics/CDN scripts\n // (GTM, Intercom, etc.) contain locale-like keys that cause false positives.\n const isSameOrigin = responseUrl.startsWith(origin);\n\n try {\n const bodyBuffer = await response.buffer();\n totalPageSize += bodyBuffer.length;\n\n if (!isJavaScript || !isSameOrigin) return;\n jsResponseMap.set(responseUrl, bodyBuffer.toString('utf-8'));\n } catch {\n /* response already consumed or aborted */\n }\n })();\n pendingResponses.push(responsePromise);\n });\n\n await page.setViewport({ width: 1280, height: 800 });\n\n page.on('requestfailed', (request) =>\n logger.warn(\n `[requestfailed] ${request.url()} ${request.failure()?.errorText}`\n )\n );\n page.on('console', (message) =>\n logger.info(`[console] ${message.type()} ${message.text()}`)\n );\n page.on('pageerror', (error) => logger.error(`[pageerror] ${error}`));\n\n await gotoWithRetries(page, targetUrl);\n\n await Promise.allSettled(pendingResponses);\n\n const html = await page.content();\n logger.info(`[runSingleAudit] Page loaded. Content length: ${html.length}`);\n const cheerioApi = load(html);\n logger.info(\n `[runSingleAudit] Cheerio loaded. Body found: ${cheerioApi('body').length > 0}`\n );\n\n // Identify main bundle scripts — scripts that are eagerly loaded on initial page load.\n // Covers:\n // <script src=\"...\"> — classic and module entry points\n // <link rel=\"modulepreload\"> — Vite preloads critical chunks this way\n // <link rel=\"preload\" as=\"script\"> — generic preload\n const mainBundleUrls = new Set<string>();\n const addMainUrl = (raw: string | undefined) => {\n if (!raw) return;\n try {\n mainBundleUrls.add(new URL(raw, targetUrl).href);\n } catch {\n /* ignore */\n }\n };\n cheerioApi('script[src]').each((_, el) =>\n addMainUrl(cheerioApi(el).attr('src'))\n );\n cheerioApi('link[rel=\"modulepreload\"][href]').each((_, el) =>\n addMainUrl(cheerioApi(el).attr('href'))\n );\n cheerioApi('link[rel=\"preload\"][as=\"script\"][href]').each((_, el) =>\n addMainUrl(cheerioApi(el).attr('href'))\n );\n\n const bundleChunks = Array.from(jsResponseMap.entries()).map(\n ([url, content]) => ({\n url,\n isMainBundle: mainBundleUrls.has(url),\n content,\n })\n );\n\n await extractPageMetadata(cheerioApi, targetUrl, handleEvent);\n\n handleEvent({ progress: 15 });\n\n const { langTag } = await checkHtmlAttributes(\n cheerioApi,\n targetUrl,\n handleEvent\n );\n\n handleEvent({\n progress: 30,\n message: 'Analyzing linguistic structure...',\n });\n\n await checkLinguisticStructure(\n cheerioApi,\n targetUrl,\n localesSet,\n handleEvent\n );\n\n handleEvent({\n progress: 40,\n message: 'Analysing bundle content & leakage...',\n });\n\n checkBundleContent(\n bundleChunks,\n html,\n langTag,\n targetUrl,\n totalPageSize,\n handleEvent\n );\n\n handleEvent({\n progress: 50,\n message: 'Checking metadata structure...',\n });\n\n await checkMetadata(cheerioApi, targetUrl, handleEvent);\n\n handleEvent({\n progress: 60,\n message: 'Analyzing URL structure...',\n });\n\n const internalUrls = await checkUrlStructure(\n page,\n origin,\n targetUrl,\n handleEvent\n );\n\n handleEvent({\n progress: 70,\n message: 'Checking robots.txt...',\n });\n\n await checkRobots(origin, localesSet, handleEvent);\n\n handleEvent({\n progress: 80,\n message: 'Checking sitemap.xml...',\n });\n\n await checkSitemap(origin, localesSet, handleEvent);\n\n handleEvent({\n progress: 100,\n message: 'Audit completed',\n });\n\n return { events, internalUrls };\n } catch (err: unknown) {\n handleEvent({\n globalError: (err as Error).message,\n });\n throw err;\n } finally {\n if (browser) await browser.close();\n }\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bitbucket.service.mjs","names":[],"sources":["../../../src/services/bitbucket.service.ts"],"sourcesContent":["import { configurationFilesCandidates } from '@intlayer/config/node';\nimport { logger } from '@logger';\nimport { AccountModel } from '@schemas/account.schema';\n\nconst BITBUCKET_API_URL = 'https://api.bitbucket.org/2.0';\nconst BITBUCKET_AUTH_URL = 'https://bitbucket.org/site/oauth2';\n\nexport type BitbucketRepository = {\n uuid: string;\n name: string;\n full_name: string;\n slug: string;\n mainbranch?: {\n name: string;\n type: string;\n };\n links: {\n html: {\n href: string;\n };\n };\n workspace: {\n slug: string;\n name: string;\n uuid: string;\n };\n owner: {\n display_name: string;\n username?: string;\n uuid: string;\n };\n updated_on: string;\n is_private: boolean;\n};\n\nexport type BitbucketTreeItem = {\n path: string;\n type: 'commit_file' | 'commit_directory';\n size?: number;\n};\n\n/**\n * Get Bitbucket (Atlassian) authorization URL for OAuth flow\n */\nexport const getAuthorizationUrl = (_redirectUri: string): string => {\n const clientId = process.env.ATLASSIAN_CLIENT_ID;\n\n if (!clientId) {\n throw new Error('Bitbucket/Atlassian Client ID is not configured');\n }\n\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n state: 'bitbucket_oauth',\n });\n\n return `${BITBUCKET_AUTH_URL}/authorize?${params.toString()}`;\n};\n\n/**\n * Exchange Bitbucket authorization code for access token\n */\nexport const exchangeCodeForToken = async (code: string): Promise<string> => {\n const clientId = process.env.ATLASSIAN_CLIENT_ID;\n const clientSecret = process.env.ATLASSIAN_CLIENT_SECRET;\n\n if (!clientId || !clientSecret) {\n throw new Error('Bitbucket OAuth credentials are not configured');\n }\n\n try {\n const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString(\n 'base64'\n );\n\n const response = await fetch(`${BITBUCKET_AUTH_URL}/access_token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Authorization: `Basic ${credentials}`,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n }),\n });\n\n if (!response.ok) {\n throw new Error(\n `Bitbucket token exchange failed: ${response.statusText}`\n );\n }\n\n const data = await response.json();\n\n if (data.error) {\n throw new Error(`Bitbucket token error: ${data.error_description}`);\n }\n\n return data.access_token;\n } catch (error) {\n logger.error('Error exchanging Bitbucket code for token:', error);\n throw error;\n }\n};\n\n/**\n * Get user's Bitbucket repositories\n */\nexport const getUserRepositories = async (\n accessToken: string\n): Promise<BitbucketRepository[]> => {\n try {\n // First, get the current user to find their workspaces\n const userResponse = await fetch(`${BITBUCKET_API_URL}/user`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n });\n\n if (!userResponse.ok) {\n throw new Error(\n `Failed to fetch Bitbucket user: ${userResponse.statusText}`\n );\n }\n\n // Get repositories the user has access to\n const reposResponse = await fetch(\n `${BITBUCKET_API_URL}/repositories?role=member&sort=-updated_on&pagelen=100`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!reposResponse.ok) {\n throw new Error(\n `Failed to fetch Bitbucket repositories: ${reposResponse.statusText}`\n );\n }\n\n const data = await reposResponse.json();\n return data.values || [];\n } catch (error) {\n logger.error('Error fetching Bitbucket repositories:', error);\n throw error;\n }\n};\n\n/**\n * Check if valid intlayer configuration files exist in a Bitbucket repository (Recursively).\n * Returns an array of file paths found.\n */\nexport const checkIntlayerConfig = async (\n accessToken: string,\n workspace: string,\n repoSlug: string,\n branch: string = 'main'\n): Promise<string[]> => {\n try {\n // Use Bitbucket's src API to list files recursively\n const response = await fetch(\n `${BITBUCKET_API_URL}/repositories/${workspace}/${repoSlug}/src/${encodeURIComponent(branch)}/?max_depth=10&pagelen=10000`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return [];\n throw new Error(\n `Failed to fetch repository tree: ${response.statusText}`\n );\n }\n\n const data = await response.json();\n const items: BitbucketTreeItem[] = data.values || [];\n\n // Filter files that match the configuration candidates\n const foundFiles = items\n .filter((item) => {\n if (item.type !== 'commit_file') return false;\n return (configurationFilesCandidates as readonly string[]).some(\n (candidate) => item.path.endsWith(candidate)\n );\n })\n .map((item) => item.path);\n\n return foundFiles;\n } catch (error: any) {\n if (error.status === 404) return [];\n logger.error('Error checking intlayer configuration on Bitbucket:', error);\n return [];\n }\n};\n\n/**\n * Get repository file contents from Bitbucket and decode it\n */\nexport const getRepositoryFileContents = async (\n accessToken: string,\n workspace: string,\n repoSlug: string,\n path: string,\n branch: string = 'main'\n): Promise<string | null> => {\n try {\n const response = await fetch(\n `${BITBUCKET_API_URL}/repositories/${workspace}/${repoSlug}/src/${encodeURIComponent(branch)}/${encodeURIComponent(path)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return null;\n throw new Error(`Failed to fetch file contents: ${response.statusText}`);\n }\n\n const content = await response.text();\n return content;\n } catch (error: any) {\n if (error.status === 404) return null;\n logger.error('Error fetching Bitbucket file contents:', error);\n throw error;\n }\n};\n\n/**\n * Get Bitbucket access token from user's linked account (Atlassian)\n */\nexport const getBitbucketTokenFromUser = async (\n userId: string\n): Promise<string | null> => {\n try {\n // Try with 'atlassian' provider ID (as it's linked through Better Auth's atlassian provider)\n const account = await AccountModel.findOne({\n userId,\n providerId: 'atlassian',\n });\n\n if (!account) {\n return null;\n }\n\n const accessToken = account.accessToken || account.access_token;\n\n return accessToken || null;\n } catch (error) {\n logger.error('Error retrieving Bitbucket token from DB:', error);\n return null;\n }\n};\n\n/**\n * Check if a Bitbucket pipeline file exists\n */\nexport const checkPipelineFileExists = async (\n accessToken: string,\n workspace: string,\n repoSlug: string,\n filename: string = 'bitbucket-pipelines.yml',\n branch: string = 'main'\n): Promise<boolean> => {\n try {\n const response = await fetch(\n `${BITBUCKET_API_URL}/repositories/${workspace}/${repoSlug}/src/${encodeURIComponent(branch)}/${encodeURIComponent(filename)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new Error(`Failed to check file existence: ${response.statusText}`);\n }\n\n return true;\n } catch (error: any) {\n if (error.status === 404) return false;\n logger.error('Error checking pipeline file existence:', error);\n throw error;\n }\n};\n\n/**\n * Create or update a Bitbucket pipeline file\n * Note: Bitbucket API doesn't support direct file creation via API v2.0\n * This function returns false for allowAutoPush, requiring manual installation\n */\nexport const createPipelineFile = async (\n _accessToken: string,\n _workspace: string,\n _repoSlug: string,\n _filename: string = 'bitbucket-pipelines.yml',\n _content: string,\n _branch: string = 'main',\n _message: string = 'Add Intlayer CI pipeline'\n): Promise<void> => {\n // Bitbucket API v2.0 doesn't support direct file creation/update\n // Users need to manually add the file or use the web interface\n // We'll throw an error indicating manual installation is required\n throw new Error(\n 'Bitbucket API does not support automatic file creation. Please manually add the pipeline file to your repository.'\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"bitbucket.service.mjs","names":[],"sources":["../../../src/services/bitbucket.service.ts"],"sourcesContent":["import { configurationFilesCandidates } from '@intlayer/config/node';\nimport { logger } from '@logger';\nimport { AccountModel } from '@schemas/account.schema';\n\nconst BITBUCKET_API_URL = 'https://api.bitbucket.org/2.0';\nconst BITBUCKET_AUTH_URL = 'https://bitbucket.org/site/oauth2';\n\nexport type BitbucketRepository = {\n uuid: string;\n name: string;\n full_name: string;\n slug: string;\n mainbranch?: {\n name: string;\n type: string;\n };\n links: {\n html: {\n href: string;\n };\n };\n workspace: {\n slug: string;\n name: string;\n uuid: string;\n };\n owner: {\n display_name: string;\n username?: string;\n uuid: string;\n };\n updated_on: string;\n is_private: boolean;\n};\n\nexport type BitbucketTreeItem = {\n path: string;\n type: 'commit_file' | 'commit_directory';\n size?: number;\n};\n\n/**\n * Get Bitbucket (Atlassian) authorization URL for OAuth flow\n */\nexport const getAuthorizationUrl = (_redirectUri: string): string => {\n const clientId = process.env.ATLASSIAN_CLIENT_ID;\n\n if (!clientId) {\n throw new Error('Bitbucket/Atlassian Client ID is not configured');\n }\n\n const params = new URLSearchParams({\n client_id: clientId,\n response_type: 'code',\n state: 'bitbucket_oauth',\n });\n\n return `${BITBUCKET_AUTH_URL}/authorize?${params.toString()}`;\n};\n\n/**\n * Exchange Bitbucket authorization code for access token\n */\nexport const exchangeCodeForToken = async (code: string): Promise<string> => {\n const clientId = process.env.ATLASSIAN_CLIENT_ID;\n const clientSecret = process.env.ATLASSIAN_CLIENT_SECRET;\n\n if (!clientId || !clientSecret) {\n throw new Error('Bitbucket OAuth credentials are not configured');\n }\n\n try {\n const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString(\n 'base64'\n );\n\n const response = await fetch(`${BITBUCKET_AUTH_URL}/access_token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Authorization: `Basic ${credentials}`,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n }),\n });\n\n if (!response.ok) {\n throw new Error(\n `Bitbucket token exchange failed: ${response.statusText}`\n );\n }\n\n const data = await response.json();\n\n if (data.error) {\n throw new Error(`Bitbucket token error: ${data.error_description}`);\n }\n\n return data.access_token;\n } catch (error) {\n logger.error('Error exchanging Bitbucket code for token:', error);\n throw error;\n }\n};\n\n/**\n * Get user's Bitbucket repositories\n */\nexport const getUserRepositories = async (\n accessToken: string\n): Promise<BitbucketRepository[]> => {\n try {\n // First, get the current user to find their workspaces\n const userResponse = await fetch(`${BITBUCKET_API_URL}/user`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n });\n\n if (!userResponse.ok) {\n throw new Error(\n `Failed to fetch Bitbucket user: ${userResponse.statusText}`\n );\n }\n\n // Get repositories the user has access to\n const reposResponse = await fetch(\n `${BITBUCKET_API_URL}/repositories?role=member&sort=-updated_on&pagelen=100`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!reposResponse.ok) {\n throw new Error(\n `Failed to fetch Bitbucket repositories: ${reposResponse.statusText}`\n );\n }\n\n const data = await reposResponse.json();\n return data.values || [];\n } catch (error) {\n logger.error('Error fetching Bitbucket repositories:', error);\n throw error;\n }\n};\n\n/**\n * Check if valid intlayer configuration files exist in a Bitbucket repository (Recursively).\n * Returns an array of file paths found.\n */\nexport const checkIntlayerConfig = async (\n accessToken: string,\n workspace: string,\n repoSlug: string,\n branch: string = 'main'\n): Promise<string[]> => {\n try {\n // Use Bitbucket's src API to list files recursively\n const response = await fetch(\n `${BITBUCKET_API_URL}/repositories/${workspace}/${repoSlug}/src/${encodeURIComponent(branch)}/?max_depth=10&pagelen=10000`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return [];\n throw new Error(\n `Failed to fetch repository tree: ${response.statusText}`\n );\n }\n\n const data = await response.json();\n const items: BitbucketTreeItem[] = data.values || [];\n\n // Filter files that match the configuration candidates\n const foundFiles = items\n .filter((item) => {\n if (item.type !== 'commit_file') return false;\n return (configurationFilesCandidates as readonly string[]).some(\n (candidate) => item.path.endsWith(candidate)\n );\n })\n .map((item) => item.path);\n\n return foundFiles;\n } catch (error: any) {\n if (error.status === 404) return [];\n logger.error('Error checking intlayer configuration on Bitbucket:', error);\n return [];\n }\n};\n\n/**\n * Get repository file contents from Bitbucket and decode it\n */\nexport const getRepositoryFileContents = async (\n accessToken: string,\n workspace: string,\n repoSlug: string,\n path: string,\n branch: string = 'main'\n): Promise<string | null> => {\n try {\n const response = await fetch(\n `${BITBUCKET_API_URL}/repositories/${workspace}/${repoSlug}/src/${encodeURIComponent(branch)}/${encodeURIComponent(path)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (!response.ok) {\n if (response.status === 404) return null;\n throw new Error(`Failed to fetch file contents: ${response.statusText}`);\n }\n\n const content = await response.text();\n return content;\n } catch (error: any) {\n if (error.status === 404) return null;\n logger.error('Error fetching Bitbucket file contents:', error);\n throw error;\n }\n};\n\n/**\n * Get Bitbucket access token from user's linked account (Atlassian)\n */\nexport const getBitbucketTokenFromUser = async (\n userId: string\n): Promise<string | null> => {\n try {\n // Try with 'atlassian' provider ID (as it's linked through Better Auth's atlassian provider)\n const account = await AccountModel.findOne({\n userId,\n providerId: 'atlassian',\n });\n\n if (!account) {\n return null;\n }\n\n const accessToken = account.accessToken || account.access_token;\n\n return accessToken || null;\n } catch (error) {\n logger.error('Error retrieving Bitbucket token from DB:', error);\n return null;\n }\n};\n\n/**\n * Check if a Bitbucket pipeline file exists\n */\nexport const checkPipelineFileExists = async (\n accessToken: string,\n workspace: string,\n repoSlug: string,\n filename: string = 'bitbucket-pipelines.yml',\n branch: string = 'main'\n): Promise<boolean> => {\n try {\n const response = await fetch(\n `${BITBUCKET_API_URL}/repositories/${workspace}/${repoSlug}/src/${encodeURIComponent(branch)}/${encodeURIComponent(filename)}`,\n {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n Accept: 'application/json',\n },\n }\n );\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new Error(`Failed to check file existence: ${response.statusText}`);\n }\n\n return true;\n } catch (error: any) {\n if (error.status === 404) return false;\n logger.error('Error checking pipeline file existence:', error);\n throw error;\n }\n};\n\n/**\n * Create or update a Bitbucket pipeline file\n * Note: Bitbucket API doesn't support direct file creation via API v2.0\n * This function returns false for allowAutoPush, requiring manual installation\n */\nexport const createPipelineFile = async (\n _accessToken: string,\n _workspace: string,\n _repoSlug: string,\n _filename: string = 'bitbucket-pipelines.yml',\n _content: string,\n _branch: string = 'main',\n _message: string = 'Add Intlayer CI pipeline'\n): Promise<void> => {\n // Bitbucket API v2.0 doesn't support direct file creation/update\n // Users need to manually add the file or use the web interface\n // We'll throw an error indicating manual installation is required\n throw new Error(\n 'Bitbucket API does not support automatic file creation. Please manually add the pipeline file to your repository.'\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ci.service.mjs","names":["githubService.getGitHubTokenFromUser","gitlabService.getGitLabTokenFromUser","bitbucketService.getBitbucketTokenFromUser","githubService.checkWorkflowFileExists","gitlabService.checkPipelineFileExists","bitbucketService.checkPipelineFileExists","githubService.createWorkflowFile","gitlabService.createPipelineFile"],"sources":["../../../src/services/ci.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport type { Types } from 'mongoose';\nimport type { Project } from '@/types/project.types';\nimport * as bitbucketService from './bitbucket.service';\nimport * as githubService from './github.service';\nimport * as gitlabService from './gitlab.service';\n\nexport const GITHUB_WORKFLOW_FILENAME = '.github/workflows/intlayer-cms.yml';\nexport const GITLAB_PIPELINE_FILENAME = '.gitlab-ci.yml';\nexport const BITBUCKET_PIPELINE_FILENAME = 'bitbucket-pipelines.yml';\n\nexport const GITHUB_TEMPLATE = `name: Intlayer CMS Update\non:\n repository_dispatch:\n types: [intlayer_cms_update]\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci\n - run: npm run build\n`;\n\nexport const GITLAB_TEMPLATE = `stages:\n - build\n\nintlayer_cms_update:\n stage: build\n only:\n - triggers\n script:\n - npm ci\n - npm run build\n`;\n\nexport const BITBUCKET_TEMPLATE = `pipelines:\n custom:\n intlayer-cms-update:\n - step:\n name: Build\n script:\n - npm ci\n - npm run build\n`;\n\nexport type CIStatus = {\n exists: boolean;\n content: string;\n path: string;\n fileUrl?: string;\n allowAutoPush: boolean;\n};\n\n/**\n * Get the best available token for CI operations on a repository.\n * Prefers the repo-scoped token stored on the repository connection,\n * falls back to the social login token from the account collection.\n */\nconst getProviderToken = async (\n repository: NonNullable<Project['repository']>,\n userId: string | Types.ObjectId\n): Promise<string | null> => {\n // 1. Prefer the repo-scoped token stored on the repository (has write access)\n if (repository.token) {\n return repository.token;\n }\n\n // 2. Fall back to the social login token from Better Auth account collection\n const userIdStr = String(userId);\n switch (repository.provider) {\n case 'github':\n return githubService.getGitHubTokenFromUser(userIdStr);\n case 'gitlab':\n return gitlabService.getGitLabTokenFromUser(userIdStr);\n case 'bitbucket':\n return bitbucketService.getBitbucketTokenFromUser(userIdStr);\n default:\n return null;\n }\n};\n\n/**\n * Get CI configuration status for a project\n */\nexport const getCIStatus = async (\n project: Project,\n userId: string | Types.ObjectId\n): Promise<CIStatus> => {\n const { repository } = project;\n\n if (!repository) {\n throw new Error('Project is not connected to a repository.');\n }\n\n const accessToken = await getProviderToken(repository, userId);\n\n if (!accessToken) {\n throw new Error(\n `No valid ${repository.provider} OAuth token found for the current user. Please reconnect your ${repository.provider} account.`\n );\n }\n\n const branch = repository.branch || 'main';\n const { provider } = repository;\n\n try {\n switch (provider) {\n case 'github': {\n const { owner, repository: repoName } = repository;\n const filename = GITHUB_WORKFLOW_FILENAME;\n const exists = await githubService.checkWorkflowFileExists(\n accessToken,\n owner,\n repoName,\n filename,\n branch\n );\n\n const content = GITHUB_TEMPLATE;\n const fileUrl = `https://github.com/${owner}/${repoName}/blob/${branch}/${filename}`;\n\n return {\n exists,\n content,\n path: filename,\n fileUrl,\n allowAutoPush: true, // GitHub allows file creation via API\n };\n }\n\n case 'gitlab': {\n const { projectId, instanceUrl } = repository;\n if (!projectId) {\n throw new Error('GitLab project ID is required.');\n }\n\n const filename = GITLAB_PIPELINE_FILENAME;\n const exists = await gitlabService.checkPipelineFileExists(\n accessToken,\n projectId,\n filename,\n branch,\n instanceUrl\n );\n\n const content = GITLAB_TEMPLATE;\n const baseUrl = instanceUrl || 'https://gitlab.com';\n const fileUrl = `${baseUrl}/${repository.owner}/${repository.repository}/-/blob/${branch}/${filename}`;\n\n return {\n exists,\n content,\n path: filename,\n fileUrl,\n allowAutoPush: true, // GitLab allows file creation via API\n };\n }\n\n case 'bitbucket': {\n const { workspace, repository: repoSlug } = repository;\n const filename = BITBUCKET_PIPELINE_FILENAME;\n const exists = await bitbucketService.checkPipelineFileExists(\n accessToken,\n workspace,\n repoSlug,\n filename,\n branch\n );\n\n const content = BITBUCKET_TEMPLATE;\n const fileUrl = `https://bitbucket.org/${workspace}/${repoSlug}/src/${branch}/${filename}`;\n\n return {\n exists,\n content,\n path: filename,\n fileUrl,\n allowAutoPush: false, // Bitbucket API doesn't support automatic file creation\n };\n }\n\n default:\n throw new Error(`Unsupported repository provider: ${provider}`);\n }\n } catch (error) {\n logger.error('Error getting CI status:', error);\n throw error;\n }\n};\n\n/**\n * Install CI configuration file in the repository\n */\nexport const installCI = async (\n project: Project,\n userId: string | Types.ObjectId\n): Promise<void> => {\n const { repository } = project;\n\n if (!repository) {\n throw new Error('Project is not connected to a repository.');\n }\n\n const accessToken = await getProviderToken(repository, userId);\n\n if (!accessToken) {\n throw new Error(\n `No valid ${repository.provider} OAuth token found for the current user. Please reconnect your ${repository.provider} account.`\n );\n }\n\n const branch = repository.branch || 'main';\n const { provider: installProvider } = repository;\n\n try {\n switch (installProvider) {\n case 'github': {\n const { owner, repository: repoName } = repository;\n await githubService.createWorkflowFile(\n accessToken,\n owner,\n repoName,\n GITHUB_WORKFLOW_FILENAME,\n GITHUB_TEMPLATE,\n branch,\n 'Add Intlayer CMS workflow'\n );\n break;\n }\n\n case 'gitlab': {\n const { projectId, instanceUrl } = repository;\n if (!projectId) {\n throw new Error('GitLab project ID is required.');\n }\n\n await gitlabService.createPipelineFile(\n accessToken,\n projectId,\n GITLAB_PIPELINE_FILENAME,\n GITLAB_TEMPLATE,\n branch,\n instanceUrl,\n 'Add Intlayer CMS pipeline'\n );\n break;\n }\n\n case 'bitbucket': {\n // Bitbucket API doesn't support automatic file creation\n // This should not be called if allowAutoPush is false\n throw new Error(\n 'Bitbucket does not support automatic CI file installation. Please add the file manually.'\n );\n }\n\n default:\n throw new Error(`Unsupported repository provider: ${installProvider}`);\n }\n\n logger.info(\n `Successfully installed CI configuration for project ${project.id}`\n );\n } catch (error) {\n logger.error('Error installing CI configuration:', error);\n throw error;\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ci.service.mjs","names":["githubService.getGitHubTokenFromUser","gitlabService.getGitLabTokenFromUser","bitbucketService.getBitbucketTokenFromUser","githubService.checkWorkflowFileExists","gitlabService.checkPipelineFileExists","bitbucketService.checkPipelineFileExists","githubService.createWorkflowFile","gitlabService.createPipelineFile"],"sources":["../../../src/services/ci.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport type { Types } from 'mongoose';\nimport type { Project } from '@/types/project.types';\nimport * as bitbucketService from './bitbucket.service';\nimport * as githubService from './github.service';\nimport * as gitlabService from './gitlab.service';\n\nexport const GITHUB_WORKFLOW_FILENAME = '.github/workflows/intlayer-cms.yml';\nexport const GITLAB_PIPELINE_FILENAME = '.gitlab-ci.yml';\nexport const BITBUCKET_PIPELINE_FILENAME = 'bitbucket-pipelines.yml';\n\nexport const GITHUB_TEMPLATE = `name: Intlayer CMS Update\non:\n repository_dispatch:\n types: [intlayer_cms_update]\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci\n - run: npm run build\n`;\n\nexport const GITLAB_TEMPLATE = `stages:\n - build\n\nintlayer_cms_update:\n stage: build\n only:\n - triggers\n script:\n - npm ci\n - npm run build\n`;\n\nexport const BITBUCKET_TEMPLATE = `pipelines:\n custom:\n intlayer-cms-update:\n - step:\n name: Build\n script:\n - npm ci\n - npm run build\n`;\n\nexport type CIStatus = {\n exists: boolean;\n content: string;\n path: string;\n fileUrl?: string;\n allowAutoPush: boolean;\n};\n\n/**\n * Get the best available token for CI operations on a repository.\n * Prefers the repo-scoped token stored on the repository connection,\n * falls back to the social login token from the account collection.\n */\nconst getProviderToken = async (\n repository: NonNullable<Project['repository']>,\n userId: string | Types.ObjectId\n): Promise<string | null> => {\n // 1. Prefer the repo-scoped token stored on the repository (has write access)\n if (repository.token) {\n return repository.token;\n }\n\n // 2. Fall back to the social login token from Better Auth account collection\n const userIdStr = String(userId);\n switch (repository.provider) {\n case 'github':\n return githubService.getGitHubTokenFromUser(userIdStr);\n case 'gitlab':\n return gitlabService.getGitLabTokenFromUser(userIdStr);\n case 'bitbucket':\n return bitbucketService.getBitbucketTokenFromUser(userIdStr);\n default:\n return null;\n }\n};\n\n/**\n * Get CI configuration status for a project\n */\nexport const getCIStatus = async (\n project: Project,\n userId: string | Types.ObjectId\n): Promise<CIStatus> => {\n const { repository } = project;\n\n if (!repository) {\n throw new Error('Project is not connected to a repository.');\n }\n\n const accessToken = await getProviderToken(repository, userId);\n\n if (!accessToken) {\n throw new Error(\n `No valid ${repository.provider} OAuth token found for the current user. Please reconnect your ${repository.provider} account.`\n );\n }\n\n const branch = repository.branch || 'main';\n const { provider } = repository;\n\n try {\n switch (provider) {\n case 'github': {\n const { owner, repository: repoName } = repository;\n const filename = GITHUB_WORKFLOW_FILENAME;\n const exists = await githubService.checkWorkflowFileExists(\n accessToken,\n owner,\n repoName,\n filename,\n branch\n );\n\n const content = GITHUB_TEMPLATE;\n const fileUrl = `https://github.com/${owner}/${repoName}/blob/${branch}/${filename}`;\n\n return {\n exists,\n content,\n path: filename,\n fileUrl,\n allowAutoPush: true, // GitHub allows file creation via API\n };\n }\n\n case 'gitlab': {\n const { projectId, instanceUrl } = repository;\n if (!projectId) {\n throw new Error('GitLab project ID is required.');\n }\n\n const filename = GITLAB_PIPELINE_FILENAME;\n const exists = await gitlabService.checkPipelineFileExists(\n accessToken,\n projectId,\n filename,\n branch,\n instanceUrl\n );\n\n const content = GITLAB_TEMPLATE;\n const baseUrl = instanceUrl || 'https://gitlab.com';\n const fileUrl = `${baseUrl}/${repository.owner}/${repository.repository}/-/blob/${branch}/${filename}`;\n\n return {\n exists,\n content,\n path: filename,\n fileUrl,\n allowAutoPush: true, // GitLab allows file creation via API\n };\n }\n\n case 'bitbucket': {\n const { workspace, repository: repoSlug } = repository;\n const filename = BITBUCKET_PIPELINE_FILENAME;\n const exists = await bitbucketService.checkPipelineFileExists(\n accessToken,\n workspace,\n repoSlug,\n filename,\n branch\n );\n\n const content = BITBUCKET_TEMPLATE;\n const fileUrl = `https://bitbucket.org/${workspace}/${repoSlug}/src/${branch}/${filename}`;\n\n return {\n exists,\n content,\n path: filename,\n fileUrl,\n allowAutoPush: false, // Bitbucket API doesn't support automatic file creation\n };\n }\n\n default:\n throw new Error(`Unsupported repository provider: ${provider}`);\n }\n } catch (error) {\n logger.error('Error getting CI status:', error);\n throw error;\n }\n};\n\n/**\n * Install CI configuration file in the repository\n */\nexport const installCI = async (\n project: Project,\n userId: string | Types.ObjectId\n): Promise<void> => {\n const { repository } = project;\n\n if (!repository) {\n throw new Error('Project is not connected to a repository.');\n }\n\n const accessToken = await getProviderToken(repository, userId);\n\n if (!accessToken) {\n throw new Error(\n `No valid ${repository.provider} OAuth token found for the current user. Please reconnect your ${repository.provider} account.`\n );\n }\n\n const branch = repository.branch || 'main';\n const { provider: installProvider } = repository;\n\n try {\n switch (installProvider) {\n case 'github': {\n const { owner, repository: repoName } = repository;\n await githubService.createWorkflowFile(\n accessToken,\n owner,\n repoName,\n GITHUB_WORKFLOW_FILENAME,\n GITHUB_TEMPLATE,\n branch,\n 'Add Intlayer CMS workflow'\n );\n break;\n }\n\n case 'gitlab': {\n const { projectId, instanceUrl } = repository;\n if (!projectId) {\n throw new Error('GitLab project ID is required.');\n }\n\n await gitlabService.createPipelineFile(\n accessToken,\n projectId,\n GITLAB_PIPELINE_FILENAME,\n GITLAB_TEMPLATE,\n branch,\n instanceUrl,\n 'Add Intlayer CMS pipeline'\n );\n break;\n }\n\n case 'bitbucket': {\n // Bitbucket API doesn't support automatic file creation\n // This should not be called if allowAutoPush is false\n throw new Error(\n 'Bitbucket does not support automatic CI file installation. Please add the file manually.'\n );\n }\n\n default:\n throw new Error(`Unsupported repository provider: ${installProvider}`);\n }\n\n logger.info(\n `Successfully installed CI configuration for project ${project.id}`\n );\n } catch (error) {\n logger.error('Error installing CI configuration:', error);\n throw error;\n }\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cliSessionToken.service.mjs","names":[],"sources":["../../../src/services/cliSessionToken.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { CliSessionTokenModel } from '@schemas/cliSessionToken.schema';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport type { Types } from 'mongoose';\nimport type { SessionContext } from '@/types/session.types';\n\nexport const CLI_SESSION_TOKEN_PREFIX = 'clisession_';\nconst CLI_SESSION_EXPIRES_MS = 2 * 60 * 60 * 1000; // 2 hours\n\nexport const isCliSessionToken = (token: string): boolean =>\n token.startsWith(CLI_SESSION_TOKEN_PREFIX);\n\nexport const createCliSessionToken = async (\n userId: string | Types.ObjectId,\n organizationId: string,\n projectId: string\n): Promise<{ token: string; expiresAt: Date }> => {\n const token = CLI_SESSION_TOKEN_PREFIX + randomBytes(32).toString('hex');\n const expiresAt = new Date(Date.now() + CLI_SESSION_EXPIRES_MS);\n\n await CliSessionTokenModel.create({\n token,\n userId,\n organizationId,\n projectId,\n expiresAt,\n });\n\n return { token, expiresAt };\n};\n\nexport const getCliSessionTokenContext = async (\n token: string\n): Promise<SessionContext> => {\n if (!isCliSessionToken(token)) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const stored = await CliSessionTokenModel.findOne({ token: String(token) });\n\n if (!stored) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n if (new Date() > stored.expiresAt) {\n await CliSessionTokenModel.deleteOne({ token: String(token) });\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n const [user, project, organization] = await Promise.all([\n getUserById(String(stored.userId)),\n getProjectById(stored.projectId),\n getOrganizationById(stored.organizationId),\n ]);\n\n if (!user || !project || !organization) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n return {\n user: mapUserToAPI(user),\n project: mapProjectToAPI(project),\n organization: mapOrganizationToAPI(organization),\n authType: 'session',\n };\n};\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,2BAA2B;AACxC,MAAM,yBAAyB,OAAc;AAE7C,MAAa,qBAAqB,UAChC,MAAM,WAAW,wBAAwB;AAE3C,MAAa,wBAAwB,OACnC,QACA,gBACA,cACgD;CAChD,MAAM,QAAQ,2BAA2B,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;CACvE,MAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,sBAAsB;CAE9D,MAAM,qBAAqB,OAAO;EAChC;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,OAAO;EAAE;EAAO;CAAU;AAC5B;AAEA,MAAa,4BAA4B,OACvC,UAC4B;CAC5B,IAAI,CAAC,kBAAkB,KAAK,GAC1B,MAAM,IAAI,aAAa,sBAAsB;CAG/C,MAAM,SAAS,MAAM,qBAAqB,QAAQ,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;CAE1E,IAAI,CAAC,QACH,MAAM,IAAI,aAAa,sBAAsB;CAG/C,oBAAI,IAAI,KAAK,IAAI,OAAO,WAAW;EACjC,MAAM,qBAAqB,UAAU,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;EAC7D,MAAM,IAAI,aAAa,sBAAsB;CAC/C;CAEA,MAAM,CAAC,MAAM,SAAS,gBAAgB,MAAM,QAAQ,IAAI;EACtD,YAAY,OAAO,OAAO,MAAM,CAAC;EACjC,eAAe,OAAO,SAAS;EAC/B,oBAAoB,OAAO,cAAc;CAC3C,CAAC;CAED,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cACxB,MAAM,IAAI,aAAa,sBAAsB;CAG/C,OAAO;EACL,MAAM,aAAa,IAAI;EACvB,SAAS,gBAAgB,OAAO;EAChC,cAAc,qBAAqB,YAAY;EAC/C,UAAU;CACZ;AACF"}
1
+ {"version":3,"file":"cliSessionToken.service.mjs","names":[],"sources":["../../../src/services/cliSessionToken.service.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { CliSessionTokenModel } from '@schemas/cliSessionToken.schema';\nimport { getOrganizationById } from '@services/organization.service';\nimport { getProjectById } from '@services/project.service';\nimport { getUserById } from '@services/user.service';\nimport { GenericError } from '@utils/errors';\nimport { mapOrganizationToAPI } from '@utils/mapper/organization';\nimport { mapProjectToAPI } from '@utils/mapper/project';\nimport { mapUserToAPI } from '@utils/mapper/user';\nimport type { Types } from 'mongoose';\nimport type { SessionContext } from '@/types/session.types';\n\nexport const CLI_SESSION_TOKEN_PREFIX = 'clisession_';\nconst CLI_SESSION_EXPIRES_MS = 2 * 60 * 60 * 1000; // 2 hours\n\nexport const isCliSessionToken = (token: string): boolean =>\n token.startsWith(CLI_SESSION_TOKEN_PREFIX);\n\nexport const createCliSessionToken = async (\n userId: string | Types.ObjectId,\n organizationId: string,\n projectId: string\n): Promise<{ token: string; expiresAt: Date }> => {\n const token = CLI_SESSION_TOKEN_PREFIX + randomBytes(32).toString('hex');\n const expiresAt = new Date(Date.now() + CLI_SESSION_EXPIRES_MS);\n\n await CliSessionTokenModel.create({\n token,\n userId,\n organizationId,\n projectId,\n expiresAt,\n });\n\n return { token, expiresAt };\n};\n\nexport const getCliSessionTokenContext = async (\n token: string\n): Promise<SessionContext> => {\n if (!isCliSessionToken(token)) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n const stored = await CliSessionTokenModel.findOne({ token: String(token) });\n\n if (!stored) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n if (new Date() > stored.expiresAt) {\n await CliSessionTokenModel.deleteOne({ token: String(token) });\n throw new GenericError('EXPIRED_ACCESS_TOKEN');\n }\n\n const [user, project, organization] = await Promise.all([\n getUserById(String(stored.userId)),\n getProjectById(stored.projectId),\n getOrganizationById(stored.organizationId),\n ]);\n\n if (!user || !project || !organization) {\n throw new GenericError('INVALID_ACCESS_TOKEN');\n }\n\n return {\n user: mapUserToAPI(user),\n project: mapProjectToAPI(project),\n organization: mapOrganizationToAPI(organization),\n authType: 'session',\n };\n};\n"],"mappings":";;;;;;;;;;;AAYA,MAAa,2BAA2B;AACxC,MAAM,yBAAyB,OAAc;AAE7C,MAAa,qBAAqB,UAChC,MAAM,WAAW,yBAAyB;AAE5C,MAAa,wBAAwB,OACnC,QACA,gBACA,cACgD;CAChD,MAAM,QAAQ,2BAA2B,YAAY,GAAG,CAAC,SAAS,MAAM;CACxE,MAAM,YAAY,IAAI,KAAK,KAAK,KAAK,GAAG,uBAAuB;AAE/D,OAAM,qBAAqB,OAAO;EAChC;EACA;EACA;EACA;EACA;EACD,CAAC;AAEF,QAAO;EAAE;EAAO;EAAW;;AAG7B,MAAa,4BAA4B,OACvC,UAC4B;AAC5B,KAAI,CAAC,kBAAkB,MAAM,CAC3B,OAAM,IAAI,aAAa,uBAAuB;CAGhD,MAAM,SAAS,MAAM,qBAAqB,QAAQ,EAAE,OAAO,OAAO,MAAM,EAAE,CAAC;AAE3E,KAAI,CAAC,OACH,OAAM,IAAI,aAAa,uBAAuB;AAGhD,qBAAI,IAAI,MAAM,GAAG,OAAO,WAAW;AACjC,QAAM,qBAAqB,UAAU,EAAE,OAAO,OAAO,MAAM,EAAE,CAAC;AAC9D,QAAM,IAAI,aAAa,uBAAuB;;CAGhD,MAAM,CAAC,MAAM,SAAS,gBAAgB,MAAM,QAAQ,IAAI;EACtD,YAAY,OAAO,OAAO,OAAO,CAAC;EAClC,eAAe,OAAO,UAAU;EAChC,oBAAoB,OAAO,eAAe;EAC3C,CAAC;AAEF,KAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,aACxB,OAAM,IAAI,aAAa,uBAAuB;AAGhD,QAAO;EACL,MAAM,aAAa,KAAK;EACxB,SAAS,gBAAgB,QAAQ;EACjC,cAAc,qBAAqB,aAAa;EAChD,UAAU;EACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"dictionary.service.mjs","names":[],"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { DictionaryModel } from '@schemas/dictionary.schema';\nimport { getDemoDictionaries } from '@utils/demoDictionaries';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport { Types } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @param sortOptions - Sorting options.\n * @param includeContent - Whether to include the dictionary content.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>,\n includeContent = true\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Sort if provided (default handled in filter builder)\n ...(sortOptions && Object.keys(sortOptions).length > 0\n ? [{ $sort: sortOptions }]\n : []),\n\n // Stage 3: Skip for pagination\n { $skip: skip },\n\n // Stage 4: Limit the number of documents\n { $limit: limit },\n\n // Stage 5: Project to include/exclude content\n ...(!includeContent ? [{ $project: { content: 0 } }] : []),\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n logger.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'versions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const id = Types.ObjectId.isValid(dictionaryId as string)\n ? new Types.ObjectId(dictionaryId as string)\n : dictionaryId;\n\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: id } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | Types.ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as unknown as Partial<Dictionary>;\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const existing = await DictionaryModel.findOne({\n key: String(dictionaryKey),\n projectIds: projectId,\n });\n\n if (!existing) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as unknown as Partial<Dictionary>;\n\n // Optional: run your validateDictionary on dictionaryToUpdate here\n\n // Apply updated fields onto the existing doc\n Object.assign(existing, dictionaryToUpdate);\n\n // Mongoose cannot track deep Map mutations done via Object.assign, so we\n // must explicitly mark 'content' as modified, otherwise the new versioned\n // content is silently dropped and the document is saved unchanged.\n existing.markModified('content');\n\n // Save – this will trigger timestamps on parent + subdocs\n await existing.save();\n\n return existing;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\n// Function to extract the numeric part of the version\nconst getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n\n const versions = [...(dictionary.content.keys() ?? [])];\n const lastVersion = versions[versions.length - 1];\n\n // Start with the next version number\n let newNumber = getVersionNumber(lastVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (versions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n\n/**\n * Creates demo dictionaries for a project.\n * @param projectIds - List of project IDs.\n * @param creatorId - The ID of the user creating the demo content.\n */\nexport const createDemoDictionaries = async (\n projectIds: string[],\n creatorId: Types.ObjectId | string\n): Promise<void> => {\n const demoDictionaries = getDemoDictionaries(projectIds, creatorId);\n\n for (const dictionary of demoDictionaries) {\n await createDictionary(dictionary);\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dictionary.service.mjs","names":[],"sources":["../../../src/services/dictionary.service.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { DictionaryModel } from '@schemas/dictionary.schema';\nimport { getDemoDictionaries } from '@utils/demoDictionaries';\nimport { ensureMongoDocumentToObject } from '@utils/ensureMongoDocumentToObject';\nimport { GenericError } from '@utils/errors';\nimport type { DictionaryFilters } from '@utils/filtersAndPagination/getDictionaryFiltersAndPagination';\nimport { removeObjectKeys } from '@utils/removeObjectKeys';\nimport {\n type DictionaryFields,\n validateDictionary,\n} from '@utils/validation/validateDictionary';\nimport { Types } from 'mongoose';\nimport type {\n Dictionary,\n DictionaryData,\n DictionaryDocument,\n} from '@/types/dictionary.types';\nimport type { Project } from '@/types/project.types';\n\n/**\n * Finds dictionaries based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @param sortOptions - Sorting options.\n * @param includeContent - Whether to include the dictionary content.\n * @returns List of dictionaries matching the filters.\n */\nexport const findDictionaries = async (\n filters: DictionaryFilters,\n skip = 0,\n limit = 100,\n sortOptions?: Record<string, 1 | -1>,\n includeContent = true\n): Promise<DictionaryDocument[]> => {\n try {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the filters\n { $match: filters },\n\n // Stage 2: Sort if provided (default handled in filter builder)\n ...(sortOptions && Object.keys(sortOptions).length > 0\n ? [{ $sort: sortOptions }]\n : []),\n\n // Stage 3: Skip for pagination\n { $skip: skip },\n\n // Stage 4: Limit the number of documents\n { $limit: limit },\n\n // Stage 5: Project to include/exclude content\n ...(!includeContent ? [{ $project: { content: 0 } }] : []),\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n } catch (error) {\n logger.error('Error fetching dictionaries:', error);\n throw error;\n }\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\n/**\n * Finds a dictionary by its ID and includes the 'versions' field.\n * @param dictionaryId - The ID of the dictionary to find.\n * @returns The dictionary matching the ID with available versions.\n */\nexport const getDictionaryById = async (\n dictionaryId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const id = Types.ObjectId.isValid(dictionaryId as string)\n ? new Types.ObjectId(dictionaryId as string)\n : dictionaryId;\n\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by ID\n { $match: { _id: id } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries.length) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return new DictionaryModel(dictionaries[0]);\n};\n\n/**\n * Finds a dictionary by its ID.\n * @param dictionaryKey - The ID of the dictionary to find.\n * @returns The dictionary matching the ID.\n */\nexport const getDictionaryByKey = async (\n dictionaryKey: string,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const dictionaries = await getDictionariesByKeys([dictionaryKey], projectId);\n\n return dictionaries[0];\n};\n\nexport const getDictionariesByKeys = async (\n dictionaryKeys: string[],\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by key\n { $match: { key: { $in: dictionaryKeys }, projectIds: projectId } },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n if (!dictionaries) {\n throw new GenericError('DICTIONARY_NOT_FOUND', {\n dictionaryKeys,\n projectId,\n });\n }\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\nexport const getDictionariesByTags = async (\n tags: string[],\n projectId: string | Project['id']\n): Promise<DictionaryDocument[]> => {\n const dictionaries = await DictionaryModel.aggregate<DictionaryDocument>([\n // Stage 1: Match the document by tags\n {\n $match: {\n tags: { $in: tags },\n projectIds: projectId,\n },\n },\n\n // Stage 2: Add the 'versions' field\n {\n $addFields: {\n versions: {\n $map: {\n input: { $objectToArray: '$content' },\n as: 'version',\n in: '$$version.k',\n },\n },\n },\n },\n ]);\n\n const formattedResults = dictionaries.map(\n (result) => new DictionaryModel(result)\n );\n\n return formattedResults;\n};\n\n/**\n * Counts the total number of dictionaries that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of dictionaries.\n */\nexport const countDictionaries = async (\n filters: DictionaryFilters\n): Promise<number> => {\n const result = await DictionaryModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('DICTIONARY_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new dictionary in the database.\n * @param dictionary - The dictionary data to create.\n * @returns The created dictionary.\n */\nexport const createDictionary = async (\n dictionary: DictionaryData\n): Promise<DictionaryDocument> => {\n const errors = await validateDictionary(dictionary);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n errors,\n });\n }\n\n return await DictionaryModel.create(dictionary);\n};\n\n/**\n * Updates an existing dictionary in the database by its ID.\n * @param dictionaryId - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryById = async (\n dictionaryId: string | Types.ObjectId,\n dictionary: Partial<Dictionary>\n): Promise<DictionaryDocument> => {\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as unknown as Partial<Dictionary>;\n\n const updatedKeys = Object.keys(dictionaryToUpdate) as DictionaryFields;\n const errors = await validateDictionary(dictionaryToUpdate, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('DICTIONARY_INVALID_FIELDS', {\n dictionaryId,\n errors,\n });\n }\n\n const result = await DictionaryModel.updateOne(\n { _id: dictionaryId },\n dictionaryToUpdate\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryId });\n }\n\n const updatedDictionary = await getDictionaryById(dictionaryId);\n\n return updatedDictionary;\n};\n\n/**\n * Updates an existing dictionary in the database by its key.\n * @param dictionaryKey - The ID of the dictionary to update.\n * @param dictionary - The updated dictionary data.\n * @returns The updated dictionary.\n */\nexport const updateDictionaryByKey = async (\n dictionaryKey: string,\n dictionary: Partial<Dictionary>,\n projectId: string | Types.ObjectId\n): Promise<DictionaryDocument> => {\n const existing = await DictionaryModel.findOne({\n key: String(dictionaryKey),\n projectIds: projectId,\n });\n\n if (!existing) {\n throw new GenericError('DICTIONARY_UPDATE_FAILED', { dictionaryKey });\n }\n\n const dictionaryObject = ensureMongoDocumentToObject(dictionary);\n const dictionaryToUpdate = removeObjectKeys(dictionaryObject, [\n 'id',\n ]) as unknown as Partial<Dictionary>;\n\n // Optional: run your validateDictionary on dictionaryToUpdate here\n\n // Apply updated fields onto the existing doc\n Object.assign(existing, dictionaryToUpdate);\n\n // Mongoose cannot track deep Map mutations done via Object.assign, so we\n // must explicitly mark 'content' as modified, otherwise the new versioned\n // content is silently dropped and the document is saved unchanged.\n existing.markModified('content');\n\n // Save – this will trigger timestamps on parent + subdocs\n await existing.save();\n\n return existing;\n};\n\n/**\n * Deletes a dictionary from the database by its ID.\n * @param dictionaryId - The ID of the dictionary to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteDictionaryById = async (\n dictionaryId: string\n): Promise<DictionaryDocument> => {\n const dictionary = await DictionaryModel.findByIdAndDelete(dictionaryId);\n\n if (!dictionary) {\n throw new GenericError('DICTIONARY_NOT_FOUND', { dictionaryId });\n }\n\n return dictionary;\n};\n\n// Function to extract the numeric part of the version\nconst getVersionNumber = (version: string): number => {\n const match = version.match(/^v(\\d+)$/);\n if (!match) {\n throw new Error(`Invalid version format: ${version}`);\n }\n return parseInt(match[1], 10);\n};\n\nexport const incrementVersion = (dictionary: Dictionary): string => {\n const VERSION_PREFIX = 'v';\n\n const versions = [...(dictionary.content.keys() ?? [])];\n const lastVersion = versions[versions.length - 1];\n\n // Start with the next version number\n let newNumber = getVersionNumber(lastVersion) + 1;\n let newVersion = `${VERSION_PREFIX}${newNumber}`;\n\n // Loop until a unique version is found\n while (versions.includes(newVersion)) {\n newNumber += 1;\n newVersion = `${VERSION_PREFIX}${newNumber}`;\n }\n\n return newVersion;\n};\n\n/**\n * Creates demo dictionaries for a project.\n * @param projectIds - List of project IDs.\n * @param creatorId - The ID of the user creating the demo content.\n */\nexport const createDemoDictionaries = async (\n projectIds: string[],\n creatorId: Types.ObjectId | string\n): Promise<void> => {\n const demoDictionaries = getDemoDictionaries(projectIds, creatorId);\n\n for (const dictionary of demoDictionaries) {\n await createDictionary(dictionary);\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,MAAa,mBAAmB,OAC9B,SACA,OAAO,GACP,QAAQ,KACR,aACA,iBAAiB,SACiB;AAClC,KAAI;AAwBF,UAJyB,MAnBE,gBAAgB,UAA8B;GAEvE,EAAE,QAAQ,SAAS;GAGnB,GAAI,eAAe,OAAO,KAAK,YAAY,CAAC,SAAS,IACjD,CAAC,EAAE,OAAO,aAAa,CAAC,GACxB,EAAE;GAGN,EAAE,OAAO,MAAM;GAGf,EAAE,QAAQ,OAAO;GAGjB,GAAI,CAAC,iBAAiB,CAAC,EAAE,UAAU,EAAE,SAAS,GAAG,EAAE,CAAC,GAAG,EAAE;GAC1D,CAAC,EAEoC,KACnC,WAAW,IAAI,gBAAgB,OAAO,CAGlB;UAChB,OAAO;AACd,SAAO,MAAM,gCAAgC,MAAM;AACnD,QAAM;;;;;;;;;;;;;AAcV,MAAa,oBAAoB,OAC/B,iBACgC;CAChC,MAAM,KAAK,MAAM,SAAS,QAAQ,aAAuB,GACrD,IAAI,MAAM,SAAS,aAAuB,GAC1C;CAEJ,MAAM,eAAe,MAAM,gBAAgB,UAA8B,CAEvE,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,EAGvB,EACE,YAAY,EACV,UAAU,EACR,MAAM;EACJ,OAAO,EAAE,gBAAgB,YAAY;EACrC,IAAI;EACJ,IAAI;EACL,EACF,EACF,EACF,CACF,CAAC;AAEF,KAAI,CAAC,aAAa,OAChB,OAAM,IAAI,aAAa,wBAAwB,EAAE,cAAc,CAAC;AAGlE,QAAO,IAAI,gBAAgB,aAAa,GAAG;;;;;;;AAQ7C,MAAa,qBAAqB,OAChC,eACA,cACgC;AAGhC,SAAO,MAFoB,sBAAsB,CAAC,cAAc,EAAE,UAAU,EAExD;;AAGtB,MAAa,wBAAwB,OACnC,gBACA,cACkC;CAClC,MAAM,eAAe,MAAM,gBAAgB,UAA8B,CAEvE,EAAE,QAAQ;EAAE,KAAK,EAAE,KAAK,gBAAgB;EAAE,YAAY;EAAW,EAAE,EAGnE,EACE,YAAY,EACV,UAAU,EACR,MAAM;EACJ,OAAO,EAAE,gBAAgB,YAAY;EACrC,IAAI;EACJ,IAAI;EACL,EACF,EACF,EACF,CACF,CAAC;AAEF,KAAI,CAAC,aACH,OAAM,IAAI,aAAa,wBAAwB;EAC7C;EACA;EACD,CAAC;AAOJ,QAJyB,aAAa,KACnC,WAAW,IAAI,gBAAgB,OAAO,CAGlB;;AAGzB,MAAa,wBAAwB,OACnC,MACA,cACkC;AA4BlC,SAJyB,MAvBE,gBAAgB,UAA8B,CAEvE,EACE,QAAQ;EACN,MAAM,EAAE,KAAK,MAAM;EACnB,YAAY;EACb,EACF,EAGD,EACE,YAAY,EACV,UAAU,EACR,MAAM;EACJ,OAAO,EAAE,gBAAgB,YAAY;EACrC,IAAI;EACJ,IAAI;EACL,EACF,EACF,EACF,CACF,CAAC,EAEoC,KACnC,WAAW,IAAI,gBAAgB,OAAO,CAGlB;;;;;;;AAQzB,MAAa,oBAAoB,OAC/B,YACoB;CACpB,MAAM,SAAS,MAAM,gBAAgB,eAAe,QAAQ;AAE5D,KAAI,OAAO,WAAW,YACpB,OAAM,IAAI,aAAa,2BAA2B,EAAE,SAAS,CAAC;AAGhE,QAAO;;;;;;;AAQT,MAAa,mBAAmB,OAC9B,eACgC;CAChC,MAAM,SAAS,MAAM,mBAAmB,WAAW;AAEnD,KAAI,OAAO,KAAK,OAAO,CAAC,SAAS,EAC/B,OAAM,IAAI,aAAa,6BAA6B,EAClD,QACD,CAAC;AAGJ,QAAO,MAAM,gBAAgB,OAAO,WAAW;;;;;;;;AASjD,MAAa,uBAAuB,OAClC,cACA,eACgC;CAEhC,MAAM,qBAAqB,iBADF,4BAA4B,WACO,EAAE,CAC5D,KACD,CAAC;CAGF,MAAM,SAAS,MAAM,mBAAmB,oBADpB,OAAO,KAAK,mBACuC,CAAC;AAExE,KAAI,OAAO,KAAK,OAAO,CAAC,SAAS,EAC/B,OAAM,IAAI,aAAa,6BAA6B;EAClD;EACA;EACD,CAAC;AAQJ,MAAI,MALiB,gBAAgB,UACnC,EAAE,KAAK,cAAc,EACrB,mBACD,EAEU,iBAAiB,EAC1B,OAAM,IAAI,aAAa,4BAA4B,EAAE,cAAc,CAAC;AAKtE,QAAO,MAFyB,kBAAkB,aAAa;;;;;;;;AAWjE,MAAa,wBAAwB,OACnC,eACA,YACA,cACgC;CAChC,MAAM,WAAW,MAAM,gBAAgB,QAAQ;EAC7C,KAAK,OAAO,cAAc;EAC1B,YAAY;EACb,CAAC;AAEF,KAAI,CAAC,SACH,OAAM,IAAI,aAAa,4BAA4B,EAAE,eAAe,CAAC;CAIvE,MAAM,qBAAqB,iBADF,4BAA4B,WACO,EAAE,CAC5D,KACD,CAAC;AAKF,QAAO,OAAO,UAAU,mBAAmB;AAK3C,UAAS,aAAa,UAAU;AAGhC,OAAM,SAAS,MAAM;AAErB,QAAO;;;;;;;AAQT,MAAa,uBAAuB,OAClC,iBACgC;CAChC,MAAM,aAAa,MAAM,gBAAgB,kBAAkB,aAAa;AAExE,KAAI,CAAC,WACH,OAAM,IAAI,aAAa,wBAAwB,EAAE,cAAc,CAAC;AAGlE,QAAO;;AAIT,MAAM,oBAAoB,YAA4B;CACpD,MAAM,QAAQ,QAAQ,MAAM,WAAW;AACvC,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,2BAA2B,UAAU;AAEvD,QAAO,SAAS,MAAM,IAAI,GAAG;;AAG/B,MAAa,oBAAoB,eAAmC;CAClE,MAAM,iBAAiB;CAEvB,MAAM,WAAW,CAAC,GAAI,WAAW,QAAQ,MAAM,IAAI,EAAE,CAAE;CACvD,MAAM,cAAc,SAAS,SAAS,SAAS;CAG/C,IAAI,YAAY,iBAAiB,YAAY,GAAG;CAChD,IAAI,aAAa,GAAG,iBAAiB;AAGrC,QAAO,SAAS,SAAS,WAAW,EAAE;AACpC,eAAa;AACb,eAAa,GAAG,iBAAiB;;AAGnC,QAAO;;;;;;;AAQT,MAAa,yBAAyB,OACpC,YACA,cACkB;CAClB,MAAM,mBAAmB,oBAAoB,YAAY,UAAU;AAEnE,MAAK,MAAM,cAAc,iBACvB,OAAM,iBAAiB,WAAW"}