@oneuptime/common 13.0.8 → 14.0.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 (333) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +23 -15
  2. package/Models/DatabaseModels/GlobalConfig.ts +24 -0
  3. package/Models/DatabaseModels/Monitor.ts +3 -2
  4. package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
  5. package/Scripts/generate-service-worker.js +65 -4
  6. package/Server/API/BaseAPI.ts +26 -5
  7. package/Server/API/GlobalConfigAPI.ts +228 -462
  8. package/Server/API/ProjectOIDC.ts +25 -16
  9. package/Server/API/ProjectSSO.ts +24 -16
  10. package/Server/API/StatusPageAPI.ts +100 -42
  11. package/Server/API/TeamMemberAPI.ts +10 -1
  12. package/Server/Enterprise/EnterpriseEdition.ts +649 -0
  13. package/Server/Enterprise/EnterpriseFeature.ts +48 -0
  14. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
  15. package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
  16. package/Server/EnvironmentConfig.ts +147 -2
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  19. package/Server/Middleware/UserAuthorization.ts +38 -16
  20. package/Server/Services/AuditLogService.ts +84 -1079
  21. package/Server/Services/EnterpriseLicenseService.ts +38 -0
  22. package/Server/Services/GlobalConfigService.ts +80 -1
  23. package/Server/Services/MonitorProbeService.ts +116 -20
  24. package/Server/Services/MonitorService.ts +56 -4
  25. package/Server/Services/MonitorStatusTimelineService.ts +284 -0
  26. package/Server/Services/MonitorTemplateService.ts +26 -0
  27. package/Server/Services/ProjectService.ts +284 -0
  28. package/Server/Services/RumSessionPinService.ts +25 -6
  29. package/Server/Services/StatusPagePrivateUserService.ts +7 -3
  30. package/Server/Services/StatusPageService.ts +78 -1
  31. package/Server/Services/TeamMemberService.ts +11 -0
  32. package/Server/Services/TeamService.ts +10 -0
  33. package/Server/Services/UserService.ts +10 -17
  34. package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
  35. package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
  36. package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
  37. package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
  38. package/Server/Types/Markdown.ts +41 -0
  39. package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
  40. package/Server/Utils/EditionEnforcement.ts +233 -0
  41. package/Server/Utils/FrontendEnvironment.ts +41 -2
  42. package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
  43. package/Server/Utils/Monitor/AffectedResourceList.ts +190 -0
  44. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
  45. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +543 -333
  46. package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
  47. package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
  48. package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
  49. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
  50. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
  51. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
  52. package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
  53. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
  54. package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
  55. package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
  56. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
  57. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
  58. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
  59. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
  60. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
  61. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
  62. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
  63. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
  64. package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
  65. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
  66. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
  67. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
  68. package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
  69. package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
  70. package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
  71. package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
  72. package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
  73. package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
  74. package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
  75. package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
  76. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
  77. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
  78. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
  79. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
  80. package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
  81. package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
  82. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
  83. package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
  84. package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
  85. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
  86. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
  87. package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
  88. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
  89. package/Tests/Server/Services/AuditLogService.test.ts +460 -990
  90. package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
  91. package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
  92. package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
  93. package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
  95. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
  96. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
  97. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
  98. package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
  99. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
  100. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
  101. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
  102. package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
  103. package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
  104. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
  105. package/Tests/Server/Types/Markdown.test.ts +181 -0
  106. package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
  107. package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
  108. package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
  109. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
  110. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
  111. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
  112. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +934 -96
  113. package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
  114. package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
  115. package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
  116. package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
  117. package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
  118. package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
  119. package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
  120. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
  121. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
  122. package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
  123. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
  124. package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
  125. package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
  126. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
  127. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
  128. package/Tests/Utils/HostAddressUtil.test.ts +138 -0
  129. package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
  130. package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
  131. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
  132. package/Types/API/Hostname.ts +63 -16
  133. package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
  134. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
  135. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
  136. package/Types/Database/CanonicalModelInstance.ts +102 -0
  137. package/Types/Database/TableColumn.ts +20 -6
  138. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
  139. package/Types/IP/IPv6.ts +6 -1
  140. package/Types/Monitor/MonitorStep.ts +6 -1
  141. package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
  142. package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
  143. package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
  144. package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
  145. package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
  146. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  147. package/UI/Components/ModelList/ModelList.tsx +11 -1
  148. package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
  149. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
  150. package/UI/esbuild-enterprise.js +263 -0
  151. package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
  152. package/Utils/HostAddressUtil.ts +94 -0
  153. package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
  154. package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
  155. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
  156. package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
  157. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
  159. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
  161. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  162. package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
  163. package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
  164. package/build/dist/Server/API/BaseAPI.js +26 -5
  165. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  166. package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
  167. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  168. package/build/dist/Server/API/ProjectOIDC.js +24 -16
  169. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  170. package/build/dist/Server/API/ProjectSSO.js +24 -16
  171. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  172. package/build/dist/Server/API/StatusPageAPI.js +93 -43
  173. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  174. package/build/dist/Server/API/TeamMemberAPI.js +8 -1
  175. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  176. package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
  177. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
  178. package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
  179. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
  180. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
  181. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
  182. package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
  183. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
  184. package/build/dist/Server/EnvironmentConfig.js +84 -2
  185. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  190. package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
  191. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  192. package/build/dist/Server/Services/AuditLogService.js +71 -740
  193. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  194. package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
  195. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  196. package/build/dist/Server/Services/GlobalConfigService.js +63 -0
  197. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  198. package/build/dist/Server/Services/MonitorProbeService.js +90 -17
  199. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  200. package/build/dist/Server/Services/MonitorService.js +45 -4
  201. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  202. package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
  203. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  204. package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
  205. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  206. package/build/dist/Server/Services/ProjectService.js +214 -0
  207. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  208. package/build/dist/Server/Services/RumSessionPinService.js +24 -6
  209. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  210. package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
  211. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  212. package/build/dist/Server/Services/StatusPageService.js +71 -0
  213. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  214. package/build/dist/Server/Services/TeamMemberService.js +10 -0
  215. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  216. package/build/dist/Server/Services/TeamService.js +9 -0
  217. package/build/dist/Server/Services/TeamService.js.map +1 -1
  218. package/build/dist/Server/Services/UserService.js +10 -16
  219. package/build/dist/Server/Services/UserService.js.map +1 -1
  220. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
  221. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  222. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
  223. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  224. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
  225. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  226. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
  227. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  228. package/build/dist/Server/Types/Markdown.js +31 -0
  229. package/build/dist/Server/Types/Markdown.js.map +1 -1
  230. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
  231. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
  232. package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
  233. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
  234. package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
  235. package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
  236. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
  237. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
  238. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +96 -0
  239. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
  240. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
  241. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  242. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +449 -261
  243. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  244. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
  245. package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
  246. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
  247. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  248. package/build/dist/Types/API/Hostname.js +59 -11
  249. package/build/dist/Types/API/Hostname.js.map +1 -1
  250. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
  251. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  252. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
  253. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  254. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
  255. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
  256. package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
  257. package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
  258. package/build/dist/Types/Database/TableColumn.js +19 -6
  259. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  260. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
  261. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
  262. package/build/dist/Types/IP/IPv6.js +6 -1
  263. package/build/dist/Types/IP/IPv6.js.map +1 -1
  264. package/build/dist/Types/Monitor/MonitorStep.js +5 -0
  265. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  266. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
  267. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
  268. package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
  269. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
  270. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  271. package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
  272. package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
  273. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
  274. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  275. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  276. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  277. package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
  278. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  279. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
  280. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  281. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
  282. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  283. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
  284. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
  285. package/build/dist/Utils/HostAddressUtil.js +80 -0
  286. package/build/dist/Utils/HostAddressUtil.js.map +1 -0
  287. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
  288. package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
  289. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
  290. package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
  291. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  292. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
  293. package/jest.config.json +3 -1
  294. package/package.json +1 -1
  295. package/test-setup.sh +103 -34
  296. package/tsconfig.json +13 -0
  297. package/Server/API/EnterpriseLicenseAPI.ts +0 -925
  298. package/Server/API/TeamComplianceAPI.ts +0 -158
  299. package/Server/Middleware/SCIMAuthorization.ts +0 -141
  300. package/Server/Services/TeamComplianceService.ts +0 -733
  301. package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
  302. package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
  303. package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
  304. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
  305. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
  306. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
  307. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
  308. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
  309. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
  310. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
  311. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
  312. package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
  313. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
  314. package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
  315. package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
  316. package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
  317. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
  318. package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
  319. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
  320. package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
  321. package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
  322. package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
  323. package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
  324. package/build/dist/Server/Services/TeamComplianceService.js +0 -537
  325. package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
  326. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
  327. package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
  328. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
  329. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
  330. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
  331. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
  332. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
  333. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
