openship 0.1.0 → 0.1.1

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 (531) hide show
  1. package/.env.example +19 -3
  2. package/CONTRIBUTING.md +69 -19
  3. package/LICENSE +661 -0
  4. package/README.md +304 -108
  5. package/apps/api/Dockerfile +6 -8
  6. package/apps/api/package.json +25 -21
  7. package/apps/api/src/app.ts +46 -3
  8. package/apps/api/src/config/env.ts +81 -7
  9. package/apps/api/src/lib/auth-mode.ts +42 -0
  10. package/apps/api/src/lib/auth.ts +105 -0
  11. package/apps/api/src/lib/cache.ts +128 -0
  12. package/apps/api/src/lib/cloud-auth-proxy.ts +310 -0
  13. package/apps/api/src/lib/cloud-client.ts +103 -0
  14. package/apps/api/src/lib/compose-parser.ts +138 -0
  15. package/apps/api/src/lib/controller-helpers.ts +90 -0
  16. package/apps/api/src/lib/email-templates.ts +94 -0
  17. package/apps/api/src/lib/encryption.ts +102 -0
  18. package/apps/api/src/lib/local-user.ts +65 -0
  19. package/apps/api/src/lib/mail.ts +48 -0
  20. package/apps/api/src/lib/notifications.ts +195 -0
  21. package/apps/api/src/lib/openship-cloud.ts +101 -0
  22. package/apps/api/src/lib/resources.ts +96 -0
  23. package/apps/api/src/lib/screenshots.ts +189 -0
  24. package/apps/api/src/lib/ssh-manager.ts +369 -0
  25. package/apps/api/src/lib/ssl-scheduler.ts +109 -0
  26. package/apps/api/src/lib/stack-detector.ts +708 -0
  27. package/apps/api/src/lib/system-debug.ts +10 -0
  28. package/apps/api/src/middleware/auth.ts +49 -13
  29. package/apps/api/src/middleware/error-handler.ts +23 -3
  30. package/apps/api/src/middleware/index.ts +2 -0
  31. package/apps/api/src/middleware/internal-auth.ts +40 -0
  32. package/apps/api/src/middleware/local-only.ts +20 -0
  33. package/apps/api/src/middleware/rate-limiter.ts +9 -0
  34. package/apps/api/src/modules/analytics/analytics.controller.ts +111 -0
  35. package/apps/api/src/modules/analytics/analytics.routes.ts +29 -0
  36. package/apps/api/src/modules/analytics/analytics.schema.ts +37 -0
  37. package/apps/api/src/modules/analytics/analytics.service.ts +264 -0
  38. package/apps/api/src/modules/auth/auth.routes.ts +233 -6
  39. package/apps/api/src/modules/billing/billing.routes.ts +8 -0
  40. package/apps/api/src/modules/cloud/cloud.controller.ts +225 -0
  41. package/apps/api/src/modules/cloud/cloud.routes.ts +40 -0
  42. package/apps/api/src/modules/deployments/build.service.ts +791 -0
  43. package/apps/api/src/modules/deployments/deployment-lifecycle.ts +138 -0
  44. package/apps/api/src/modules/deployments/deployment.controller.ts +324 -16
  45. package/apps/api/src/modules/deployments/deployment.routes.ts +33 -7
  46. package/apps/api/src/modules/deployments/deployment.schema.ts +37 -5
  47. package/apps/api/src/modules/deployments/deployment.service.ts +178 -11
  48. package/apps/api/src/modules/deployments/preflight.ts +280 -0
  49. package/apps/api/src/modules/deployments/prepare.service.ts +252 -0
  50. package/apps/api/src/modules/deployments/session-manager.ts +283 -0
  51. package/apps/api/src/modules/deployments/ssl.service.ts +73 -0
  52. package/apps/api/src/modules/domains/domain.controller.ts +59 -7
  53. package/apps/api/src/modules/domains/domain.routes.ts +20 -5
  54. package/apps/api/src/modules/domains/domain.schema.ts +35 -0
  55. package/apps/api/src/modules/domains/domain.service.ts +375 -9
  56. package/apps/api/src/modules/github/github.auth.ts +397 -0
  57. package/apps/api/src/modules/github/github.controller.ts +387 -0
  58. package/apps/api/src/modules/github/github.local-auth.ts +251 -0
  59. package/apps/api/src/modules/github/github.routes.ts +50 -0
  60. package/apps/api/src/modules/github/github.schema.ts +52 -0
  61. package/apps/api/src/modules/github/github.service.ts +592 -0
  62. package/apps/api/src/modules/github/github.types.ts +158 -0
  63. package/apps/api/src/modules/github/github.webhook.ts +186 -0
  64. package/apps/api/src/modules/github/index.ts +16 -0
  65. package/apps/api/src/modules/health/health.routes.ts +35 -0
  66. package/apps/api/src/modules/projects/project.controller.ts +481 -12
  67. package/apps/api/src/modules/projects/project.routes.ts +63 -6
  68. package/apps/api/src/modules/projects/project.schema.ts +104 -6
  69. package/apps/api/src/modules/projects/project.service.ts +502 -11
  70. package/apps/api/src/modules/settings/settings.controller.ts +55 -0
  71. package/apps/api/src/modules/settings/settings.routes.ts +25 -0
  72. package/apps/api/src/modules/settings/settings.service.ts +49 -0
  73. package/apps/api/src/modules/system/filesystem.controller.ts +66 -0
  74. package/apps/api/src/modules/system/index.ts +1 -0
  75. package/apps/api/src/modules/system/server-check.controller.ts +475 -0
  76. package/apps/api/src/modules/system/setup-session.ts +255 -0
  77. package/apps/api/src/modules/system/setup.controller.ts +128 -0
  78. package/apps/api/src/modules/system/system.routes.ts +42 -0
  79. package/apps/api/src/modules/webhooks/webhook.controller.ts +57 -15
  80. package/apps/api/src/modules/webhooks/webhook.routes.ts +11 -8
  81. package/apps/api/src/modules/webhooks/webhook.service.ts +46 -5
  82. package/apps/api/src/modules/webhooks/webhook.types.ts +35 -0
  83. package/apps/api/tsconfig.json +3 -1
  84. package/apps/cli/package.json +8 -10
  85. package/apps/cli/tsconfig.json +2 -1
  86. package/apps/dashboard/Dockerfile +4 -6
  87. package/apps/dashboard/components.json +20 -0
  88. package/apps/dashboard/next-env.d.ts +6 -0
  89. package/apps/dashboard/next.config.mjs +11 -1
  90. package/apps/dashboard/package.json +25 -13
  91. package/apps/dashboard/postcss.config.mjs +5 -0
  92. package/apps/dashboard/public/android-chrome-192x192.png +0 -0
  93. package/apps/dashboard/public/android-chrome-512x512.png +0 -0
  94. package/apps/dashboard/public/apple-touch-icon.png +0 -0
  95. package/apps/dashboard/public/favicon-16x16.png +0 -0
  96. package/apps/dashboard/public/favicon-32x32.png +0 -0
  97. package/apps/dashboard/public/favicon.ico +0 -0
  98. package/apps/dashboard/public/site.webmanifest +19 -0
  99. package/apps/dashboard/src/app/(auth)/forgot-password/page.tsx +114 -0
  100. package/apps/dashboard/src/app/(auth)/layout.tsx +18 -3
  101. package/apps/dashboard/src/app/(auth)/login/page.tsx +215 -6
  102. package/apps/dashboard/src/app/(auth)/providers.tsx +34 -0
  103. package/apps/dashboard/src/app/(auth)/register/page.tsx +171 -5
  104. package/apps/dashboard/src/app/(auth)/reset-password/page.tsx +172 -0
  105. package/apps/dashboard/src/app/(auth)/verify-email/page.tsx +190 -0
  106. package/apps/dashboard/src/app/(dashboard)/(deployment)/build/[id]/page.tsx +162 -0
  107. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/BuildSummary.tsx +166 -0
  108. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DnsConfiguration.tsx +119 -0
  109. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/DomainSettings.tsx +291 -0
  110. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Header.tsx +71 -0
  111. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/Sidebar.tsx +270 -0
  112. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/components/SkeletonLoader.tsx +87 -0
  113. package/apps/dashboard/src/app/(dashboard)/(deployment)/deploy/[slug]/page.tsx +169 -0
  114. package/apps/dashboard/src/app/(dashboard)/(deployment)/layout.tsx +7 -0
  115. package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +1 -8
  116. package/apps/dashboard/src/app/(dashboard)/deployments/components/CommitDetailsModal.tsx +240 -0
  117. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentCard.tsx +121 -0
  118. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentHeader.tsx +89 -0
  119. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentMenu.tsx +182 -0
  120. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsContent.tsx +324 -0
  121. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsFilters.tsx +99 -0
  122. package/apps/dashboard/src/app/(dashboard)/deployments/components/DeploymentsList.tsx +31 -0
  123. package/apps/dashboard/src/app/(dashboard)/deployments/components/EmptyState.tsx +101 -0
  124. package/apps/dashboard/src/app/(dashboard)/deployments/components/LoadingSkeleton.tsx +57 -0
  125. package/apps/dashboard/src/app/(dashboard)/deployments/components/ProjectFilter.tsx +109 -0
  126. package/apps/dashboard/src/app/(dashboard)/deployments/components/index.ts +9 -0
  127. package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +9 -3
  128. package/apps/dashboard/src/app/(dashboard)/deployments/types.ts +44 -0
  129. package/apps/dashboard/src/app/(dashboard)/deployments/utils.ts +176 -0
  130. package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +1 -6
  131. package/apps/dashboard/src/app/(dashboard)/integrations/page.tsx +321 -0
  132. package/apps/dashboard/src/app/(dashboard)/layout.tsx +22 -32
  133. package/apps/dashboard/src/app/(dashboard)/library/components/ConnectPrompt.tsx +187 -0
  134. package/apps/dashboard/src/app/(dashboard)/library/components/LibrarySidebar.tsx +147 -0
  135. package/apps/dashboard/src/app/(dashboard)/library/components/LoadingSkeleton.tsx +39 -0
  136. package/apps/dashboard/src/app/(dashboard)/library/components/LocalProjects.tsx +548 -0
  137. package/apps/dashboard/src/app/(dashboard)/library/components/RepositoryList.tsx +292 -0
  138. package/apps/dashboard/src/app/(dashboard)/library/components/TemplateGrid.tsx +36 -0
  139. package/apps/dashboard/src/app/(dashboard)/library/components/UrlImport.tsx +74 -0
  140. package/apps/dashboard/src/app/(dashboard)/library/page.tsx +121 -0
  141. package/apps/dashboard/src/app/(dashboard)/library/types.ts +26 -0
  142. package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +1 -6
  143. package/apps/dashboard/src/app/(dashboard)/page.tsx +485 -0
  144. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/layout.tsx +22 -0
  145. package/apps/dashboard/src/app/(dashboard)/projects/[id]/[[...slug]]/page.tsx +286 -0
  146. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/AdvancedSettings.tsx +235 -0
  147. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/BuildSettings.tsx +174 -0
  148. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeleteConfirmationDialog.tsx +45 -0
  149. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DeletionModal.tsx +95 -0
  150. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/Deployments.tsx +11 -0
  151. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/DomainSettings.tsx +523 -0
  152. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/EnvironmentSettings.tsx +246 -0
  153. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GeneralSettings.tsx +93 -0
  154. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitInfo.tsx +261 -0
  155. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/GitSettings.tsx +391 -0
  156. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/LogsSettings.tsx +133 -0
  157. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectNotFound.tsx +95 -0
  158. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ProjectsBottomNavigation.tsx +62 -0
  159. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/ResourceSettings.tsx +333 -0
  160. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingSection.tsx +66 -0
  161. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SettingsNavigation.tsx +27 -0
  162. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/SleepModeSettings.tsx +134 -0
  163. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProductionUrl.tsx +25 -0
  164. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectIdentity.tsx +159 -0
  165. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/ProjectInfo.tsx +58 -0
  166. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/SkeletonLoader.tsx +170 -0
  167. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/StatsCards.tsx +33 -0
  168. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TopPaths.tsx +56 -0
  169. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/TrafficChart.tsx +157 -0
  170. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/general/index.ts +14 -0
  171. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/LogsActions.tsx +52 -0
  172. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/ServerLogs.tsx +249 -0
  173. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/TerminalLogs.tsx +682 -0
  174. package/apps/dashboard/src/app/(dashboard)/projects/[id]/components/logs/logs.css +127 -0
  175. package/apps/dashboard/src/app/(dashboard)/projects/components/DeploymentCard.tsx +73 -0
  176. package/apps/dashboard/src/app/(dashboard)/projects/components/ProjectCard.tsx +330 -0
  177. package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +326 -4
  178. package/apps/dashboard/src/app/(dashboard)/providers.tsx +23 -0
  179. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/components-tab.tsx +329 -0
  180. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/overview-tab.tsx +229 -0
  181. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/_components/terminal-tab.tsx +38 -0
  182. package/apps/dashboard/src/app/(dashboard)/servers/[serverId]/page.tsx +437 -0
  183. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/checking-state.tsx +15 -0
  184. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/choose-mode.tsx +54 -0
  185. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/component-row.tsx +65 -0
  186. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/error-banner.tsx +13 -0
  187. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/installing-panel.tsx +227 -0
  188. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/results-panel.tsx +90 -0
  189. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/setup-header.tsx +56 -0
  190. package/apps/dashboard/src/app/(dashboard)/servers/new/_components/types.ts +13 -0
  191. package/apps/dashboard/src/app/(dashboard)/servers/new/page.tsx +666 -0
  192. package/apps/dashboard/src/app/(dashboard)/servers/page.tsx +283 -0
  193. package/apps/dashboard/src/app/(dashboard)/settings/_components/BuildPreferences.tsx +110 -0
  194. package/apps/dashboard/src/app/(dashboard)/settings/_components/CloudConnection.tsx +147 -0
  195. package/apps/dashboard/src/app/(dashboard)/settings/_components/InstanceInfo.tsx +54 -0
  196. package/apps/dashboard/src/app/(dashboard)/settings/_components/ServerConnection.tsx +407 -0
  197. package/apps/dashboard/src/app/(dashboard)/settings/_components/SettingsSection.tsx +32 -0
  198. package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +62 -3
  199. package/apps/dashboard/src/app/auth/callback/close/page.tsx +23 -0
  200. package/apps/dashboard/src/app/auth/callback/install/page.tsx +40 -0
  201. package/apps/dashboard/src/app/authorize/page.tsx +137 -0
  202. package/apps/dashboard/src/app/globals.css +57 -3
  203. package/apps/dashboard/src/app/layout.tsx +43 -3
  204. package/apps/dashboard/src/components/auth-shell.tsx +66 -0
  205. package/apps/dashboard/src/components/deployments/types.ts +44 -0
  206. package/apps/dashboard/src/components/global-deployments/index.ts +17 -0
  207. package/apps/dashboard/src/components/i18n-provider.tsx +100 -0
  208. package/apps/dashboard/src/components/import-project/BuildSettings.tsx +376 -0
  209. package/apps/dashboard/src/components/import-project/BuildSkeleton.tsx +161 -0
  210. package/apps/dashboard/src/components/import-project/BuildTerminal.tsx +358 -0
  211. package/apps/dashboard/src/components/import-project/ComposeServices.tsx +455 -0
  212. package/apps/dashboard/src/components/import-project/DeploymentProcessing.tsx +510 -0
  213. package/apps/dashboard/src/components/import-project/DockerSettings.tsx +64 -0
  214. package/apps/dashboard/src/components/import-project/EnvironmentVariables.tsx +481 -0
  215. package/apps/dashboard/src/components/import-project/Frameworks.tsx +90 -0
  216. package/apps/dashboard/src/components/import-project/ProjectSettings.tsx +160 -0
  217. package/apps/dashboard/src/components/import-project/types.ts +82 -0
  218. package/apps/dashboard/src/components/integrations/GithubPermissionModal.tsx +173 -0
  219. package/apps/dashboard/src/components/language-switcher.tsx +36 -0
  220. package/apps/dashboard/src/components/logo.tsx +13 -0
  221. package/apps/dashboard/src/components/network-error-handler.tsx +27 -0
  222. package/apps/dashboard/src/components/oauth-buttons.tsx +81 -0
  223. package/apps/dashboard/src/components/overview/ActivityChart.tsx +247 -0
  224. package/apps/dashboard/src/components/overview/ApiRequestsCard.tsx +111 -0
  225. package/apps/dashboard/src/components/overview/CreditsCard.tsx +137 -0
  226. package/apps/dashboard/src/components/overview/DeploymentsChart.tsx +195 -0
  227. package/apps/dashboard/src/components/overview/EmptyState.tsx +126 -0
  228. package/apps/dashboard/src/components/overview/LoadingSkeleton.tsx +92 -0
  229. package/apps/dashboard/src/components/overview/OverviewHeader.tsx +74 -0
  230. package/apps/dashboard/src/components/overview/OverviewHeroChart.tsx +344 -0
  231. package/apps/dashboard/src/components/overview/ProjectsCard.tsx +134 -0
  232. package/apps/dashboard/src/components/overview/QuickLinks.tsx +119 -0
  233. package/apps/dashboard/src/components/overview/SandboxesCard.tsx +150 -0
  234. package/apps/dashboard/src/components/overview/StatsGrid.tsx +166 -0
  235. package/apps/dashboard/src/components/overview/TokenUsageChart.tsx +206 -0
  236. package/apps/dashboard/src/components/overview/UsageStats.tsx +79 -0
  237. package/apps/dashboard/src/components/overview/index.ts +30 -0
  238. package/apps/dashboard/src/components/overview/types.ts +116 -0
  239. package/apps/dashboard/src/components/page-header.tsx +25 -0
  240. package/apps/dashboard/src/components/project-settings/ServerSideSwitch.jsx +60 -0
  241. package/apps/dashboard/src/components/shared/AlertBox.tsx +39 -0
  242. package/apps/dashboard/src/components/shared/CTASection.tsx +45 -0
  243. package/apps/dashboard/src/components/shared/ErrorState.tsx +176 -0
  244. package/apps/dashboard/src/components/shared/FeatureCard.tsx +20 -0
  245. package/apps/dashboard/src/components/shared/MDXComponents.tsx +24 -0
  246. package/apps/dashboard/src/components/shared/MachineSettingsModal.tsx +286 -0
  247. package/apps/dashboard/src/components/shared/Modal.tsx +39 -0
  248. package/apps/dashboard/src/components/shared/OTPInput.tsx +122 -0
  249. package/apps/dashboard/src/components/shared/PageHero.tsx +21 -0
  250. package/apps/dashboard/src/components/shared/PlatformFeatureCard.tsx +27 -0
  251. package/apps/dashboard/src/components/shared/StatCard.tsx +14 -0
  252. package/apps/dashboard/src/components/shared/ValueCard.tsx +20 -0
  253. package/apps/dashboard/src/components/shared/index.ts +8 -0
  254. package/apps/dashboard/src/components/sidebar.tsx +282 -0
  255. package/apps/dashboard/src/components/theme-provider.tsx +96 -0
  256. package/apps/dashboard/src/components/toast.tsx +106 -0
  257. package/apps/dashboard/src/components/ui/CustomCursor.tsx +107 -0
  258. package/apps/dashboard/src/components/ui/CustomSelect.tsx +126 -0
  259. package/apps/dashboard/src/components/ui/DropdownMenu.tsx +174 -0
  260. package/apps/dashboard/src/components/ui/FileIcon.jsx +59 -0
  261. package/apps/dashboard/src/components/ui/IconPickerModal.tsx +273 -0
  262. package/apps/dashboard/src/components/ui/InfoBanner.jsx +39 -0
  263. package/apps/dashboard/src/components/ui/Logo.tsx +14 -0
  264. package/apps/dashboard/src/components/ui/Modal.tsx +117 -0
  265. package/apps/dashboard/src/components/ui/PageContainer.tsx +12 -0
  266. package/apps/dashboard/src/components/ui/SectionContainer.jsx +7 -0
  267. package/apps/dashboard/src/components/ui/SlidingToggle.tsx +128 -0
  268. package/apps/dashboard/src/components/ui/Toast.tsx +29 -0
  269. package/apps/dashboard/src/components/ui/button.tsx +58 -0
  270. package/apps/dashboard/src/components/ui/card.tsx +60 -0
  271. package/apps/dashboard/src/components/ui/input.tsx +20 -0
  272. package/apps/dashboard/src/components/ui/label.tsx +19 -0
  273. package/apps/dashboard/src/components/ui/select.tsx +10 -0
  274. package/apps/dashboard/src/components/ui/textarea.tsx +9 -0
  275. package/apps/dashboard/src/constants/lang-colors.ts +25 -0
  276. package/apps/dashboard/src/constants/mock.ts +38 -0
  277. package/apps/dashboard/src/context/AuthContext.tsx +91 -0
  278. package/apps/dashboard/src/context/CloudContext.tsx +275 -0
  279. package/apps/dashboard/src/context/DeploymentContext.tsx +71 -0
  280. package/apps/dashboard/src/context/GitHubContext.tsx +288 -0
  281. package/apps/dashboard/src/context/ModalContext.tsx +300 -0
  282. package/apps/dashboard/src/context/NetworkContext.tsx +32 -0
  283. package/apps/dashboard/src/context/ProjectSettingsContext.tsx +727 -0
  284. package/apps/dashboard/src/context/ToastContext.tsx +129 -0
  285. package/apps/dashboard/src/context/deployment/index.ts +3 -0
  286. package/apps/dashboard/src/context/deployment/types.ts +183 -0
  287. package/apps/dashboard/src/context/deployment/useDeploymentBuild.ts +547 -0
  288. package/apps/dashboard/src/context/deployment/useDeploymentConfig.ts +176 -0
  289. package/apps/dashboard/src/hooks/useBuildConnection.ts +334 -0
  290. package/apps/dashboard/src/hooks/useMonitorStream.ts +146 -0
  291. package/apps/dashboard/src/hooks/useProjectData.ts +110 -0
  292. package/apps/dashboard/src/hooks/useSSEConnection.ts +465 -0
  293. package/apps/dashboard/src/hooks/useSSEStream.ts +309 -0
  294. package/apps/dashboard/src/hooks/useSessions.ts +104 -0
  295. package/apps/dashboard/src/hooks/useSetupStream.ts +223 -0
  296. package/apps/dashboard/src/i18n/dictionaries/ar.json +218 -0
  297. package/apps/dashboard/src/i18n/dictionaries/en.json +218 -0
  298. package/apps/dashboard/src/i18n/index.ts +16 -0
  299. package/apps/dashboard/src/lib/api/ai.ts +22 -0
  300. package/apps/dashboard/src/lib/api/auth.ts +13 -0
  301. package/apps/dashboard/src/lib/api/client.ts +199 -0
  302. package/apps/dashboard/src/lib/api/cloud.ts +15 -0
  303. package/apps/dashboard/src/lib/api/deploy.ts +58 -0
  304. package/apps/dashboard/src/lib/api/domains.ts +13 -0
  305. package/apps/dashboard/src/lib/api/endpoints.ts +129 -0
  306. package/apps/dashboard/src/lib/api/github.ts +31 -0
  307. package/apps/dashboard/src/lib/api/icons.ts +12 -0
  308. package/apps/dashboard/src/lib/api/index.ts +41 -0
  309. package/apps/dashboard/src/lib/api/projects.ts +153 -0
  310. package/apps/dashboard/src/lib/api/sandbox.ts +14 -0
  311. package/apps/dashboard/src/lib/api/settings.ts +21 -0
  312. package/apps/dashboard/src/lib/api/system.ts +159 -0
  313. package/apps/dashboard/src/lib/auth-client.ts +54 -0
  314. package/apps/dashboard/src/lib/country.ts +25 -0
  315. package/apps/dashboard/src/lib/server/api.ts +140 -0
  316. package/apps/dashboard/src/lib/server/session.ts +103 -0
  317. package/apps/dashboard/src/lib/sseClient.ts +241 -0
  318. package/apps/dashboard/src/lib/sseMessageProcessors.ts +323 -0
  319. package/apps/dashboard/src/lib/utils.ts +6 -0
  320. package/apps/dashboard/src/proxy.ts +46 -0
  321. package/apps/dashboard/src/styles/Toast.module.css +92 -0
  322. package/apps/dashboard/src/styles/fonts.css +93 -0
  323. package/apps/dashboard/src/styles/theme.css +336 -0
  324. package/apps/dashboard/src/types/research.ts +42 -0
  325. package/apps/dashboard/src/utils/SSEParser.js +150 -0
  326. package/apps/dashboard/src/utils/authWindow.ts +143 -0
  327. package/apps/dashboard/src/utils/codeTheme.ts +127 -0
  328. package/apps/dashboard/src/utils/colors.json +2810 -0
  329. package/apps/dashboard/src/utils/date.ts +121 -0
  330. package/apps/dashboard/src/utils/deployment.ts +128 -0
  331. package/apps/dashboard/src/utils/deploymentPhaseDetector.ts +232 -0
  332. package/apps/dashboard/src/utils/extToLang.js +103 -0
  333. package/apps/dashboard/src/utils/github.ts +62 -0
  334. package/apps/dashboard/src/utils/icons.js +46 -0
  335. package/apps/dashboard/src/utils/repoSlug.ts +110 -0
  336. package/apps/dashboard/src/utils/research-api.ts +163 -0
  337. package/apps/dashboard/src/utils/suggestions.ts +84 -0
  338. package/apps/dashboard/src/utils/theme.js +1 -0
  339. package/apps/dashboard/src/utils/ui-helpers.tsx +30 -0
  340. package/apps/dashboard/src/utils/upload.js +12 -0
  341. package/apps/dashboard/tsconfig.json +27 -5
  342. package/apps/desktop/forge.config.json +36 -0
  343. package/apps/desktop/package.json +26 -0
  344. package/apps/desktop/scripts/dev.mjs +58 -0
  345. package/apps/desktop/src/main/index.ts +675 -0
  346. package/apps/desktop/src/main/types/desktop.d.ts +33 -0
  347. package/apps/desktop/src/preload/index.ts +88 -0
  348. package/apps/desktop/src/renderer/index.html +462 -0
  349. package/apps/desktop/src/renderer/onboarding.js +511 -0
  350. package/apps/desktop/src/renderer/styles.css +1139 -0
  351. package/apps/desktop/tsconfig.json +17 -0
  352. package/apps/web/.source/browser.ts +13 -0
  353. package/apps/web/.source/dynamic.ts +8 -0
  354. package/apps/web/.source/server.ts +22 -0
  355. package/apps/web/.source/source.config.mjs +20 -0
  356. package/apps/web/Dockerfile +4 -6
  357. package/apps/web/content/blog/how-ai-builds-work.mdx +55 -0
  358. package/apps/web/content/blog/introducing-openship.mdx +46 -0
  359. package/apps/web/content/blog/self-hosting-cost-breakdown.mdx +59 -0
  360. package/apps/web/content/docs/_meta.json +14 -0
  361. package/apps/web/content/docs/api.mdx +98 -0
  362. package/apps/web/content/docs/cli.mdx +80 -0
  363. package/apps/web/content/docs/first-deployment.mdx +56 -0
  364. package/apps/web/content/docs/index.mdx +27 -0
  365. package/apps/web/content/docs/installation.mdx +65 -0
  366. package/apps/web/content/docs/quickstart.mdx +45 -0
  367. package/apps/web/next-env.d.ts +6 -0
  368. package/apps/web/next.config.mjs +16 -2
  369. package/apps/web/package.json +19 -12
  370. package/apps/web/postcss.config.mjs +5 -0
  371. package/apps/web/public/android-chrome-192x192.png +0 -0
  372. package/apps/web/public/android-chrome-512x512.png +0 -0
  373. package/apps/web/public/apple-touch-icon.png +0 -0
  374. package/apps/web/public/favicon-16x16.png +0 -0
  375. package/apps/web/public/favicon-32x32.png +0 -0
  376. package/apps/web/public/favicon.ico +0 -0
  377. package/apps/web/public/site.webmanifest +1 -0
  378. package/apps/web/source.config.ts +17 -0
  379. package/apps/web/src/app/(marketing)/layout.tsx +7 -22
  380. package/apps/web/src/app/blog/[...slug]/page.tsx +247 -0
  381. package/apps/web/src/app/blog/blog.css +575 -0
  382. package/apps/web/src/app/blog/layout.tsx +50 -0
  383. package/apps/web/src/app/blog/page.tsx +235 -0
  384. package/apps/web/src/app/docs/[[...slug]]/page.tsx +67 -0
  385. package/apps/web/src/app/docs/layout.tsx +62 -0
  386. package/apps/web/src/app/docs/toc-theme.css +71 -0
  387. package/apps/web/src/app/download/page.tsx +396 -0
  388. package/apps/web/src/app/globals.css +20 -3
  389. package/apps/web/src/app/layout.tsx +57 -3
  390. package/apps/web/src/app/page.tsx +47 -6
  391. package/apps/web/src/app/robots.ts +15 -0
  392. package/apps/web/src/app/sitemap.ts +35 -0
  393. package/apps/web/src/components/blog/share-sidebar.tsx +81 -0
  394. package/apps/web/src/components/blog/writer-sidebar.tsx +81 -0
  395. package/apps/web/src/components/landing/ai-native.tsx +169 -0
  396. package/apps/web/src/components/landing/backups.tsx +139 -0
  397. package/apps/web/src/components/landing/cdn.tsx +109 -0
  398. package/apps/web/src/components/landing/coming-soon.tsx +180 -0
  399. package/apps/web/src/components/landing/dark-section.tsx +166 -0
  400. package/apps/web/src/components/landing/dashboard.tsx +407 -0
  401. package/apps/web/src/components/landing/developer-experience.tsx +224 -0
  402. package/apps/web/src/components/landing/dns-domains.tsx +146 -0
  403. package/apps/web/src/components/landing/download-button.tsx +151 -0
  404. package/apps/web/src/components/landing/features/ai-visual.tsx +48 -0
  405. package/apps/web/src/components/landing/features/cloud-visual.tsx +44 -0
  406. package/apps/web/src/components/landing/features/git-visual.tsx +61 -0
  407. package/apps/web/src/components/landing/features/index.tsx +642 -0
  408. package/apps/web/src/components/landing/features/infra-visual.tsx +69 -0
  409. package/apps/web/src/components/landing/features/rollback-visual.tsx +42 -0
  410. package/apps/web/src/components/landing/features/ssl-visual.tsx +41 -0
  411. package/apps/web/src/components/landing/features/stacks-visual.tsx +54 -0
  412. package/apps/web/src/components/landing/final-cta.tsx +116 -0
  413. package/apps/web/src/components/landing/footer.tsx +145 -0
  414. package/apps/web/src/components/landing/hero.tsx +110 -0
  415. package/apps/web/src/components/landing/how-it-works.tsx +210 -0
  416. package/apps/web/src/components/landing/index.ts +21 -0
  417. package/apps/web/src/components/landing/mail-server.tsx +122 -0
  418. package/apps/web/src/components/landing/navbar.tsx +199 -0
  419. package/apps/web/src/components/landing/no-lockin.tsx +185 -0
  420. package/apps/web/src/components/landing/open-source-cta.tsx +151 -0
  421. package/apps/web/src/components/landing/portability.tsx +190 -0
  422. package/apps/web/src/components/landing/pricing.tsx +263 -0
  423. package/apps/web/src/components/landing/scaling.tsx +209 -0
  424. package/apps/web/src/components/landing/why-openship.tsx +291 -0
  425. package/apps/web/src/hooks/use-gsap-scroll.ts +158 -0
  426. package/apps/web/src/hooks/use-platform.ts +86 -0
  427. package/apps/web/src/lib/source.ts +12 -0
  428. package/apps/web/src/styles/fonts.css +84 -0
  429. package/apps/web/src/styles/landing.css +951 -0
  430. package/apps/web/src/styles/theme.css +227 -0
  431. package/apps/web/tsconfig.json +8 -3
  432. package/bun.lock +3260 -0
  433. package/docker-compose.yml +3 -0
  434. package/docs/screenshots/.gitkeep +0 -0
  435. package/docs/screenshots/screen.png +0 -0
  436. package/openship.code-workspace +7 -0
  437. package/package.json +23 -9
  438. package/packages/adapters/docs/ARCHITECTURE.md +160 -0
  439. package/packages/adapters/docs/BUILD-PIPELINE.md +171 -0
  440. package/packages/adapters/docs/CLOUD.md +239 -0
  441. package/packages/adapters/docs/EXECUTOR.md +174 -0
  442. package/packages/adapters/docs/LOG-STREAMING.md +175 -0
  443. package/packages/adapters/docs/SYSTEM.md +191 -0
  444. package/packages/adapters/package.json +14 -9
  445. package/packages/adapters/src/index.ts +144 -4
  446. package/packages/adapters/src/infra/cloud.ts +42 -0
  447. package/packages/adapters/src/infra/index.ts +9 -0
  448. package/packages/adapters/src/infra/noop.ts +27 -0
  449. package/packages/adapters/src/infra/traefik.ts +297 -0
  450. package/packages/adapters/src/infra/types.ts +36 -0
  451. package/packages/adapters/src/platform.ts +255 -0
  452. package/packages/adapters/src/runtime/bare.ts +567 -0
  453. package/packages/adapters/src/runtime/build-pipeline.ts +237 -0
  454. package/packages/adapters/src/runtime/cloud.ts +791 -0
  455. package/packages/adapters/src/runtime/deploy-pipeline.ts +146 -0
  456. package/packages/adapters/src/runtime/docker.ts +443 -0
  457. package/packages/adapters/src/runtime/index.ts +69 -0
  458. package/packages/adapters/src/runtime/local-build.ts +112 -0
  459. package/packages/adapters/src/runtime/transfer.ts +82 -0
  460. package/packages/adapters/src/runtime/types.ts +138 -0
  461. package/packages/adapters/src/system/catalog.ts +188 -0
  462. package/packages/adapters/src/system/checks.ts +228 -0
  463. package/packages/adapters/src/system/components.ts +45 -0
  464. package/packages/adapters/src/system/debug.ts +18 -0
  465. package/packages/adapters/src/system/environment.ts +168 -0
  466. package/packages/adapters/src/system/errors.ts +34 -0
  467. package/packages/adapters/src/system/executor.ts +600 -0
  468. package/packages/adapters/src/system/index.ts +58 -0
  469. package/packages/adapters/src/system/installer.ts +310 -0
  470. package/packages/adapters/src/system/setup.ts +442 -0
  471. package/packages/adapters/src/system/state.ts +96 -0
  472. package/packages/adapters/src/system/types.ts +116 -0
  473. package/packages/adapters/src/toolchain/catalog.ts +439 -0
  474. package/packages/adapters/src/toolchain/checks.ts +181 -0
  475. package/packages/adapters/src/toolchain/index.ts +38 -0
  476. package/packages/adapters/src/toolchain/installer.ts +189 -0
  477. package/packages/adapters/src/toolchain/types.ts +84 -0
  478. package/packages/adapters/src/types.ts +277 -0
  479. package/packages/adapters/tsconfig.json +3 -1
  480. package/packages/core/package.json +9 -9
  481. package/packages/core/src/constants.ts +44 -1
  482. package/packages/core/src/index.ts +2 -0
  483. package/packages/core/src/stacks.ts +778 -0
  484. package/packages/core/src/system.ts +111 -0
  485. package/packages/core/src/types.ts +22 -1
  486. package/packages/core/src/utils.ts +8 -0
  487. package/packages/core/tsconfig.json +3 -1
  488. package/packages/db/drizzle/0000_init.sql +231 -0
  489. package/packages/db/drizzle/0001_add_server_name.sql +1 -0
  490. package/packages/db/drizzle/meta/_journal.json +20 -0
  491. package/packages/db/drizzle.config.ts +10 -0
  492. package/packages/db/package.json +26 -14
  493. package/packages/db/src/client.ts +119 -0
  494. package/packages/db/src/index.ts +57 -8
  495. package/packages/db/src/repos/account.repo.ts +92 -0
  496. package/packages/db/src/repos/deployment.repo.ts +168 -0
  497. package/packages/db/src/repos/domain.repo.ts +102 -0
  498. package/packages/db/src/repos/git-installation.repo.ts +95 -0
  499. package/packages/db/src/repos/index.ts +43 -0
  500. package/packages/db/src/repos/instance-settings.repo.ts +43 -0
  501. package/packages/db/src/repos/project.repo.ts +159 -0
  502. package/packages/db/src/repos/session.repo.ts +82 -0
  503. package/packages/db/src/repos/settings.repo.ts +51 -0
  504. package/packages/db/src/repos/user.repo.ts +94 -0
  505. package/packages/db/src/schema/auth.ts +56 -0
  506. package/packages/db/src/schema/deployment.ts +87 -0
  507. package/packages/db/src/schema/domain.ts +46 -0
  508. package/packages/db/src/schema/github.ts +24 -0
  509. package/packages/db/src/schema/index.ts +6 -0
  510. package/packages/db/src/schema/project.ts +117 -0
  511. package/packages/db/src/schema/settings.ts +103 -0
  512. package/packages/db/tsconfig.json +3 -1
  513. package/packages/ui/package.json +17 -17
  514. package/packages/ui/src/components/status-dot.tsx +1 -1
  515. package/packages/ui/tsconfig.json +7 -2
  516. package/pnpm-workspace.yaml +0 -1
  517. package/{tooling/tsconfig/base.json → tsconfig.base.json} +0 -1
  518. package/turbo.json +4 -0
  519. package/apps/api/src/modules/auth/auth.controller.ts +0 -26
  520. package/apps/api/src/modules/auth/auth.schema.ts +0 -12
  521. package/apps/api/src/modules/auth/auth.service.ts +0 -23
  522. package/apps/dashboard/src/app/page.tsx +0 -6
  523. package/apps/web/src/app/(marketing)/docs/page.tsx +0 -10
  524. package/packages/adapters/src/base-adapter.ts +0 -49
  525. package/packages/adapters/src/docker-adapter.ts +0 -43
  526. package/packages/adapters/src/oblien-adapter.ts +0 -46
  527. package/packages/adapters/src/registry.ts +0 -22
  528. package/packages/db/prisma/schema.prisma +0 -160
  529. package/tooling/tsconfig/nextjs.json +0 -10
  530. package/tooling/tsconfig/node.json +0 -10
  531. package/tooling/tsconfig/package.json +0 -7
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Deployment notifications — email alerts for build/deploy lifecycle events.
3
+ *
4
+ * Uses the existing SMTP transport from lib/mail.ts.
5
+ * Gracefully no-ops when SMTP is not configured (self-hosted without email).
6
+ *
7
+ * Event types:
8
+ * - deployment_success — build & deploy completed
9
+ * - build_failed — build phase failed
10
+ * - deployment_failed — deploy phase failed (container didn't start)
11
+ * - push_received — git push triggered a deployment
12
+ * - ssl_expiring — TLS certificate expires soon
13
+ * - ssl_renewed — TLS certificate was auto-renewed
14
+ * - project_limit — user approaching project/build limits
15
+ */
16
+
17
+ import { sendMail, smtpEnabled } from "./mail";
18
+ import { env } from "../config/env";
19
+
20
+ // ─── Event types ─────────────────────────────────────────────────────────────
21
+
22
+ export type NotificationEvent =
23
+ | "deployment_success"
24
+ | "build_failed"
25
+ | "deployment_failed"
26
+ | "push_received"
27
+ | "ssl_expiring"
28
+ | "ssl_renewed"
29
+ | "project_limit";
30
+
31
+ export interface NotificationContext {
32
+ /** User email to send to */
33
+ email: string;
34
+ /** Project name */
35
+ projectName: string;
36
+ /** Additional event-specific data */
37
+ data?: Record<string, unknown>;
38
+ }
39
+
40
+ // ─── Templates ───────────────────────────────────────────────────────────────
41
+
42
+ function subjectFor(event: NotificationEvent, ctx: NotificationContext): string {
43
+ switch (event) {
44
+ case "deployment_success":
45
+ return `✅ Deployment succeeded — ${ctx.projectName}`;
46
+ case "build_failed":
47
+ return `❌ Build failed — ${ctx.projectName}`;
48
+ case "deployment_failed":
49
+ return `❌ Deployment failed — ${ctx.projectName}`;
50
+ case "push_received":
51
+ return `🔄 New push detected — ${ctx.projectName}`;
52
+ case "ssl_expiring":
53
+ return `⚠️ SSL certificate expiring — ${ctx.data?.domain ?? ctx.projectName}`;
54
+ case "ssl_renewed":
55
+ return `🔒 SSL certificate renewed — ${ctx.data?.domain ?? ctx.projectName}`;
56
+ case "project_limit":
57
+ return `⚠️ Approaching project limit`;
58
+ }
59
+ }
60
+
61
+ function htmlFor(event: NotificationEvent, ctx: NotificationContext): string {
62
+ const dashboardUrl = env.DASHBOARD_URL;
63
+ const d = ctx.data ?? {};
64
+
65
+ switch (event) {
66
+ case "deployment_success":
67
+ return `
68
+ <h2>Deployment Succeeded</h2>
69
+ <p><strong>${ctx.projectName}</strong> was deployed successfully.</p>
70
+ ${d.branch ? `<p>Branch: <code>${d.branch}</code></p>` : ""}
71
+ ${d.commitSha ? `<p>Commit: <code>${String(d.commitSha).slice(0, 7)}</code></p>` : ""}
72
+ ${d.url ? `<p>Live at: <a href="${d.url}">${d.url}</a></p>` : ""}
73
+ ${d.durationMs ? `<p>Build time: ${Math.round(Number(d.durationMs) / 1000)}s</p>` : ""}
74
+ <p><a href="${dashboardUrl}">Open Dashboard</a></p>
75
+ `;
76
+
77
+ case "build_failed":
78
+ return `
79
+ <h2>Build Failed</h2>
80
+ <p>The build for <strong>${ctx.projectName}</strong> failed.</p>
81
+ ${d.branch ? `<p>Branch: <code>${d.branch}</code></p>` : ""}
82
+ ${d.error ? `<p>Error: <code>${d.error}</code></p>` : ""}
83
+ ${d.logs ? `<details><summary>Build Logs (last 50 lines)</summary><pre>${d.logs}</pre></details>` : ""}
84
+ <p><a href="${dashboardUrl}">Open Dashboard</a></p>
85
+ `;
86
+
87
+ case "deployment_failed":
88
+ return `
89
+ <h2>Deployment Failed</h2>
90
+ <p>The container for <strong>${ctx.projectName}</strong> failed to start.</p>
91
+ ${d.error ? `<p>Error: <code>${d.error}</code></p>` : ""}
92
+ <p><a href="${dashboardUrl}">Open Dashboard</a></p>
93
+ `;
94
+
95
+ case "push_received":
96
+ return `
97
+ <h2>New Push Detected</h2>
98
+ <p>A new push was received for <strong>${ctx.projectName}</strong>.</p>
99
+ ${d.branch ? `<p>Branch: <code>${d.branch}</code></p>` : ""}
100
+ ${d.author ? `<p>Author: ${d.author}</p>` : ""}
101
+ ${d.commitMessage ? `<p>Message: ${d.commitMessage}</p>` : ""}
102
+ <p>A new deployment has been triggered automatically.</p>
103
+ `;
104
+
105
+ case "ssl_expiring":
106
+ return `
107
+ <h2>SSL Certificate Expiring</h2>
108
+ <p>The TLS certificate for <strong>${d.domain}</strong> expires in <strong>${d.daysLeft} days</strong>.</p>
109
+ <p>Auto-renewal will be attempted. If it fails, manual intervention may be required.</p>
110
+ `;
111
+
112
+ case "ssl_renewed":
113
+ return `
114
+ <h2>SSL Certificate Renewed</h2>
115
+ <p>The TLS certificate for <strong>${d.domain}</strong> has been renewed.</p>
116
+ ${d.expiresAt ? `<p>New expiry: ${d.expiresAt}</p>` : ""}
117
+ `;
118
+
119
+ case "project_limit":
120
+ return `
121
+ <h2>Approaching Project Limit</h2>
122
+ <p>You are using <strong>${d.current}</strong> of <strong>${d.limit}</strong> allowed projects.</p>
123
+ <p>Consider archiving unused projects or upgrading your plan.</p>
124
+ `;
125
+ }
126
+ }
127
+
128
+ // ─── Public API ──────────────────────────────────────────────────────────────
129
+
130
+ /**
131
+ * Send a deployment lifecycle notification.
132
+ * No-ops when SMTP is unconfigured — safe to call unconditionally.
133
+ */
134
+ export async function notify(
135
+ event: NotificationEvent,
136
+ ctx: NotificationContext,
137
+ ): Promise<void> {
138
+ if (!smtpEnabled) return;
139
+
140
+ try {
141
+ await sendMail({
142
+ to: ctx.email,
143
+ subject: subjectFor(event, ctx),
144
+ html: htmlFor(event, ctx),
145
+ });
146
+ } catch (err) {
147
+ // Never throw from notifications — they are best-effort
148
+ console.error(`[notify] Failed to send ${event} email to ${ctx.email}:`, err);
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Convenience: send a deployment success notification.
154
+ */
155
+ export async function notifyDeploySuccess(
156
+ email: string,
157
+ project: { name: string },
158
+ deployment: { branch: string; commitSha?: string | null; url?: string | null; durationMs?: number },
159
+ ): Promise<void> {
160
+ await notify("deployment_success", {
161
+ email,
162
+ projectName: project.name,
163
+ data: {
164
+ branch: deployment.branch,
165
+ commitSha: deployment.commitSha,
166
+ url: deployment.url,
167
+ durationMs: deployment.durationMs,
168
+ },
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Convenience: send a build failure notification.
174
+ */
175
+ export async function notifyBuildFailed(
176
+ email: string,
177
+ project: { name: string },
178
+ details: { branch: string; error: string; logs?: string },
179
+ ): Promise<void> {
180
+ // Trim logs to last 50 lines to avoid huge emails
181
+ const trimmedLogs = details.logs
182
+ ?.split("\n")
183
+ .slice(-50)
184
+ .join("\n");
185
+
186
+ await notify("build_failed", {
187
+ email,
188
+ projectName: project.name,
189
+ data: {
190
+ branch: details.branch,
191
+ error: details.error,
192
+ logs: trimmedLogs,
193
+ },
194
+ });
195
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Openship Cloud — namespace provisioning + token minting.
3
+ *
4
+ * Runs on the SaaS API (CLOUD_MODE=true) only. Local instances
5
+ * call POST /api/cloud/token to get a namespace-scoped token,
6
+ * then use `new Oblien({ token })` to drive the full pipeline
7
+ * themselves (workspaces.create, build, deploy — everything).
8
+ *
9
+ * Two responsibilities:
10
+ * 1. ensureNamespace(userId) — create-if-not-exists, cached
11
+ * 2. issueNamespaceToken(userId) — mint a scoped token for the namespace
12
+ */
13
+
14
+ import { Oblien } from "oblien";
15
+ import { env } from "../config/env";
16
+
17
+ // ─── Oblien client (master credentials — SaaS only) ─────────────────────────
18
+
19
+ let _client: Oblien | null = null;
20
+
21
+ function getClient(): Oblien {
22
+ if (_client) return _client;
23
+
24
+ const clientId = env.OBLIEN_CLIENT_ID;
25
+ const clientSecret = env.OBLIEN_CLIENT_SECRET;
26
+
27
+ if (!clientId || !clientSecret) {
28
+ throw new Error("Oblien credentials not configured (OBLIEN_CLIENT_ID / OBLIEN_CLIENT_SECRET)");
29
+ }
30
+
31
+ _client = new Oblien({ clientId, clientSecret });
32
+ return _client;
33
+ }
34
+
35
+ // ─── Namespace management ────────────────────────────────────────────────────
36
+
37
+ /** Cache: userId → namespace slug */
38
+ const namespaceCache = new Map<string, string>();
39
+
40
+ /**
41
+ * Ensure an Oblien namespace exists for a user.
42
+ *
43
+ * Slug: `os-{userId}`. Idempotent — 409 on duplicate is handled.
44
+ */
45
+ export async function ensureNamespace(userId: string): Promise<string> {
46
+ const cached = namespaceCache.get(userId);
47
+ if (cached) return cached;
48
+
49
+ const client = getClient();
50
+ const slug = `os-${userId}`;
51
+
52
+ try {
53
+ const res = await client.namespaces.create({ name: `Openship ${userId}`, slug });
54
+ const nsSlug = res.data.slug ?? slug;
55
+ namespaceCache.set(userId, nsSlug);
56
+ return nsSlug;
57
+ } catch (err: unknown) {
58
+ const status = (err as { status?: number }).status;
59
+ if (status !== 409) throw err;
60
+
61
+ // Already exists — fetch and cache
62
+ const existing = await client.namespaces.get(slug);
63
+ const nsSlug = existing.data.slug ?? slug;
64
+ namespaceCache.set(userId, nsSlug);
65
+ return nsSlug;
66
+ }
67
+ }
68
+
69
+ // ─── Token minting ───────────────────────────────────────────────────────────
70
+
71
+ export interface NamespaceTokenResult {
72
+ token: string;
73
+ namespace: string;
74
+ expiresAt: string;
75
+ }
76
+
77
+ /**
78
+ * Issue a namespace-scoped Oblien token for a user.
79
+ *
80
+ * The token gives full access to the user's namespace — create workspaces,
81
+ * manage lifecycle, deploy, etc. Local instances use this to construct
82
+ * `new Oblien({ token })` and run the full CloudRuntime pipeline.
83
+ *
84
+ * TTL: 30 minutes (covers build + deploy + some buffer).
85
+ */
86
+ export async function issueNamespaceToken(userId: string): Promise<NamespaceTokenResult> {
87
+ const client = getClient();
88
+ const namespace = await ensureNamespace(userId);
89
+
90
+ const result = await client.tokens.create({
91
+ scope: "namespace",
92
+ namespace,
93
+ ttl: 1800,
94
+ });
95
+
96
+ return {
97
+ token: result.token,
98
+ namespace,
99
+ expiresAt: result.expiresAt,
100
+ };
101
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Resource utilities.
3
+ *
4
+ * Single source of truth: DEFAULT_RESOURCE_CONFIG / DEFAULT_BUILD_RESOURCE_CONFIG
5
+ * in @repo/adapters. Everything flows from there.
6
+ */
7
+
8
+ import {
9
+ DEFAULT_RESOURCE_CONFIG,
10
+ DEFAULT_BUILD_RESOURCE_CONFIG,
11
+ type ResourceConfig,
12
+ } from "@repo/adapters";
13
+
14
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
15
+
16
+ /**
17
+ * Normalize a possibly-legacy DB value into cpuCores.
18
+ * Old format: { cpus, cpuConfig: { quotaUs, periodUs }, memoryMb }
19
+ * New format: { cpuCores, memoryMb }
20
+ */
21
+ function extractCpuCores(raw: Record<string, unknown>): number | undefined {
22
+ if (typeof raw.cpuCores === "number") return raw.cpuCores;
23
+ const cfg = raw.cpuConfig as { quotaUs?: number; periodUs?: number } | undefined;
24
+ if (cfg?.quotaUs && cfg?.periodUs) return cfg.quotaUs / cfg.periodUs;
25
+ if (typeof raw.cpus === "number") return raw.cpus;
26
+ return undefined;
27
+ }
28
+
29
+ // ─── Public API ──────────────────────────────────────────────────────────────
30
+
31
+ export interface DeploymentResources {
32
+ build: ResourceConfig;
33
+ production: ResourceConfig;
34
+ sleepMode: string;
35
+ port: number;
36
+ }
37
+
38
+ /**
39
+ * Encode ResourceConfig → display format (for API responses).
40
+ */
41
+ export function encodeResources(
42
+ production?: ResourceConfig | null,
43
+ build?: ResourceConfig | null,
44
+ sleepMode = "auto_sleep",
45
+ port = 3000,
46
+ ): DeploymentResources {
47
+ return {
48
+ build: build ?? { ...DEFAULT_BUILD_RESOURCE_CONFIG },
49
+ production: production ?? { ...DEFAULT_RESOURCE_CONFIG },
50
+ sleepMode,
51
+ port,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Validate user resource input → ResourceConfig.
57
+ */
58
+ export function decodeResources(input: {
59
+ cpuCores?: number;
60
+ memoryMb?: number;
61
+ diskMb?: number;
62
+ }): ResourceConfig {
63
+ const cores = input.cpuCores ?? DEFAULT_RESOURCE_CONFIG.cpuCores;
64
+ const mem = input.memoryMb ?? DEFAULT_RESOURCE_CONFIG.memoryMb;
65
+ const disk = input.diskMb ?? DEFAULT_RESOURCE_CONFIG.diskMb;
66
+
67
+ if (cores < 0.25 || cores > 4.0) {
68
+ throw new Error("CPU cores must be between 0.25 and 4.00");
69
+ }
70
+ if (mem < 128 || mem > 8192) {
71
+ throw new Error("Memory must be between 128 MB and 8192 MB");
72
+ }
73
+ if (disk < 64 || disk > 204800) {
74
+ throw new Error("Disk must be between 64 MB and 204800 MB");
75
+ }
76
+
77
+ return { cpuCores: cores, memoryMb: mem, diskMb: disk };
78
+ }
79
+
80
+ /**
81
+ * Ensure a ResourceConfig has all fields populated with safe defaults.
82
+ * Handles legacy DB format ({ cpus, cpuConfig }) transparently.
83
+ */
84
+ export function withDefaults(
85
+ config?: ResourceConfig | Record<string, unknown> | null,
86
+ defaults = DEFAULT_RESOURCE_CONFIG,
87
+ ): ResourceConfig {
88
+ if (!config) return { ...defaults };
89
+
90
+ const raw = config as Record<string, unknown>;
91
+ const cpuCores = extractCpuCores(raw) ?? defaults.cpuCores;
92
+ const memoryMb = (typeof raw.memoryMb === "number" ? raw.memoryMb : undefined) ?? defaults.memoryMb;
93
+ const diskMb = (typeof raw.diskMb === "number" ? raw.diskMb : undefined) ?? defaults.diskMb;
94
+
95
+ return { cpuCores, memoryMb, diskMb };
96
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Screenshot service — captures website screenshots after deployment.
3
+ *
4
+ * Dispatches to a headless browser service (Puppeteer/Playwright container)
5
+ * and optionally uploads the result to a CDN.
6
+ *
7
+ * Configurable via env vars:
8
+ * SCREENSHOT_SERVICE_URL — URL of the screenshot micro-service
9
+ * CDN_UPLOAD_URL — URL of the CDN upload endpoint
10
+ *
11
+ * Gracefully no-ops when the screenshot service is not configured.
12
+ */
13
+
14
+ import { env } from "../config/env";
15
+
16
+ // ─── Types ───────────────────────────────────────────────────────────────────
17
+
18
+ export interface ScreenshotOptions {
19
+ /** URL to capture */
20
+ url: string;
21
+ /** Viewport width (default 1600) */
22
+ width?: number;
23
+ /** Viewport height (default 900) */
24
+ height?: number;
25
+ /** Device scale factor (default 2 for retina) */
26
+ scale?: number;
27
+ /** Whether to capture full-page screenshot */
28
+ fullPage?: boolean;
29
+ /** Timeout in ms before aborting (default 15000) */
30
+ timeout?: number;
31
+ }
32
+
33
+ export interface ScreenshotResult {
34
+ /** Temporary URL of the captured screenshot */
35
+ tempUrl: string;
36
+ /** CDN URL after upload (null if CDN not configured) */
37
+ cdnUrl: string | null;
38
+ /** Image dimensions */
39
+ width: number;
40
+ height: number;
41
+ /** File size in bytes */
42
+ sizeBytes: number;
43
+ }
44
+
45
+ // ─── Service ─────────────────────────────────────────────────────────────────
46
+
47
+ const screenshotServiceUrl = env.SCREENSHOT_SERVICE_URL;
48
+ const cdnUploadUrl = env.CDN_UPLOAD_URL;
49
+
50
+ /** Whether the screenshot service is available */
51
+ export const screenshotEnabled = !!screenshotServiceUrl;
52
+
53
+ /**
54
+ * Capture a screenshot of a deployed site.
55
+ * Returns null when screenshot service is not configured.
56
+ */
57
+ export async function captureScreenshot(
58
+ opts: ScreenshotOptions,
59
+ ): Promise<ScreenshotResult | null> {
60
+ if (!screenshotServiceUrl) return null;
61
+
62
+ const {
63
+ url,
64
+ width = 1600,
65
+ height = 900,
66
+ scale = 2,
67
+ fullPage = false,
68
+ timeout = 15_000,
69
+ } = opts;
70
+
71
+ try {
72
+ const controller = new AbortController();
73
+ const timer = setTimeout(() => controller.abort(), timeout);
74
+
75
+ const res = await fetch(`${screenshotServiceUrl}/screenshot`, {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify({ url, width, height, scale, fullPage }),
79
+ signal: controller.signal,
80
+ });
81
+
82
+ clearTimeout(timer);
83
+
84
+ if (!res.ok) {
85
+ console.error(`[screenshot] Service returned ${res.status}: ${await res.text()}`);
86
+ return null;
87
+ }
88
+
89
+ const data = (await res.json()) as {
90
+ url: string;
91
+ width: number;
92
+ height: number;
93
+ size: number;
94
+ };
95
+
96
+ // Upload to CDN if configured
97
+ let cdnUrl: string | null = null;
98
+ if (cdnUploadUrl && data.url) {
99
+ cdnUrl = await uploadToCdn(data.url);
100
+ }
101
+
102
+ return {
103
+ tempUrl: data.url,
104
+ cdnUrl,
105
+ width: data.width,
106
+ height: data.height,
107
+ sizeBytes: data.size,
108
+ };
109
+ } catch (err) {
110
+ if ((err as Error).name === "AbortError") {
111
+ console.warn(`[screenshot] Timed out after ${timeout}ms for ${url}`);
112
+ } else {
113
+ console.error(`[screenshot] Failed to capture ${url}:`, err);
114
+ }
115
+ return null;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Capture a screenshot after deploying a project.
121
+ * Skips backend-only stacks (no UI to screenshot).
122
+ */
123
+ export async function capturePostDeployScreenshot(
124
+ siteUrl: string,
125
+ stack: string,
126
+ ): Promise<ScreenshotResult | null> {
127
+ // Skip for backend-only stacks — no point screenshotting an API
128
+ const skipStacks = new Set([
129
+ "express", "fastify", "hono", "nestjs", "koa", "elysia", "adonis",
130
+ "go", "gin", "fiber", "echo",
131
+ "rust", "actix", "axum", "rocket",
132
+ "python", "django", "flask", "fastapi",
133
+ "rails", "sinatra", "laravel", "symfony",
134
+ "springboot", "quarkus", "dotnet", "phoenix",
135
+ "node", "docker",
136
+ ]);
137
+
138
+ if (skipStacks.has(stack)) return null;
139
+
140
+ return captureScreenshot({
141
+ url: siteUrl,
142
+ width: 1600,
143
+ height: 900,
144
+ scale: 2,
145
+ timeout: 15_000,
146
+ });
147
+ }
148
+
149
+ // ─── CDN upload ──────────────────────────────────────────────────────────────
150
+
151
+ async function uploadToCdn(imageUrl: string): Promise<string | null> {
152
+ if (!cdnUploadUrl) return null;
153
+
154
+ try {
155
+ const res = await fetch(cdnUploadUrl, {
156
+ method: "POST",
157
+ headers: { "Content-Type": "application/json" },
158
+ body: JSON.stringify({ url: imageUrl }),
159
+ });
160
+
161
+ if (!res.ok) {
162
+ console.error(`[screenshot] CDN upload failed: ${res.status}`);
163
+ return null;
164
+ }
165
+
166
+ const data = (await res.json()) as { url: string };
167
+ return data.url;
168
+ } catch (err) {
169
+ console.error("[screenshot] CDN upload error:", err);
170
+ return null;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Clean up temporary screenshot from the browser service.
176
+ */
177
+ export async function cleanupScreenshot(tempUrl: string): Promise<void> {
178
+ if (!screenshotServiceUrl) return;
179
+
180
+ try {
181
+ await fetch(`${screenshotServiceUrl}/cleanup`, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: JSON.stringify({ url: tempUrl }),
185
+ });
186
+ } catch {
187
+ // Best-effort cleanup
188
+ }
189
+ }