feat(deployments): environment variable desteği ekle
Deployment projelerine environment variable konfigürasyonu eklendi. Backend tarafında DeploymentProject modeline envContent ve envExampleName alanları eklendi. Repo içindeki .env.example dosyalarını listelemek için yeni bir endpoint eklendi. Deployment sürecinde belirlenen env içeriği .proje dizinine .env dosyası olarak yazılıyor. Frontend tarafında deployment formuna "Genel" ve "Environment" sekmeleri eklendi. Remote repodan .env.example dosyaları çekilebiliyor ve içerik düzenlenebiliyor. Env içeriği için göster/gizle toggle'ı eklendi.
This commit is contained in:
@@ -13,6 +13,8 @@ export interface DeploymentProjectDocument extends Document {
|
||||
webhookToken: string;
|
||||
env: DeploymentEnv;
|
||||
port?: number;
|
||||
envContent?: string;
|
||||
envExampleName?: string;
|
||||
lastDeployAt?: Date;
|
||||
lastStatus: DeploymentStatus;
|
||||
lastMessage?: string;
|
||||
@@ -34,6 +36,8 @@ const DeploymentProjectSchema = new Schema<DeploymentProjectDocument>(
|
||||
webhookToken: { type: String, required: true, unique: true, index: true },
|
||||
env: { type: String, required: true, enum: ["dev", "prod"] },
|
||||
port: { type: Number },
|
||||
envContent: { type: String },
|
||||
envExampleName: { type: String },
|
||||
lastDeployAt: { type: Date },
|
||||
lastStatus: { type: String, enum: ["idle", "running", "success", "failed"], default: "idle" },
|
||||
lastMessage: { type: String }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Router } from "express";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { authMiddleware } from "../middleware/authMiddleware.js";
|
||||
import { deploymentService } from "../services/deploymentService.js";
|
||||
@@ -71,6 +70,22 @@ router.get("/compose-files", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/env-examples", async (req, res) => {
|
||||
authMiddleware(req, res, async () => {
|
||||
const repoUrl = req.query.repoUrl as string | undefined;
|
||||
const branch = req.query.branch as string | undefined;
|
||||
if (!repoUrl || !branch) {
|
||||
return res.status(400).json({ message: "repoUrl ve branch gerekli" });
|
||||
}
|
||||
try {
|
||||
const examples = await deploymentService.listRemoteEnvExamples(repoUrl, branch);
|
||||
return res.json({ examples });
|
||||
} catch (err) {
|
||||
return res.status(400).json({ message: "Env example alınamadı", error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/metrics/summary", async (req, res) => {
|
||||
authMiddleware(req, res, async () => {
|
||||
const since = new Date();
|
||||
@@ -129,7 +144,7 @@ router.get("/:id", async (req, res) => {
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
authMiddleware(req, res, async () => {
|
||||
const { name, repoUrl, branch, composeFile, port } = req.body;
|
||||
const { name, repoUrl, branch, composeFile, port, envContent, envExampleName } = req.body;
|
||||
if (!name || !repoUrl || !branch || !composeFile) {
|
||||
return res.status(400).json({ message: "Tüm alanlar gerekli" });
|
||||
}
|
||||
@@ -139,7 +154,9 @@ router.post("/", async (req, res) => {
|
||||
repoUrl,
|
||||
branch,
|
||||
composeFile,
|
||||
port
|
||||
port,
|
||||
envContent,
|
||||
envExampleName
|
||||
});
|
||||
deploymentService
|
||||
.runDeployment(created._id.toString(), { message: "First deployment" })
|
||||
@@ -154,7 +171,7 @@ router.post("/", async (req, res) => {
|
||||
router.put("/:id", async (req, res) => {
|
||||
authMiddleware(req, res, async () => {
|
||||
const { id } = req.params;
|
||||
const { name, repoUrl, branch, composeFile, port } = req.body;
|
||||
const { name, repoUrl, branch, composeFile, port, envContent, envExampleName } = req.body;
|
||||
if (!name || !repoUrl || !branch || !composeFile) {
|
||||
return res.status(400).json({ message: "Tüm alanlar gerekli" });
|
||||
}
|
||||
@@ -164,7 +181,9 @@ router.put("/:id", async (req, res) => {
|
||||
repoUrl,
|
||||
branch,
|
||||
composeFile,
|
||||
port
|
||||
port,
|
||||
envContent,
|
||||
envExampleName
|
||||
});
|
||||
if (!updated) return res.status(404).json({ message: "Deployment bulunamadı" });
|
||||
return res.json(updated);
|
||||
|
||||
@@ -218,6 +218,32 @@ class DeploymentService {
|
||||
}
|
||||
}
|
||||
|
||||
async listRemoteEnvExamples(repoUrl: string, branch: string) {
|
||||
await fs.promises.mkdir(deploymentsRoot, { recursive: true });
|
||||
const tmpBase = await fs.promises.mkdtemp(path.join(deploymentsRoot, ".tmp-"));
|
||||
try {
|
||||
await runCommand(
|
||||
`git clone --depth 1 --single-branch --branch ${branch} ${repoUrl} ${tmpBase}`,
|
||||
process.cwd(),
|
||||
() => undefined
|
||||
);
|
||||
const entries = await fs.promises.readdir(tmpBase, { withFileTypes: true });
|
||||
const files = entries
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name)
|
||||
.filter((name) => name.toLowerCase().endsWith(".env.example"));
|
||||
const items = await Promise.all(
|
||||
files.map(async (name) => ({
|
||||
name,
|
||||
content: await fs.promises.readFile(path.join(tmpBase, name), "utf8")
|
||||
}))
|
||||
);
|
||||
return items;
|
||||
} finally {
|
||||
await fs.promises.rm(tmpBase, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async ensureSettings() {
|
||||
const existing = await Settings.findOne();
|
||||
if (existing) return existing;
|
||||
@@ -248,6 +274,8 @@ class DeploymentService {
|
||||
branch: string;
|
||||
composeFile: ComposeFile;
|
||||
port?: number;
|
||||
envContent?: string;
|
||||
envExampleName?: string;
|
||||
}) {
|
||||
const repoUrl = normalizeRepoUrl(input.repoUrl);
|
||||
const existingRepo = await DeploymentProject.findOne({ repoUrl });
|
||||
@@ -281,7 +309,9 @@ class DeploymentService {
|
||||
composeFile: input.composeFile,
|
||||
webhookToken,
|
||||
env,
|
||||
port: input.port
|
||||
port: input.port,
|
||||
envContent: input.envContent,
|
||||
envExampleName: input.envExampleName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,6 +323,8 @@ class DeploymentService {
|
||||
branch: string;
|
||||
composeFile: ComposeFile;
|
||||
port?: number;
|
||||
envContent?: string;
|
||||
envExampleName?: string;
|
||||
}
|
||||
) {
|
||||
const project = await DeploymentProject.findById(id);
|
||||
@@ -317,7 +349,9 @@ class DeploymentService {
|
||||
branch: input.branch,
|
||||
composeFile: input.composeFile,
|
||||
env,
|
||||
port: input.port
|
||||
port: input.port,
|
||||
envContent: input.envContent,
|
||||
envExampleName: input.envExampleName
|
||||
},
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
@@ -362,6 +396,10 @@ class DeploymentService {
|
||||
|
||||
try {
|
||||
await ensureRepo(project, (line) => pushLog(line));
|
||||
if (project.envContent) {
|
||||
await fs.promises.writeFile(path.join(project.rootPath, ".env"), project.envContent, "utf8");
|
||||
pushLog(".env güncellendi");
|
||||
}
|
||||
pushLog("Deploy komutları çalıştırılıyor...");
|
||||
await runCompose(project, (line) => pushLog(line));
|
||||
const duration = Date.now() - startedAt;
|
||||
|
||||
Reference in New Issue
Block a user