@@ -0,0 +1,314 @@
1
+ import { afterEach, describe, expect, it, jest } from "@jest/globals";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import type URL from "../../Types/API/URL";
5
+
6
+ /*
7
+ * The environment variables behind the Community / Enterprise split:
8
+ * ONEUPTIME_EDITION, ONEUPTIME_EE_DIR, ALLOW_BILLING_WITHOUT_ENTERPRISE and
9
+ * ENTERPRISE_LICENSE_SERVER_URL (which the two license-server endpoint URLs
10
+ * derive from).
11
+ *
12
+ * EnvironmentConfig reads process.env at module load, so every case here
13
+ * sets the environment, resets the module registry and imports afresh.
14
+ */
15
+
16
+ type OneUptimeEditionSetting = "auto" | "community" | "enterprise";
17
+
18
+ interface EnvironmentConfigShape {
19
+ OneUptimeEdition: OneUptimeEditionSetting | null;
20
+ ONEUPTIME_EDITION_SETTINGS: ReadonlyArray<OneUptimeEditionSetting>;
21
+ parseOneUptimeEdition: (
22
+ rawValue: string | undefined,
23
+ ) => OneUptimeEditionSetting | null;
24
+ OneUptimeEnterpriseDirectory: string | undefined;
25
+ AllowBillingWithoutEnterprise: boolean;
26
+ IsEnterpriseEdition: boolean;
27
+ DEFAULT_ENTERPRISE_LICENSE_SERVER_URL: string;
28
+ resolveEnterpriseLicenseServerUrl: (
29
+ rawValue: string | undefined,
30
+ allowInsecure: boolean,
31
+ ) => string;
32
+ EnterpriseLicenseServerUrl: URL;
33
+ EnterpriseLicenseValidationUrl: URL;
34
+ EnterpriseLicenseUserCountReportUrl: URL;
35
+ }
36
+
37
+ const MANAGED_KEYS: Array<string> = [
38
+ "ONEUPTIME_EDITION",
39
+ "ONEUPTIME_EE_DIR",
40
+ "ALLOW_BILLING_WITHOUT_ENTERPRISE",
41
+ "ENTERPRISE_LICENSE_SERVER_URL",
42
+ "IS_ENTERPRISE_EDITION",
43
+ "ENVIRONMENT",
44
+ ];
45
+
46
+ const originalEnv: NodeJS.ProcessEnv = { ...process.env };
47
+
48
+ async function load(
49
+ overrides: Record<string, string | undefined>,
50
+ ): Promise<EnvironmentConfigShape> {
51
+ for (const key of MANAGED_KEYS) {
52
+ delete process.env[key];
53
+ }
54
+
55
+ for (const [key, value] of Object.entries(overrides)) {
56
+ if (value === undefined) {
57
+ delete process.env[key];
58
+ } else {
59
+ process.env[key] = value;
60
+ }
61
+ }
62
+
63
+ jest.resetModules();
64
+
65
+ return (await import(
66
+ "../../Server/EnvironmentConfig"
67
+ )) as unknown as EnvironmentConfigShape;
68
+ }
69
+
70
+ afterEach(() => {
71
+ process.env = { ...originalEnv };
72
+ jest.resetModules();
73
+ });
74
+
75
+ describe("ONEUPTIME_EDITION", () => {
76
+ it("defaults to auto when unset", async () => {
77
+ const config: EnvironmentConfigShape = await load({});
78
+
79
+ expect(config.OneUptimeEdition).toBe("auto");
80
+ });
81
+
82
+ it("reads a blank value as auto", async () => {
83
+ const config: EnvironmentConfigShape = await load({
84
+ ONEUPTIME_EDITION: " ",
85
+ });
86
+
87
+ expect(config.OneUptimeEdition).toBe("auto");
88
+ });
89
+
90
+ it.each([
91
+ "auto",
92
+ "community",
93
+ "enterprise",
94
+ ] as Array<OneUptimeEditionSetting>)(
95
+ "accepts %s",
96
+ async (edition: OneUptimeEditionSetting) => {
97
+ const config: EnvironmentConfigShape = await load({
98
+ ONEUPTIME_EDITION: edition,
99
+ });
100
+
101
+ expect(config.OneUptimeEdition).toBe(edition);
102
+ },
103
+ );
104
+
105
+ it("is case- and whitespace-insensitive", async () => {
106
+ const config: EnvironmentConfigShape = await load({
107
+ ONEUPTIME_EDITION: " Enterprise \n",
108
+ });
109
+
110
+ expect(config.OneUptimeEdition).toBe("enterprise");
111
+ });
112
+
113
+ it.each(["enterprize", "ce", "ee", "true", "1", "communityedition"])(
114
+ "reports an unrecognised value (%s) as null so the loader can refuse to boot",
115
+ async (rawValue: string) => {
116
+ const config: EnvironmentConfigShape = await load({
117
+ ONEUPTIME_EDITION: rawValue,
118
+ });
119
+
120
+ expect(config.OneUptimeEdition).toBeNull();
121
+ },
122
+ );
123
+
124
+ it("lists exactly the three settings", async () => {
125
+ const config: EnvironmentConfigShape = await load({});
126
+
127
+ expect([...config.ONEUPTIME_EDITION_SETTINGS]).toEqual([
128
+ "auto",
129
+ "community",
130
+ "enterprise",
131
+ ]);
132
+ });
133
+
134
+ it("parseOneUptimeEdition is a pure function of its argument", async () => {
135
+ const config: EnvironmentConfigShape = await load({
136
+ ONEUPTIME_EDITION: "community",
137
+ });
138
+
139
+ expect(config.parseOneUptimeEdition(undefined)).toBe("auto");
140
+ expect(config.parseOneUptimeEdition("")).toBe("auto");
141
+ expect(config.parseOneUptimeEdition("COMMUNITY")).toBe("community");
142
+ expect(config.parseOneUptimeEdition("nope")).toBeNull();
143
+ });
144
+
145
+ it("does not change what IS_ENTERPRISE_EDITION means", async () => {
146
+ const enterprise: EnvironmentConfigShape = await load({
147
+ IS_ENTERPRISE_EDITION: "true",
148
+ ONEUPTIME_EDITION: "community",
149
+ });
150
+
151
+ expect(enterprise.IsEnterpriseEdition).toBe(true);
152
+ expect(enterprise.OneUptimeEdition).toBe("community");
153
+
154
+ const community: EnvironmentConfigShape = await load({
155
+ ONEUPTIME_EDITION: "enterprise",
156
+ });
157
+
158
+ expect(community.IsEnterpriseEdition).toBe(false);
159
+ });
160
+ });
161
+
162
+ describe("ONEUPTIME_EE_DIR", () => {
163
+ it("is undefined when unset or blank", async () => {
164
+ expect((await load({})).OneUptimeEnterpriseDirectory).toBeUndefined();
165
+ expect(
166
+ (await load({ ONEUPTIME_EE_DIR: " " })).OneUptimeEnterpriseDirectory,
167
+ ).toBeUndefined();
168
+ });
169
+
170
+ it("is the trimmed path when set", async () => {
171
+ const config: EnvironmentConfigShape = await load({
172
+ ONEUPTIME_EE_DIR: " /opt/fixtures/ee \n",
173
+ });
174
+
175
+ expect(config.OneUptimeEnterpriseDirectory).toBe("/opt/fixtures/ee");
176
+ });
177
+ });
178
+
179
+ describe("ALLOW_BILLING_WITHOUT_ENTERPRISE", () => {
180
+ it("is off unless exactly true", async () => {
181
+ expect((await load({})).AllowBillingWithoutEnterprise).toBe(false);
182
+ expect(
183
+ (await load({ ALLOW_BILLING_WITHOUT_ENTERPRISE: "1" }))
184
+ .AllowBillingWithoutEnterprise,
185
+ ).toBe(false);
186
+ expect(
187
+ (await load({ ALLOW_BILLING_WITHOUT_ENTERPRISE: "TRUE" }))
188
+ .AllowBillingWithoutEnterprise,
189
+ ).toBe(false);
190
+ expect(
191
+ (await load({ ALLOW_BILLING_WITHOUT_ENTERPRISE: "true" }))
192
+ .AllowBillingWithoutEnterprise,
193
+ ).toBe(true);
194
+ });
195
+ });
196
+
197
+ describe("ENTERPRISE_LICENSE_SERVER_URL", () => {
198
+ it("defaults to oneuptime.com and keeps the historical endpoint URLs byte-identical", async () => {
199
+ const config: EnvironmentConfigShape = await load({});
200
+
201
+ expect(config.DEFAULT_ENTERPRISE_LICENSE_SERVER_URL).toBe(
202
+ "https://oneuptime.com",
203
+ );
204
+ // The URL type renders a bare origin with its root path.
205
+ expect(config.EnterpriseLicenseServerUrl.toString()).toBe(
206
+ "https://oneuptime.com/",
207
+ );
208
+ expect(config.EnterpriseLicenseValidationUrl.toString()).toBe(
209
+ "https://oneuptime.com/api/enterprise-license/validate",
210
+ );
211
+ expect(config.EnterpriseLicenseUserCountReportUrl.toString()).toBe(
212
+ "https://oneuptime.com/api/enterprise-license/report-user-count",
213
+ );
214
+ });
215
+
216
+ it("derives both endpoints from an https override", async () => {
217
+ const config: EnvironmentConfigShape = await load({
218
+ ENTERPRISE_LICENSE_SERVER_URL: "https://licenses.staging.example.com/",
219
+ ENVIRONMENT: "production",
220
+ });
221
+
222
+ expect(config.EnterpriseLicenseServerUrl.toString()).toBe(
223
+ "https://licenses.staging.example.com/",
224
+ );
225
+ expect(config.EnterpriseLicenseValidationUrl.toString()).toBe(
226
+ "https://licenses.staging.example.com/api/enterprise-license/validate",
227
+ );
228
+ expect(config.EnterpriseLicenseUserCountReportUrl.toString()).toBe(
229
+ "https://licenses.staging.example.com/api/enterprise-license/report-user-count",
230
+ );
231
+ });
232
+
233
+ it("keeps a path prefix on the override", async () => {
234
+ const config: EnvironmentConfigShape = await load({
235
+ ENTERPRISE_LICENSE_SERVER_URL: "https://relay.example.com/oneuptime//",
236
+ ENVIRONMENT: "production",
237
+ });
238
+
239
+ expect(config.EnterpriseLicenseValidationUrl.toString()).toBe(
240
+ "https://relay.example.com/oneuptime/api/enterprise-license/validate",
241
+ );
242
+ });
243
+
244
+ it("refuses plain http in production (the license key would travel in the clear)", async () => {
245
+ const config: EnvironmentConfigShape = await load({
246
+ ENTERPRISE_LICENSE_SERVER_URL: "http://licenses.example.com",
247
+ ENVIRONMENT: "production",
248
+ });
249
+
250
+ expect(config.EnterpriseLicenseValidationUrl.toString()).toBe(
251
+ "https://oneuptime.com/api/enterprise-license/validate",
252
+ );
253
+ });
254
+
255
+ it.each(["development", "test"])(
256
+ "allows plain http in %s",
257
+ async (environment: string) => {
258
+ const config: EnvironmentConfigShape = await load({
259
+ ENTERPRISE_LICENSE_SERVER_URL: "http://localhost:3002",
260
+ ENVIRONMENT: environment,
261
+ });
262
+
263
+ expect(config.EnterpriseLicenseValidationUrl.toString()).toBe(
264
+ "http://localhost:3002/api/enterprise-license/validate",
265
+ );
266
+ },
267
+ );
268
+
269
+ it("resolveEnterpriseLicenseServerUrl falls back to the default for unusable values", async () => {
270
+ const config: EnvironmentConfigShape = await load({});
271
+ const resolve: EnvironmentConfigShape["resolveEnterpriseLicenseServerUrl"] =
272
+ config.resolveEnterpriseLicenseServerUrl;
273
+
274
+ expect(resolve(undefined, false)).toBe("https://oneuptime.com");
275
+ expect(resolve("", true)).toBe("https://oneuptime.com");
276
+ expect(resolve(" ", true)).toBe("https://oneuptime.com");
277
+ expect(resolve("licenses.example.com", true)).toBe("https://oneuptime.com");
278
+ expect(resolve("ftp://licenses.example.com", true)).toBe(
279
+ "https://oneuptime.com",
280
+ );
281
+ expect(resolve("javascript:alert(1)", true)).toBe("https://oneuptime.com");
282
+ expect(resolve("http://licenses.example.com", false)).toBe(
283
+ "https://oneuptime.com",
284
+ );
285
+ expect(resolve("https://", true)).toBe("https://oneuptime.com");
286
+ });
287
+
288
+ it("resolveEnterpriseLicenseServerUrl keeps usable values, minus trailing slashes", async () => {
289
+ const config: EnvironmentConfigShape = await load({});
290
+ const resolve: EnvironmentConfigShape["resolveEnterpriseLicenseServerUrl"] =
291
+ config.resolveEnterpriseLicenseServerUrl;
292
+
293
+ expect(resolve("https://licenses.example.com", false)).toBe(
294
+ "https://licenses.example.com",
295
+ );
296
+ expect(resolve(" HTTPS://Licenses.Example.com/// ", false)).toBe(
297
+ "HTTPS://Licenses.Example.com",
298
+ );
299
+ expect(resolve("http://localhost:3002/", true)).toBe(
300
+ "http://localhost:3002",
301
+ );
302
+ });
303
+ });
304
+
305
+ describe("EnvironmentConfig import hygiene", () => {
306
+ it("EnvironmentConfig never imports the enterprise module (no import cycle)", () => {
307
+ const source: string = fs.readFileSync(
308
+ path.join(__dirname, "../../Server/EnvironmentConfig.ts"),
309
+ "utf8",
310
+ );
311
+
312
+ expect(source).not.toContain("./Enterprise/");
313
+ });
314
+ });
@@ -59,10 +59,26 @@ interface ServiceImage {
59
59
  * we must not confuse with the production one.
60
60
  */
61
61
  productionStanza: string;
62
+ /*
63
+ * The whole Dockerfile as it renders for production: everything before the
64
+ * development branch plus the production stanza. Stages defined before the
65
+ * branch (a shared `base`) belong to the production build too.
66
+ */
67
+ productionDockerfile: string;
62
68
  // The `start` script the production CMD ultimately runs, if it runs one.
63
69
  startScript: string | null;
64
70
  }
