openship 0.1.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.
- package/.env.example +22 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/CONTRIBUTING.md +40 -0
- package/README.md +164 -0
- package/apps/api/.env.example +30 -0
- package/apps/api/Dockerfile +24 -0
- package/apps/api/package.json +37 -0
- package/apps/api/src/app.ts +26 -0
- package/apps/api/src/config/env.ts +39 -0
- package/apps/api/src/config/index.ts +1 -0
- package/apps/api/src/index.ts +8 -0
- package/apps/api/src/middleware/auth.ts +24 -0
- package/apps/api/src/middleware/error-handler.ts +21 -0
- package/apps/api/src/middleware/index.ts +3 -0
- package/apps/api/src/middleware/rate-limiter.ts +26 -0
- package/apps/api/src/modules/auth/auth.controller.ts +26 -0
- package/apps/api/src/modules/auth/auth.routes.ts +10 -0
- package/apps/api/src/modules/auth/auth.schema.ts +12 -0
- package/apps/api/src/modules/auth/auth.service.ts +23 -0
- package/apps/api/src/modules/billing/billing.controller.ts +60 -0
- package/apps/api/src/modules/billing/billing.routes.ts +26 -0
- package/apps/api/src/modules/billing/billing.schema.ts +9 -0
- package/apps/api/src/modules/billing/billing.service.ts +33 -0
- package/apps/api/src/modules/deployments/deployment.controller.ts +35 -0
- package/apps/api/src/modules/deployments/deployment.routes.ts +11 -0
- package/apps/api/src/modules/deployments/deployment.schema.ts +7 -0
- package/apps/api/src/modules/deployments/deployment.service.ts +23 -0
- package/apps/api/src/modules/domains/domain.controller.ts +21 -0
- package/apps/api/src/modules/domains/domain.routes.ts +9 -0
- package/apps/api/src/modules/domains/domain.service.ts +19 -0
- package/apps/api/src/modules/health/health.routes.ts +10 -0
- package/apps/api/src/modules/projects/project.controller.ts +29 -0
- package/apps/api/src/modules/projects/project.routes.ts +10 -0
- package/apps/api/src/modules/projects/project.schema.ts +9 -0
- package/apps/api/src/modules/projects/project.service.ts +23 -0
- package/apps/api/src/modules/webhooks/webhook.controller.ts +22 -0
- package/apps/api/src/modules/webhooks/webhook.routes.ts +12 -0
- package/apps/api/src/modules/webhooks/webhook.service.ts +11 -0
- package/apps/api/tsconfig.json +12 -0
- package/apps/cli/package.json +28 -0
- package/apps/cli/src/commands/deploy.ts +11 -0
- package/apps/cli/src/commands/init.ts +8 -0
- package/apps/cli/src/commands/login.ts +8 -0
- package/apps/cli/src/commands/logs.ts +9 -0
- package/apps/cli/src/index.ts +21 -0
- package/apps/cli/src/lib/api-client.ts +35 -0
- package/apps/cli/tsconfig.json +12 -0
- package/apps/dashboard/Dockerfile +24 -0
- package/apps/dashboard/next.config.mjs +6 -0
- package/apps/dashboard/package.json +28 -0
- package/apps/dashboard/src/app/(auth)/layout.tsx +6 -0
- package/apps/dashboard/src/app/(auth)/login/page.tsx +11 -0
- package/apps/dashboard/src/app/(auth)/register/page.tsx +10 -0
- package/apps/dashboard/src/app/(dashboard)/billing/page.tsx +10 -0
- package/apps/dashboard/src/app/(dashboard)/deployments/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/domains/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/layout.tsx +37 -0
- package/apps/dashboard/src/app/(dashboard)/monitoring/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/projects/page.tsx +8 -0
- package/apps/dashboard/src/app/(dashboard)/settings/page.tsx +8 -0
- package/apps/dashboard/src/app/globals.css +3 -0
- package/apps/dashboard/src/app/layout.tsx +15 -0
- package/apps/dashboard/src/app/page.tsx +6 -0
- package/apps/dashboard/tsconfig.json +11 -0
- package/apps/web/Dockerfile +24 -0
- package/apps/web/next.config.mjs +6 -0
- package/apps/web/package.json +26 -0
- package/apps/web/src/app/(marketing)/docs/page.tsx +10 -0
- package/apps/web/src/app/(marketing)/layout.tsx +24 -0
- package/apps/web/src/app/(marketing)/page.tsx +2 -0
- package/apps/web/src/app/(marketing)/pricing/page.tsx +10 -0
- package/apps/web/src/app/globals.css +3 -0
- package/apps/web/src/app/layout.tsx +16 -0
- package/apps/web/src/app/page.tsx +10 -0
- package/apps/web/tsconfig.json +11 -0
- package/docker-compose.yml +87 -0
- package/package.json +31 -0
- package/packages/adapters/package.json +27 -0
- package/packages/adapters/src/base-adapter.ts +49 -0
- package/packages/adapters/src/docker-adapter.ts +43 -0
- package/packages/adapters/src/index.ts +4 -0
- package/packages/adapters/src/oblien-adapter.ts +46 -0
- package/packages/adapters/src/registry.ts +22 -0
- package/packages/adapters/tsconfig.json +9 -0
- package/packages/core/package.json +27 -0
- package/packages/core/src/constants.ts +28 -0
- package/packages/core/src/errors.ts +49 -0
- package/packages/core/src/index.ts +4 -0
- package/packages/core/src/types.ts +47 -0
- package/packages/core/src/utils.ts +35 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/db/package.json +32 -0
- package/packages/db/prisma/schema.prisma +160 -0
- package/packages/db/src/index.ts +11 -0
- package/packages/db/tsconfig.json +9 -0
- package/packages/ui/package.json +38 -0
- package/packages/ui/src/components/badge.tsx +29 -0
- package/packages/ui/src/components/button.tsx +44 -0
- package/packages/ui/src/components/card.tsx +26 -0
- package/packages/ui/src/components/status-dot.tsx +25 -0
- package/packages/ui/src/globals.css +18 -0
- package/packages/ui/src/index.tsx +8 -0
- package/packages/ui/src/lib/cn.ts +7 -0
- package/packages/ui/tsconfig.json +9 -0
- package/pnpm-workspace.yaml +4 -0
- package/tooling/tsconfig/base.json +19 -0
- package/tooling/tsconfig/nextjs.json +10 -0
- package/tooling/tsconfig/node.json +10 -0
- package/tooling/tsconfig/package.json +7 -0
- package/turbo.json +32 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: "Dashboard — Openship",
|
|
6
|
+
description: "Manage your deployments, domains, and infrastructure.",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
10
|
+
return (
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<body>{children}</body>
|
|
13
|
+
</html>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ─── Build stage ────────────────────────────────────────
|
|
2
|
+
FROM node:20-alpine AS builder
|
|
3
|
+
RUN corepack enable && corepack prepare pnpm@9.0.0 --activate
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
COPY pnpm-workspace.yaml pnpm-lock.yaml* package.json ./
|
|
7
|
+
COPY tooling/ ./tooling/
|
|
8
|
+
COPY packages/ ./packages/
|
|
9
|
+
COPY apps/web/ ./apps/web/
|
|
10
|
+
|
|
11
|
+
RUN pnpm install --frozen-lockfile
|
|
12
|
+
RUN pnpm run build --filter=openship-web...
|
|
13
|
+
|
|
14
|
+
# ─── Production stage ──────────────────────────────────
|
|
15
|
+
FROM node:20-alpine AS runner
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
|
|
18
|
+
COPY --from=builder /app/apps/web/.next/standalone ./
|
|
19
|
+
COPY --from=builder /app/apps/web/.next/static ./.next/static
|
|
20
|
+
COPY --from=builder /app/apps/web/public ./public
|
|
21
|
+
|
|
22
|
+
ENV NODE_ENV=production
|
|
23
|
+
EXPOSE 3000
|
|
24
|
+
CMD ["node", "server.js"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openship-web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev --port 3000",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint",
|
|
10
|
+
"clean": "rm -rf .next .turbo node_modules"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"openship-ui": "workspace:*",
|
|
14
|
+
"openship-core": "workspace:*",
|
|
15
|
+
"next": "^14.2.0",
|
|
16
|
+
"react": "^18.3.0",
|
|
17
|
+
"react-dom": "^18.3.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"openship-tsconfig": "workspace:*",
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"@types/react": "^18.3.0",
|
|
23
|
+
"@types/react-dom": "^18.3.0",
|
|
24
|
+
"typescript": "^5.4.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function DocsPage() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="mx-auto max-w-5xl px-6 py-20">
|
|
4
|
+
<h1 className="text-4xl font-bold">Documentation</h1>
|
|
5
|
+
<p className="mt-4 text-gray-600">
|
|
6
|
+
Everything you need to get started with Openship.
|
|
7
|
+
</p>
|
|
8
|
+
</main>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
|
|
2
|
+
return (
|
|
3
|
+
<div className="min-h-screen">
|
|
4
|
+
{/* Marketing nav / header goes here */}
|
|
5
|
+
<header className="border-b px-6 py-4">
|
|
6
|
+
<nav className="mx-auto flex max-w-7xl items-center justify-between">
|
|
7
|
+
<span className="text-xl font-bold">Openship</span>
|
|
8
|
+
<div className="flex items-center gap-6">
|
|
9
|
+
<a href="/pricing" className="text-sm hover:underline">
|
|
10
|
+
Pricing
|
|
11
|
+
</a>
|
|
12
|
+
<a href="/docs" className="text-sm hover:underline">
|
|
13
|
+
Docs
|
|
14
|
+
</a>
|
|
15
|
+
<a href="/login" className="text-sm font-medium hover:underline">
|
|
16
|
+
Login
|
|
17
|
+
</a>
|
|
18
|
+
</div>
|
|
19
|
+
</nav>
|
|
20
|
+
</header>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function PricingPage() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="mx-auto max-w-5xl px-6 py-20">
|
|
4
|
+
<h1 className="text-4xl font-bold">Pricing</h1>
|
|
5
|
+
<p className="mt-4 text-gray-600">
|
|
6
|
+
Free to self-host. Simple, transparent pricing for our hosted cloud.
|
|
7
|
+
</p>
|
|
8
|
+
</main>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: "Openship — Self-Hostable Deployment Platform",
|
|
6
|
+
description:
|
|
7
|
+
"Deploy anywhere. Open-source deployment platform you can self-host or use our cloud.",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
11
|
+
return (
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<body>{children}</body>
|
|
14
|
+
</html>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function HomePage() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="flex min-h-screen flex-col items-center justify-center">
|
|
4
|
+
<h1 className="text-5xl font-bold tracking-tight">Openship</h1>
|
|
5
|
+
<p className="mt-4 text-lg text-gray-600">
|
|
6
|
+
The open-source deployment platform you can self-host or use our cloud.
|
|
7
|
+
</p>
|
|
8
|
+
</main>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# ──────────────────────────────────────────────────────────
|
|
2
|
+
# Openship — Self-Hosted Docker Compose
|
|
3
|
+
#
|
|
4
|
+
# Quick start:
|
|
5
|
+
# cp .env.example .env
|
|
6
|
+
# docker compose up -d
|
|
7
|
+
#
|
|
8
|
+
# This spins up the full Openship stack:
|
|
9
|
+
# • Dashboard (Next.js) → localhost:3001
|
|
10
|
+
# • Web / Landing → localhost:3000
|
|
11
|
+
# • API (Hono) → localhost:4000
|
|
12
|
+
# • PostgreSQL → localhost:5432
|
|
13
|
+
# • Redis → localhost:6379
|
|
14
|
+
# ──────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
version: "3.9"
|
|
17
|
+
|
|
18
|
+
services:
|
|
19
|
+
# ─── Database ─────────────────────────────────────────
|
|
20
|
+
postgres:
|
|
21
|
+
image: postgres:16-alpine
|
|
22
|
+
restart: unless-stopped
|
|
23
|
+
environment:
|
|
24
|
+
POSTGRES_USER: openship
|
|
25
|
+
POSTGRES_PASSWORD: openship
|
|
26
|
+
POSTGRES_DB: openship
|
|
27
|
+
ports:
|
|
28
|
+
- "5432:5432"
|
|
29
|
+
volumes:
|
|
30
|
+
- postgres_data:/var/lib/postgresql/data
|
|
31
|
+
|
|
32
|
+
# ─── Cache / Queue ───────────────────────────────────
|
|
33
|
+
redis:
|
|
34
|
+
image: redis:7-alpine
|
|
35
|
+
restart: unless-stopped
|
|
36
|
+
ports:
|
|
37
|
+
- "6379:6379"
|
|
38
|
+
volumes:
|
|
39
|
+
- redis_data:/data
|
|
40
|
+
|
|
41
|
+
# ─── API ──────────────────────────────────────────────
|
|
42
|
+
api:
|
|
43
|
+
build:
|
|
44
|
+
context: .
|
|
45
|
+
dockerfile: apps/api/Dockerfile
|
|
46
|
+
restart: unless-stopped
|
|
47
|
+
ports:
|
|
48
|
+
- "4000:4000"
|
|
49
|
+
environment:
|
|
50
|
+
NODE_ENV: production
|
|
51
|
+
PORT: 4000
|
|
52
|
+
CLOUD_MODE: "false"
|
|
53
|
+
DATABASE_URL: postgresql://openship:openship@postgres:5432/openship
|
|
54
|
+
REDIS_URL: redis://redis:6379
|
|
55
|
+
JWT_SECRET: change-me-in-production
|
|
56
|
+
depends_on:
|
|
57
|
+
- postgres
|
|
58
|
+
- redis
|
|
59
|
+
|
|
60
|
+
# ─── Dashboard ────────────────────────────────────────
|
|
61
|
+
dashboard:
|
|
62
|
+
build:
|
|
63
|
+
context: .
|
|
64
|
+
dockerfile: apps/dashboard/Dockerfile
|
|
65
|
+
restart: unless-stopped
|
|
66
|
+
ports:
|
|
67
|
+
- "3001:3001"
|
|
68
|
+
environment:
|
|
69
|
+
NODE_ENV: production
|
|
70
|
+
NEXT_PUBLIC_API_URL: http://api:4000/api
|
|
71
|
+
depends_on:
|
|
72
|
+
- api
|
|
73
|
+
|
|
74
|
+
# ─── Web / Landing ───────────────────────────────────
|
|
75
|
+
web:
|
|
76
|
+
build:
|
|
77
|
+
context: .
|
|
78
|
+
dockerfile: apps/web/Dockerfile
|
|
79
|
+
restart: unless-stopped
|
|
80
|
+
ports:
|
|
81
|
+
- "3000:3000"
|
|
82
|
+
environment:
|
|
83
|
+
NODE_ENV: production
|
|
84
|
+
|
|
85
|
+
volumes:
|
|
86
|
+
postgres_data:
|
|
87
|
+
redis_data:
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openship",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Open-source, self-hostable deployment platform",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/oblien/openship.git"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "turbo run build",
|
|
12
|
+
"dev": "turbo run dev",
|
|
13
|
+
"lint": "turbo run lint",
|
|
14
|
+
"test": "turbo run test",
|
|
15
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
16
|
+
"db:generate": "turbo run db:generate --filter=openship-db",
|
|
17
|
+
"db:push": "turbo run db:push --filter=openship-db",
|
|
18
|
+
"db:migrate": "turbo run db:migrate --filter=openship-db",
|
|
19
|
+
"clean": "turbo run clean && rm -rf node_modules"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"openship-tsconfig": "workspace:*",
|
|
23
|
+
"prettier": "^3.2.0",
|
|
24
|
+
"turbo": "^2.0.0",
|
|
25
|
+
"typescript": "^5.4.0"
|
|
26
|
+
},
|
|
27
|
+
"packageManager": "pnpm@9.0.0",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openship-adapters",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
15
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
16
|
+
"lint": "tsc --noEmit",
|
|
17
|
+
"clean": "rm -rf dist .turbo node_modules"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"openship-core": "workspace:*"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"openship-tsconfig": "workspace:*",
|
|
24
|
+
"tsup": "^8.1.0",
|
|
25
|
+
"typescript": "^5.4.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base adapter interface.
|
|
3
|
+
*
|
|
4
|
+
* Every deployment target (Docker, Oblien, AWS, etc.) must implement this contract.
|
|
5
|
+
* This is the abstraction that makes Openship platform-agnostic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface DeploymentConfig {
|
|
9
|
+
projectId: string;
|
|
10
|
+
branch: string;
|
|
11
|
+
commitSha?: string;
|
|
12
|
+
environment: "production" | "preview";
|
|
13
|
+
envVars?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeploymentResult {
|
|
17
|
+
id: string;
|
|
18
|
+
url: string;
|
|
19
|
+
status: "queued" | "building" | "deploying" | "ready" | "failed" | "cancelled";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BuildLog {
|
|
23
|
+
timestamp: string;
|
|
24
|
+
message: string;
|
|
25
|
+
level: "info" | "warn" | "error";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DeploymentAdapter {
|
|
29
|
+
/** Human-readable name of the adapter */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
|
|
32
|
+
/** Deploy a project and return the deployment result */
|
|
33
|
+
deploy(config: DeploymentConfig): Promise<DeploymentResult>;
|
|
34
|
+
|
|
35
|
+
/** Get the current status of a deployment */
|
|
36
|
+
getStatus(deploymentId: string): Promise<DeploymentResult>;
|
|
37
|
+
|
|
38
|
+
/** Stream or fetch build/deploy logs */
|
|
39
|
+
getLogs(deploymentId: string): Promise<BuildLog[]>;
|
|
40
|
+
|
|
41
|
+
/** Cancel an in-progress deployment */
|
|
42
|
+
cancel(deploymentId: string): Promise<void>;
|
|
43
|
+
|
|
44
|
+
/** Rollback to a previous deployment */
|
|
45
|
+
rollback(deploymentId: string): Promise<DeploymentResult>;
|
|
46
|
+
|
|
47
|
+
/** Tear down / delete a deployment and its resources */
|
|
48
|
+
destroy(deploymentId: string): Promise<void>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DeploymentAdapter, DeploymentConfig, DeploymentResult, BuildLog } from "./base-adapter";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Docker adapter — used for self-hosted deployments.
|
|
5
|
+
*
|
|
6
|
+
* Builds and runs containers on the host machine using Docker Engine API.
|
|
7
|
+
* This is the default adapter for open-source / self-hosted installations.
|
|
8
|
+
*/
|
|
9
|
+
export class DockerAdapter implements DeploymentAdapter {
|
|
10
|
+
readonly name = "docker";
|
|
11
|
+
|
|
12
|
+
async deploy(config: DeploymentConfig): Promise<DeploymentResult> {
|
|
13
|
+
// TODO: Clone repo, docker build, docker run
|
|
14
|
+
return {
|
|
15
|
+
id: `docker-${Date.now()}`,
|
|
16
|
+
url: `http://localhost:3000`,
|
|
17
|
+
status: "queued",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async getStatus(deploymentId: string): Promise<DeploymentResult> {
|
|
22
|
+
// TODO: Inspect container status
|
|
23
|
+
return { id: deploymentId, url: "", status: "ready" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getLogs(deploymentId: string): Promise<BuildLog[]> {
|
|
27
|
+
// TODO: docker logs
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async cancel(deploymentId: string): Promise<void> {
|
|
32
|
+
// TODO: docker stop
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async rollback(deploymentId: string): Promise<DeploymentResult> {
|
|
36
|
+
// TODO: Restart previous container
|
|
37
|
+
return { id: deploymentId, url: "", status: "ready" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async destroy(deploymentId: string): Promise<void> {
|
|
41
|
+
// TODO: docker rm -f
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DeploymentAdapter, DeploymentConfig, DeploymentResult, BuildLog } from "./base-adapter";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Oblien adapter — used for cloud-managed deployments.
|
|
5
|
+
*
|
|
6
|
+
* Communicates with the Oblien infrastructure API to provision and manage
|
|
7
|
+
* containers/VMs for paying cloud users. This adapter is what makes the
|
|
8
|
+
* hosted SaaS version work.
|
|
9
|
+
*/
|
|
10
|
+
export class OblienAdapter implements DeploymentAdapter {
|
|
11
|
+
readonly name = "oblien";
|
|
12
|
+
|
|
13
|
+
constructor(private apiUrl: string, private apiKey: string) {}
|
|
14
|
+
|
|
15
|
+
async deploy(config: DeploymentConfig): Promise<DeploymentResult> {
|
|
16
|
+
// TODO: Call Oblien API to create deployment
|
|
17
|
+
return {
|
|
18
|
+
id: `oblien-${Date.now()}`,
|
|
19
|
+
url: `https://${config.projectId}.openship.cloud`,
|
|
20
|
+
status: "queued",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async getStatus(deploymentId: string): Promise<DeploymentResult> {
|
|
25
|
+
// TODO: Query Oblien API
|
|
26
|
+
return { id: deploymentId, url: "", status: "ready" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getLogs(deploymentId: string): Promise<BuildLog[]> {
|
|
30
|
+
// TODO: Fetch logs from Oblien
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async cancel(deploymentId: string): Promise<void> {
|
|
35
|
+
// TODO: Cancel via Oblien API
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async rollback(deploymentId: string): Promise<DeploymentResult> {
|
|
39
|
+
// TODO: Rollback via Oblien API
|
|
40
|
+
return { id: deploymentId, url: "", status: "ready" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async destroy(deploymentId: string): Promise<void> {
|
|
44
|
+
// TODO: Destroy via Oblien API
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DeploymentAdapter } from "./base-adapter";
|
|
2
|
+
import { DockerAdapter } from "./docker-adapter";
|
|
3
|
+
import { OblienAdapter } from "./oblien-adapter";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adapter registry — resolves the correct adapter based on config.
|
|
7
|
+
*
|
|
8
|
+
* Self-hosted → DockerAdapter
|
|
9
|
+
* Cloud mode → OblienAdapter
|
|
10
|
+
*/
|
|
11
|
+
export function getAdapter(mode: "docker" | "oblien" = "docker"): DeploymentAdapter {
|
|
12
|
+
switch (mode) {
|
|
13
|
+
case "oblien":
|
|
14
|
+
return new OblienAdapter(
|
|
15
|
+
process.env.OBLIEN_API_URL || "https://api.oblien.com",
|
|
16
|
+
process.env.OBLIEN_API_KEY || "",
|
|
17
|
+
);
|
|
18
|
+
case "docker":
|
|
19
|
+
default:
|
|
20
|
+
return new DockerAdapter();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openship-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
15
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
16
|
+
"lint": "tsc --noEmit",
|
|
17
|
+
"clean": "rm -rf dist .turbo node_modules"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"zod": "^3.23.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"openship-tsconfig": "workspace:*",
|
|
24
|
+
"tsup": "^8.1.0",
|
|
25
|
+
"typescript": "^5.4.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants used across the monorepo.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const APP_NAME = "Openship";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_PORT = {
|
|
8
|
+
web: 3000,
|
|
9
|
+
dashboard: 3001,
|
|
10
|
+
api: 4000,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export const DEPLOYMENT_STATUSES = [
|
|
14
|
+
"queued",
|
|
15
|
+
"building",
|
|
16
|
+
"deploying",
|
|
17
|
+
"ready",
|
|
18
|
+
"failed",
|
|
19
|
+
"cancelled",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export const FRAMEWORKS = ["nextjs", "node", "static", "docker"] as const;
|
|
23
|
+
|
|
24
|
+
export const PLANS = {
|
|
25
|
+
free: { name: "Free", price: 0, buildMinutes: 100, bandwidth: 1 },
|
|
26
|
+
pro: { name: "Pro", price: 20, buildMinutes: 1000, bandwidth: 100 },
|
|
27
|
+
team: { name: "Team", price: 50, buildMinutes: 5000, bandwidth: 500 },
|
|
28
|
+
} as const;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared error classes used across the monorepo.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class AppError extends Error {
|
|
6
|
+
constructor(
|
|
7
|
+
message: string,
|
|
8
|
+
public statusCode: number = 500,
|
|
9
|
+
public code?: string,
|
|
10
|
+
) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "AppError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class NotFoundError extends AppError {
|
|
17
|
+
constructor(resource: string, id?: string) {
|
|
18
|
+
super(id ? `${resource} '${id}' not found` : `${resource} not found`, 404, "NOT_FOUND");
|
|
19
|
+
this.name = "NotFoundError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class UnauthorizedError extends AppError {
|
|
24
|
+
constructor(message = "Unauthorized") {
|
|
25
|
+
super(message, 401, "UNAUTHORIZED");
|
|
26
|
+
this.name = "UnauthorizedError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ForbiddenError extends AppError {
|
|
31
|
+
constructor(message = "Forbidden") {
|
|
32
|
+
super(message, 403, "FORBIDDEN");
|
|
33
|
+
this.name = "ForbiddenError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ValidationError extends AppError {
|
|
38
|
+
constructor(message: string, public details?: Record<string, string[]>) {
|
|
39
|
+
super(message, 400, "VALIDATION_ERROR");
|
|
40
|
+
this.name = "ValidationError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ConflictError extends AppError {
|
|
45
|
+
constructor(message: string) {
|
|
46
|
+
super(message, 409, "CONFLICT");
|
|
47
|
+
this.name = "ConflictError";
|
|
48
|
+
}
|
|
49
|
+
}
|