@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,1127 @@
1
+ import { afterAll, describe, expect, test } from "@jest/globals";
2
+ import childProcess from "child_process";
3
+ import fs from "fs";
4
+ import os from "os";
5
+ import path from "path";
6
+
7
+ /*
8
+ * How the Dashboard and AdminDashboard builds decide between the Community
9
+ * and the Enterprise UI (UI/esbuild-enterprise.js).
10
+ *
11
+ * Each frontend imports its Enterprise UI through one bare specifier, from
12
+ * one file (src/Enterprise/Plugins.ts). The helper aliases that specifier to
13
+ * ee/<Frontend>/Index.tsx when the Enterprise plugin is on disk (and
14
+ * ONEUPTIME_EDITION allows it), and to the Community stub otherwise. Getting
15
+ * it wrong fails silently in the worst direction: esbuild reads the
16
+ * frontend's tsconfig "paths" natively, so a broken alias quietly bundles the
17
+ * Community stub into an image tagged Enterprise. Hence the sentinel string,
18
+ * and hence these tests building real bundles.
19
+ *
20
+ * Everything runs in a node SUBPROCESS, the way the build loads these files:
21
+ * esbuild refuses to load under Common's jsdom test environment (see
22
+ * EsbuildConfig.test.ts), and the helper reads the edition from the
23
+ * environment, which a subprocess gets exactly as the build does.
24
+ *
25
+ * The fixtures are temp directories laid out like the repository
26
+ * (packages/App/FeatureSet/<Frontend> next to ee/) and like the image
27
+ * (/usr/src/app/FeatureSet/<Frontend> next to /usr/src/ee). The bundle
28
+ * fixture uses the REAL src/Enterprise/{Plugins,EnterprisePlugins,
29
+ * CommunityPlugins}.ts, so what is proven is the shipped door, not a copy.
30
+ */
31
+
32
+ const UI_DIR: string = path.resolve(__dirname, "..", "..", "UI");
33
+ const ESBUILD_ENTERPRISE: string = path.join(UI_DIR, "esbuild-enterprise.js");
34
+ const ESBUILD_CONFIG: string = path.join(UI_DIR, "esbuild-config.js");
35
+ const PACKAGES_DIR: string = path.resolve(__dirname, "..", "..", "..");
36
+ const REPOSITORY_ROOT: string = path.resolve(PACKAGES_DIR, "..");
37
+
38
+ const DASHBOARD_DIR: string = path.join(
39
+ PACKAGES_DIR,
40
+ "App",
41
+ "FeatureSet",
42
+ "Dashboard",
43
+ );
44
+ const ADMIN_DASHBOARD_DIR: string = path.join(
45
+ PACKAGES_DIR,
46
+ "App",
47
+ "FeatureSet",
48
+ "AdminDashboard",
49
+ );
50
+
51
+ const DASHBOARD_SENTINEL: string = "ONEUPTIME_EE_DASHBOARD_PLUGIN_v1";
52
+ const COMMUNITY_FIXTURE_MARKER: string = "COMMUNITY_STUB_FIXTURE_MARKER";
53
+
54
+ const temporaryRoots: Array<string> = [];
55
+
56
+ type ChildEnvironment = Record<string, string>;
57
+
58
+ /*
59
+ * The child gets this process's environment minus everything that steers the
60
+ * edition or the build mode, plus the overrides. null deletes a variable.
61
+ */
62
+ function childEnvironment(
63
+ overrides: Record<string, string | null>,
64
+ ): ChildEnvironment {
65
+ const environment: ChildEnvironment = {};
66
+
67
+ for (const key of Object.keys(process.env)) {
68
+ const value: string | undefined = process.env[key];
69
+
70
+ if (typeof value === "string") {
71
+ environment[key] = value;
72
+ }
73
+ }
74
+
75
+ for (const key of [
76
+ "ONEUPTIME_EDITION",
77
+ "ONEUPTIME_EE_DIR",
78
+ "NODE_ENV",
79
+ "analyze",
80
+ ]) {
81
+ delete environment[key];
82
+ }
83
+
84
+ for (const [key, value] of Object.entries(overrides)) {
85
+ if (value === null) {
86
+ delete environment[key];
87
+ } else {
88
+ environment[key] = value;
89
+ }
90
+ }
91
+
92
+ return environment;
93
+ }
94
+
95
+ function runNode(
96
+ script: string,
97
+ overrides: Record<string, string | null>,
98
+ cwd: string = REPOSITORY_ROOT,
99
+ preload: string | null = null,
100
+ ): string {
101
+ const args: Array<string> = preload ? ["-r", preload] : [];
102
+
103
+ return childProcess
104
+ .execFileSync(process.execPath, [...args, "-e", script], {
105
+ cwd,
106
+ encoding: "utf8",
107
+ env: childEnvironment(overrides),
108
+ })
109
+ .trim();
110
+ }
111
+
112
+ function makeTempDir(prefix: string): string {
113
+ // realpath: macOS-style /tmp symlinks must not make paths compare unequal.
114
+ const root: string = fs.realpathSync(
115
+ fs.mkdtempSync(path.join(os.tmpdir(), prefix)),
116
+ );
117
+ temporaryRoots.push(root);
118
+ return root;
119
+ }
120
+
121
+ function writeFile(filePath: string, contents: string): void {
122
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
123
+ fs.writeFileSync(filePath, contents);
124
+ }
125
+
126
+ interface ResolveOptions {
127
+ frontendDir: string;
128
+ pluginSpecifier: string;
129
+ eeSubdir: string;
130
+ internalSpecifier: string;
131
+ }
132
+
133
+ interface BuildDecision {
134
+ edition: string;
135
+ requestedEdition: string;
136
+ pluginEntry: string | null;
137
+ communityStub: string;
138
+ candidates: Array<string>;
139
+ alias: Record<string, string>;
140
+ }
141
+
142
+ interface ResolveResult {
143
+ ok: boolean;
144
+ error: string;
145
+ decision: BuildDecision | null;
146
+ description: string;
147
+ }
148
+
149
+ function dashboardOptions(frontendDir: string): ResolveOptions {
150
+ return {
151
+ frontendDir,
152
+ pluginSpecifier: "@oneuptime/ee-dashboard",
153
+ eeSubdir: "Dashboard",
154
+ internalSpecifier: "@oneuptime/dashboard",
155
+ };
156
+ }
157
+
158
+ function adminDashboardOptions(frontendDir: string): ResolveOptions {
159
+ return {
160
+ frontendDir,
161
+ pluginSpecifier: "@oneuptime/ee-admin-dashboard",
162
+ eeSubdir: "AdminDashboard",
163
+ internalSpecifier: "@oneuptime/admin-dashboard",
164
+ };
165
+ }
166
+
167
+ /*
168
+ * Runs resolveEnterpriseBuild in a child with the given environment. Options
169
+ * are passed as JSON, so a missing key really is missing.
170
+ */
171
+ function resolveInChild(
172
+ options: Partial<ResolveOptions> | null,
173
+ overrides: Record<string, string | null> = {},
174
+ ): ResolveResult {
175
+ const script: string = `
176
+ const helper = require(${JSON.stringify(ESBUILD_ENTERPRISE)});
177
+ try {
178
+ const decision = helper.resolveEnterpriseBuild(${JSON.stringify(options)});
179
+ console.log(JSON.stringify({
180
+ ok: true,
181
+ error: "",
182
+ decision: decision,
183
+ description: helper.describeEnterpriseBuild("Dashboard", decision),
184
+ }));
185
+ } catch (error) {
186
+ console.log(JSON.stringify({
187
+ ok: false,
188
+ error: String((error && error.message) || error),
189
+ decision: null,
190
+ description: "",
191
+ }));
192
+ }
193
+ `;
194
+
195
+ return JSON.parse(runNode(script, overrides)) as ResolveResult;
196
+ }
197
+
198
+ function readRequestedEditionInChild(
199
+ overrides: Record<string, string | null>,
200
+ ): { ok: boolean; value: string; error: string } {
201
+ const script: string = `
202
+ const helper = require(${JSON.stringify(ESBUILD_ENTERPRISE)});
203
+ try {
204
+ console.log(JSON.stringify({ ok: true, value: helper.readRequestedEdition(), error: "" }));
205
+ } catch (error) {
206
+ console.log(JSON.stringify({ ok: false, value: "", error: String(error.message) }));
207
+ }
208
+ `;
209
+
210
+ return JSON.parse(runNode(script, overrides)) as {
211
+ ok: boolean;
212
+ value: string;
213
+ error: string;
214
+ };
215
+ }
216
+
217
+ interface Layout {
218
+ root: string;
219
+ frontendDir: string;
220
+ eeDir: string;
221
+ communityStub: string;
222
+ eeEntry: string;
223
+ }
224
+
225
+ /*
226
+ * A minimal tree the resolver can decide on. "repository" mirrors
227
+ * packages/App/FeatureSet/<Frontend> + <repo>/ee; "container" mirrors
228
+ * /usr/src/app/FeatureSet/<Frontend> + /usr/src/ee.
229
+ */
230
+ // Whether the temp directory tells "CaseProbe" and "caseprobe" apart.
231
+ function isCaseSensitiveFileSystem(): boolean {
232
+ const directory: string = fs.mkdtempSync(
233
+ path.join(os.tmpdir(), "oneuptime-ee-case-"),
234
+ );
235
+
236
+ try {
237
+ fs.writeFileSync(path.join(directory, "CaseProbe"), "");
238
+ return !fs.existsSync(path.join(directory, "caseprobe"));
239
+ } finally {
240
+ fs.rmSync(directory, { recursive: true, force: true });
241
+ }
242
+ }
243
+
244
+ function makeLayout(
245
+ shape: "repository" | "container",
246
+ options: { withEnterprise: boolean; frontend?: string; eeSubdir?: string },
247
+ ): Layout {
248
+ const root: string = makeTempDir("oneuptime-ee-alias-");
249
+ const frontend: string = options.frontend || "Dashboard";
250
+ const eeSubdir: string = options.eeSubdir || frontend;
251
+
252
+ const frontendDir: string =
253
+ shape === "repository"
254
+ ? path.join(root, "packages", "App", "FeatureSet", frontend)
255
+ : path.join(root, "usr", "src", "app", "FeatureSet", frontend);
256
+
257
+ const eeDir: string =
258
+ shape === "repository"
259
+ ? path.join(root, "ee")
260
+ : path.join(root, "usr", "src", "ee");
261
+
262
+ const communityStub: string = path.join(
263
+ frontendDir,
264
+ "src",
265
+ "Enterprise",
266
+ "CommunityPlugins.ts",
267
+ );
268
+ writeFile(communityStub, "export default {};\n");
269
+
270
+ const eeEntry: string = path.join(eeDir, eeSubdir, "Index.tsx");
271
+
272
+ if (options.withEnterprise) {
273
+ writeFile(eeEntry, "export default {};\n");
274
+ }
275
+
276
+ return { root, frontendDir, eeDir, communityStub, eeEntry };
277
+ }
278
+
279
+ afterAll(() => {
280
+ while (temporaryRoots.length > 0) {
281
+ fs.rmSync(temporaryRoots.pop() as string, {
282
+ recursive: true,
283
+ force: true,
284
+ });
285
+ }
286
+ });
287
+
288
+ describe("choosing the edition (resolveEnterpriseBuild)", () => {
289
+ test("a checkout without ee/ builds the Community stub", () => {
290
+ const layout: Layout = makeLayout("repository", { withEnterprise: false });
291
+ const result: ResolveResult = resolveInChild(
292
+ dashboardOptions(layout.frontendDir),
293
+ );
294
+
295
+ expect([result.ok, result.error]).toEqual([true, ""]);
296
+ expect(result.decision!.edition).toBe("community");
297
+ expect(result.decision!.requestedEdition).toBe("auto");
298
+ expect(result.decision!.pluginEntry).toBeNull();
299
+ expect(result.decision!.alias["@oneuptime/ee-dashboard"]).toBe(
300
+ layout.communityStub,
301
+ );
302
+ });
303
+
304
+ test("a checkout with ee/ builds the Enterprise plugin (repository layout)", () => {
305
+ const layout: Layout = makeLayout("repository", { withEnterprise: true });
306
+ const result: ResolveResult = resolveInChild(
307
+ dashboardOptions(layout.frontendDir),
308
+ );
309
+
310
+ expect(result.decision!.edition).toBe("enterprise");
311
+ expect(result.decision!.pluginEntry).toBe(layout.eeEntry);
312
+ // The FILE, not the directory: a directory alias misses Index.tsx.
313
+ expect(result.decision!.alias["@oneuptime/ee-dashboard"]).toBe(
314
+ path.join(layout.root, "ee", "Dashboard", "Index.tsx"),
315
+ );
316
+ });
317
+
318
+ test("the image layout finds /usr/src/ee next to /usr/src/app", () => {
319
+ const layout: Layout = makeLayout("container", { withEnterprise: true });
320
+ const result: ResolveResult = resolveInChild(
321
+ dashboardOptions(layout.frontendDir),
322
+ );
323
+
324
+ expect(result.decision!.edition).toBe("enterprise");
325
+ expect(result.decision!.alias["@oneuptime/ee-dashboard"]).toBe(
326
+ path.join(layout.root, "usr", "src", "ee", "Dashboard", "Index.tsx"),
327
+ );
328
+ });
329
+
330
+ test("tries the repository location first, then the image location", () => {
331
+ const layout: Layout = makeLayout("repository", { withEnterprise: false });
332
+ const result: ResolveResult = resolveInChild(
333
+ dashboardOptions(layout.frontendDir),
334
+ );
335
+
336
+ expect(result.decision!.candidates).toEqual([
337
+ path.join(layout.root, "ee", "Dashboard", "Index.tsx"),
338
+ path.join(layout.root, "packages", "ee", "Dashboard", "Index.tsx"),
339
+ ]);
340
+ });
341
+
342
+ test("ONEUPTIME_EDITION=community builds the stub even with ee/ present", () => {
343
+ const layout: Layout = makeLayout("repository", { withEnterprise: true });
344
+ const result: ResolveResult = resolveInChild(
345
+ dashboardOptions(layout.frontendDir),
346
+ { ONEUPTIME_EDITION: "community" },
347
+ );
348
+
349
+ expect(result.decision!.edition).toBe("community");
350
+ expect(result.decision!.requestedEdition).toBe("community");
351
+ expect(result.decision!.pluginEntry).toBeNull();
352
+ expect(result.decision!.alias["@oneuptime/ee-dashboard"]).toBe(
353
+ layout.communityStub,
354
+ );
355
+ });
356
+
357
+ test("ONEUPTIME_EDITION=enterprise builds the plugin when it is there", () => {
358
+ const layout: Layout = makeLayout("repository", { withEnterprise: true });
359
+ const result: ResolveResult = resolveInChild(
360
+ dashboardOptions(layout.frontendDir),
361
+ { ONEUPTIME_EDITION: "enterprise" },
362
+ );
363
+
364
+ expect(result.decision!.edition).toBe("enterprise");
365
+ expect(result.decision!.alias["@oneuptime/ee-dashboard"]).toBe(
366
+ layout.eeEntry,
367
+ );
368
+ });
369
+
370
+ test("ONEUPTIME_EDITION=enterprise with no plugin fails the build instead of shipping Community", () => {
371
+ const layout: Layout = makeLayout("repository", { withEnterprise: false });
372
+ const result: ResolveResult = resolveInChild(
373
+ dashboardOptions(layout.frontendDir),
374
+ { ONEUPTIME_EDITION: "enterprise" },
375
+ );
376
+
377
+ expect(result.ok).toBe(false);
378
+ expect(result.error).toContain("ONEUPTIME_EDITION=enterprise");
379
+ expect(result.error).toContain("Enterprise UI plugin for Dashboard");
380
+ // It names every place it looked, so the fix is obvious from the log.
381
+ expect(result.error).toContain(
382
+ path.join(layout.root, "ee", "Dashboard", "Index.tsx"),
383
+ );
384
+ expect(result.error).toContain(
385
+ path.join(layout.root, "packages", "ee", "Dashboard", "Index.tsx"),
386
+ );
387
+ });
388
+
389
+ test("an ee/ directory without the plugin file is not the Enterprise Edition", () => {
390
+ /*
391
+ * A leftover ee/node_modules after switching branches, or a directory
392
+ * that happens to be called Index.tsx, must not count: "found" means the
393
+ * Index.tsx FILE.
394
+ */
395
+ const layout: Layout = makeLayout("repository", { withEnterprise: false });
396
+ fs.mkdirSync(path.join(layout.eeDir, "node_modules"), { recursive: true });
397
+ fs.mkdirSync(path.join(layout.eeDir, "Dashboard", "Index.tsx", "nested"), {
398
+ recursive: true,
399
+ });
400
+
401
+ const auto: ResolveResult = resolveInChild(
402
+ dashboardOptions(layout.frontendDir),
403
+ );
404
+ expect(auto.decision!.edition).toBe("community");
405
+
406
+ const enterprise: ResolveResult = resolveInChild(
407
+ dashboardOptions(layout.frontendDir),
408
+ { ONEUPTIME_EDITION: "enterprise" },
409
+ );
410
+ expect(enterprise.ok).toBe(false);
411
+ });
412
+
413
+ /*
414
+ * On a case-insensitive file system (macOS, Windows) a lowercase index.tsx
415
+ * IS Index.tsx, so this only means something where names are
416
+ * case-sensitive - the Linux build machines and the image.
417
+ */
418
+ const caseSensitiveTest: typeof test.skip = isCaseSensitiveFileSystem()
419
+ ? test
420
+ : test.skip;
421
+
422
+ caseSensitiveTest("a lowercase index.tsx is not the plugin file", () => {
423
+ const layout: Layout = makeLayout("repository", { withEnterprise: false });
424
+ writeFile(path.join(layout.eeDir, "Dashboard", "index.tsx"), "");
425
+
426
+ const auto: ResolveResult = resolveInChild(
427
+ dashboardOptions(layout.frontendDir),
428
+ );
429
+ expect(auto.decision!.edition).toBe("community");
430
+
431
+ const enterprise: ResolveResult = resolveInChild(
432
+ dashboardOptions(layout.frontendDir),
433
+ { ONEUPTIME_EDITION: "enterprise" },
434
+ );
435
+ expect(enterprise.ok).toBe(false);
436
+ });
437
+
438
+ test("each frontend picks its own ee subdirectory", () => {
439
+ const layout: Layout = makeLayout("repository", {
440
+ withEnterprise: true,
441
+ frontend: "AdminDashboard",
442
+ });
443
+ // A Dashboard plugin alone must not satisfy the AdminDashboard.
444
+ writeFile(path.join(layout.eeDir, "Dashboard", "Index.tsx"), "");
445
+
446
+ const admin: ResolveResult = resolveInChild(
447
+ adminDashboardOptions(layout.frontendDir),
448
+ );
449
+
450
+ expect(admin.decision!.alias["@oneuptime/ee-admin-dashboard"]).toBe(
451
+ path.join(layout.eeDir, "AdminDashboard", "Index.tsx"),
452
+ );
453
+
454
+ fs.rmSync(path.join(layout.eeDir, "AdminDashboard"), {
455
+ recursive: true,
456
+ force: true,
457
+ });
458
+
459
+ const adminWithoutPlugin: ResolveResult = resolveInChild(
460
+ adminDashboardOptions(layout.frontendDir),
461
+ );
462
+ expect(adminWithoutPlugin.decision!.edition).toBe("community");
463
+ });
464
+
465
+ test("ONEUPTIME_EE_DIR replaces the default locations", () => {
466
+ const layout: Layout = makeLayout("repository", { withEnterprise: true });
467
+ const elsewhere: string = makeTempDir("oneuptime-ee-elsewhere-");
468
+ writeFile(path.join(elsewhere, "Dashboard", "Index.tsx"), "");
469
+
470
+ const moved: ResolveResult = resolveInChild(
471
+ dashboardOptions(layout.frontendDir),
472
+ { ONEUPTIME_EE_DIR: elsewhere },
473
+ );
474
+
475
+ expect(moved.decision!.candidates).toEqual([
476
+ path.join(elsewhere, "Dashboard", "Index.tsx"),
477
+ ]);
478
+ expect(moved.decision!.alias["@oneuptime/ee-dashboard"]).toBe(
479
+ path.join(elsewhere, "Dashboard", "Index.tsx"),
480
+ );
481
+
482
+ // Pointed at a directory with no plugin, the repository ee/ is NOT used.
483
+ const empty: string = makeTempDir("oneuptime-ee-empty-");
484
+ const none: ResolveResult = resolveInChild(
485
+ dashboardOptions(layout.frontendDir),
486
+ { ONEUPTIME_EE_DIR: empty },
487
+ );
488
+ expect(none.decision!.edition).toBe("community");
489
+ });
490
+
491
+ test("always maps the internal specifier and Common into the frontend", () => {
492
+ /*
493
+ * ee code reaches core as "@oneuptime/dashboard/..." and "Common/...",
494
+ * both served from the frontend itself so bundling never needs an
495
+ * install inside ee/. Both editions get the same aliases, so core
496
+ * resolves identically either way.
497
+ */
498
+ for (const withEnterprise of [false, true]) {
499
+ const layout: Layout = makeLayout("repository", { withEnterprise });
500
+ const result: ResolveResult = resolveInChild(
501
+ dashboardOptions(layout.frontendDir),
502
+ );
503
+
504
+ expect(result.decision!.alias["@oneuptime/dashboard"]).toBe(
505
+ path.join(layout.frontendDir, "src"),
506
+ );
507
+ expect(result.decision!.alias["Common"]).toBe(
508
+ path.join(layout.frontendDir, "node_modules", "Common"),
509
+ );
510
+ expect(Object.keys(result.decision!.alias).sort()).toEqual(
511
+ ["@oneuptime/dashboard", "@oneuptime/ee-dashboard", "Common"].sort(),
512
+ );
513
+ }
514
+ });
515
+
516
+ test("says which edition it built, for the build log", () => {
517
+ const enterpriseLayout: Layout = makeLayout("repository", {
518
+ withEnterprise: true,
519
+ });
520
+ const enterprise: ResolveResult = resolveInChild(
521
+ dashboardOptions(enterpriseLayout.frontendDir),
522
+ );
523
+ expect(enterprise.description).toBe(
524
+ `Dashboard: building the Enterprise Edition UI from ${enterpriseLayout.eeEntry}`,
525
+ );
526
+
527
+ const communityLayout: Layout = makeLayout("repository", {
528
+ withEnterprise: true,
529
+ });
530
+ const community: ResolveResult = resolveInChild(
531
+ dashboardOptions(communityLayout.frontendDir),
532
+ { ONEUPTIME_EDITION: "community" },
533
+ );
534
+ expect(community.description).toBe(
535
+ "Dashboard: building the Community Edition UI (ONEUPTIME_EDITION=community)",
536
+ );
537
+ });
538
+
539
+ test("refuses a frontend with no Community stub", () => {
540
+ const layout: Layout = makeLayout("repository", { withEnterprise: true });
541
+ fs.rmSync(layout.communityStub);
542
+
543
+ const result: ResolveResult = resolveInChild(
544
+ dashboardOptions(layout.frontendDir),
545
+ );
546
+
547
+ expect(result.ok).toBe(false);
548
+ expect(result.error).toContain("Community plugin stub is missing");
549
+ });
550
+
551
+ test("refuses missing options and a relative frontend directory", () => {
552
+ const layout: Layout = makeLayout("repository", { withEnterprise: false });
553
+
554
+ for (const key of [
555
+ "frontendDir",
556
+ "pluginSpecifier",
557
+ "eeSubdir",
558
+ "internalSpecifier",
559
+ ] as Array<keyof ResolveOptions>) {
560
+ const options: Partial<ResolveOptions> = {
561
+ ...dashboardOptions(layout.frontendDir),
562
+ };
563
+ delete options[key];
564
+
565
+ const result: ResolveResult = resolveInChild(options);
566
+ expect([key, result.ok, result.error]).toEqual([
567
+ key,
568
+ false,
569
+ `resolveEnterpriseBuild: "${key}" is required.`,
570
+ ]);
571
+ }
572
+
573
+ expect(resolveInChild(null).ok).toBe(false);
574
+
575
+ const relative: ResolveResult = resolveInChild({
576
+ ...dashboardOptions("packages/App/FeatureSet/Dashboard"),
577
+ });
578
+ expect(relative.ok).toBe(false);
579
+ expect(relative.error).toContain("must be absolute");
580
+ });
581
+ });
582
+
583
+ describe("reading ONEUPTIME_EDITION", () => {
584
+ test.each([
585
+ [null, "auto"],
586
+ ["", "auto"],
587
+ [" ", "auto"],
588
+ ["auto", "auto"],
589
+ ["community", "community"],
590
+ ["enterprise", "enterprise"],
591
+ [" Enterprise ", "enterprise"],
592
+ ["COMMUNITY", "community"],
593
+ ])("%p reads as %p", (raw: string | null, expected: string) => {
594
+ const result: { ok: boolean; value: string; error: string } =
595
+ readRequestedEditionInChild({ ONEUPTIME_EDITION: raw });
596
+
597
+ expect([result.ok, result.value]).toEqual([true, expected]);
598
+ });
599
+
600
+ test.each(["enterprize", "ee", "ce", "true"])(
601
+ "a typo (%p) fails the build rather than guessing an edition",
602
+ (raw: string) => {
603
+ const result: { ok: boolean; value: string; error: string } =
604
+ readRequestedEditionInChild({ ONEUPTIME_EDITION: raw });
605
+
606
+ expect(result.ok).toBe(false);
607
+ expect(result.error).toContain(
608
+ "ONEUPTIME_EDITION must be one of auto, community, enterprise",
609
+ );
610
+ expect(result.error).toContain(`"${raw}"`);
611
+ },
612
+ );
613
+ });
614
+
615
+ describe("the real frontends", () => {
616
+ /*
617
+ * The two shipped esbuild.config.js files, run for real with esbuild's
618
+ * build() swapped for a probe (preloaded into the same module instance),
619
+ * so what is asserted is the config they would build with. NODE_PATH lets
620
+ * "Common/..." resolve even where the frontend is not installed - the
621
+ * Common Test CI job installs Common alone.
622
+ */
623
+ interface ProbedBuild {
624
+ alias: Record<string, string>;
625
+ keys: Array<string>;
626
+ log: Array<string>;
627
+ }
628
+
629
+ function probeFrontendBuild(
630
+ frontendDir: string,
631
+ overrides: Record<string, string | null>,
632
+ ): ProbedBuild {
633
+ const probeDir: string = makeTempDir("oneuptime-ee-probe-");
634
+ const preload: string = path.join(probeDir, "probe.js");
635
+
636
+ writeFile(
637
+ preload,
638
+ `
639
+ const config = require(${JSON.stringify(ESBUILD_CONFIG)});
640
+ const log = [];
641
+ const originalLog = console.log;
642
+ console.log = function () {
643
+ log.push(Array.prototype.join.call(arguments, " "));
644
+ };
645
+ function report(built) {
646
+ originalLog(JSON.stringify({
647
+ alias: built.alias,
648
+ keys: Object.keys(built),
649
+ log: log,
650
+ }));
651
+ }
652
+ config.build = report;
653
+ config.watch = report;
654
+ `,
655
+ );
656
+
657
+ const output: string = runNode(
658
+ `require(${JSON.stringify(path.join(frontendDir, "esbuild.config.js"))});`,
659
+ { NODE_PATH: PACKAGES_DIR, ...overrides },
660
+ frontendDir,
661
+ preload,
662
+ );
663
+
664
+ return JSON.parse(output) as ProbedBuild;
665
+ }
666
+
667
+ const EXPECTED_CONFIG_KEYS: Array<string> = [
668
+ "entryPoints",
669
+ "bundle",
670
+ "outdir",
671
+ "format",
672
+ "platform",
673
+ "target",
674
+ "sourcemap",
675
+ "minify",
676
+ "keepNames",
677
+ "treeShaking",
678
+ "splitting",
679
+ "publicPath",
680
+ "define",
681
+ "external",
682
+ "alias",
683
+ "plugins",
684
+ "loader",
685
+ "resolveExtensions",
686
+ "metafile",
687
+ ];
688
+
689
+ test.each([
690
+ [
691
+ "Dashboard",
692
+ DASHBOARD_DIR,
693
+ "@oneuptime/ee-dashboard",
694
+ "@oneuptime/dashboard",
695
+ ],
696
+ [
697
+ "AdminDashboard",
698
+ ADMIN_DASHBOARD_DIR,
699
+ "@oneuptime/ee-admin-dashboard",
700
+ "@oneuptime/admin-dashboard",
701
+ ],
702
+ ])(
703
+ "%s builds the Community stub under ONEUPTIME_EDITION=community",
704
+ (
705
+ name: string,
706
+ frontendDir: string,
707
+ pluginSpecifier: string,
708
+ internalSpecifier: string,
709
+ ) => {
710
+ const probed: ProbedBuild = probeFrontendBuild(frontendDir, {
711
+ ONEUPTIME_EDITION: "community",
712
+ });
713
+
714
+ expect(probed.alias[pluginSpecifier]).toBe(
715
+ path.join(frontendDir, "src", "Enterprise", "CommunityPlugins.ts"),
716
+ );
717
+ expect(probed.alias[internalSpecifier]).toBe(
718
+ path.join(frontendDir, "src"),
719
+ );
720
+ expect(probed.alias["Common"]).toBe(
721
+ path.join(frontendDir, "node_modules", "Common"),
722
+ );
723
+
724
+ // The shared aliases createConfig adds are still there.
725
+ expect(Object.keys(probed.alias)).toEqual(
726
+ expect.arrayContaining(["react", "react-dom", "i18next"]),
727
+ );
728
+
729
+ // additionalAlias only: no new top-level config keys.
730
+ expect([...probed.keys].sort()).toEqual([...EXPECTED_CONFIG_KEYS].sort());
731
+
732
+ expect(probed.log).toContain(
733
+ `${name}: building the Community Edition UI (ONEUPTIME_EDITION=community)`,
734
+ );
735
+ },
736
+ );
737
+
738
+ test.each([
739
+ ["Dashboard", DASHBOARD_DIR, "@oneuptime/ee-dashboard"],
740
+ ["AdminDashboard", ADMIN_DASHBOARD_DIR, "@oneuptime/ee-admin-dashboard"],
741
+ ])(
742
+ "%s builds the Enterprise plugin when one is on disk",
743
+ (name: string, frontendDir: string, pluginSpecifier: string) => {
744
+ const eeDir: string = makeTempDir("oneuptime-ee-real-frontend-");
745
+ const entry: string = path.join(eeDir, name, "Index.tsx");
746
+ writeFile(entry, "export default {};\n");
747
+
748
+ const probed: ProbedBuild = probeFrontendBuild(frontendDir, {
749
+ ONEUPTIME_EE_DIR: eeDir,
750
+ });
751
+
752
+ expect(probed.alias[pluginSpecifier]).toBe(entry);
753
+ expect(probed.log).toContain(
754
+ `${name}: building the Enterprise Edition UI from ${entry}`,
755
+ );
756
+ },
757
+ );
758
+ });
759
+
760
+ describe("bundling for real", () => {
761
+ interface BundleResult {
762
+ ok: boolean;
763
+ error: string;
764
+ // Every emitted file concatenated - chunks included.
765
+ output: string;
766
+ inputs: Array<string>;
767
+ evaluated: {
768
+ ok: boolean;
769
+ error: string;
770
+ value: {
771
+ shell: string;
772
+ pluginKeys: Array<string>;
773
+ buildMarker: string | null;
774
+ areaResult: string | null;
775
+ sharedCount: number;
776
+ commonLabel: string;
777
+ } | null;
778
+ };
779
+ }
780
+
781
+ const REAL_ENTERPRISE_DIR: string = path.join(
782
+ DASHBOARD_DIR,
783
+ "src",
784
+ "Enterprise",
785
+ );
786
+
787
+ /*
788
+ * A frontend that uses the REAL core door. The ee area imports a core shell
789
+ * back through "@oneuptime/dashboard/...", closing the same import cycle
790
+ * the real Enterprise bundle has (core shell -> Plugins -> ee Index -> ee
791
+ * area -> core shell).
792
+ */
793
+ function makeBundleFixture(options: {
794
+ withEnterprise: boolean;
795
+ topLevelPluginRead?: boolean;
796
+ }): Layout {
797
+ const layout: Layout = makeLayout("repository", {
798
+ withEnterprise: false,
799
+ });
800
+ const src: string = path.join(layout.frontendDir, "src");
801
+
802
+ for (const file of [
803
+ "Plugins.ts",
804
+ "EnterprisePlugins.ts",
805
+ "CommunityPlugins.ts",
806
+ ]) {
807
+ writeFile(
808
+ path.join(src, "Enterprise", file),
809
+ fs.readFileSync(path.join(REAL_ENTERPRISE_DIR, file), "utf8"),
810
+ );
811
+ }
812
+
813
+ // Proves the stub is what ships when ee/ is not bundled.
814
+ fs.appendFileSync(
815
+ path.join(src, "Enterprise", "CommunityPlugins.ts"),
816
+ `\nexport const COMMUNITY_MARKER: string = "${COMMUNITY_FIXTURE_MARKER}";\n(globalThis as any).__communityMarker = COMMUNITY_MARKER;\n`,
817
+ );
818
+
819
+ writeFile(
820
+ path.join(src, "Utils", "Shared.ts"),
821
+ "export const sharedToken: { count: number } = { count: 0 };\n",
822
+ );
823
+
824
+ writeFile(
825
+ path.join(src, "Components", "Shell.ts"),
826
+ `
827
+ import { getDashboardPlugins } from "../Enterprise/Plugins";
828
+ export const renderShell = (): string => {
829
+ return getDashboardPlugins().buildMarker || "community";
830
+ };
831
+ `,
832
+ );
833
+
834
+ writeFile(
835
+ path.join(src, "Index.tsx"),
836
+ `
837
+ import { renderShell } from "./Components/Shell";
838
+ import { getDashboardPlugins } from "./Enterprise/Plugins";
839
+ import { sharedToken } from "./Utils/Shared";
840
+ import { commonLabel } from "Common/Label";
841
+
842
+ sharedToken.count += 1;
843
+
844
+ const plugins: any = getDashboardPlugins();
845
+ (globalThis as any).__fixtureResult = {
846
+ shell: renderShell(),
847
+ pluginKeys: Object.keys(plugins).sort(),
848
+ buildMarker: plugins.buildMarker || null,
849
+ areaResult: plugins.SettingsSSO ? plugins.SettingsSSO() : null,
850
+ sharedCount: sharedToken.count,
851
+ commonLabel: commonLabel,
852
+ };
853
+ `,
854
+ );
855
+
856
+ // A stand-in Common, reached through <frontend>/node_modules/Common.
857
+ const common: string = path.join(layout.root, "packages", "Common");
858
+ writeFile(
859
+ path.join(common, "Label.ts"),
860
+ 'export const commonLabel: string = "common-label";\n',
861
+ );
862
+ fs.mkdirSync(path.join(layout.frontendDir, "node_modules"), {
863
+ recursive: true,
864
+ });
865
+ fs.symlinkSync(
866
+ common,
867
+ path.join(layout.frontendDir, "node_modules", "Common"),
868
+ "dir",
869
+ );
870
+
871
+ if (options.withEnterprise) {
872
+ writeFile(
873
+ path.join(layout.eeDir, "Dashboard", "Area", "Plugins.ts"),
874
+ options.topLevelPluginRead
875
+ ? `
876
+ import { getDashboardPlugins } from "@oneuptime/dashboard/Enterprise/Plugins";
877
+ const eager: any = getDashboardPlugins();
878
+ export default { SettingsSSO: (): string => String(eager) };
879
+ `
880
+ : `
881
+ import { renderShell } from "@oneuptime/dashboard/Components/Shell";
882
+ import { sharedToken } from "@oneuptime/dashboard/Utils/Shared";
883
+ import { commonLabel } from "Common/Label";
884
+ export default {
885
+ SettingsSSO: (): string => {
886
+ return commonLabel + ":" + typeof renderShell + ":" + sharedToken.count;
887
+ },
888
+ };
889
+ `,
890
+ );
891
+
892
+ writeFile(
893
+ layout.eeEntry,
894
+ `
895
+ import { DashboardEnterprisePlugins } from "@oneuptime/dashboard/Enterprise/EnterprisePlugins";
896
+ import AreaPlugins from "./Area/Plugins";
897
+
898
+ export const ONEUPTIME_EE_DASHBOARD_PLUGIN_SENTINEL: string =
899
+ "${DASHBOARD_SENTINEL}";
900
+
901
+ export default {
902
+ ...AreaPlugins,
903
+ buildMarker: ONEUPTIME_EE_DASHBOARD_PLUGIN_SENTINEL,
904
+ } satisfies DashboardEnterprisePlugins;
905
+ `,
906
+ );
907
+ }
908
+
909
+ return layout;
910
+ }
911
+
912
+ /*
913
+ * Builds the fixture with the real createConfig plus the helper's aliases -
914
+ * exactly what esbuild.config.js does - then loads the bundle as ESM.
915
+ */
916
+ function bundle(
917
+ layout: Layout,
918
+ overrides: Record<string, string | null>,
919
+ ): BundleResult {
920
+ const outdir: string = path.join(layout.root, "out");
921
+
922
+ const script: string = `
923
+ const fs = require("fs");
924
+ const path = require("path");
925
+ const url = require("url");
926
+ const { createConfig } = require(${JSON.stringify(ESBUILD_CONFIG)});
927
+ const { resolveEnterpriseAliases } = require(${JSON.stringify(ESBUILD_ENTERPRISE)});
928
+ const esbuild = require(require.resolve("esbuild", {
929
+ paths: [${JSON.stringify(UI_DIR)}],
930
+ }));
931
+
932
+ const outdir = ${JSON.stringify(outdir)};
933
+ const empty = { ok: false, error: "", value: null };
934
+
935
+ (async function () {
936
+ let cfg;
937
+ let result;
938
+ try {
939
+ cfg = createConfig({
940
+ serviceName: "Dashboard",
941
+ publicPath: "/dashboard/dist/",
942
+ additionalAlias: resolveEnterpriseAliases({
943
+ frontendDir: ${JSON.stringify(layout.frontendDir)},
944
+ pluginSpecifier: "@oneuptime/ee-dashboard",
945
+ eeSubdir: "Dashboard",
946
+ internalSpecifier: "@oneuptime/dashboard",
947
+ }),
948
+ });
949
+ cfg.entryPoints = [${JSON.stringify(path.join(layout.frontendDir, "src", "Index.tsx"))}];
950
+ cfg.outdir = outdir;
951
+ cfg.metafile = true;
952
+ cfg.logLevel = "silent";
953
+ result = await esbuild.build(cfg);
954
+ } catch (error) {
955
+ console.log(JSON.stringify({
956
+ ok: false,
957
+ error: String((error && error.message) || error),
958
+ output: "",
959
+ inputs: [],
960
+ evaluated: empty,
961
+ }));
962
+ return;
963
+ }
964
+
965
+ const files = fs.readdirSync(outdir).filter(function (file) {
966
+ return file.endsWith(".js");
967
+ });
968
+ const output = files.map(function (file) {
969
+ return fs.readFileSync(path.join(outdir, file), "utf8");
970
+ }).join("\\n");
971
+
972
+ // Load the bundle the way a browser would: as ES modules.
973
+ fs.writeFileSync(path.join(outdir, "package.json"), '{"type":"module"}');
974
+ let evaluated;
975
+ try {
976
+ await import(url.pathToFileURL(path.join(outdir, "Index.js")).href);
977
+ evaluated = { ok: true, error: "", value: globalThis.__fixtureResult };
978
+ } catch (error) {
979
+ evaluated = {
980
+ ok: false,
981
+ error: String((error && error.message) || error),
982
+ value: null,
983
+ };
984
+ }
985
+
986
+ console.log(JSON.stringify({
987
+ ok: true,
988
+ error: "",
989
+ output: output,
990
+ inputs: Object.keys(result.metafile.inputs),
991
+ evaluated: evaluated,
992
+ }));
993
+ })();
994
+ `;
995
+
996
+ return JSON.parse(runNode(script, overrides)) as BundleResult;
997
+ }
998
+
999
+ function countInputs(inputs: Array<string>, suffix: string): number {
1000
+ return inputs.filter((input: string): boolean => {
1001
+ return input.endsWith(suffix);
1002
+ }).length;
1003
+ }
1004
+
1005
+ test("an Enterprise production build carries the sentinel and loads", () => {
1006
+ const layout: Layout = makeBundleFixture({ withEnterprise: true });
1007
+ const result: BundleResult = bundle(layout, { NODE_ENV: "production" });
1008
+
1009
+ expect([result.ok, result.error]).toEqual([true, ""]);
1010
+
1011
+ // Minified and tree-shaken, and the sentinel is still there.
1012
+ expect(result.output).toContain(DASHBOARD_SENTINEL);
1013
+ expect(result.output).not.toContain(COMMUNITY_FIXTURE_MARKER);
1014
+ expect(countInputs(result.inputs, "ee/Dashboard/Index.tsx")).toBe(1);
1015
+ expect(countInputs(result.inputs, "Enterprise/CommunityPlugins.ts")).toBe(
1016
+ 0,
1017
+ );
1018
+
1019
+ expect([result.evaluated.ok, result.evaluated.error]).toEqual([true, ""]);
1020
+ expect(result.evaluated.value).toEqual({
1021
+ shell: DASHBOARD_SENTINEL,
1022
+ pluginKeys: ["SettingsSSO", "buildMarker"],
1023
+ buildMarker: DASHBOARD_SENTINEL,
1024
+ // ee reached core through "@oneuptime/dashboard/..." and "Common/...".
1025
+ areaResult: "common-label:function:1",
1026
+ sharedCount: 1,
1027
+ commonLabel: "common-label",
1028
+ });
1029
+ });
1030
+
1031
+ test("core and ee share ONE instance of a core module", () => {
1032
+ /*
1033
+ * The entry imports Utils/Shared relatively and the ee area imports it as
1034
+ * "@oneuptime/dashboard/Utils/Shared". Two copies would mean two
1035
+ * singletons (two i18n instances, two stores) - the count above would
1036
+ * read 0 from the ee side.
1037
+ */
1038
+ const layout: Layout = makeBundleFixture({ withEnterprise: true });
1039
+ const result: BundleResult = bundle(layout, { NODE_ENV: "production" });
1040
+
1041
+ expect(countInputs(result.inputs, "src/Utils/Shared.ts")).toBe(1);
1042
+ expect(countInputs(result.inputs, "Label.ts")).toBe(1);
1043
+ });
1044
+
1045
+ test("an Enterprise development build carries the sentinel too", () => {
1046
+ const layout: Layout = makeBundleFixture({ withEnterprise: true });
1047
+ const result: BundleResult = bundle(layout, {});
1048
+
1049
+ expect([result.ok, result.error]).toEqual([true, ""]);
1050
+ expect(result.output).toContain(DASHBOARD_SENTINEL);
1051
+ expect(result.evaluated.value?.buildMarker).toBe(DASHBOARD_SENTINEL);
1052
+ });
1053
+
1054
+ test("a Community build has no sentinel and no ee code", () => {
1055
+ const layout: Layout = makeBundleFixture({ withEnterprise: false });
1056
+ const result: BundleResult = bundle(layout, { NODE_ENV: "production" });
1057
+
1058
+ expect([result.ok, result.error]).toEqual([true, ""]);
1059
+ expect(result.output).not.toContain(DASHBOARD_SENTINEL);
1060
+ expect(result.output).toContain(COMMUNITY_FIXTURE_MARKER);
1061
+ expect(countInputs(result.inputs, "Enterprise/CommunityPlugins.ts")).toBe(
1062
+ 1,
1063
+ );
1064
+ expect(
1065
+ result.inputs.some((input: string): boolean => {
1066
+ return input.includes("ee/");
1067
+ }),
1068
+ ).toBe(false);
1069
+
1070
+ expect(result.evaluated.value).toEqual({
1071
+ shell: "community",
1072
+ pluginKeys: [],
1073
+ buildMarker: null,
1074
+ areaResult: null,
1075
+ sharedCount: 1,
1076
+ commonLabel: "common-label",
1077
+ });
1078
+ });
1079
+
1080
+ test("ONEUPTIME_EDITION=community leaves ee/ out even when it is on disk", () => {
1081
+ const layout: Layout = makeBundleFixture({ withEnterprise: true });
1082
+ const result: BundleResult = bundle(layout, {
1083
+ NODE_ENV: "production",
1084
+ ONEUPTIME_EDITION: "community",
1085
+ });
1086
+
1087
+ expect([result.ok, result.error]).toEqual([true, ""]);
1088
+ expect(result.output).not.toContain(DASHBOARD_SENTINEL);
1089
+ expect(
1090
+ result.inputs.some((input: string): boolean => {
1091
+ return input.includes("ee/Dashboard");
1092
+ }),
1093
+ ).toBe(false);
1094
+ expect(result.evaluated.value?.shell).toBe("community");
1095
+ });
1096
+
1097
+ test("ONEUPTIME_EDITION=enterprise without ee/ fails before bundling anything", () => {
1098
+ const layout: Layout = makeBundleFixture({ withEnterprise: false });
1099
+ const result: BundleResult = bundle(layout, {
1100
+ NODE_ENV: "production",
1101
+ ONEUPTIME_EDITION: "enterprise",
1102
+ });
1103
+
1104
+ expect(result.ok).toBe(false);
1105
+ expect(result.error).toContain("ONEUPTIME_EDITION=enterprise");
1106
+ expect(fs.existsSync(path.join(layout.root, "out"))).toBe(false);
1107
+ });
1108
+
1109
+ test("reading the plugins at module load crashes the Enterprise bundle (why the rule exists)", () => {
1110
+ /*
1111
+ * The negative control for the "call getDashboardPlugins() only inside a
1112
+ * function" rule: the same cycle, but the ee area reads the plugins at
1113
+ * its top level. It builds fine and dies on load - and only in the
1114
+ * Enterprise build, which is why it needs a guard of its own
1115
+ * (App/Tests/EnterpriseImportGuard.test.ts).
1116
+ */
1117
+ const layout: Layout = makeBundleFixture({
1118
+ withEnterprise: true,
1119
+ topLevelPluginRead: true,
1120
+ });
1121
+ const result: BundleResult = bundle(layout, { NODE_ENV: "production" });
1122
+
1123
+ expect([result.ok, result.error]).toEqual([true, ""]);
1124
+ expect(result.evaluated.ok).toBe(false);
1125
+ expect(result.evaluated.error).not.toBe("");
1126
+ });
1127
+ });