65
71
 
72
+ // One `FROM` stage of a Dockerfile.
73
+ interface DockerfileStage {
74
+ // The `AS <name>`, lower-cased, or null for an unnamed stage.
75
+ name: string | null;
76
+ // What it is built FROM: an image or an earlier stage's name (lower-cased).
77
+ from: string;
78
+ // The instructions after the FROM line, up to the next FROM.
79
+ body: string;
80
+ }
81
+
66
82
  // CMD forms that hand control to `npm start`.
67
83
  const CMD_NPM_START_EXEC_FORM: RegExp = /CMD\s*\[\s*"npm"\s*,\s*"start"\s*\]/;
68
84
  const CMD_NPM_START_SHELL_FORM: RegExp = /CMD\s+npm\s+start/;
@@ -80,6 +96,81 @@ const RUN_COMPILE_INSTRUCTION: RegExp = /^\s*RUN\s+npm\s+run\s+compile\s*$/m;
80
96
  const INCLUDE_ARRAY_BLOCK: RegExp = /"include"\s*:\s*\[([^\]]*)\]/;
81
97
  const QUOTED_STRING: RegExp = /"[^"]*"/g;
82
98
 
99
+ // Where the development branch of a Dockerfile.tpl starts.
100
+ const DEVELOPMENT_BRANCH_START: string =
101
+ '{{ if eq .Env.ENVIRONMENT "development" }}';
102
+ // A FROM line: `FROM <image or stage> [AS <name>]`, case-insensitive like Docker.
103
+ const FROM_INSTRUCTION: RegExp =
104
+ /^\s*FROM\s+(?:--\S+\s+)*(\S+)(?:\s+AS\s+(\S+))?\s*$/i;
105
+ const USER_NODE_INSTRUCTION: RegExp = /^\s*USER\s+node\s*$/m;
106
+ // Any COPY/ADD whose source is the ee/ directory (./ee, ee, ./ee/...).
107
+ const COPIES_ENTERPRISE_DIRECTORY: RegExp =
108
+ /^\s*(?:COPY|ADD)\s+(?:--\S+\s+)*(?:\.\/)?ee(?:\/\S*)?\s/im;
109
+ const SETS_ONEUPTIME_EDITION: RegExp = /^\s*ENV\s+ONEUPTIME_EDITION[=\s]/m;
110
+ const SETS_ENTERPRISE_EDITION_MARKER: RegExp =
111
+ /^\s*ENV\s+ONEUPTIME_EDITION=enterprise\s*$/m;
112
+
113
+ // The final stages the edition split builds (packages/App/Dockerfile.tpl).
114
+ const EDITION_STAGE_NAMES: Array<string> = ["community", "enterprise"];
115
+
116
+ // Splits a Dockerfile into its FROM stages, in order.
117
+ const splitStages: (dockerfile: string) => Array<DockerfileStage> = (
118
+ dockerfile: string,
119
+ ): Array<DockerfileStage> => {
120
+ const stages: Array<DockerfileStage> = [];
121
+ let current: DockerfileStage | null = null;
122
+
123
+ for (const line of dockerfile.split("\n")) {
124
+ const from: RegExpMatchArray | null = line.match(FROM_INSTRUCTION);
125
+
126
+ if (from) {
127
+ current = {
128
+ from: (from[1] ?? "").toLowerCase(),
129
+ name: from[2] ? from[2].toLowerCase() : null,
130
+ body: "",
131
+ };
132
+ stages.push(current);
133
+ continue;
134
+ }
135
+
136
+ if (current) {
137
+ current.body += `${line}\n`;
138
+ }
139
+ }
140
+
141
+ return stages;
142
+ };
143
+
144
+ // The stage and every stage it is built FROM, nearest first.
145
+ const stageAncestry: (
146
+ stages: Array<DockerfileStage>,
147
+ name: string,
148
+ ) => Array<DockerfileStage> = (
149
+ stages: Array<DockerfileStage>,
150
+ name: string,
151
+ ): Array<DockerfileStage> => {
152
+ const chain: Array<DockerfileStage> = [];
153
+ let next: string | null = name;
154
+
155
+ while (next !== null) {
156
+ const wanted: string = next;
157
+ const stage: DockerfileStage | undefined = stages.find(
158
+ (candidate: DockerfileStage) => {
159
+ return candidate.name === wanted;
160
+ },
161
+ );
162
+
163
+ if (!stage || chain.includes(stage)) {
164
+ break;
165
+ }
166
+
167
+ chain.push(stage);
168
+ next = stage.from;
169
+ }
170
+
171
+ return chain;
172
+ };
173
+
83
174
  const listServiceImages: () => Array<ServiceImage> =
