@omnizap-system/omnizap 2.5.12

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 (425) hide show
  1. package/.clusterfuzzlite/Dockerfile +10 -0
  2. package/.env.example +907 -0
  3. package/.github/codeql/codeql-config.yml +10 -0
  4. package/.github/dependabot.yml +35 -0
  5. package/.github/workflows/ci.yml +73 -0
  6. package/.github/workflows/codeql.yml +106 -0
  7. package/.github/workflows/db-migration-check.yml +98 -0
  8. package/.github/workflows/dependency-review.yml +22 -0
  9. package/.github/workflows/deploy.yml +95 -0
  10. package/.github/workflows/release.yml +106 -0
  11. package/.github/workflows/security-attest-provenance.yml +51 -0
  12. package/.github/workflows/security-gitleaks.yml +34 -0
  13. package/.github/workflows/security-runner-hardening.yml +31 -0
  14. package/.github/workflows/security-scorecard.yml +44 -0
  15. package/.github/workflows/security-zap-baseline.yml +44 -0
  16. package/.github/workflows/security-zap-full-scan.yml +43 -0
  17. package/.github/workflows/security-zizmor.yml +36 -0
  18. package/.github/workflows/wiki-sync.yml +44 -0
  19. package/.gitleaks.toml +15 -0
  20. package/.prettierrc +34 -0
  21. package/CODE_OF_CONDUCT.md +114 -0
  22. package/LICENSE +56 -0
  23. package/README.md +110 -0
  24. package/SECURITY.md +110 -0
  25. package/app/config/index.js +4 -0
  26. package/app/configParts/adminIdentity.js +92 -0
  27. package/app/configParts/baileysConfig.js +1818 -0
  28. package/app/configParts/groupUtils.js +692 -0
  29. package/app/configParts/loggerConfig.js +394 -0
  30. package/app/configParts/messagePersistenceService.js +305 -0
  31. package/app/connection/baileysCompatibility.test.js +40 -0
  32. package/app/connection/baileysDbAuthState.js +344 -0
  33. package/app/connection/socketController.js +2243 -0
  34. package/app/controllers/messageController.js +7 -0
  35. package/app/controllers/messagePipeline/commandMiddleware.js +146 -0
  36. package/app/controllers/messagePipeline/conversationMiddleware.js +183 -0
  37. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +522 -0
  38. package/app/controllers/messagePipeline/postProcessingMiddleware.js +41 -0
  39. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +166 -0
  40. package/app/controllers/messageProcessingPipeline.js +699 -0
  41. package/app/modules/adminModule/AGENT.md +4056 -0
  42. package/app/modules/adminModule/adminAiHelpService.js +56 -0
  43. package/app/modules/adminModule/adminConfigRuntime.js +177 -0
  44. package/app/modules/adminModule/commandConfig.json +7122 -0
  45. package/app/modules/adminModule/groupCommandHandlers.js +1823 -0
  46. package/app/modules/adminModule/groupCommandHandlers.test.js +350 -0
  47. package/app/modules/adminModule/groupEventHandlers.js +399 -0
  48. package/app/modules/aiModule/AGENT.md +547 -0
  49. package/app/modules/aiModule/aiAiHelpService.js +14 -0
  50. package/app/modules/aiModule/aiConfigRuntime.js +135 -0
  51. package/app/modules/aiModule/catCommand.js +967 -0
  52. package/app/modules/aiModule/commandConfig.json +981 -0
  53. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +83 -0
  54. package/app/modules/gameModule/AGENT.md +196 -0
  55. package/app/modules/gameModule/commandConfig.json +366 -0
  56. package/app/modules/gameModule/diceCommand.js +42 -0
  57. package/app/modules/gameModule/gameAiHelpService.js +14 -0
  58. package/app/modules/gameModule/gameConfigRuntime.js +68 -0
  59. package/app/modules/menuModule/AGENT.md +205 -0
  60. package/app/modules/menuModule/commandConfig.json +366 -0
  61. package/app/modules/menuModule/common.js +316 -0
  62. package/app/modules/menuModule/menuAiHelpService.js +14 -0
  63. package/app/modules/menuModule/menuConfigRuntime.js +68 -0
  64. package/app/modules/menuModule/menus.js +66 -0
  65. package/app/modules/playModule/AGENT.md +321 -0
  66. package/app/modules/playModule/commandConfig.json +584 -0
  67. package/app/modules/playModule/playAiHelpService.js +14 -0
  68. package/app/modules/playModule/playCommand.js +1417 -0
  69. package/app/modules/playModule/playConfigRuntime.js +68 -0
  70. package/app/modules/quoteModule/AGENT.md +199 -0
  71. package/app/modules/quoteModule/commandConfig.json +366 -0
  72. package/app/modules/quoteModule/quoteAiHelpService.js +14 -0
  73. package/app/modules/quoteModule/quoteCommand.js +842 -0
  74. package/app/modules/quoteModule/quoteConfigRuntime.js +68 -0
  75. package/app/modules/rpgPokemonModule/AGENT.md +229 -0
  76. package/app/modules/rpgPokemonModule/commandConfig.json +386 -0
  77. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +795 -0
  78. package/app/modules/rpgPokemonModule/rpgBattleService.js +2110 -0
  79. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +770 -0
  80. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  81. package/app/modules/rpgPokemonModule/rpgPokemonAiHelpService.js +14 -0
  82. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +174 -0
  83. package/app/modules/rpgPokemonModule/rpgPokemonConfigRuntime.js +68 -0
  84. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  85. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  86. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  87. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  88. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1847 -0
  89. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6839 -0
  90. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  91. package/app/modules/statsModule/AGENT.md +320 -0
  92. package/app/modules/statsModule/commandConfig.json +540 -0
  93. package/app/modules/statsModule/globalRankingCommand.js +64 -0
  94. package/app/modules/statsModule/rankingCommand.js +41 -0
  95. package/app/modules/statsModule/rankingCommon.js +1305 -0
  96. package/app/modules/statsModule/statsAiHelpService.js +14 -0
  97. package/app/modules/statsModule/statsConfigRuntime.js +68 -0
  98. package/app/modules/stickerModule/AGENT.md +692 -0
  99. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  100. package/app/modules/stickerModule/commandConfig.json +1216 -0
  101. package/app/modules/stickerModule/convertToWebp.js +367 -0
  102. package/app/modules/stickerModule/stickerAiHelpService.js +14 -0
  103. package/app/modules/stickerModule/stickerCommand.js +446 -0
  104. package/app/modules/stickerModule/stickerConfigRuntime.js +68 -0
  105. package/app/modules/stickerModule/stickerConvertCommand.js +159 -0
  106. package/app/modules/stickerModule/stickerTextCommand.js +653 -0
  107. package/app/modules/stickerPackModule/AGENT.md +215 -0
  108. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  109. package/app/modules/stickerPackModule/autoPackCollectorService.js +357 -0
  110. package/app/modules/stickerPackModule/commandConfig.json +387 -0
  111. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +227 -0
  112. package/app/modules/stickerPackModule/domainEvents.js +52 -0
  113. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +429 -0
  114. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +75 -0
  115. package/app/modules/stickerPackModule/semanticThemeClusterService.js +544 -0
  116. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +400 -0
  117. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  118. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +175 -0
  119. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +3702 -0
  120. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +559 -0
  121. package/app/modules/stickerPackModule/stickerClassificationService.js +557 -0
  122. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +249 -0
  123. package/app/modules/stickerPackModule/stickerDomainEventBus.js +65 -0
  124. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +208 -0
  125. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +99 -0
  126. package/app/modules/stickerPackModule/stickerObjectStorageService.js +285 -0
  127. package/app/modules/stickerPackModule/stickerPackAiHelpService.js +14 -0
  128. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1148 -0
  129. package/app/modules/stickerPackModule/stickerPackConfigRuntime.js +68 -0
  130. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +152 -0
  131. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  132. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +101 -0
  133. package/app/modules/stickerPackModule/stickerPackItemRepository.js +432 -0
  134. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +313 -0
  135. package/app/modules/stickerPackModule/stickerPackMessageService.js +268 -0
  136. package/app/modules/stickerPackModule/stickerPackRepository.js +450 -0
  137. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +179 -0
  138. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +271 -0
  139. package/app/modules/stickerPackModule/stickerPackService.js +733 -0
  140. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +32 -0
  141. package/app/modules/stickerPackModule/stickerPackUtils.js +107 -0
  142. package/app/modules/stickerPackModule/stickerStorageService.js +559 -0
  143. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +242 -0
  144. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +242 -0
  145. package/app/modules/systemMetricsModule/AGENT.md +193 -0
  146. package/app/modules/systemMetricsModule/commandConfig.json +344 -0
  147. package/app/modules/systemMetricsModule/pingCommand.js +399 -0
  148. package/app/modules/systemMetricsModule/systemMetricsAiHelpService.js +14 -0
  149. package/app/modules/systemMetricsModule/systemMetricsConfigRuntime.js +68 -0
  150. package/app/modules/tiktokModule/AGENT.md +196 -0
  151. package/app/modules/tiktokModule/commandConfig.json +366 -0
  152. package/app/modules/tiktokModule/tiktokAiHelpService.js +14 -0
  153. package/app/modules/tiktokModule/tiktokCommand.js +716 -0
  154. package/app/modules/tiktokModule/tiktokConfigRuntime.js +68 -0
  155. package/app/modules/userModule/AGENT.md +200 -0
  156. package/app/modules/userModule/commandConfig.json +386 -0
  157. package/app/modules/userModule/userAiHelpService.js +14 -0
  158. package/app/modules/userModule/userCommand.js +1155 -0
  159. package/app/modules/userModule/userConfigRuntime.js +68 -0
  160. package/app/modules/waifuPicsModule/AGENT.md +431 -0
  161. package/app/modules/waifuPicsModule/commandConfig.json +780 -0
  162. package/app/modules/waifuPicsModule/waifuPicsAiHelpService.js +14 -0
  163. package/app/modules/waifuPicsModule/waifuPicsCommand.js +586 -0
  164. package/app/modules/waifuPicsModule/waifuPicsConfigRuntime.js +68 -0
  165. package/app/observability/metrics.js +766 -0
  166. package/app/services/ai/aiHelpResponseCacheRepository.js +280 -0
  167. package/app/services/ai/aiLearningRepository.js +400 -0
  168. package/app/services/ai/commandConfigEnrichmentRepository.js +769 -0
  169. package/app/services/ai/commandConfigEnrichmentService.js +452 -0
  170. package/app/services/ai/commandConfigValidationService.js +443 -0
  171. package/app/services/ai/commandToolBuilderService.js +192 -0
  172. package/app/services/ai/conversationRouterService.js +516 -0
  173. package/app/services/ai/geminiService.js +115 -0
  174. package/app/services/ai/geminiService.test.js +87 -0
  175. package/app/services/ai/globalModuleAiHelpService.js +1412 -0
  176. package/app/services/ai/globalToolCallingService.js +203 -0
  177. package/app/services/ai/messageCommandExecutionService.js +391 -0
  178. package/app/services/ai/moduleAiHelpCoreService.js +1099 -0
  179. package/app/services/ai/moduleAiHelpWrapperFactory.js +65 -0
  180. package/app/services/ai/moduleCommandConfigRuntimeService.js +113 -0
  181. package/app/services/ai/moduleToolExecutorService.js +464 -0
  182. package/app/services/ai/moduleToolRegistryService.js +178 -0
  183. package/app/services/ai/toolCandidateSelectorService.js +781 -0
  184. package/app/services/auth/googleWebLinkService.js +80 -0
  185. package/app/services/auth/whatsappLoginLinkService.js +230 -0
  186. package/app/services/external/pokeApiService.js +398 -0
  187. package/app/services/group/groupMetadataService.js +311 -0
  188. package/app/services/infra/dbWriteQueue.js +874 -0
  189. package/app/services/infra/featureFlagService.js +131 -0
  190. package/app/services/infra/queueUtils.js +55 -0
  191. package/app/services/messaging/captchaService.js +491 -0
  192. package/app/services/messaging/messagePersistenceService.js +1 -0
  193. package/app/services/messaging/newsBroadcastService.js +347 -0
  194. package/app/services/sticker/stickerFocusService.js +347 -0
  195. package/app/services/sticker/stickerFocusService.test.js +43 -0
  196. package/app/store/aiPromptStore.js +38 -0
  197. package/app/store/conversationSessionStore.js +131 -0
  198. package/app/store/groupConfigStore.js +58 -0
  199. package/app/store/premiumUserStore.js +54 -0
  200. package/app/utils/antiLink/antiLinkModule.js +700 -0
  201. package/app/utils/http/getImageBufferModule.js +18 -0
  202. package/app/utils/json/jsonSanitizer.js +113 -0
  203. package/app/utils/json/jsonSanitizer.test.js +40 -0
  204. package/app/utils/systemMetrics/systemMetricsModule.js +88 -0
  205. package/app/workers/aiLearningWorker.js +605 -0
  206. package/app/workers/commandConfigEnrichmentWorker.js +242 -0
  207. package/database/index.js +2075 -0
  208. package/database/init.js +151 -0
  209. package/database/migrations/.gitkeep +0 -0
  210. package/database/migrations/20260307_d0_hardening_down.sql +64 -0
  211. package/database/migrations/20260307_d0_hardening_up.sql +79 -0
  212. package/database/migrations/20260307_d1_terms_acceptance_down.sql +11 -0
  213. package/database/migrations/20260307_d1_terms_acceptance_up.sql +37 -0
  214. package/database/migrations/20260307_d2_auth_hardening_down.sql +75 -0
  215. package/database/migrations/20260307_d2_auth_hardening_up.sql +100 -0
  216. package/database/migrations/20260314_d7_canonical_sender_down.sql +53 -0
  217. package/database/migrations/20260314_d7_canonical_sender_up.sql +114 -0
  218. package/database/migrations/20260406_d30_security_analytics_down.sql +95 -0
  219. package/database/migrations/20260406_d30_security_analytics_up.sql +292 -0
  220. package/database/migrations/20260407_d31_web_google_session_token_hardening_down.sql +2 -0
  221. package/database/migrations/20260407_d31_web_google_session_token_hardening_up.sql +17 -0
  222. package/database/migrations/20260408_d32_ai_help_response_cache_down.sql +1 -0
  223. package/database/migrations/20260408_d32_ai_help_response_cache_up.sql +22 -0
  224. package/database/migrations/20260409_d33_ai_learning_tables_down.sql +4 -0
  225. package/database/migrations/20260409_d33_ai_learning_tables_up.sql +52 -0
  226. package/database/migrations/20260410_d34_command_config_enrichment_down.sql +3 -0
  227. package/database/migrations/20260410_d34_command_config_enrichment_up.sql +48 -0
  228. package/database/schema.sql +1186 -0
  229. package/docker-compose.yml +104 -0
  230. package/docs/audits/stickerCatalogController-out-of-scope.md +103 -0
  231. package/docs/audits/stickerCatalogController-symbols.md +58 -0
  232. package/docs/compliance/acceptable-use-policy-2026-03-07.md +35 -0
  233. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +80 -0
  234. package/docs/compliance/monthly-compliance-checklist-2026-03-07.md +88 -0
  235. package/docs/compliance/notice-and-takedown-policy-2026-03-07.md +34 -0
  236. package/docs/compliance/privacy-policy-2026-03-07.md +75 -0
  237. package/docs/compliance/subprocessors-inventory-2026-03-07.md +16 -0
  238. package/docs/database/production-db-evolution-runbook-2026q1.md +365 -0
  239. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +86 -0
  240. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +77 -0
  241. package/docs/security/network-hardening-runbook-2026-03-07.md +137 -0
  242. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +238 -0
  243. package/docs/seo/satellite-page-template.md +116 -0
  244. package/docs/seo/satellite-pages-phase1.json +364 -0
  245. package/docs/wiki/Home.md +120 -0
  246. package/docs/wiki/pair-extraordinaire-2026-03-08.md +3 -0
  247. package/docs/wiki/recent-changes-2026-03-08.md +47 -0
  248. package/ecosystem.prod.config.cjs +135 -0
  249. package/eslint.config.js +89 -0
  250. package/index.js +488 -0
  251. package/ml/clip_classifier/Dockerfile +18 -0
  252. package/ml/clip_classifier/README.md +118 -0
  253. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  254. package/ml/clip_classifier/classifier.py +654 -0
  255. package/ml/clip_classifier/embedding_store.py +481 -0
  256. package/ml/clip_classifier/env_loader.py +15 -0
  257. package/ml/clip_classifier/llm_label_expander.py +144 -0
  258. package/ml/clip_classifier/main.py +213 -0
  259. package/ml/clip_classifier/requirements.txt +10 -0
  260. package/ml/clip_classifier/similarity_engine.py +74 -0
  261. package/new-logo.png +0 -0
  262. package/observability/alert-rules.yml +60 -0
  263. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  264. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  265. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  266. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  267. package/observability/loki-config.yml +38 -0
  268. package/observability/mysql-setup.sql +46 -0
  269. package/observability/prometheus.yml +35 -0
  270. package/observability/promtail-config.yml +84 -0
  271. package/observability/sticker-catalog-slo.md +83 -0
  272. package/observability/sticker-scale-hardening-rollout.md +128 -0
  273. package/package.json +144 -0
  274. package/public/apple-touch-icon.png +0 -0
  275. package/public/assets/css/commands-react.input.css +71 -0
  276. package/public/assets/css/create-pack-react.input.css +31 -0
  277. package/public/assets/css/home-react.input.css +106 -0
  278. package/public/assets/css/login-react.input.css +58 -0
  279. package/public/assets/css/stickers-react.input.css +18 -0
  280. package/public/assets/css/terms-react.input.css +115 -0
  281. package/public/assets/css/user-react.input.css +57 -0
  282. package/public/assets/images/brand-icon-192.png +0 -0
  283. package/public/assets/images/brand-logo-128.webp +0 -0
  284. package/public/assets/images/hero-banner-1280.jpg +0 -0
  285. package/public/comandos/commands-catalog.json +4517 -0
  286. package/public/css/api-docs.css +161 -0
  287. package/public/css/stickers-admin.css +1288 -0
  288. package/public/css/styles.css +679 -0
  289. package/public/css/systemadm/admin.css +474 -0
  290. package/public/css/systemadm/base.css +73 -0
  291. package/public/css/systemadm/components.css +662 -0
  292. package/public/css/systemadm/layout.css +229 -0
  293. package/public/css/systemadm/tokens.css +56 -0
  294. package/public/favicon-16x16.png +0 -0
  295. package/public/favicon-32x32.png +0 -0
  296. package/public/favicon.ico +0 -0
  297. package/public/js/apps/apiDocsApp.js +235 -0
  298. package/public/js/apps/commandsReactApp.js +528 -0
  299. package/public/js/apps/createPackApp.js +1646 -0
  300. package/public/js/apps/homeReactApp.js +942 -0
  301. package/public/js/apps/loginReactApp.js +496 -0
  302. package/public/js/apps/stickersAdminApp.js +1753 -0
  303. package/public/js/apps/stickersApp.js +3797 -0
  304. package/public/js/apps/termsReactApp.js +528 -0
  305. package/public/js/apps/userApp.js +2540 -0
  306. package/public/js/apps/userProfile/actions.js +66 -0
  307. package/public/js/apps/userReactApp.js +547 -0
  308. package/public/js/catalog.js +950 -0
  309. package/public/pages/api-docs.html +40 -0
  310. package/public/pages/aup.html +158 -0
  311. package/public/pages/comandos.html +41 -0
  312. package/public/pages/dpa.html +227 -0
  313. package/public/pages/home.html +45 -0
  314. package/public/pages/licenca.html +182 -0
  315. package/public/pages/login.html +40 -0
  316. package/public/pages/notice-and-takedown.html +234 -0
  317. package/public/pages/politica-de-privacidade.html +251 -0
  318. package/public/pages/seo-bot-whatsapp-para-grupo.html +350 -0
  319. package/public/pages/seo-bot-whatsapp-sem-programar.html +350 -0
  320. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +350 -0
  321. package/public/pages/seo-como-criar-comandos-whatsapp.html +350 -0
  322. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +350 -0
  323. package/public/pages/seo-como-moderar-grupo-whatsapp.html +350 -0
  324. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +350 -0
  325. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +350 -0
  326. package/public/pages/stickers-admin.html +31 -0
  327. package/public/pages/stickers-create.html +41 -0
  328. package/public/pages/stickers.html +45 -0
  329. package/public/pages/suboperadores.html +237 -0
  330. package/public/pages/termos-de-uso-texto-integral.html +241 -0
  331. package/public/pages/termos-de-uso.html +41 -0
  332. package/public/pages/user-password-reset.html +32 -0
  333. package/public/pages/user-systemadm.html +508 -0
  334. package/public/pages/user.html +39 -0
  335. package/public/robots.txt +9 -0
  336. package/public/site.webmanifest +24 -0
  337. package/public/sitemap.xml +98 -0
  338. package/schemas/command-config.schema.json +582 -0
  339. package/scripts/baileys-compat-smoke.mjs +12 -0
  340. package/scripts/cache-bust.mjs +142 -0
  341. package/scripts/deploy.sh +916 -0
  342. package/scripts/email-broadcast-terms-update.mjs +170 -0
  343. package/scripts/enrich-command-discovery-fields.mjs +286 -0
  344. package/scripts/generate-command-config-schema.mjs +273 -0
  345. package/scripts/generate-commands-catalog.mjs +308 -0
  346. package/scripts/generate-module-agents.mjs +631 -0
  347. package/scripts/generate-seo-satellite-pages.mjs +400 -0
  348. package/scripts/github-deploy-notify.mjs +174 -0
  349. package/scripts/github-release-notify.mjs +219 -0
  350. package/scripts/release.sh +599 -0
  351. package/scripts/run-codeql-local.sh +116 -0
  352. package/scripts/run-prettier-all.mjs +25 -0
  353. package/scripts/security-smoketest.mjs +581 -0
  354. package/scripts/sticker-catalog-loadtest.mjs +210 -0
  355. package/scripts/sticker-worker-task.mjs +119 -0
  356. package/scripts/sync-readme-snapshot.mjs +133 -0
  357. package/scripts/validate-command-config-schema.mjs +130 -0
  358. package/scripts/validate-command-configs.mjs +15 -0
  359. package/scripts/wiki-sync.sh +191 -0
  360. package/server/auth/googleWebAuth/googleWebAuthRuntime.js +62 -0
  361. package/server/auth/googleWebAuth/googleWebAuthService.js +807 -0
  362. package/server/auth/jwt/webJwtService.js +147 -0
  363. package/server/auth/stickerCatalogAuthContext.js +165 -0
  364. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +189 -0
  365. package/server/auth/userPassword/index.js +14 -0
  366. package/server/auth/userPassword/userPasswordAuthService.js +422 -0
  367. package/server/auth/userPassword/userPasswordCrypto.js +199 -0
  368. package/server/auth/userPassword/userPasswordCrypto.test.js +76 -0
  369. package/server/auth/userPassword/userPasswordRecoveryService.js +728 -0
  370. package/server/auth/validation/authSchemas.js +236 -0
  371. package/server/auth/webAccount/webAccountHandlers.js +1434 -0
  372. package/server/controllers/admin/adminBanService.js +138 -0
  373. package/server/controllers/admin/adminPanelHandlers.js +2083 -0
  374. package/server/controllers/admin/stickerCatalogAdminContext.js +17 -0
  375. package/server/controllers/admin/systemAdminController.js +201 -0
  376. package/server/controllers/email/emailAutomationController.js +239 -0
  377. package/server/controllers/metricsController.js +21 -0
  378. package/server/controllers/seo/stickerCatalogSeoContext.js +514 -0
  379. package/server/controllers/sticker/nonCatalogHandlers.js +303 -0
  380. package/server/controllers/sticker/stickerCatalogController.js +4700 -0
  381. package/server/controllers/system/contactController.js +115 -0
  382. package/server/controllers/system/githubController.js +137 -0
  383. package/server/controllers/system/stickerCatalogSystemContext.js +758 -0
  384. package/server/controllers/system/storageController.js +154 -0
  385. package/server/controllers/system/systemController.js +135 -0
  386. package/server/controllers/system/systemMetricsController.js +156 -0
  387. package/server/controllers/system/visitController.js +90 -0
  388. package/server/controllers/userController.js +145 -0
  389. package/server/email/emailAutomationRuntime.js +225 -0
  390. package/server/email/emailAutomationService.js +125 -0
  391. package/server/email/emailOutboxRepository.js +282 -0
  392. package/server/email/emailTemplateService.js +480 -0
  393. package/server/email/emailTransportService.js +156 -0
  394. package/server/http/clientIp.js +95 -0
  395. package/server/http/httpRequestUtils.js +262 -0
  396. package/server/http/httpRequestUtils.test.js +80 -0
  397. package/server/http/httpServer.js +180 -0
  398. package/server/http/requestContext.js +20 -0
  399. package/server/http/siteRoutingUtils.js +87 -0
  400. package/server/index.js +1 -0
  401. package/server/middleware/cachePolicy.js +26 -0
  402. package/server/middleware/cachePolicyHelpers.js +1 -0
  403. package/server/middleware/endpointRateLimit.js +181 -0
  404. package/server/middleware/rateLimit.js +70 -0
  405. package/server/middleware/requireAdminAuth.js +48 -0
  406. package/server/middleware/securityHeaders.js +97 -0
  407. package/server/routes/admin/systemAdminRouter.js +64 -0
  408. package/server/routes/email/emailAutomationRouter.js +46 -0
  409. package/server/routes/health/healthRouter.js +41 -0
  410. package/server/routes/indexRouter.js +234 -0
  411. package/server/routes/metrics/metricsRouter.js +58 -0
  412. package/server/routes/static/staticPageRouter.js +134 -0
  413. package/server/routes/sticker/catalogHandlers/catalogAdminHttp.js +105 -0
  414. package/server/routes/sticker/catalogHandlers/catalogAuthHttp.js +77 -0
  415. package/server/routes/sticker/catalogHandlers/catalogPublicHttp.js +120 -0
  416. package/server/routes/sticker/catalogHandlers/catalogUploadHttp.js +83 -0
  417. package/server/routes/sticker/catalogRouter.js +77 -0
  418. package/server/routes/sticker/stickerApiRouter.js +84 -0
  419. package/server/routes/sticker/stickerDataRouter.js +145 -0
  420. package/server/routes/sticker/stickerSiteRouter.js +43 -0
  421. package/server/routes/user/userApiPaths.js +66 -0
  422. package/server/routes/user/userRouter.js +65 -0
  423. package/server/utils/safePath.js +26 -0
  424. package/utils/logger/loggerModule.js +35 -0
  425. package/vite.config.mjs +38 -0