84
175
  (): Array<ServiceImage> => {
85
176
  const images: Array<ServiceImage> = [];
@@ -103,6 +194,13 @@ const listServiceImages: () => Array<ServiceImage> =
103
194
  const elseIndex: number = dockerfile.lastIndexOf("{{ else }}");
104
195
  const productionStanza: string =
105
196
  elseIndex === -1 ? dockerfile : dockerfile.slice(elseIndex);
197
+ const developmentIndex: number = dockerfile.indexOf(
198
+ DEVELOPMENT_BRANCH_START,
199
+ );
200
+ const productionDockerfile: string =
201
+ elseIndex === -1 || developmentIndex === -1
202
+ ? dockerfile
203
+ : dockerfile.slice(0, developmentIndex) + productionStanza;
106
204
 
107
205
  let startScript: string | null = null;
108
206
  const packageJsonPath: string = path.join(
@@ -120,6 +218,7 @@ const listServiceImages: () => Array<ServiceImage> =
120
218
  images.push({
121
219
  service: path.basename(directory),
122
220
  productionStanza,
221
+ productionDockerfile,
123
222
  startScript,
124
223
  });
125
224
  }
@@ -378,4 +477,194 @@ describe("Container boot configuration", () => {
378
477
  },
379
478
  );
380
479
  });
480
+
481
+ describe("images built as Community and Enterprise targets", () => {
482
+ /*
483
+ * The App builds both editions from one Dockerfile: `--target community`
484
+ * and `--target enterprise` (packages/App/Dockerfile.tpl). Docker does
485
+ * inherit USER, ENV and CMD from a parent stage, but that is exactly why
486
+ * the whole-stanza checks above cannot see a final stage that lost one of
487
+ * them: they match the instruction anywhere in the production branch. Each
488
+ * final stage is an image of its own, so each states its own boot
489
+ * configuration, and nothing the Community image is built from may copy
490
+ * ee/ in.
491
+ */
492
+ const stagesOf: (service: string) => Array<DockerfileStage> = (
493
+ service: string,
494
+ ): Array<DockerfileStage> => {
495
+ const image: ServiceImage | undefined = SERVICE_IMAGES.find(
496
+ (candidate: ServiceImage) => {
497
+ return candidate.service === service;
498
+ },
499
+ );
500
+
501
+ return image ? splitStages(image.productionDockerfile) : [];
502
+ };
503
+
504
+ const stageNamed: (
505
+ service: string,
506
+ name: string,
507
+ ) => DockerfileStage | undefined = (
508
+ service: string,
509
+ name: string,
510
+ ): DockerfileStage | undefined => {
511
+ return stagesOf(service).find((stage: DockerfileStage) => {
512
+ return stage.name === name;
513
+ });
514
+ };
515
+
516
+ const editionServices: Array<string> = SERVICE_IMAGES.filter(
517
+ (image: ServiceImage) => {
518
+ return splitStages(image.productionDockerfile).some(
519
+ (stage: DockerfileStage) => {
520
+ return (
521
+ stage.name !== null && EDITION_STAGE_NAMES.includes(stage.name)
522
+ );
523
+ },
524
+ );
525
+ },
526
+ ).map((image: ServiceImage) => {
527
+ return image.service;
528
+ });
529
+
530
+ const finalStages: Array<[string, string]> = editionServices.flatMap(
531
+ (service: string) => {
532
+ return EDITION_STAGE_NAMES.map((name: string): [string, string] => {
533
+ return [service, name];
534
+ });
535
+ },
536
+ );
537
+
538
+ test("the App is built this way, with exactly one stage per edition", () => {
539
+ expect(editionServices).toContain("App");
540
+
541
+ for (const name of EDITION_STAGE_NAMES) {
542
+ expect(
543
+ stagesOf("App").filter((stage: DockerfileStage) => {
544
+ return stage.name === name;
545
+ }),
546
+ ).toHaveLength(1);
547
+ }
548
+ });
549
+
550
+ test.each(editionServices)(
551
+ "%s: the community stage is last, so a build without --target is the Community Edition",
552
+ (service: string) => {
553
+ const stages: Array<DockerfileStage> = stagesOf(service);
554
+
555
+ expect(stages[stages.length - 1]?.name).toBe("community");
556
+ },
557
+ );
558
+
559
+ test.each(finalStages)(
560
+ "%s: the %s stage runs as the node user",
561
+ (service: string, name: string) => {
562
+ expect(stageNamed(service, name)?.body).toMatch(USER_NODE_INSTRUCTION);
563
+ },
564
+ );
565
+
566
+ test.each(finalStages)(
567
+ "%s: the %s stage sets TS_NODE_TRANSPILE_ONLY=1",
568
+ (service: string, name: string) => {
569
+ expect(stageNamed(service, name)?.body).toMatch(TRANSPILE_ONLY_ENV);
570
+ },
571
+ );
572
+
573
+ test.each(finalStages)(
574
+ "%s: the %s stage boots with CMD npm start",
575
+ (service: string, name: string) => {
576
+ const body: string = stageNamed(service, name)?.body ?? "";
577
+
578
+ expect(
579
+ CMD_NPM_START_EXEC_FORM.test(body) ||
580
+ CMD_NPM_START_SHELL_FORM.test(body),
581
+ ).toBe(true);
582
+ },
583
+ );
584
+
585
+ test.each(finalStages)(
586
+ "%s: the %s stage is type-checked at build time (npm run compile in its ancestry)",
587
+ (service: string, name: string) => {
588
+ const ancestry: Array<DockerfileStage> = stageAncestry(
589
+ stagesOf(service),
590
+ name,
591
+ );
592
+
593
+ expect(
594
+ ancestry.some((stage: DockerfileStage) => {
595
+ return RUN_COMPILE_INSTRUCTION.test(stage.body);
596
+ }),
597
+ ).toBe(true);
598
+ },
599
+ );
600
+
601
+ test.each(editionServices)(
602
+ "%s: nothing the community stage is built from copies ee/",
603
+ (service: string) => {
604
+ const ancestry: Array<DockerfileStage> = stageAncestry(
605
+ stagesOf(service),
606
+ "community",
607
+ );
608
+
609
+ expect(ancestry.length).toBeGreaterThan(1);
610
+ expect(
611
+ ancestry
612
+ .filter((stage: DockerfileStage) => {
613
+ return COPIES_ENTERPRISE_DIRECTORY.test(stage.body);
614
+ })
615
+ .map((stage: DockerfileStage) => {
616
+ return stage.name;
617
+ }),
618
+ ).toEqual([]);
619
+ },
620
+ );
621
+
622
+ test.each(editionServices)(
623
+ "%s: the enterprise stage's ancestry does copy ee/ (so the check above can fail)",
624
+ (service: string) => {
625
+ expect(
626
+ stageAncestry(stagesOf(service), "enterprise").some(
627
+ (stage: DockerfileStage) => {
628
+ return COPIES_ENTERPRISE_DIRECTORY.test(stage.body);
629
+ },
630
+ ),
631
+ ).toBe(true);
632
+ },
633
+ );
634
+
635
+ test.each(editionServices)(
636
+ "%s: the enterprise image is the community build plus ee/ (they share its layers)",
637
+ (service: string) => {
638
+ const communityParent: string | undefined = stageNamed(
639
+ service,
640
+ "community",
641
+ )?.from;
642
+ const enterpriseAncestry: Array<string | null> = stageAncestry(
643
+ stagesOf(service),
644
+ "enterprise",
645
+ ).map((stage: DockerfileStage) => {
646
+ return stage.name;
647
+ });
648
+
649
+ expect(communityParent).toBeDefined();
650
+ expect(enterpriseAncestry).toContain(communityParent);
651
+ },
652
+ );
653
+
654
+ test.each(editionServices)(
655
+ "%s: only the enterprise stage sets ONEUPTIME_EDITION, to enterprise",
656
+ (service: string) => {
657
+ expect(stageNamed(service, "enterprise")?.body).toMatch(
658
+ SETS_ENTERPRISE_EDITION_MARKER,
659
+ );
660
+ expect(
661
+ stageAncestry(stagesOf(service), "community").some(
662
+ (stage: DockerfileStage) => {
663
+ return SETS_ONEUPTIME_EDITION.test(stage.body);
664
+ },
665
+ ),
666
+ ).toBe(false);
667
+ },
668
+ );
669
+ });
381
670
  });