@@ -0,0 +1,916 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ SOURCE_DIR="${DEPLOY_SOURCE_DIR:-$PROJECT_ROOT/public}"
6
+ DEPLOY_DIR="${DEPLOY_TARGET_DIR:-/var/www/omnizap}"
7
+ BACKUP_ENABLED="${DEPLOY_CREATE_BACKUP:-1}"
8
+ BACKUP_DIR="${DEPLOY_BACKUP_DIR:-$DEPLOY_DIR/.backup}"
9
+ NGINX_SERVICE="${DEPLOY_NGINX_SERVICE:-nginx}"
10
+ RESTART_PM2="${DEPLOY_RESTART_PM2:-1}"
11
+ PM2_APP_NAME="${DEPLOY_PM2_APP_NAME:-omnizap-system-production}"
12
+ BUILD_ID="${DEPLOY_BUILD_ID:-$(date -u +%Y%m%d%H%M%S)}"
13
+ VERIFY_URL="${DEPLOY_VERIFY_URL:-https://omnizap.shop/}"
14
+ DRY_RUN="${DEPLOY_DRY_RUN:-0}"
15
+ GITHUB_NOTIFY="${DEPLOY_GITHUB_NOTIFY:-1}"
16
+ GITHUB_ENVIRONMENT="${DEPLOY_GITHUB_ENVIRONMENT:-production}"
17
+ GITHUB_DEPLOYMENT_ID=""
18
+ PACKAGE_STEP="${DEPLOY_PACKAGE_STEP:-0}"
19
+ PACKAGE_INSTALL="${DEPLOY_PACKAGE_INSTALL:-1}"
20
+ PACKAGE_TEST="${DEPLOY_PACKAGE_TEST:-0}"
21
+ PACKAGE_PACK="${DEPLOY_PACKAGE_PACK:-0}"
22
+ PACKAGE_ARTIFACTS_DIR="${DEPLOY_PACKAGE_ARTIFACTS_DIR:-$PROJECT_ROOT/.artifacts}"
23
+ PACKAGE_PUBLISH="${DEPLOY_PACKAGE_PUBLISH:-0}"
24
+ PACKAGE_PUBLISH_SKIP_IF_EXISTS="${DEPLOY_PACKAGE_PUBLISH_SKIP_IF_EXISTS:-1}"
25
+ PACKAGE_REGISTRY="${DEPLOY_PACKAGE_REGISTRY:-https://npm.pkg.github.com}"
26
+ PACKAGE_TAG="${DEPLOY_PACKAGE_TAG:-latest}"
27
+ PACKAGE_TOKEN="${DEPLOY_PACKAGE_TOKEN:-}"
28
+ PACKAGE_OTP="${DEPLOY_PACKAGE_OTP:-}"
29
+ PACKAGE_PUBLISH_SECONDARY="${DEPLOY_PACKAGE_PUBLISH_SECONDARY:-0}"
30
+ PACKAGE_SECONDARY_REGISTRY="${DEPLOY_PACKAGE_SECONDARY_REGISTRY:-https://registry.npmjs.org}"
31
+ PACKAGE_SECONDARY_TAG="${DEPLOY_PACKAGE_SECONDARY_TAG:-latest}"
32
+ PACKAGE_SECONDARY_TOKEN="${DEPLOY_PACKAGE_SECONDARY_TOKEN:-}"
33
+ PACKAGE_SECONDARY_OTP="${DEPLOY_PACKAGE_SECONDARY_OTP:-}"
34
+ PACKAGE_SECONDARY_ACCESS="${DEPLOY_PACKAGE_SECONDARY_ACCESS:-public}"
35
+ PACKAGE_SECONDARY_PUBLISH_SKIP_IF_EXISTS="${DEPLOY_PACKAGE_SECONDARY_PUBLISH_SKIP_IF_EXISTS:-$PACKAGE_PUBLISH_SKIP_IF_EXISTS}"
36
+ PACKAGE_SECONDARY_TOKEN_KEYS="${DEPLOY_PACKAGE_SECONDARY_TOKEN_KEYS:-}"
37
+ ASSET_BUILD_ENABLED="${DEPLOY_BUILD_ASSETS:-1}"
38
+ ASSET_BUILD_CMD="${DEPLOY_BUILD_ASSETS_CMD:-npm run build:all}"
39
+ BACKEND_CACHE_BUST_ENABLED="${DEPLOY_BACKEND_CACHE_BUST_ENABLED:-1}"
40
+ BACKEND_BUILD_ID_ENV="${DEPLOY_BACKEND_BUILD_ID_ENV:-OMNIZAP_BUILD_ID}"
41
+ BACKEND_BUILD_ID_VALUE="${DEPLOY_BACKEND_BUILD_ID_VALUE:-$BUILD_ID}"
42
+ BACKEND_ASSET_VERSION_ENV="${DEPLOY_BACKEND_ASSET_VERSION_ENV:-STICKER_WEB_ASSET_VERSION}"
43
+ BACKEND_ASSET_VERSION_VALUE="${DEPLOY_BACKEND_ASSET_VERSION_VALUE:-$BUILD_ID}"
44
+ VERIFY_BUILD_OUTPUTS_ENABLED="${DEPLOY_VERIFY_BUILD_OUTPUTS:-1}"
45
+ VERIFY_CACHE_BUST_ENABLED="${DEPLOY_VERIFY_CACHE_BUST:-1}"
46
+ REQUIRE_CACHE_BUST_REFERENCES="${DEPLOY_REQUIRE_CACHE_BUST_REFERENCES:-1}"
47
+ VERIFY_POST_SYNC_CACHE_BUST_ENABLED="${DEPLOY_VERIFY_POST_SYNC_CACHE_BUST:-1}"
48
+ NPMRC_TMP_FILES=()
49
+
50
+ log() {
51
+ printf '[deploy] %s\n' "$*"
52
+ }
53
+
54
+ is_valid_env_key() {
55
+ [[ "$1" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]
56
+ }
57
+
58
+ resolve_github_repo() {
59
+ local explicit_repo="${DEPLOY_GITHUB_REPO:-${GITHUB_REPOSITORY:-}}"
60
+ if [ -n "$explicit_repo" ] && printf '%s' "$explicit_repo" | grep -q '/'; then
61
+ printf '%s' "$explicit_repo"
62
+ return 0
63
+ fi
64
+
65
+ local remote_url=""
66
+ remote_url="$(cd "$PROJECT_ROOT" && git config --get remote.origin.url 2>/dev/null || true)"
67
+ if [ -z "$remote_url" ]; then
68
+ return 0
69
+ fi
70
+
71
+ local repo=""
72
+ repo="$(printf '%s' "$remote_url" | sed -nE 's#.*github\.com[:/]([^/]+/[^/.]+)(\.git)?$#\1#p' | head -n 1)"
73
+ if [ -n "$repo" ]; then
74
+ printf '%s' "$repo"
75
+ fi
76
+ }
77
+
78
+ resolve_github_owner() {
79
+ local repo=""
80
+ repo="$(resolve_github_repo)"
81
+ if [ -z "$repo" ]; then
82
+ return 0
83
+ fi
84
+ printf '%s' "$repo" | cut -d'/' -f1
85
+ }
86
+
87
+ require_cmd() {
88
+ if ! command -v "$1" >/dev/null 2>&1; then
89
+ printf '[deploy] comando ausente: %s\n' "$1" >&2
90
+ exit 1
91
+ fi
92
+ }
93
+
94
+ resolve_token_from_dotenv() {
95
+ local token_keys="$1"
96
+ if [ -z "$token_keys" ]; then
97
+ return 0
98
+ fi
99
+
100
+ (
101
+ cd "$PROJECT_ROOT" && TOKEN_KEYS="$token_keys" node --input-type=module -e "
102
+ import dotenv from 'dotenv';
103
+ dotenv.config({ path: '.env' });
104
+ const keys = String(process.env.TOKEN_KEYS || '')
105
+ .split(',')
106
+ .map((item) => item.trim())
107
+ .filter(Boolean);
108
+ for (const key of keys) {
109
+ const value = process.env[key];
110
+ if (value && String(value).trim()) {
111
+ process.stdout.write(String(value).trim());
112
+ process.exit(0);
113
+ }
114
+ }
115
+ " 2>/dev/null || true
116
+ )
117
+ }
118
+
119
+ default_token_keys_for_registry() {
120
+ local registry="$1"
121
+ if printf '%s' "$registry" | grep -q 'npm.pkg.github.com'; then
122
+ printf 'DEPLOY_PACKAGE_TOKEN,DEPLOY_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,NPM_TOKEN,NODE_AUTH_TOKEN'
123
+ else
124
+ printf 'DEPLOY_PACKAGE_SECONDARY_TOKEN,NPM_TOKEN,NODE_AUTH_TOKEN'
125
+ fi
126
+ }
127
+
128
+ create_npmrc_for_registry() {
129
+ local registry="$1"
130
+ local token="$2"
131
+ local scope_owner="$3"
132
+ local registry_host=""
133
+ registry_host="$(printf '%s' "$registry" | sed -E 's#^https?://##; s#/*$##')"
134
+
135
+ local npmrc_tmp=""
136
+ npmrc_tmp="$(mktemp /tmp/omnizap-npmrc.XXXXXX)"
137
+ {
138
+ printf 'registry=%s\n' "$registry"
139
+ if [ -n "$scope_owner" ]; then
140
+ printf '@%s:registry=%s\n' "$scope_owner" "$registry"
141
+ fi
142
+ printf '//%s/:_authToken=%s\n' "$registry_host" "$token"
143
+ printf '//%s:_authToken=%s\n' "$registry_host" "$token"
144
+ } > "$npmrc_tmp"
145
+ chmod 600 "$npmrc_tmp"
146
+ NPMRC_TMP_FILES+=("$npmrc_tmp")
147
+ printf '%s' "$npmrc_tmp"
148
+ }
149
+
150
+ publish_package_to_registry() {
151
+ local pkg_name="$1"
152
+ local pkg_version="$2"
153
+ local registry="$3"
154
+ local tag="$4"
155
+ local explicit_token="$5"
156
+ local skip_if_exists="$6"
157
+ local access="$7"
158
+ local token_keys_override="$8"
159
+ local otp="$9"
160
+
161
+ local token="$explicit_token"
162
+ if [ -z "$token" ]; then
163
+ local token_keys="$token_keys_override"
164
+ if [ -z "$token_keys" ]; then
165
+ token_keys="$(default_token_keys_for_registry "$registry")"
166
+ fi
167
+ token="$(resolve_token_from_dotenv "$token_keys")"
168
+ fi
169
+
170
+ if [ -z "$token" ]; then
171
+ printf '[deploy] Publish habilitado para %s, mas nenhum token foi encontrado.\n' "$registry" >&2
172
+ exit 1
173
+ fi
174
+
175
+ if printf '%s' "$registry" | grep -q 'registry.npmjs.org'; then
176
+ if printf '%s' "$token" | grep -Eq '^(ghp_|gho_|ghu_|ghs_|ghr_|github_pat_)'; then
177
+ printf '[deploy] Token incompatível para npmjs.org (parece token do GitHub).\n' >&2
178
+ printf '[deploy] Configure DEPLOY_PACKAGE_SECONDARY_TOKEN ou NPM_TOKEN com token do npmjs.\n' >&2
179
+ exit 1
180
+ fi
181
+ fi
182
+
183
+ local scope_owner=""
184
+ scope_owner="$(printf '%s' "$pkg_name" | sed -nE 's#^@([^/]+)/.*#\1#p')"
185
+ local pkg_base_name=""
186
+ pkg_base_name="$(printf '%s' "$pkg_name" | sed -E 's#^@[^/]+/##')"
187
+
188
+ if printf '%s' "$registry" | grep -q 'npm.pkg.github.com'; then
189
+ if ! printf '%s' "$pkg_name" | grep -q '^@'; then
190
+ printf '[deploy] Para GitHub Packages o nome do pacote deve ser escopado (ex: @owner/repo).\n' >&2
191
+ exit 1
192
+ fi
193
+
194
+ local expected_owner="${DEPLOY_PACKAGE_SCOPE_OWNER:-}"
195
+ if [ -z "$expected_owner" ]; then
196
+ expected_owner="$(resolve_github_owner)"
197
+ fi
198
+
199
+ if [ -n "$expected_owner" ] && [ -n "$scope_owner" ] && [ "$scope_owner" != "$expected_owner" ]; then
200
+ printf '[deploy] Scope do pacote (%s) difere do owner GitHub esperado (%s).\n' "$scope_owner" "$expected_owner" >&2
201
+ printf '[deploy] Ajuste com: npm pkg set name=\"@%s/%s\"\n' "$expected_owner" "$pkg_base_name" >&2
202
+ printf '[deploy] Ou defina DEPLOY_PACKAGE_SCOPE_OWNER para publicar em outro owner.\n' >&2
203
+ exit 1
204
+ fi
205
+ fi
206
+
207
+ local npmrc_tmp=""
208
+ npmrc_tmp="$(create_npmrc_for_registry "$registry" "$token" "$scope_owner")"
209
+
210
+ if ! (
211
+ cd "$PROJECT_ROOT" &&
212
+ npm_config_userconfig="$npmrc_tmp" npm whoami --registry "$registry" --userconfig "$npmrc_tmp" >/dev/null 2>&1
213
+ ); then
214
+ printf '[deploy] Falha de autenticação no registry %s. Verifique token/permissões.\n' "$registry" >&2
215
+ exit 1
216
+ fi
217
+
218
+ if [ "$skip_if_exists" = "1" ]; then
219
+ if (
220
+ cd "$PROJECT_ROOT" &&
221
+ npm_config_userconfig="$npmrc_tmp" npm view "${pkg_name}@${pkg_version}" --registry "$registry" --userconfig "$npmrc_tmp" >/dev/null 2>&1
222
+ ); then
223
+ log "Pacote ${pkg_name}@${pkg_version} já publicado em $registry (skip)."
224
+ return 0
225
+ fi
226
+ fi
227
+
228
+ log "Publicando ${pkg_name}@${pkg_version} em $registry (tag=$tag)"
229
+ local publish_cmd=(npm publish --registry "$registry" --tag "$tag" --userconfig "$npmrc_tmp")
230
+ if [ -n "$access" ] && printf '%s' "$registry" | grep -q 'registry.npmjs.org' && printf '%s' "$pkg_name" | grep -q '^@'; then
231
+ publish_cmd+=(--access "$access")
232
+ fi
233
+ if [ -n "$otp" ]; then
234
+ publish_cmd+=(--otp "$otp")
235
+ fi
236
+ (
237
+ cd "$PROJECT_ROOT" &&
238
+ npm_config_userconfig="$npmrc_tmp" "${publish_cmd[@]}"
239
+ )
240
+ log "Publish concluído para ${pkg_name}@${pkg_version} em $registry."
241
+ }
242
+
243
+ as_root() {
244
+ if [ "$(id -u)" -eq 0 ]; then
245
+ "$@"
246
+ return
247
+ fi
248
+
249
+ if command -v sudo >/dev/null 2>&1; then
250
+ sudo "$@"
251
+ return
252
+ fi
253
+
254
+ printf '[deploy] precisa de root/sudo para executar: %s\n' "$*" >&2
255
+ exit 1
256
+ }
257
+
258
+ require_cmd node
259
+ require_cmd rsync
260
+ require_cmd nginx
261
+ require_cmd systemctl
262
+
263
+ if [ ! -d "$SOURCE_DIR" ]; then
264
+ printf '[deploy] pasta de origem não encontrada: %s\n' "$SOURCE_DIR" >&2
265
+ exit 1
266
+ fi
267
+
268
+ STAGING_DIR="$(mktemp -d /tmp/omnizap-deploy.XXXXXX)"
269
+ PM2_RESTART_ENV_ARGS=()
270
+
271
+ if [ "$BACKEND_CACHE_BUST_ENABLED" = "1" ]; then
272
+ if ! is_valid_env_key "$BACKEND_BUILD_ID_ENV"; then
273
+ printf '[deploy] nome inválido para DEPLOY_BACKEND_BUILD_ID_ENV: %s\n' "$BACKEND_BUILD_ID_ENV" >&2
274
+ exit 1
275
+ fi
276
+ if ! is_valid_env_key "$BACKEND_ASSET_VERSION_ENV"; then
277
+ printf '[deploy] nome inválido para DEPLOY_BACKEND_ASSET_VERSION_ENV: %s\n' "$BACKEND_ASSET_VERSION_ENV" >&2
278
+ exit 1
279
+ fi
280
+ PM2_RESTART_ENV_ARGS+=("${BACKEND_BUILD_ID_ENV}=${BACKEND_BUILD_ID_VALUE}")
281
+ PM2_RESTART_ENV_ARGS+=("${BACKEND_ASSET_VERSION_ENV}=${BACKEND_ASSET_VERSION_VALUE}")
282
+ fi
283
+
284
+ github_deploy_start() {
285
+ if [ "$DRY_RUN" = "1" ] || [ "$GITHUB_NOTIFY" != "1" ]; then
286
+ return 0
287
+ fi
288
+
289
+ local deployment_id=""
290
+ deployment_id="$(
291
+ node "$PROJECT_ROOT/scripts/github-deploy-notify.mjs" start \
292
+ --build-id "$BUILD_ID" \
293
+ --environment "$GITHUB_ENVIRONMENT" \
294
+ --environment-url "$VERIFY_URL" \
295
+ --log-url "$VERIFY_URL" 2>/dev/null || true
296
+ )"
297
+ deployment_id="$(printf '%s' "$deployment_id" | tr -d '[:space:]')"
298
+
299
+ if [ -n "$deployment_id" ]; then
300
+ GITHUB_DEPLOYMENT_ID="$deployment_id"
301
+ log "GitHub deployment iniciado: id=$GITHUB_DEPLOYMENT_ID"
302
+ else
303
+ log "GitHub deployment não iniciado (token/repo ausente ou API indisponível)."
304
+ fi
305
+ }
306
+
307
+ github_deploy_status() {
308
+ local state="$1"
309
+ if [ -z "$GITHUB_DEPLOYMENT_ID" ] || [ "$GITHUB_NOTIFY" != "1" ]; then
310
+ return 0
311
+ fi
312
+
313
+ if node "$PROJECT_ROOT/scripts/github-deploy-notify.mjs" status \
314
+ --deployment-id "$GITHUB_DEPLOYMENT_ID" \
315
+ --state "$state" \
316
+ --build-id "$BUILD_ID" \
317
+ --environment "$GITHUB_ENVIRONMENT" \
318
+ --environment-url "$VERIFY_URL" \
319
+ --log-url "$VERIFY_URL" >/dev/null 2>&1; then
320
+ log "GitHub deployment atualizado: id=$GITHUB_DEPLOYMENT_ID state=$state"
321
+ else
322
+ log "Aviso: falha ao atualizar status do deployment no GitHub (state=$state)."
323
+ fi
324
+ }
325
+
326
+ run_package_stage() {
327
+ if [ "$DRY_RUN" = "1" ] || [ "$PACKAGE_STEP" != "1" ]; then
328
+ return 0
329
+ fi
330
+
331
+ require_cmd npm
332
+
333
+ local pkg_version="n/d"
334
+ pkg_version="$(cd "$PROJECT_ROOT" && npm pkg get version 2>/dev/null | tr -d '"[:space:]' || true)"
335
+ log "Etapa package iniciada (versão=$pkg_version)."
336
+
337
+ if [ "$PACKAGE_INSTALL" = "1" ]; then
338
+ if [ -f "$PROJECT_ROOT/package-lock.json" ]; then
339
+ log "Instalando dependências com npm ci --omit=dev"
340
+ (cd "$PROJECT_ROOT" && npm ci --omit=dev)
341
+ else
342
+ printf '[deploy] package-lock.json ausente; npm install foi bloqueado por segurança.\n' >&2
343
+ printf '[deploy] Gere/commite o lockfile e mantenha instalação reprodutível via npm ci.\n' >&2
344
+ exit 1
345
+ fi
346
+ fi
347
+
348
+ if [ "$PACKAGE_TEST" = "1" ]; then
349
+ log "Executando testes de package (npm test)"
350
+ (cd "$PROJECT_ROOT" && npm test)
351
+ fi
352
+
353
+ if [ "$PACKAGE_PACK" = "1" ]; then
354
+ log "Gerando artefato npm pack"
355
+ mkdir -p "$PACKAGE_ARTIFACTS_DIR"
356
+ local pack_name=""
357
+ pack_name="$(cd "$PROJECT_ROOT" && npm pack --silent)"
358
+ if [ -n "$pack_name" ] && [ -f "$PROJECT_ROOT/$pack_name" ]; then
359
+ mv "$PROJECT_ROOT/$pack_name" "$PACKAGE_ARTIFACTS_DIR/$pack_name"
360
+ log "Artefato salvo em $PACKAGE_ARTIFACTS_DIR/$pack_name"
361
+ fi
362
+ fi
363
+
364
+ if [ "$PACKAGE_PUBLISH" = "1" ] || [ "$PACKAGE_PUBLISH_SECONDARY" = "1" ]; then
365
+ local pkg_name=""
366
+ pkg_name="$(cd "$PROJECT_ROOT" && npm pkg get name 2>/dev/null | tr -d '"[:space:]' || true)"
367
+ if [ -z "$pkg_name" ] || [ -z "$pkg_version" ] || [ "$pkg_version" = "n/d" ]; then
368
+ printf '[deploy] não foi possível ler nome/versão do package para publish.\n' >&2
369
+ exit 1
370
+ fi
371
+
372
+ if [ "$PACKAGE_PUBLISH" = "1" ]; then
373
+ publish_package_to_registry \
374
+ "$pkg_name" \
375
+ "$pkg_version" \
376
+ "$PACKAGE_REGISTRY" \
377
+ "$PACKAGE_TAG" \
378
+ "$PACKAGE_TOKEN" \
379
+ "$PACKAGE_PUBLISH_SKIP_IF_EXISTS" \
380
+ "" \
381
+ "" \
382
+ "$PACKAGE_OTP"
383
+ fi
384
+
385
+ if [ "$PACKAGE_PUBLISH_SECONDARY" = "1" ]; then
386
+ if [ "$PACKAGE_PUBLISH" = "1" ] && [ "$PACKAGE_SECONDARY_REGISTRY" = "$PACKAGE_REGISTRY" ] && [ "$PACKAGE_SECONDARY_TAG" = "$PACKAGE_TAG" ]; then
387
+ log "Registry/tag secundário igual ao primário. Publish secundário ignorado."
388
+ else
389
+ publish_package_to_registry \
390
+ "$pkg_name" \
391
+ "$pkg_version" \
392
+ "$PACKAGE_SECONDARY_REGISTRY" \
393
+ "$PACKAGE_SECONDARY_TAG" \
394
+ "$PACKAGE_SECONDARY_TOKEN" \
395
+ "$PACKAGE_SECONDARY_PUBLISH_SKIP_IF_EXISTS" \
396
+ "$PACKAGE_SECONDARY_ACCESS" \
397
+ "$PACKAGE_SECONDARY_TOKEN_KEYS" \
398
+ "$PACKAGE_SECONDARY_OTP"
399
+ fi
400
+ fi
401
+ fi
402
+
403
+ log "Etapa package concluída."
404
+ }
405
+
406
+ run_assets_build_stage() {
407
+ if [ "$ASSET_BUILD_ENABLED" != "1" ]; then
408
+ log "Build de assets desativado (DEPLOY_BUILD_ASSETS=$ASSET_BUILD_ENABLED)."
409
+ return 0
410
+ fi
411
+
412
+ require_cmd npm
413
+ log "Compilando assets frontend: $ASSET_BUILD_CMD"
414
+ (
415
+ cd "$PROJECT_ROOT" &&
416
+ bash -lc "$ASSET_BUILD_CMD"
417
+ )
418
+ }
419
+
420
+ verify_build_outputs() {
421
+ if [ "$VERIFY_BUILD_OUTPUTS_ENABLED" != "1" ]; then
422
+ log "Validação de artefatos de build desativada (DEPLOY_VERIFY_BUILD_OUTPUTS=$VERIFY_BUILD_OUTPUTS_ENABLED)."
423
+ return 0
424
+ fi
425
+
426
+ log "Validando artefatos obrigatórios de build em $SOURCE_DIR"
427
+ (
428
+ SOURCE_DIR="$SOURCE_DIR" node --input-type=module <<'NODE'
429
+ import fs from 'node:fs';
430
+ import path from 'node:path';
431
+
432
+ const sourceDir = process.env.SOURCE_DIR;
433
+ if (!sourceDir) {
434
+ console.error('[deploy] SOURCE_DIR ausente para validação de build.');
435
+ process.exit(1);
436
+ }
437
+
438
+ const requiredJsBundles = [
439
+ 'home-react.bundle.js',
440
+ 'user-react.bundle.js',
441
+ 'login-react.bundle.js',
442
+ 'terms-react.bundle.js',
443
+ 'api-docs.bundle.js',
444
+ 'stickers-react.bundle.js',
445
+ 'create-pack-react.bundle.js',
446
+ 'stickers-admin.bundle.js',
447
+ 'user-systemadm.bundle.js',
448
+ ];
449
+ const requiredCssBundles = [
450
+ 'home-react.css',
451
+ 'user-react.css',
452
+ 'login-react.css',
453
+ 'terms-react.css',
454
+ 'api-docs.css',
455
+ 'stickers-react.css',
456
+ 'create-pack-react.css',
457
+ 'stickers-admin.css',
458
+ 'user-systemadm.css',
459
+ ];
460
+
461
+ const missing = [];
462
+ const empty = [];
463
+ const inspectFile = (absolutePath, label) => {
464
+ if (!fs.existsSync(absolutePath)) {
465
+ missing.push(label);
466
+ return;
467
+ }
468
+ const stats = fs.statSync(absolutePath);
469
+ if (!stats.isFile() || stats.size <= 0) {
470
+ empty.push(label);
471
+ }
472
+ };
473
+
474
+ for (const file of requiredJsBundles) {
475
+ inspectFile(path.join(sourceDir, 'assets', 'js', file), `assets/js/${file}`);
476
+ }
477
+ for (const file of requiredCssBundles) {
478
+ inspectFile(path.join(sourceDir, 'assets', 'css', file), `assets/css/${file}`);
479
+ }
480
+
481
+ if (missing.length > 0) {
482
+ console.error('[deploy] Artefatos ausentes após build:');
483
+ for (const item of missing) {
484
+ console.error(`[deploy] - ${item}`);
485
+ }
486
+ process.exit(1);
487
+ }
488
+
489
+ if (empty.length > 0) {
490
+ console.error('[deploy] Artefatos vazios ou inválidos após build:');
491
+ for (const item of empty) {
492
+ console.error(`[deploy] - ${item}`);
493
+ }
494
+ process.exit(1);
495
+ }
496
+
497
+ let chunkCount = 0;
498
+ const chunksDir = path.join(sourceDir, 'assets', 'js', 'chunks');
499
+ if (fs.existsSync(chunksDir)) {
500
+ const entries = fs.readdirSync(chunksDir, { withFileTypes: true });
501
+ chunkCount = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.js')).length;
502
+ }
503
+
504
+ console.log(`[deploy] Artefatos de build OK. js=${requiredJsBundles.length} css=${requiredCssBundles.length} chunks=${chunkCount}`);
505
+ NODE
506
+ )
507
+ }
508
+
509
+ verify_compiled_asset_refs() {
510
+ local verify_assets="${DEPLOY_VERIFY_ASSETS:-1}"
511
+ if [ "$verify_assets" != "1" ]; then
512
+ log "Verificação de assets desativada (DEPLOY_VERIFY_ASSETS=$verify_assets)."
513
+ return 0
514
+ fi
515
+
516
+ log "Validando referências de assets compilados em $SOURCE_DIR"
517
+ (
518
+ SOURCE_DIR="$SOURCE_DIR" node --input-type=module <<'NODE'
519
+ import fs from 'node:fs';
520
+ import path from 'node:path';
521
+
522
+ const sourceDir = process.env.SOURCE_DIR;
523
+ if (!sourceDir) {
524
+ console.error('[deploy] SOURCE_DIR ausente para validação de assets.');
525
+ process.exit(1);
526
+ }
527
+ if (!fs.existsSync(sourceDir)) {
528
+ console.error(`[deploy] SOURCE_DIR inexistente: ${sourceDir}`);
529
+ process.exit(1);
530
+ }
531
+
532
+ const htmlFiles = [];
533
+ const stack = [sourceDir];
534
+ while (stack.length > 0) {
535
+ const current = stack.pop();
536
+ const entries = fs.readdirSync(current, { withFileTypes: true });
537
+ for (const entry of entries) {
538
+ const fullPath = path.join(current, entry.name);
539
+ if (entry.isDirectory()) {
540
+ stack.push(fullPath);
541
+ continue;
542
+ }
543
+ if (entry.isFile() && entry.name.endsWith('.html')) {
544
+ htmlFiles.push(fullPath);
545
+ }
546
+ }
547
+ }
548
+
549
+ const refRegex = /\b(?:src|href)=["'](\/assets\/(?:css|js)\/[^"'?#]+)(?:\?[^"']*)?["']/g;
550
+ const missing = [];
551
+
552
+ for (const filePath of htmlFiles) {
553
+ const html = fs.readFileSync(filePath, 'utf8');
554
+ let match = null;
555
+ while ((match = refRegex.exec(html)) !== null) {
556
+ const assetRef = match[1];
557
+ const diskPath = path.join(sourceDir, assetRef.replace(/^\//, ''));
558
+ if (!fs.existsSync(diskPath)) {
559
+ missing.push({
560
+ file: path.relative(sourceDir, filePath),
561
+ asset: assetRef,
562
+ });
563
+ }
564
+ }
565
+ }
566
+
567
+ if (missing.length > 0) {
568
+ console.error('[deploy] Assets ausentes referenciados em HTML:');
569
+ for (const item of missing) {
570
+ console.error(`[deploy] - ${item.file} -> ${item.asset}`);
571
+ }
572
+ process.exit(1);
573
+ }
574
+
575
+ console.log('[deploy] Referências de assets compilados OK.');
576
+ NODE
577
+ )
578
+ }
579
+
580
+ verify_cache_bust_refs() {
581
+ local target_dir="$1"
582
+ local label="${2:-cache-bust}"
583
+ if [ "$VERIFY_CACHE_BUST_ENABLED" != "1" ]; then
584
+ log "Verificação de cache-bust desativada (DEPLOY_VERIFY_CACHE_BUST=$VERIFY_CACHE_BUST_ENABLED)."
585
+ return 0
586
+ fi
587
+
588
+ log "Validando cache-bust ($label) em $target_dir (v=$BUILD_ID)"
589
+ (
590
+ SOURCE_DIR="$target_dir" BUILD_ID="$BUILD_ID" REQUIRE_REFS="$REQUIRE_CACHE_BUST_REFERENCES" node --input-type=module <<'NODE'
591
+ import fs from 'node:fs';
592
+ import path from 'node:path';
593
+ import { URLSearchParams } from 'node:url';
594
+
595
+ const sourceDir = process.env.SOURCE_DIR;
596
+ const buildId = String(process.env.BUILD_ID || '').trim();
597
+ const requireRefs = String(process.env.REQUIRE_REFS || '1') === '1';
598
+ if (!sourceDir || !buildId) {
599
+ console.error('[deploy] SOURCE_DIR/BUILD_ID ausentes para validação de cache-bust.');
600
+ process.exit(1);
601
+ }
602
+ if (!fs.existsSync(sourceDir)) {
603
+ console.error(`[deploy] Diretório inexistente para validação de cache-bust: ${sourceDir}`);
604
+ process.exit(1);
605
+ }
606
+
607
+ const targetExtensions = new Set(['.html', '.js', '.mjs', '.css']);
608
+ const assetPathPattern = /\.(?:js|mjs|cjs|css|png|jpe?g|gif|svg|webp|ico|json|map|woff2?|ttf|eot)(?:\?[^"'#\s)]*)?(?:#[^"' \s)]*)?$/i;
609
+ const sourcePatterns = [
610
+ { regex: /\b(?:src|href|poster)=["']([^"']+)["']/gi, index: 1 },
611
+ { regex: /(["'])((?:\/|\.{1,2}\/)[^"'\s]+)\1/gi, index: 2 },
612
+ { regex: /url\(\s*["']?([^"')\s]+)["']?\s*\)/gi, index: 1 },
613
+ ];
614
+ const urlSchemePattern = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
615
+
616
+ const isLocalAssetPath = (assetPath) => {
617
+ const value = String(assetPath || '').trim();
618
+ if (!value) return false;
619
+ if (value.startsWith('//') || value.startsWith('#') || urlSchemePattern.test(value)) return false;
620
+ return value.startsWith('/') || value.startsWith('./') || value.startsWith('../');
621
+ };
622
+
623
+ const hasExpectedVersion = (assetPath) => {
624
+ const [withoutHash] = String(assetPath || '').split('#', 1);
625
+ const queryIndex = withoutHash.indexOf('?');
626
+ if (queryIndex < 0) return false;
627
+ const params = new URLSearchParams(withoutHash.slice(queryIndex + 1));
628
+ return params.get('v') === buildId;
629
+ };
630
+
631
+ const listFiles = [];
632
+ const stack = [sourceDir];
633
+ while (stack.length > 0) {
634
+ const current = stack.pop();
635
+ const entries = fs.readdirSync(current, { withFileTypes: true });
636
+ for (const entry of entries) {
637
+ const absolute = path.join(current, entry.name);
638
+ if (entry.isDirectory()) {
639
+ stack.push(absolute);
640
+ continue;
641
+ }
642
+ if (!entry.isFile()) continue;
643
+ if (targetExtensions.has(path.extname(absolute).toLowerCase())) {
644
+ listFiles.push(absolute);
645
+ }
646
+ }
647
+ }
648
+
649
+ let totalRefs = 0;
650
+ let versionedRefs = 0;
651
+ const missingVersion = [];
652
+
653
+ for (const filePath of listFiles) {
654
+ const source = fs.readFileSync(filePath, 'utf8');
655
+ const relativeFile = path.relative(sourceDir, filePath);
656
+ for (const { regex, index } of sourcePatterns) {
657
+ regex.lastIndex = 0;
658
+ let match = null;
659
+ while ((match = regex.exec(source)) !== null) {
660
+ const assetPath = String(match[index] || '').trim();
661
+ if (!assetPathPattern.test(assetPath)) continue;
662
+ if (!isLocalAssetPath(assetPath)) continue;
663
+ totalRefs += 1;
664
+ if (hasExpectedVersion(assetPath)) {
665
+ versionedRefs += 1;
666
+ continue;
667
+ }
668
+ if (missingVersion.length < 80) {
669
+ missingVersion.push({ file: relativeFile, asset: assetPath });
670
+ }
671
+ }
672
+ }
673
+ }
674
+
675
+ if (requireRefs && totalRefs === 0) {
676
+ console.error('[deploy] Nenhuma referência local de asset encontrada para validar cache-bust.');
677
+ process.exit(1);
678
+ }
679
+
680
+ if (missingVersion.length > 0) {
681
+ console.error(`[deploy] Referências sem ?v=${buildId}:`);
682
+ for (const item of missingVersion) {
683
+ console.error(`[deploy] - ${item.file} -> ${item.asset}`);
684
+ }
685
+ process.exit(1);
686
+ }
687
+
688
+ console.log(`[deploy] Cache-bust validado. refs=${totalRefs} versioned=${versionedRefs} build_id=${buildId}`);
689
+ NODE
690
+ )
691
+ }
692
+
693
+ prepare_legacy_static_entrypoints() {
694
+ local target_dir="$1"
695
+ local pages_dir="$target_dir/pages"
696
+ if [ ! -d "$pages_dir" ]; then
697
+ log "Pasta pages ausente em $target_dir; aliases legados nao serao gerados."
698
+ return 0
699
+ fi
700
+
701
+ log "Gerando aliases estaticos legados em $target_dir"
702
+
703
+ copy_page_alias() {
704
+ local source_name="$1"
705
+ local dest_relative="$2"
706
+ local source_path="$pages_dir/$source_name"
707
+ local dest_path="$target_dir/$dest_relative"
708
+
709
+ if [ ! -f "$source_path" ]; then
710
+ log "Aviso: template ausente para alias legado ($source_name)."
711
+ return 0
712
+ fi
713
+
714
+ mkdir -p "$(dirname "$dest_path")"
715
+ cp "$source_path" "$dest_path"
716
+ }
717
+
718
+ copy_page_alias "home.html" "index.html"
719
+ copy_page_alias "api-docs.html" "api-docs/index.html"
720
+ copy_page_alias "aup.html" "aup/index.html"
721
+ copy_page_alias "comandos.html" "comandos/index.html"
722
+ copy_page_alias "dpa.html" "dpa/index.html"
723
+ copy_page_alias "licenca.html" "licenca/index.html"
724
+ copy_page_alias "notice-and-takedown.html" "notice-and-takedown/index.html"
725
+ copy_page_alias "politica-de-privacidade.html" "politica-de-privacidade/index.html"
726
+ copy_page_alias "suboperadores.html" "suboperadores/index.html"
727
+ copy_page_alias "termos-de-uso.html" "termos-de-uso/index.html"
728
+ copy_page_alias "termos-de-uso-texto-integral.html" "termos-de-uso/texto-integral/index.html"
729
+ copy_page_alias "termos-de-uso-texto-integral.html" "termos-de-uso/texto-integral.html"
730
+ copy_page_alias "login.html" "login/index.html"
731
+ copy_page_alias "user.html" "user/index.html"
732
+ copy_page_alias "user-password-reset.html" "user/password-reset/index.html"
733
+ copy_page_alias "user-systemadm.html" "user/systemadm/index.html"
734
+ copy_page_alias "stickers.html" "stickers/index.html"
735
+ copy_page_alias "stickers-admin.html" "stickers/admin/index.html"
736
+ copy_page_alias "stickers-create.html" "stickers/create/index.html"
737
+
738
+ for seo_template in "$pages_dir"/seo-*.html; do
739
+ if [ ! -f "$seo_template" ]; then
740
+ continue
741
+ fi
742
+
743
+ local seo_slug=""
744
+ seo_slug="$(basename "$seo_template")"
745
+ seo_slug="${seo_slug#seo-}"
746
+ seo_slug="${seo_slug%.html}"
747
+ if [ -z "$seo_slug" ]; then
748
+ continue
749
+ fi
750
+
751
+ local seo_dest_dir="$target_dir/seo/$seo_slug"
752
+ mkdir -p "$seo_dest_dir"
753
+ cp "$seo_template" "$seo_dest_dir/index.html"
754
+ done
755
+ }
756
+
757
+ verify_post_sync_cache_bust() {
758
+ if [ "$VERIFY_POST_SYNC_CACHE_BUST_ENABLED" != "1" ]; then
759
+ log "Verificação pós-sync de cache-bust desativada (DEPLOY_VERIFY_POST_SYNC_CACHE_BUST=$VERIFY_POST_SYNC_CACHE_BUST_ENABLED)."
760
+ return 0
761
+ fi
762
+
763
+ log "Validando cache-bust em páginas-chave no diretório de deploy"
764
+ local sample_files=(
765
+ "pages/home.html"
766
+ "pages/login.html"
767
+ "pages/user.html"
768
+ "pages/user-password-reset.html"
769
+ "pages/termos-de-uso.html"
770
+ "pages/stickers.html"
771
+ "pages/stickers-create.html"
772
+ "pages/stickers-admin.html"
773
+ "pages/api-docs.html"
774
+ "pages/user-systemadm.html"
775
+ )
776
+ local missing_samples=()
777
+
778
+ for sample in "${sample_files[@]}"; do
779
+ local absolute_path="$DEPLOY_DIR/$sample"
780
+ if [ ! -f "$absolute_path" ]; then
781
+ continue
782
+ fi
783
+
784
+ if command -v rg >/dev/null 2>&1; then
785
+ if ! rg -q "/assets/(css|js)/[^\"']*\\?[^\"']*v=${BUILD_ID}" "$absolute_path"; then
786
+ missing_samples+=("$sample")
787
+ fi
788
+ else
789
+ if ! grep -Eq "/assets/(css|js)/[^\"']*\\?[^\"']*v=${BUILD_ID}" "$absolute_path"; then
790
+ missing_samples+=("$sample")
791
+ fi
792
+ fi
793
+ done
794
+
795
+ if [ "${#missing_samples[@]}" -gt 0 ]; then
796
+ printf '[deploy] páginas sem marker de cache-bust esperado (v=%s):\n' "$BUILD_ID" >&2
797
+ for sample in "${missing_samples[@]}"; do
798
+ printf '[deploy] - %s\n' "$sample" >&2
799
+ done
800
+ exit 1
801
+ fi
802
+
803
+ log "Cache-bust pós-sync validado nas páginas-chave."
804
+ }
805
+
806
+ finalize() {
807
+ local exit_code=$?
808
+ if [ "$exit_code" -eq 0 ]; then
809
+ github_deploy_status "success"
810
+ else
811
+ github_deploy_status "failure"
812
+ fi
813
+ for npmrc_tmp in "${NPMRC_TMP_FILES[@]:-}"; do
814
+ if [ -n "$npmrc_tmp" ] && [ -f "$npmrc_tmp" ]; then
815
+ rm -f "$npmrc_tmp"
816
+ fi
817
+ done
818
+ rm -rf "$STAGING_DIR"
819
+ }
820
+ trap finalize EXIT
821
+
822
+ log "build_id=$BUILD_ID"
823
+ if [ "$BACKEND_CACHE_BUST_ENABLED" = "1" ]; then
824
+ log "Cache-bust backend ativo: ${BACKEND_BUILD_ID_ENV}=${BACKEND_BUILD_ID_VALUE} | ${BACKEND_ASSET_VERSION_ENV}=${BACKEND_ASSET_VERSION_VALUE}"
825
+ else
826
+ log "Cache-bust backend desativado (DEPLOY_BACKEND_CACHE_BUST_ENABLED=$BACKEND_CACHE_BUST_ENABLED)."
827
+ fi
828
+ run_assets_build_stage
829
+ verify_build_outputs
830
+ verify_compiled_asset_refs
831
+ log "Preparando staging em $STAGING_DIR"
832
+ rsync -a --delete "$SOURCE_DIR"/ "$STAGING_DIR"/
833
+ log "Aplicando cache-bust de frontend (versão=$BUILD_ID)"
834
+ CACHE_BUST_OUTPUT="$(node "$PROJECT_ROOT/scripts/cache-bust.mjs" --dir "$STAGING_DIR" --version "$BUILD_ID")"
835
+ printf '%s\n' "$CACHE_BUST_OUTPUT"
836
+ if [ "$REQUIRE_CACHE_BUST_REFERENCES" = "1" ]; then
837
+ CACHE_BUST_REFS="$(printf '%s' "$CACHE_BUST_OUTPUT" | sed -nE 's/.* refs=([0-9]+).*/\1/p' | tail -n 1)"
838
+ if [ -z "$CACHE_BUST_REFS" ] || ! [[ "$CACHE_BUST_REFS" =~ ^[0-9]+$ ]]; then
839
+ printf '[deploy] Não foi possível validar o total de refs do cache-bust.\n' >&2
840
+ exit 1
841
+ fi
842
+ if [ "$CACHE_BUST_REFS" -le 0 ]; then
843
+ printf '[deploy] Cache-bust não alterou nenhuma referência (refs=%s).\n' "$CACHE_BUST_REFS" >&2
844
+ exit 1
845
+ fi
846
+ fi
847
+ prepare_legacy_static_entrypoints "$STAGING_DIR"
848
+ verify_cache_bust_refs "$STAGING_DIR" "staging"
849
+
850
+ if [ "$BACKUP_ENABLED" = "1" ] && [ "$DRY_RUN" != "1" ] && [ -d "$DEPLOY_DIR" ]; then
851
+ BACKUP_STAMP="$(date -u +%Y%m%d-%H%M%S)"
852
+ BACKUP_PATH="$BACKUP_DIR/$BACKUP_STAMP"
853
+ log "Criando backup em $BACKUP_PATH"
854
+ as_root mkdir -p "$BACKUP_PATH"
855
+ as_root rsync -a --delete --exclude '.backup/' "$DEPLOY_DIR"/ "$BACKUP_PATH"/
856
+ fi
857
+
858
+ if [ ! -d "$DEPLOY_DIR" ]; then
859
+ log "Criando diretório de deploy $DEPLOY_DIR"
860
+ as_root mkdir -p "$DEPLOY_DIR"
861
+ fi
862
+
863
+ if [ "$DRY_RUN" = "1" ]; then
864
+ log "DRY_RUN=1 ativo. Simulando sync para $DEPLOY_DIR"
865
+ as_root rsync -avhn --delete --exclude '.backup/' "$STAGING_DIR"/ "$DEPLOY_DIR"/
866
+ log "Dry-run finalizado."
867
+ exit 0
868
+ fi
869
+
870
+ github_deploy_start
871
+ run_package_stage
872
+
873
+ log "Sincronizando arquivos para $DEPLOY_DIR"
874
+ as_root rsync -a --delete --exclude '.backup/' "$STAGING_DIR"/ "$DEPLOY_DIR"/
875
+ verify_post_sync_cache_bust
876
+
877
+ log "Validando configuração do nginx"
878
+ as_root nginx -t
879
+
880
+ log "Recarregando serviço $NGINX_SERVICE"
881
+ as_root systemctl reload "$NGINX_SERVICE"
882
+ as_root systemctl is-active --quiet "$NGINX_SERVICE"
883
+
884
+ if [ "$RESTART_PM2" = "1" ] && command -v pm2 >/dev/null 2>&1; then
885
+ if as_root pm2 describe "$PM2_APP_NAME" >/dev/null 2>&1; then
886
+ log "Reiniciando PM2 app $PM2_APP_NAME"
887
+ if [ "${#PM2_RESTART_ENV_ARGS[@]}" -gt 0 ]; then
888
+ as_root env "${PM2_RESTART_ENV_ARGS[@]}" pm2 restart "$PM2_APP_NAME" --update-env >/dev/null
889
+ else
890
+ as_root pm2 restart "$PM2_APP_NAME" --update-env >/dev/null
891
+ fi
892
+ else
893
+ log "PM2 app '$PM2_APP_NAME' não encontrada. Restart ignorado."
894
+ fi
895
+ fi
896
+
897
+ if [ -f "$DEPLOY_DIR/pages/home.html" ]; then
898
+ if command -v rg >/dev/null 2>&1; then
899
+ DEPLOYED_REF="$(rg -o '/assets/js/home-react\\.bundle\\.js\\?v=[^"]+' "$DEPLOY_DIR/pages/home.html" -m 1 || true)"
900
+ else
901
+ DEPLOYED_REF="$(grep -oE '/assets/js/home-react\\.bundle\\.js\\?v=[^"]+' "$DEPLOY_DIR/pages/home.html" | head -n 1 || true)"
902
+ fi
903
+ if [ -n "$DEPLOYED_REF" ]; then
904
+ log "Cache-bust aplicado: $DEPLOYED_REF"
905
+ fi
906
+ fi
907
+
908
+ if command -v curl >/dev/null 2>&1; then
909
+ if curl -kfsS "$VERIFY_URL" >/dev/null; then
910
+ log "Health check OK em $VERIFY_URL"
911
+ else
912
+ log "Health check falhou em $VERIFY_URL (deploy concluído, verifique manualmente)."
913
+ fi
914
+ fi
915
+
916
+ log "Deploy concluído com sucesso."