@@ -68,9 +68,13 @@ const CLICKHOUSE_HTTP_CONTAINER_PORT: string = "8123";
68
68
  * The step that runs the suite:
69
69
  * `docker compose --project-directory . -f Scripts/Dev/docker-compose.dev.yml up ... e2e`,
70
70
  * or `... -f packages/E2E/docker-compose.e2e.yml up ... e2e` for the release image.
71
+ *
72
+ * `run --rm e2e <command>` counts too: the enterprise e2e jobs run one named
73
+ * suite per phase, which needs `run` to override the image's default CMD. Those
74
+ * suites use the same fixtures, so they need the same published port.
71
75
  */
72
76
  const RUNS_E2E_SERVICE: RegExp =
73
- /docker compose\s[^\n]*-f (?:Scripts\/Dev\/docker-compose\.dev|packages\/E2E\/docker-compose\.e2e)\.yml\s+up\b[^\n]*\se2e\b/;
77
+ /docker compose\s[^\n]*-f (?:Scripts\/Dev\/docker-compose\.dev|packages\/E2E\/docker-compose\.e2e)\.yml\s+(?:up|run)\b[^\n]*\se2e\b/;
74
78
  const UP_SUBCOMMAND: RegExp = /\sup(?:\s|$)/;
75
79
  const DOWN_SUBCOMMAND: RegExp = /\sdown(?:\s|$)/;
76
80
  const ROOT_COMPOSE_FILE_FLAG: RegExp = /-f docker-compose\.yml\s/;