mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-16 12:45:21 +02:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
569d43ae7f | ||
|
|
d22ed9b569 | ||
|
|
8b88c85b37 | ||
|
|
11fbd047d0 | ||
|
|
69af9c0312 | ||
|
|
063d51e442 | ||
|
|
0a789e1d6f | ||
|
|
671cd497fd | ||
|
|
8ddc254252 | ||
|
|
2668e22302 | ||
|
|
37145fbdf2 | ||
|
|
6847d8dbef | ||
|
|
032bcb7459 | ||
|
|
68be7a259f | ||
|
|
7d682870ff | ||
|
|
d1a1a80c77 | ||
|
|
3d7dc82232 | ||
|
|
fedc88eb40 | ||
|
|
5d0f6a4657 | ||
|
|
4718461405 | ||
|
|
80b22d9458 | ||
|
|
8fa5fe7f2c | ||
|
|
4ced8bec96 | ||
|
|
9ecb770a01 | ||
|
|
8ac586b2f7 | ||
|
|
0a1800ba6d | ||
|
|
f13028ee70 | ||
|
|
b6b6b9f2ce | ||
|
|
f46637b8e1 | ||
|
|
948ed2cc0d | ||
|
|
a536c977f0 | ||
|
|
8524cd0972 | ||
|
|
ac1e51cd11 | ||
|
|
ca243d7259 | ||
|
|
e1ce54c159 | ||
|
|
031302d808 | ||
|
|
5e01505e4d | ||
|
|
c423724972 | ||
|
|
f1f7639708 | ||
|
|
9ef1a76a85 | ||
|
|
30b66a4828 | ||
|
|
f2ead66890 | ||
|
|
64475bbb13 | ||
|
|
c1896f8877 | ||
|
|
d13975adac | ||
|
|
b8e9602538 | ||
|
|
afca968853 | ||
|
|
c9715b19a3 | ||
|
|
a6ca41f91f | ||
|
|
b2b649c5cd | ||
|
|
225c398d31 | ||
|
|
07b99bd4e4 | ||
|
|
652e8910f4 | ||
|
|
e04e25385d | ||
|
|
68945c6888 | ||
|
|
146d82b6c4 | ||
|
|
02215d4e21 | ||
|
|
4ca05414af |
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllProperties } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile1 = `
|
||||
version: "3.8"
|
||||
@@ -61,7 +61,7 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFile1 = load(`
|
||||
const expectedComposeFile1 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -120,7 +120,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const composeData = parse(composeFile1) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
@@ -185,7 +185,7 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFile2 = load(`
|
||||
const expectedComposeFile2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -243,7 +243,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
@@ -308,7 +308,7 @@ secrets:
|
||||
file: ./service_secret.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFile3 = load(`
|
||||
const expectedComposeFile3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -366,7 +366,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
@@ -420,7 +420,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedComposeFile = load(`
|
||||
const expectedComposeFile = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -467,7 +467,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all properties in Plausible compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllProperties(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -23,7 +23,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -59,7 +59,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to multiple configs in root property", () => {
|
||||
const composeData = load(composeFileMultipleConfigs) as ComposeSpecification;
|
||||
const composeData = parse(composeFileMultipleConfigs) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -92,7 +92,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs with different properties in root property", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileDifferentProperties,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -137,7 +137,7 @@ configs:
|
||||
`;
|
||||
|
||||
// Expected compose file con el prefijo `testhash`
|
||||
const expectedComposeFileConfigRoot = load(`
|
||||
const expectedComposeFileConfigRoot = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -162,7 +162,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs in root property", () => {
|
||||
const composeData = load(composeFileConfigRoot) as ComposeSpecification;
|
||||
const composeData = parse(composeFileConfigRoot) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToConfigsInServices,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -22,7 +22,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -54,7 +54,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in services with single config", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileSingleServiceConfig,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -108,7 +108,7 @@ configs:
|
||||
`;
|
||||
|
||||
test("Add suffix to configs in services with multiple configs", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileMultipleServicesConfigs,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -157,7 +157,7 @@ services:
|
||||
`;
|
||||
|
||||
// Expected compose file con el prefijo `testhash`
|
||||
const expectedComposeFileConfigServices = load(`
|
||||
const expectedComposeFileConfigServices = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -182,7 +182,7 @@ services:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs in services", () => {
|
||||
const composeData = load(composeFileConfigServices) as ComposeSpecification;
|
||||
const composeData = parse(composeFileConfigServices) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -43,7 +43,7 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedConfigs = load(`
|
||||
const expectedComposeFileCombinedConfigs = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -77,7 +77,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all configs in root and services", () => {
|
||||
const composeData = load(composeFileCombinedConfigs) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombinedConfigs) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -122,7 +122,7 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`;
|
||||
|
||||
const expectedComposeFileWithEnvAndExternal = load(`
|
||||
const expectedComposeFileWithEnvAndExternal = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -159,7 +159,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs with environment and external", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileWithEnvAndExternal,
|
||||
) as ComposeSpecification;
|
||||
|
||||
@@ -200,7 +200,7 @@ configs:
|
||||
file: ./db-config.yml
|
||||
`;
|
||||
|
||||
const expectedComposeFileWithTemplateDriverAndLabels = load(`
|
||||
const expectedComposeFileWithTemplateDriverAndLabels = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -231,7 +231,7 @@ configs:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to configs with template driver and labels", () => {
|
||||
const composeData = load(
|
||||
const composeData = parse(
|
||||
composeFileWithTemplateDriverAndLabels,
|
||||
) as ComposeSpecification;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -35,7 +35,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
});
|
||||
|
||||
test("Add suffix to networks root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -79,7 +79,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to advanced networks root property (2 TRY)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -120,7 +120,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with external properties", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -160,7 +160,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with IPAM configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -201,7 +201,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with custom options", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
const composeData = parse(composeFile5) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -264,7 +264,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks with static suffix", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
const composeData = parse(composeFile6) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -273,7 +273,7 @@ test("Add suffix to networks with static suffix", () => {
|
||||
}
|
||||
const networks = addSuffixToNetworksRoot(composeData.networks, suffix);
|
||||
|
||||
const expectedComposeData = load(
|
||||
const expectedComposeData = parse(
|
||||
expectedComposeFile6,
|
||||
) as ComposeSpecification;
|
||||
expect(networks).toStrictEqual(expectedComposeData.networks);
|
||||
@@ -293,7 +293,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToServiceNetworks,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -23,7 +23,7 @@ services:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -67,7 +67,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services with aliases", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -107,7 +107,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services (Object with simple networks)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -153,7 +153,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -196,7 +196,7 @@ services:
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -245,7 +245,7 @@ services:
|
||||
`;
|
||||
|
||||
test("It shoudn't add suffix to dokploy-network in services multiples cases", () => {
|
||||
const composeData = load(composeFile8) as ComposeSpecification;
|
||||
const composeData = parse(composeFile8) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
addSuffixToServiceNetworks,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileCombined = `
|
||||
version: "3.8"
|
||||
@@ -39,7 +39,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to networks in services and root (combined case)", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -89,7 +89,7 @@ test("Add suffix to networks in services and root (combined case)", () => {
|
||||
expect(redisNetworks).not.toHaveProperty("backend");
|
||||
});
|
||||
|
||||
const expectedComposeFile = load(`
|
||||
const expectedComposeFile = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -120,7 +120,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to networks in compose file", () => {
|
||||
const composeData = load(composeFileCombined) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombined) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
if (!composeData?.networks) {
|
||||
@@ -156,7 +156,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile2 = load(`
|
||||
const expectedComposeFile2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -182,7 +182,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to networks in compose file with external and internal networks", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
@@ -218,7 +218,7 @@ networks:
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
`;
|
||||
|
||||
const expectedComposeFile3 = load(`
|
||||
const expectedComposeFile3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -247,7 +247,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to networks in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
@@ -289,7 +289,7 @@ networks:
|
||||
|
||||
`;
|
||||
|
||||
const expectedComposeFile4 = load(`
|
||||
const expectedComposeFile4 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -326,7 +326,7 @@ networks:
|
||||
`);
|
||||
|
||||
test("Expect don't add suffix to dokploy-network in compose file with multiple services and complex network configurations", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
const updatedComposeData = addSuffixToAllNetworks(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -23,7 +23,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in root property", () => {
|
||||
const composeData = load(composeFileSecretsRoot) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsRoot) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
@@ -52,7 +52,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in root property (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsRoot1) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsRoot1) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
@@ -84,7 +84,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in root property (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsRoot2) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsRoot2) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData?.secrets) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToSecretsInServices,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileSecretsServices = `
|
||||
version: "3.8"
|
||||
@@ -21,7 +21,7 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in services", () => {
|
||||
const composeData = load(composeFileSecretsServices) as ComposeSpecification;
|
||||
const composeData = parse(composeFileSecretsServices) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
@@ -54,7 +54,9 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in services (Test 1)", () => {
|
||||
const composeData = load(composeFileSecretsServices1) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileSecretsServices1,
|
||||
) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
@@ -93,7 +95,9 @@ secrets:
|
||||
`;
|
||||
|
||||
test("Add suffix to secrets in services (Test 2)", () => {
|
||||
const composeData = load(composeFileSecretsServices2) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileSecretsServices2,
|
||||
) as ComposeSpecification;
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
if (!composeData.services) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllSecrets } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileCombinedSecrets = `
|
||||
version: "3.8"
|
||||
@@ -25,7 +25,7 @@ secrets:
|
||||
file: ./app_secret.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedSecrets = load(`
|
||||
const expectedComposeFileCombinedSecrets = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -48,7 +48,7 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all secrets", () => {
|
||||
const composeData = load(composeFileCombinedSecrets) as ComposeSpecification;
|
||||
const composeData = parse(composeFileCombinedSecrets) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
@@ -77,7 +77,7 @@ secrets:
|
||||
file: ./cache_secret.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedSecrets3 = load(`
|
||||
const expectedComposeFileCombinedSecrets3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -99,7 +99,9 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all secrets (3rd Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets3) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileCombinedSecrets3,
|
||||
) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
@@ -128,7 +130,7 @@ secrets:
|
||||
file: ./db_password.txt
|
||||
`;
|
||||
|
||||
const expectedComposeFileCombinedSecrets4 = load(`
|
||||
const expectedComposeFileCombinedSecrets4 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -150,7 +152,9 @@ secrets:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all secrets (4th Case)", () => {
|
||||
const composeData = load(composeFileCombinedSecrets4) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileCombinedSecrets4,
|
||||
) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllSecrets(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -27,7 +27,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
});
|
||||
|
||||
test("Add suffix to service names with container_name in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -32,7 +32,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with depends_on (array) in compose file", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -102,7 +102,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with depends_on (object) in compose file", () => {
|
||||
const composeData = load(composeFile5) as ComposeSpecification;
|
||||
const composeData = parse(composeFile5) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -30,7 +30,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with extends (string) in compose file", () => {
|
||||
const composeData = load(composeFile6) as ComposeSpecification;
|
||||
const composeData = parse(composeFile6) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -90,7 +90,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with extends (object) in compose file", () => {
|
||||
const composeData = load(composeFile7) as ComposeSpecification;
|
||||
const composeData = parse(composeFile7) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -31,7 +31,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with links in compose file", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -26,7 +26,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names in compose file", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToAllServiceNames,
|
||||
addSuffixToServiceNames,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileCombinedAllCases = `
|
||||
version: "3.8"
|
||||
@@ -38,7 +38,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile = load(`
|
||||
const expectedComposeFile = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -71,7 +71,9 @@ networks:
|
||||
`);
|
||||
|
||||
test("Add suffix to all service names in compose file", () => {
|
||||
const composeData = load(composeFileCombinedAllCases) as ComposeSpecification;
|
||||
const composeData = parse(
|
||||
composeFileCombinedAllCases,
|
||||
) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -131,7 +133,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile1 = load(`
|
||||
const expectedComposeFile1 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -176,7 +178,7 @@ networks:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all service names in compose file 1", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const composeData = parse(composeFile1) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
@@ -227,7 +229,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile2 = load(`
|
||||
const expectedComposeFile2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -271,7 +273,7 @@ networks:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all service names in compose file 2", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
@@ -322,7 +324,7 @@ networks:
|
||||
driver: bridge
|
||||
`;
|
||||
|
||||
const expectedComposeFile3 = load(`
|
||||
const expectedComposeFile3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -366,7 +368,7 @@ networks:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to all service names in compose file 3", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllServiceNames(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -35,7 +35,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to service names with volumes_from in compose file", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
addSuffixToVolumesRoot,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
services:
|
||||
@@ -70,7 +70,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedDockerCompose = load(`
|
||||
const expectedDockerCompose = parse(`
|
||||
services:
|
||||
mail:
|
||||
image: bytemark/smtp
|
||||
@@ -143,7 +143,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
// Docker compose needs unique names for services, volumes, networks and containers
|
||||
// So base on a input which is a dockercompose file, it should replace the name with a hash and return a new dockercompose file
|
||||
test("Add suffix to volumes root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -165,7 +165,7 @@ test("Add suffix to volumes root property", () => {
|
||||
});
|
||||
|
||||
test("Expect to change the suffix in all the possible places", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -195,7 +195,7 @@ volumes:
|
||||
mongo-data:
|
||||
`;
|
||||
|
||||
const expectedDockerCompose2 = load(`
|
||||
const expectedDockerCompose2 = parse(`
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
@@ -218,7 +218,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the suffix in all the possible places (2 Try)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -248,7 +248,7 @@ volumes:
|
||||
mongo-data:
|
||||
`;
|
||||
|
||||
const expectedDockerCompose3 = load(`
|
||||
const expectedDockerCompose3 = parse(`
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
@@ -271,7 +271,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the suffix in all the possible places (3 Try)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -645,7 +645,7 @@ volumes:
|
||||
db-config:
|
||||
`;
|
||||
|
||||
const expectedDockerComposeComplex = load(`
|
||||
const expectedDockerComposeComplex = parse(`
|
||||
version: "3.8"
|
||||
services:
|
||||
studio:
|
||||
@@ -1012,7 +1012,7 @@ volumes:
|
||||
`);
|
||||
|
||||
test("Expect to change the suffix in all the possible places (4 Try)", () => {
|
||||
const composeData = load(composeFileComplex) as ComposeSpecification;
|
||||
const composeData = parse(composeFileComplex) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -1065,7 +1065,7 @@ volumes:
|
||||
db-data:
|
||||
`;
|
||||
|
||||
const expectedDockerComposeExample1 = load(`
|
||||
const expectedDockerComposeExample1 = parse(`
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
@@ -1111,7 +1111,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Expect to change the suffix in all the possible places (5 Try)", () => {
|
||||
const composeData = load(composeFileExample1) as ComposeSpecification;
|
||||
const composeData = parse(composeFileExample1) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
@@ -1143,7 +1143,7 @@ volumes:
|
||||
backrest-cache:
|
||||
`;
|
||||
|
||||
const expectedDockerComposeBackrest = load(`
|
||||
const expectedDockerComposeBackrest = parse(`
|
||||
services:
|
||||
backrest:
|
||||
image: garethgeorge/backrest:v1.7.3
|
||||
@@ -1168,7 +1168,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Should handle volume paths with subdirectories correctly", () => {
|
||||
const composeData = load(composeFileBackrest) as ComposeSpecification;
|
||||
const composeData = parse(composeFileBackrest) as ComposeSpecification;
|
||||
const suffix = "testhash";
|
||||
|
||||
const updatedComposeData = addSuffixToAllVolumes(composeData, suffix);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFile = `
|
||||
version: "3.8"
|
||||
@@ -29,7 +29,7 @@ test("Generate random hash with 8 characters", () => {
|
||||
});
|
||||
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -67,7 +67,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes in root property (Case 2)", () => {
|
||||
const composeData = load(composeFile2) as ComposeSpecification;
|
||||
const composeData = parse(composeFile2) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -101,7 +101,7 @@ networks:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes in root property (Case 3)", () => {
|
||||
const composeData = load(composeFile3) as ComposeSpecification;
|
||||
const composeData = parse(composeFile3) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -148,7 +148,7 @@ volumes:
|
||||
`;
|
||||
|
||||
// Expected compose file con el prefijo `testhash`
|
||||
const expectedComposeFile4 = load(`
|
||||
const expectedComposeFile4 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -179,7 +179,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to volumes in root property", () => {
|
||||
const composeData = load(composeFile4) as ComposeSpecification;
|
||||
const composeData = parse(composeFile4) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
addSuffixToVolumesInServices,
|
||||
generateRandomHash,
|
||||
} from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
test("Generate random hash with 8 characters", () => {
|
||||
const hash = generateRandomHash();
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes declared directly in services", () => {
|
||||
const composeData = load(composeFile1) as ComposeSpecification;
|
||||
const composeData = parse(composeFile1) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
@@ -59,7 +59,7 @@ volumes:
|
||||
`;
|
||||
|
||||
test("Add suffix to volumes declared directly in services (Case 2)", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const suffix = generateRandomHash();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ComposeSpecification } from "@dokploy/server";
|
||||
import { addSuffixToAllVolumes } from "@dokploy/server";
|
||||
import { load } from "js-yaml";
|
||||
import { expect, test } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
|
||||
const composeFileTypeVolume = `
|
||||
version: "3.8"
|
||||
@@ -23,7 +23,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume = load(`
|
||||
const expectedComposeFileTypeVolume = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -44,7 +44,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to volumes with type: volume in services", () => {
|
||||
const composeData = load(composeFileTypeVolume) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -73,7 +73,7 @@ volumes:
|
||||
driver: local
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume1 = load(`
|
||||
const expectedComposeFileTypeVolume1 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -93,7 +93,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to mixed volumes in services", () => {
|
||||
const composeData = load(composeFileTypeVolume1) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume1) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -128,7 +128,7 @@ volumes:
|
||||
device: /path/to/app/logs
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume2 = load(`
|
||||
const expectedComposeFileTypeVolume2 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -154,7 +154,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to complex volume configurations in services", () => {
|
||||
const composeData = load(composeFileTypeVolume2) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume2) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
@@ -218,7 +218,7 @@ volumes:
|
||||
device: /path/to/shared/logs
|
||||
`;
|
||||
|
||||
const expectedComposeFileTypeVolume3 = load(`
|
||||
const expectedComposeFileTypeVolume3 = parse(`
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
@@ -273,7 +273,7 @@ volumes:
|
||||
`) as ComposeSpecification;
|
||||
|
||||
test("Add suffix to complex nested volumes configuration in services", () => {
|
||||
const composeData = load(composeFileTypeVolume3) as ComposeSpecification;
|
||||
const composeData = parse(composeFileTypeVolume3) as ComposeSpecification;
|
||||
|
||||
const suffix = "testhash";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import jsyaml from "js-yaml";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { parse, stringify, YAMLParseError } from "yaml";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
@@ -38,11 +38,11 @@ interface Props {
|
||||
|
||||
export const validateAndFormatYAML = (yamlText: string) => {
|
||||
try {
|
||||
const obj = jsyaml.load(yamlText);
|
||||
const formattedYaml = jsyaml.dump(obj, { indent: 4 });
|
||||
const obj = parse(yamlText);
|
||||
const formattedYaml = stringify(obj, { indent: 4 });
|
||||
return { valid: true, formattedYaml, error: null };
|
||||
} catch (error) {
|
||||
if (error instanceof jsyaml.YAMLException) {
|
||||
if (error instanceof YAMLParseError) {
|
||||
return {
|
||||
valid: false,
|
||||
formattedYaml: yamlText,
|
||||
@@ -89,7 +89,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
||||
if (!valid) {
|
||||
form.setError("traefikConfig", {
|
||||
type: "manual",
|
||||
message: error || "Invalid YAML",
|
||||
message: (error as string) || "Invalid YAML",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
||||
<Input
|
||||
placeholder="Search Template"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
className="w-full sm:w-[200px]"
|
||||
className="w-full"
|
||||
value={query}
|
||||
/>
|
||||
<Input
|
||||
@@ -248,7 +248,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
||||
onClick={() =>
|
||||
setViewMode(viewMode === "detailed" ? "icon" : "detailed")
|
||||
}
|
||||
className="h-9 w-9"
|
||||
className="h-9 w-9 flex-shrink-0"
|
||||
>
|
||||
{viewMode === "detailed" ? (
|
||||
<LayoutGrid className="size-4" />
|
||||
|
||||
@@ -88,7 +88,7 @@ export const StepThree = ({ templateInfo }: StepProps) => {
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">Configuration Files</h3>
|
||||
<ul className="list-disc pl-5">
|
||||
{templateInfo?.details?.configFiles.map((file, index) => (
|
||||
{templateInfo?.details?.configFiles?.map((file, index) => (
|
||||
<li key={index}>
|
||||
<strong className="text-sm font-semibold">
|
||||
{file.filePath}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Bot, Eye, EyeOff, PlusCircle, Trash2 } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Bot, PlusCircle, Trash2 } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { toast } from "sonner";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
@@ -27,7 +27,6 @@ export interface StepProps {
|
||||
export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
const suggestions = templateInfo.suggestions || [];
|
||||
const selectedVariant = templateInfo.details;
|
||||
const [showValues, setShowValues] = useState<Record<string, boolean>>({});
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.ai.suggest.useMutation();
|
||||
@@ -44,7 +43,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
.then((data) => {
|
||||
setTemplateInfo({
|
||||
...templateInfo,
|
||||
suggestions: data,
|
||||
suggestions: data || [],
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -54,10 +53,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
});
|
||||
}, [templateInfo.userInput]);
|
||||
|
||||
const toggleShowValue = (name: string) => {
|
||||
setShowValues((prev) => ({ ...prev, [name]: !prev[name] }));
|
||||
};
|
||||
|
||||
const handleEnvVariableChange = (
|
||||
index: number,
|
||||
field: "name" | "value",
|
||||
@@ -308,11 +303,9 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
placeholder="Variable Name"
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex-1 relative">
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={
|
||||
showValues[env.name] ? "text" : "password"
|
||||
}
|
||||
type={"password"}
|
||||
value={env.value}
|
||||
onChange={(e) =>
|
||||
handleEnvVariableChange(
|
||||
@@ -323,19 +316,6 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
}
|
||||
placeholder="Variable Value"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-2 top-1/2 transform -translate-y-1/2"
|
||||
onClick={() => toggleShowValue(env.name)}
|
||||
>
|
||||
{showValues[env.name] ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -437,13 +417,14 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
||||
<AccordionContent>
|
||||
<ScrollArea className="w-full rounded-md border">
|
||||
<div className="p-4 space-y-4">
|
||||
{selectedVariant?.configFiles?.length > 0 ? (
|
||||
{selectedVariant?.configFiles?.length &&
|
||||
selectedVariant?.configFiles?.length > 0 ? (
|
||||
<>
|
||||
<div className="text-sm text-muted-foreground mb-4">
|
||||
This template requires the following
|
||||
configuration files to be mounted:
|
||||
</div>
|
||||
{selectedVariant.configFiles.map(
|
||||
{selectedVariant?.configFiles?.map(
|
||||
(config, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
||||
@@ -47,7 +47,7 @@ interface Details {
|
||||
envVariables: EnvVariable[];
|
||||
shortDescription: string;
|
||||
domains: Domain[];
|
||||
configFiles: Mount[];
|
||||
configFiles?: Mount[];
|
||||
}
|
||||
|
||||
interface Mount {
|
||||
|
||||
@@ -291,45 +291,48 @@ export const ShowProjects = () => {
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
{/*
|
||||
{project.compose.length > 0 && (
|
||||
{project.environments.some(
|
||||
(env) => env.compose.length > 0,
|
||||
) && (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>
|
||||
Compose
|
||||
</DropdownMenuLabel>
|
||||
{project.compose.map((comp) => (
|
||||
<div key={comp.composeId}>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||
{comp.name}
|
||||
<StatusTooltip
|
||||
status={comp.composeStatus}
|
||||
/>
|
||||
</DropdownMenuLabel>
|
||||
{project.environments.map((env) =>
|
||||
env.compose.map((comp) => (
|
||||
<div key={comp.composeId}>
|
||||
<DropdownMenuSeparator />
|
||||
{comp.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||
{comp.name}
|
||||
<StatusTooltip
|
||||
status={comp.composeStatus}
|
||||
/>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{comp.domains.map((domain) => (
|
||||
<DropdownMenuItem
|
||||
key={domain.domainId}
|
||||
asChild
|
||||
>
|
||||
<span className="truncate">
|
||||
{domain.host}
|
||||
</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
))}
|
||||
<Link
|
||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||
target="_blank"
|
||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||
>
|
||||
<span className="truncate">
|
||||
{domain.host}
|
||||
</span>
|
||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</div>
|
||||
)),
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
)} */}
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -27,18 +26,12 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/utils/api";
|
||||
import { useUrl } from "@/utils/hooks/use-url";
|
||||
|
||||
const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
username: z.string().min(1, {
|
||||
message: "Username is required",
|
||||
}),
|
||||
password: z.string().min(1, {
|
||||
message: "App Password is required",
|
||||
}),
|
||||
name: z.string().min(1, { message: "Name is required" }),
|
||||
username: z.string().min(1, { message: "Username is required" }),
|
||||
email: z.string().email().optional(),
|
||||
apiToken: z.string().min(1, { message: "API Token is required" }),
|
||||
workspaceName: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -47,14 +40,12 @@ type Schema = z.infer<typeof Schema>;
|
||||
export const AddBitbucketProvider = () => {
|
||||
const utils = api.useUtils();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const _url = useUrl();
|
||||
const { mutateAsync, error, isError } = api.bitbucket.create.useMutation();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
const _router = useRouter();
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
apiToken: "",
|
||||
workspaceName: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
@@ -63,7 +54,8 @@ export const AddBitbucketProvider = () => {
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
username: "",
|
||||
password: "",
|
||||
email: "",
|
||||
apiToken: "",
|
||||
workspaceName: "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
@@ -71,10 +63,11 @@ export const AddBitbucketProvider = () => {
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
bitbucketUsername: data.username,
|
||||
appPassword: data.password,
|
||||
apiToken: data.apiToken,
|
||||
bitbucketWorkspaceName: data.workspaceName || "",
|
||||
authId: auth?.id || "",
|
||||
name: data.name || "",
|
||||
bitbucketEmail: data.email || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
@@ -113,37 +106,46 @@ export const AddBitbucketProvider = () => {
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<AlertBlock type="warning">
|
||||
Bitbucket App Passwords are deprecated for new providers. Use
|
||||
an API Token instead. Existing providers with App Passwords
|
||||
will continue to work until 9th June 2026.
|
||||
</AlertBlock>
|
||||
|
||||
<div className="mt-1 text-sm">
|
||||
Manage tokens in
|
||||
<Link
|
||||
href="https://id.atlassian.com/manage-profile/security/api-tokens"
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-1 ml-1"
|
||||
>
|
||||
<span>Bitbucket settings</span>
|
||||
<ExternalLink className="w-fit text-primary size-4" />
|
||||
</Link>
|
||||
</div>
|
||||
<ul className="list-disc list-inside ml-4 text-sm text-muted-foreground">
|
||||
<li className="text-muted-foreground text-sm">
|
||||
Click on Create API token with scopes
|
||||
</li>
|
||||
<li className="text-muted-foreground text-sm">
|
||||
Select the expiration date (Max 1 year)
|
||||
</li>
|
||||
<li className="text-muted-foreground text-sm">
|
||||
Select Bitbucket product.
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
To integrate your Bitbucket account, you need to create a new
|
||||
App Password in your Bitbucket settings. Follow these steps:
|
||||
Select the following scopes:
|
||||
</p>
|
||||
<ol className="list-decimal list-inside text-sm text-muted-foreground">
|
||||
<li className="flex flex-row gap-2 items-center">
|
||||
Create new App Password{" "}
|
||||
<Link
|
||||
href="https://bitbucket.org/account/settings/app-passwords/new"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="w-fit text-primary size-4" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
When creating the App Password, ensure you grant the
|
||||
following permissions:
|
||||
<ul className="list-disc list-inside ml-4">
|
||||
<li>Account: Read</li>
|
||||
<li>Workspace membership: Read</li>
|
||||
<li>Projects: Read</li>
|
||||
<li>Repositories: Read</li>
|
||||
<li>Pull requests: Read</li>
|
||||
<li>Webhooks: Read and write</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
After creating, you'll receive an App Password. Copy it and
|
||||
paste it below along with your Bitbucket username.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<ul className="list-disc list-inside ml-4 text-sm text-muted-foreground">
|
||||
<li>read:repository:bitbucket</li>
|
||||
<li>read:pullrequest:bitbucket</li>
|
||||
<li>read:webhook:bitbucket</li>
|
||||
<li>read:workspace:bitbucket</li>
|
||||
<li>write:webhook:bitbucket</li>
|
||||
</ul>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -152,7 +154,7 @@ export const AddBitbucketProvider = () => {
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Random Name eg(my-personal-account)"
|
||||
placeholder="Your Bitbucket Provider, eg: my-personal-account"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -179,14 +181,27 @@ export const AddBitbucketProvider = () => {
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Password</FormLabel>
|
||||
<FormLabel>Bitbucket Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Your Bitbucket email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiToken"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>API Token</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="ATBBPDYUC94nR96Nj7Cqpp4pfwKk03573DD2"
|
||||
placeholder="Paste your Bitbucket API token"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -200,7 +215,7 @@ export const AddBitbucketProvider = () => {
|
||||
name="workspaceName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Workspace Name (Optional)</FormLabel>
|
||||
<FormLabel>Workspace Name (optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="For organization accounts"
|
||||
|
||||
@@ -33,7 +33,10 @@ const Schema = z.object({
|
||||
username: z.string().min(1, {
|
||||
message: "Username is required",
|
||||
}),
|
||||
email: z.string().email().optional(),
|
||||
workspaceName: z.string().optional(),
|
||||
apiToken: z.string().optional(),
|
||||
appPassword: z.string().optional(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
@@ -60,19 +63,28 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
username: "",
|
||||
email: "",
|
||||
workspaceName: "",
|
||||
apiToken: "",
|
||||
appPassword: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
|
||||
const username = form.watch("username");
|
||||
const email = form.watch("email");
|
||||
const workspaceName = form.watch("workspaceName");
|
||||
const apiToken = form.watch("apiToken");
|
||||
const appPassword = form.watch("appPassword");
|
||||
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
username: bitbucket?.bitbucketUsername || "",
|
||||
email: bitbucket?.bitbucketEmail || "",
|
||||
workspaceName: bitbucket?.bitbucketWorkspaceName || "",
|
||||
name: bitbucket?.gitProvider.name || "",
|
||||
apiToken: bitbucket?.apiToken || "",
|
||||
appPassword: bitbucket?.appPassword || "",
|
||||
});
|
||||
}, [form, isOpen, bitbucket]);
|
||||
|
||||
@@ -81,8 +93,11 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
bitbucketId,
|
||||
gitProviderId: bitbucket?.gitProviderId || "",
|
||||
bitbucketUsername: data.username,
|
||||
bitbucketEmail: data.email || "",
|
||||
bitbucketWorkspaceName: data.workspaceName || "",
|
||||
name: data.name || "",
|
||||
apiToken: data.apiToken || "",
|
||||
appPassword: data.appPassword || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
@@ -121,6 +136,12 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Update your Bitbucket authentication. Use API Token for
|
||||
enhanced security (recommended) or App Password for legacy
|
||||
support.
|
||||
</p>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -154,6 +175,24 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email (Required for API Tokens)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Your Bitbucket email address"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="workspaceName"
|
||||
@@ -171,6 +210,49 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col gap-2 border-t pt-4">
|
||||
<h3 className="text-sm font-medium mb-2">
|
||||
Authentication (Update to use API Token)
|
||||
</h3>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiToken"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>API Token (Recommended)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your Bitbucket API Token"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
App Password (Legacy - will be deprecated June 2026)
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your Bitbucket App Password"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full justify-between gap-4 mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -180,7 +262,10 @@ export const EditBitbucketProvider = ({ bitbucketId }: Props) => {
|
||||
await testConnection({
|
||||
bitbucketId,
|
||||
bitbucketUsername: username,
|
||||
bitbucketEmail: email,
|
||||
workspaceName: workspaceName,
|
||||
apiToken: apiToken,
|
||||
appPassword: appPassword,
|
||||
})
|
||||
.then(async (message) => {
|
||||
toast.info(`Message: ${message}`);
|
||||
|
||||
@@ -30,6 +30,9 @@ const Schema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "Name is required",
|
||||
}),
|
||||
appName: z.string().min(1, {
|
||||
message: "App Name is required",
|
||||
}),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof Schema>;
|
||||
@@ -55,6 +58,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
appName: "",
|
||||
},
|
||||
resolver: zodResolver(Schema),
|
||||
});
|
||||
@@ -62,6 +66,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
name: github?.gitProvider.name || "",
|
||||
appName: github?.githubAppName || "",
|
||||
});
|
||||
}, [form, isOpen]);
|
||||
|
||||
@@ -70,6 +75,7 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
githubId,
|
||||
name: data.name || "",
|
||||
gitProviderId: github?.gitProviderId || "",
|
||||
githubAppName: data.appName || "",
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.gitProvider.getAll.invalidate();
|
||||
@@ -124,6 +130,22 @@ export const EditGithubProvider = ({ githubId }: Props) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="pp Name eg(my-personal)"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full justify-between gap-4 mt-4">
|
||||
<Button
|
||||
|
||||
@@ -157,7 +157,13 @@ export const ShowGitProviders = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-1">
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
{isBitbucket &&
|
||||
gitProvider.bitbucket?.appPassword &&
|
||||
!gitProvider.bitbucket?.apiToken ? (
|
||||
<Badge variant="yellow">Deprecated</Badge>
|
||||
) : null}
|
||||
|
||||
{!haveGithubRequirements && isGithub && (
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
<Badge
|
||||
|
||||
@@ -33,7 +33,10 @@ import { Disable2FA } from "./disable-2fa";
|
||||
import { Enable2FA } from "./enable-2fa";
|
||||
|
||||
const profileSchema = z.object({
|
||||
email: z.string(),
|
||||
email: z
|
||||
.string()
|
||||
.email("Please enter a valid email address")
|
||||
.min(1, "Email is required"),
|
||||
password: z.string().nullable(),
|
||||
currentPassword: z.string().nullable(),
|
||||
image: z.string().optional(),
|
||||
|
||||
1
apps/dokploy/drizzle/0111_mushy_wolfsbane.sql
Normal file
1
apps/dokploy/drizzle/0111_mushy_wolfsbane.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "bitbucket" ADD COLUMN "apiToken" text;
|
||||
1
apps/dokploy/drizzle/0112_freezing_skrulls.sql
Normal file
1
apps/dokploy/drizzle/0112_freezing_skrulls.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "bitbucket" ADD COLUMN "bitbucketEmail" text;
|
||||
6565
apps/dokploy/drizzle/meta/0111_snapshot.json
Normal file
6565
apps/dokploy/drizzle/meta/0111_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
6571
apps/dokploy/drizzle/meta/0112_snapshot.json
Normal file
6571
apps/dokploy/drizzle/meta/0112_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -778,6 +778,20 @@
|
||||
"when": 1757189541734,
|
||||
"tag": "0110_red_psynapse",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 111,
|
||||
"version": "7",
|
||||
"when": 1758445844561,
|
||||
"tag": "0111_mushy_wolfsbane",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 112,
|
||||
"version": "7",
|
||||
"when": 1758483520214,
|
||||
"tag": "0112_freezing_skrulls",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.25.2",
|
||||
"version": "v0.25.3",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -112,7 +112,7 @@
|
||||
"i18next": "^23.16.8",
|
||||
"input-otp": "^1.4.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-yaml": "4.1.0",
|
||||
"yaml": "2.8.1",
|
||||
"lodash": "4.17.21",
|
||||
"lucide-react": "^0.469.0",
|
||||
"micromatch": "4.0.8",
|
||||
@@ -160,7 +160,6 @@
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/lodash": "4.17.4",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "^18.19.104",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { IS_CLOUD, shouldDeploy } from "@dokploy/server";
|
||||
import {
|
||||
type Bitbucket,
|
||||
getBitbucketHeaders,
|
||||
IS_CLOUD,
|
||||
shouldDeploy,
|
||||
} from "@dokploy/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { db } from "@/server/db";
|
||||
@@ -146,10 +151,10 @@ export default async function handler(
|
||||
|
||||
const commitedPaths = await extractCommitedPaths(
|
||||
req.body,
|
||||
application.bitbucketOwner,
|
||||
application.bitbucket?.appPassword || "",
|
||||
application.bitbucket,
|
||||
application.bitbucketRepository || "",
|
||||
);
|
||||
|
||||
const shouldDeployPaths = shouldDeploy(
|
||||
application.watchPaths,
|
||||
commitedPaths,
|
||||
@@ -354,9 +359,8 @@ export const getProviderByHeader = (headers: any) => {
|
||||
|
||||
export const extractCommitedPaths = async (
|
||||
body: any,
|
||||
bitbucketUsername: string | null,
|
||||
bitbucketAppPassword: string | null,
|
||||
repository: string | null,
|
||||
bitbucket: Bitbucket | null,
|
||||
repository: string,
|
||||
) => {
|
||||
const changes = body.push?.changes || [];
|
||||
|
||||
@@ -365,18 +369,16 @@ export const extractCommitedPaths = async (
|
||||
.filter(Boolean);
|
||||
const commitedPaths: string[] = [];
|
||||
for (const commit of commitHashes) {
|
||||
const url = `https://api.bitbucket.org/2.0/repositories/${bitbucketUsername}/${repository}/diffstat/${commit}`;
|
||||
const url = `https://api.bitbucket.org/2.0/repositories/${bitbucket?.bitbucketUsername}/${repository}/diffstat/${commit}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketUsername}:${bitbucketAppPassword}`).toString("base64")}`,
|
||||
},
|
||||
headers: getBitbucketHeaders(bitbucket!),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
for (const value of data.values) {
|
||||
commitedPaths.push(value.new?.path);
|
||||
if (value?.new?.path) commitedPaths.push(value.new.path);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
||||
@@ -99,8 +99,7 @@ export default async function handler(
|
||||
|
||||
const commitedPaths = await extractCommitedPaths(
|
||||
req.body,
|
||||
composeResult.bitbucketOwner,
|
||||
composeResult.bitbucket?.appPassword || "",
|
||||
composeResult.bitbucket,
|
||||
composeResult.bitbucketRepository || "",
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
||||
import { getLocalServerIp } from "@/server/wss/terminal";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
export const clusterRouter = createTRPCRouter({
|
||||
getNodes: protectedProcedure
|
||||
@@ -61,7 +61,7 @@ export const clusterRouter = createTRPCRouter({
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
|
||||
let ip = await getPublicIpWithFallback();
|
||||
let ip = await getLocalServerIp();
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
ip = server?.ipAddress;
|
||||
@@ -85,7 +85,7 @@ export const clusterRouter = createTRPCRouter({
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
|
||||
let ip = await getPublicIpWithFallback();
|
||||
let ip = await getLocalServerIp();
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
ip = server?.ipAddress;
|
||||
|
||||
@@ -39,10 +39,10 @@ import {
|
||||
import { processTemplate } from "@dokploy/server/templates/processors";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { dump } from "js-yaml";
|
||||
import _ from "lodash";
|
||||
import { nanoid } from "nanoid";
|
||||
import { parse } from "toml";
|
||||
import { stringify } from "yaml";
|
||||
import { z } from "zod";
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { db } from "@/server/db";
|
||||
@@ -364,7 +364,7 @@ export const composeRouter = createTRPCRouter({
|
||||
}
|
||||
const domains = await findDomainsByComposeId(input.composeId);
|
||||
const composeFile = await addDomainToCompose(compose, domains);
|
||||
return dump(composeFile, {
|
||||
return stringify(composeFile, {
|
||||
lineWidth: 1000,
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
getGithubBranches,
|
||||
getGithubRepositories,
|
||||
haveGithubRequirements,
|
||||
updateGithub,
|
||||
updateGitProvider,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -134,5 +135,9 @@ export const githubRouter = createTRPCRouter({
|
||||
name: input.name,
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
});
|
||||
|
||||
await updateGithub(input.githubId, {
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -46,8 +46,8 @@ import {
|
||||
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
@@ -657,7 +657,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
const config = readMainConfig();
|
||||
|
||||
if (!config) return false;
|
||||
const parsedConfig = load(config) as {
|
||||
const parsedConfig = parse(config) as {
|
||||
accessLog?: {
|
||||
filePath: string;
|
||||
};
|
||||
@@ -678,7 +678,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
const mainConfig = readMainConfig();
|
||||
if (!mainConfig) return false;
|
||||
|
||||
const currentConfig = load(mainConfig) as {
|
||||
const currentConfig = parse(mainConfig) as {
|
||||
accessLog?: {
|
||||
filePath: string;
|
||||
};
|
||||
@@ -701,7 +701,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
currentConfig.accessLog = undefined;
|
||||
}
|
||||
|
||||
writeMainConfig(dump(currentConfig));
|
||||
writeMainConfig(stringify(currentConfig));
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
@@ -192,7 +192,16 @@ export const userRouter = createTRPCRouter({
|
||||
})
|
||||
.where(eq(account.userId, ctx.user.id));
|
||||
}
|
||||
return await updateUser(ctx.user.id, input);
|
||||
|
||||
try {
|
||||
return await updateUser(ctx.user.id, input);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
error instanceof Error ? error.message : "Failed to update user",
|
||||
});
|
||||
}
|
||||
}),
|
||||
getUserByToken: publicProcedure
|
||||
.input(apiFindOneToken)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type http from "node:http";
|
||||
import { findServerById, IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import {
|
||||
execAsync,
|
||||
findServerById,
|
||||
IS_CLOUD,
|
||||
validateRequest,
|
||||
} from "@dokploy/server";
|
||||
import { publicIpv4, publicIpv6 } from "public-ip";
|
||||
import { Client, type ConnectConfig } from "ssh2";
|
||||
import { WebSocketServer } from "ws";
|
||||
@@ -44,6 +49,21 @@ export const getPublicIpWithFallback = async () => {
|
||||
return ip;
|
||||
};
|
||||
|
||||
export const getLocalServerIp = async () => {
|
||||
try {
|
||||
const command = `ip addr show | grep -E "inet (192\.168\.|10\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.)" | head -n1 | awk '{print $2}' | cut -d/ -f1`;
|
||||
const { stdout } = await execAsync(command);
|
||||
const ip = stdout.trim();
|
||||
return (
|
||||
ip ||
|
||||
"We were unable to obtain the local server IP, please use your private IP address"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error to obtain local server IP", error);
|
||||
return "We were unable to obtain the local server IP, please use your private IP address";
|
||||
}
|
||||
};
|
||||
|
||||
export const setupTerminalWebSocketServer = (
|
||||
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>,
|
||||
) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { exit } from "node:process";
|
||||
import { execAsync } from "@dokploy/server";
|
||||
import { setupDirectories } from "@dokploy/server/setup/config-paths";
|
||||
import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
|
||||
@@ -25,6 +26,8 @@ import {
|
||||
await initializeStandaloneTraefik();
|
||||
await initializeRedis();
|
||||
await initializePostgres();
|
||||
console.log("Dokploy setup completed");
|
||||
exit(0);
|
||||
} catch (e) {
|
||||
console.error("Error in dokploy setup", e);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"drizzle-orm": "^0.39.3",
|
||||
"drizzle-zod": "0.5.1",
|
||||
"hi-base32": "^0.5.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"yaml": "2.8.1",
|
||||
"lodash": "4.17.21",
|
||||
"micromatch": "4.0.8",
|
||||
"nanoid": "3.3.11",
|
||||
@@ -85,7 +85,6 @@
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/dockerode": "3.3.23",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/lodash": "4.17.4",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "^18.19.104",
|
||||
|
||||
@@ -11,7 +11,9 @@ export const bitbucket = pgTable("bitbucket", {
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
bitbucketUsername: text("bitbucketUsername"),
|
||||
bitbucketEmail: text("bitbucketEmail"),
|
||||
appPassword: text("appPassword"),
|
||||
apiToken: text("apiToken"),
|
||||
bitbucketWorkspaceName: text("bitbucketWorkspaceName"),
|
||||
gitProviderId: text("gitProviderId")
|
||||
.notNull()
|
||||
@@ -29,7 +31,9 @@ const createSchema = createInsertSchema(bitbucket);
|
||||
|
||||
export const apiCreateBitbucket = createSchema.extend({
|
||||
bitbucketUsername: z.string().optional(),
|
||||
bitbucketEmail: z.string().email().optional(),
|
||||
appPassword: z.string().optional(),
|
||||
apiToken: z.string().optional(),
|
||||
bitbucketWorkspaceName: z.string().optional(),
|
||||
gitProviderId: z.string().optional(),
|
||||
authId: z.string().min(1),
|
||||
@@ -46,9 +50,19 @@ export const apiBitbucketTestConnection = createSchema
|
||||
.extend({
|
||||
bitbucketId: z.string().min(1),
|
||||
bitbucketUsername: z.string().optional(),
|
||||
bitbucketEmail: z.string().email().optional(),
|
||||
workspaceName: z.string().optional(),
|
||||
apiToken: z.string().optional(),
|
||||
appPassword: z.string().optional(),
|
||||
})
|
||||
.pick({ bitbucketId: true, bitbucketUsername: true, workspaceName: true });
|
||||
.pick({
|
||||
bitbucketId: true,
|
||||
bitbucketUsername: true,
|
||||
bitbucketEmail: true,
|
||||
workspaceName: true,
|
||||
apiToken: true,
|
||||
appPassword: true,
|
||||
});
|
||||
|
||||
export const apiFindBitbucketBranches = z.object({
|
||||
owner: z.string(),
|
||||
@@ -60,6 +74,9 @@ export const apiUpdateBitbucket = createSchema.extend({
|
||||
bitbucketId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
bitbucketUsername: z.string().optional(),
|
||||
bitbucketEmail: z.string().email().optional(),
|
||||
appPassword: z.string().optional(),
|
||||
apiToken: z.string().optional(),
|
||||
bitbucketWorkspaceName: z.string().optional(),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -58,4 +58,5 @@ export const apiUpdateGithub = createSchema.extend({
|
||||
githubId: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
gitProviderId: z.string().min(1),
|
||||
githubAppName: z.string().min(1),
|
||||
});
|
||||
|
||||
@@ -322,6 +322,11 @@ export const apiUpdateWebServerMonitoring = z.object({
|
||||
});
|
||||
|
||||
export const apiUpdateUser = createSchema.partial().extend({
|
||||
email: z
|
||||
.string()
|
||||
.email("Please enter a valid email address")
|
||||
.min(1, "Email is required")
|
||||
.optional(),
|
||||
password: z.string().optional(),
|
||||
currentPassword: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
|
||||
@@ -92,31 +92,48 @@ export const suggestVariants = async ({
|
||||
|
||||
const { object } = await generateObject({
|
||||
model,
|
||||
output: "array",
|
||||
output: "object",
|
||||
schema: z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
shortDescription: z.string(),
|
||||
description: z.string(),
|
||||
suggestions: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
shortDescription: z.string(),
|
||||
description: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
prompt: `
|
||||
Act as advanced DevOps engineer and generate a list of open source projects what can cover users needs(up to 3 items), the suggestion
|
||||
should include id, name, shortDescription, and description. Use slug of title for id.
|
||||
Act as advanced DevOps engineer and generate a list of open source projects what can cover users needs(up to 3 items).
|
||||
|
||||
Return your response as a JSON object with the following structure:
|
||||
{
|
||||
"suggestions": [
|
||||
{
|
||||
"id": "project-slug",
|
||||
"name": "Project Name",
|
||||
"shortDescription": "Brief one-line description",
|
||||
"description": "Detailed description"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Important rules for the response:
|
||||
1. The description field should ONLY contain a plain text description of the project, its features, and use cases
|
||||
2. Do NOT include any code snippets, configuration examples, or installation instructions in the description
|
||||
3. The shortDescription should be a single-line summary focusing on the main technologies
|
||||
1. Use slug format for the id field (lowercase, hyphenated)
|
||||
2. The description field should ONLY contain a plain text description of the project, its features, and use cases
|
||||
3. Do NOT include any code snippets, configuration examples, or installation instructions in the description
|
||||
4. The shortDescription should be a single-line summary focusing on the main technologies
|
||||
5. All projects should be installable in docker and have docker compose support
|
||||
|
||||
User wants to create a new project with the following details, it should be installable in docker and can be docker compose generated for it:
|
||||
User wants to create a new project with the following details:
|
||||
|
||||
${input}
|
||||
`,
|
||||
});
|
||||
|
||||
if (object?.length) {
|
||||
if (object?.suggestions?.length) {
|
||||
const result = [];
|
||||
for (const suggestion of object) {
|
||||
for (const suggestion of object.suggestions) {
|
||||
try {
|
||||
const { object: docker } = await generateObject({
|
||||
model,
|
||||
@@ -136,16 +153,29 @@ export const suggestVariants = async ({
|
||||
serviceName: z.string(),
|
||||
}),
|
||||
),
|
||||
configFiles: z.array(
|
||||
z.object({
|
||||
content: z.string(),
|
||||
filePath: z.string(),
|
||||
}),
|
||||
),
|
||||
configFiles: z
|
||||
.array(
|
||||
z.object({
|
||||
content: z.string(),
|
||||
filePath: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
prompt: `
|
||||
Act as advanced DevOps engineer and generate docker compose with environment variables and domain configurations needed to install the following project.
|
||||
Return the docker compose as a YAML string and environment variables configuration. Follow these rules:
|
||||
|
||||
Return your response as a JSON object with this structure:
|
||||
{
|
||||
"dockerCompose": "yaml string here",
|
||||
"envVariables": [{"name": "VAR_NAME", "value": "example_value"}],
|
||||
"domains": [{"host": "domain.com", "port": 3000, "serviceName": "service"}],
|
||||
"configFiles": [{"content": "file content", "filePath": "path/to/file"}]
|
||||
}
|
||||
|
||||
Note: configFiles is optional - only include it if configuration files are absolutely required.
|
||||
|
||||
Follow these rules:
|
||||
|
||||
Docker Compose Rules:
|
||||
1. Use placeholder like \${VARIABLE_NAME-default} for generated variables in the docker-compose.yml
|
||||
@@ -198,6 +228,7 @@ export const suggestVariants = async ({
|
||||
console.error("Error in docker compose generation:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,10 +68,26 @@ export const updateBitbucket = async (
|
||||
input: typeof apiUpdateBitbucket._type,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
// First get the current bitbucket provider to get gitProviderId
|
||||
const currentProvider = await tx.query.bitbucket.findFirst({
|
||||
where: eq(bitbucket.bitbucketId, bitbucketId),
|
||||
});
|
||||
|
||||
if (!currentProvider) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Bitbucket provider not found",
|
||||
});
|
||||
}
|
||||
|
||||
const result = await tx
|
||||
.update(bitbucket)
|
||||
.set({
|
||||
...input,
|
||||
bitbucketUsername: input.bitbucketUsername,
|
||||
bitbucketEmail: input.bitbucketEmail,
|
||||
appPassword: input.appPassword,
|
||||
apiToken: input.apiToken,
|
||||
bitbucketWorkspaceName: input.bitbucketWorkspaceName,
|
||||
})
|
||||
.where(eq(bitbucket.bitbucketId, bitbucketId))
|
||||
.returning();
|
||||
@@ -83,7 +99,7 @@ export const updateBitbucket = async (
|
||||
name: input.name,
|
||||
organizationId: input.organizationId,
|
||||
})
|
||||
.where(eq(gitProvider.gitProviderId, input.gitProviderId))
|
||||
.where(eq(gitProvider.gitProviderId, currentProvider.gitProviderId))
|
||||
.returning();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { dump } from "js-yaml";
|
||||
import { stringify } from "yaml";
|
||||
import type { z } from "zod";
|
||||
import { encodeBase64 } from "../utils/docker/utils";
|
||||
import { execAsyncRemote } from "../utils/process/execAsync";
|
||||
@@ -101,7 +101,7 @@ const createCertificateFiles = async (certificate: Certificate) => {
|
||||
],
|
||||
},
|
||||
};
|
||||
const yamlConfig = dump(traefikConfig);
|
||||
const yamlConfig = stringify(traefikConfig);
|
||||
const configFile = path.join(certDir, "certificate.yml");
|
||||
|
||||
if (certificate.serverId) {
|
||||
|
||||
@@ -10,6 +10,22 @@ import { IS_CLOUD } from "../constants";
|
||||
|
||||
export type Registry = typeof registry.$inferSelect;
|
||||
|
||||
function shEscape(s: string | undefined): string {
|
||||
if (!s) return "''";
|
||||
return `'${s.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
function safeDockerLoginCommand(
|
||||
registry: string | undefined,
|
||||
user: string | undefined,
|
||||
pass: string | undefined,
|
||||
) {
|
||||
const escapedRegistry = shEscape(registry);
|
||||
const escapedUser = shEscape(user);
|
||||
const escapedPassword = shEscape(pass);
|
||||
return `printf %s ${escapedPassword} | docker login ${escapedRegistry} -u ${escapedUser} --password-stdin`;
|
||||
}
|
||||
|
||||
export const createRegistry = async (
|
||||
input: typeof apiCreateRegistry._type,
|
||||
organizationId: string,
|
||||
@@ -37,7 +53,11 @@ export const createRegistry = async (
|
||||
message: "Select a server to add the registry",
|
||||
});
|
||||
}
|
||||
const loginCommand = `echo ${input.password} | docker login ${input.registryUrl} --username ${input.username} --password-stdin`;
|
||||
const loginCommand = safeDockerLoginCommand(
|
||||
input.registryUrl,
|
||||
input.username,
|
||||
input.password,
|
||||
);
|
||||
if (input.serverId && input.serverId !== "none") {
|
||||
await execAsyncRemote(input.serverId, loginCommand);
|
||||
} else if (newRegistry.registryType === "cloud") {
|
||||
@@ -91,7 +111,11 @@ export const updateRegistry = async (
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
const loginCommand = `echo ${response?.password} | docker login ${response?.registryUrl} --username ${response?.username} --password-stdin`;
|
||||
const loginCommand = safeDockerLoginCommand(
|
||||
response?.registryUrl,
|
||||
response?.username,
|
||||
response?.password,
|
||||
);
|
||||
|
||||
if (
|
||||
IS_CLOUD &&
|
||||
|
||||
@@ -296,6 +296,19 @@ export const findMemberById = async (
|
||||
};
|
||||
|
||||
export const updateUser = async (userId: string, userData: Partial<User>) => {
|
||||
// Validate email if it's being updated
|
||||
if (userData.email !== undefined) {
|
||||
if (!userData.email || userData.email.trim() === "") {
|
||||
throw new Error("Email is required and cannot be empty");
|
||||
}
|
||||
|
||||
// Basic email format validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(userData.email)) {
|
||||
throw new Error("Please enter a valid email address");
|
||||
}
|
||||
}
|
||||
|
||||
const user = await db
|
||||
.update(users_temp)
|
||||
.set({
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
chmodSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { ContainerCreateOptions, CreateServiceOptions } from "dockerode";
|
||||
import { dump } from "js-yaml";
|
||||
import { stringify } from "yaml";
|
||||
import { paths } from "../constants";
|
||||
import { getRemoteDocker } from "../utils/servers/remote-docker";
|
||||
import type { FileConfig } from "../utils/traefik/file-types";
|
||||
@@ -234,7 +241,7 @@ export const createDefaultServerTraefikConfig = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const yamlStr = dump(config);
|
||||
const yamlStr = stringify(config);
|
||||
mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
|
||||
@@ -308,7 +315,7 @@ export const getDefaultTraefikConfig = () => {
|
||||
}),
|
||||
};
|
||||
|
||||
const yamlStr = dump(configObject);
|
||||
const yamlStr = stringify(configObject);
|
||||
|
||||
return yamlStr;
|
||||
};
|
||||
@@ -362,7 +369,7 @@ export const getDefaultServerTraefikConfig = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const yamlStr = dump(configObject);
|
||||
const yamlStr = stringify(configObject);
|
||||
|
||||
return yamlStr;
|
||||
};
|
||||
@@ -375,13 +382,26 @@ export const createDefaultTraefikConfig = () => {
|
||||
if (existsSync(acmeJsonPath)) {
|
||||
chmodSync(acmeJsonPath, "600");
|
||||
}
|
||||
if (existsSync(mainConfig)) {
|
||||
console.log("Main config already exists");
|
||||
return;
|
||||
}
|
||||
const yamlStr = getDefaultTraefikConfig();
|
||||
|
||||
// Create the traefik directory first
|
||||
mkdirSync(MAIN_TRAEFIK_PATH, { recursive: true });
|
||||
|
||||
// Check if traefik.yml exists and handle the case where it might be a directory
|
||||
if (existsSync(mainConfig)) {
|
||||
const stats = statSync(mainConfig);
|
||||
if (stats.isDirectory()) {
|
||||
// If traefik.yml is a directory, remove it
|
||||
console.log("Found traefik.yml as directory, removing it...");
|
||||
rmSync(mainConfig, { recursive: true, force: true });
|
||||
} else if (stats.isFile()) {
|
||||
console.log("Main config already exists");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const yamlStr = getDefaultTraefikConfig();
|
||||
writeFileSync(mainConfig, yamlStr, "utf8");
|
||||
console.log("Traefik config created successfully");
|
||||
};
|
||||
|
||||
export const getDefaultMiddlewares = () => {
|
||||
@@ -397,7 +417,7 @@ export const getDefaultMiddlewares = () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
const yamlStr = dump(defaultMiddlewares);
|
||||
const yamlStr = stringify(defaultMiddlewares);
|
||||
return yamlStr;
|
||||
};
|
||||
export const createDefaultMiddlewares = () => {
|
||||
|
||||
@@ -89,7 +89,7 @@ export const getMariadbBackupCommand = (
|
||||
databaseUser: string,
|
||||
databasePassword: string,
|
||||
) => {
|
||||
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --databases ${database} | gzip"`;
|
||||
return `docker exec -i $CONTAINER_ID bash -c "set -o pipefail; mariadb-dump --user='${databaseUser}' --password='${databasePassword}' --single-transaction --quick --databases ${database} | gzip"`;
|
||||
};
|
||||
|
||||
export const getMysqlBackupCommand = (
|
||||
|
||||
@@ -220,8 +220,8 @@ const getImageName = (application: ApplicationNested) => {
|
||||
if (registry) {
|
||||
const { registryUrl, imagePrefix, username } = registry;
|
||||
const registryTag = imagePrefix
|
||||
? `${registryUrl}/${imagePrefix}/${imageName}`
|
||||
: `${registryUrl}/${username}/${imageName}`;
|
||||
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
||||
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
||||
return registryTag;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ export const uploadImage = async (
|
||||
// For ghcr.io: ghcr.io/username/image:tag
|
||||
// For docker.io: docker.io/username/image:tag
|
||||
const registryTag = imagePrefix
|
||||
? `${registryUrl}/${imagePrefix}/${imageName}`
|
||||
: `${registryUrl}/${username}/${imageName}`;
|
||||
? `${registryUrl ? `${registryUrl}/` : ""}${imagePrefix}/${imageName}`
|
||||
: `${registryUrl ? `${registryUrl}/` : ""}${username}/${imageName}`;
|
||||
|
||||
try {
|
||||
writeStream.write(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { findComposeById } from "@dokploy/server/services/compose";
|
||||
import { dump } from "js-yaml";
|
||||
import { stringify } from "yaml";
|
||||
import { addAppNameToAllServiceNames } from "./collision/root-network";
|
||||
import { generateRandomHash } from "./compose";
|
||||
import { addSuffixToAllVolumes } from "./compose/volume";
|
||||
@@ -59,7 +59,7 @@ export const randomizeIsolatedDeploymentComposeFile = async (
|
||||
)
|
||||
: composeData;
|
||||
|
||||
return dump(newComposeFile);
|
||||
return stringify(newComposeFile);
|
||||
};
|
||||
|
||||
export const randomizeDeployableSpecificationFile = (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "node:crypto";
|
||||
import { findComposeById } from "@dokploy/server/services/compose";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { addSuffixToAllConfigs } from "./compose/configs";
|
||||
import { addSuffixToAllNetworks } from "./compose/network";
|
||||
import { addSuffixToAllSecrets } from "./compose/secrets";
|
||||
@@ -18,13 +18,13 @@ export const randomizeComposeFile = async (
|
||||
) => {
|
||||
const compose = await findComposeById(composeId);
|
||||
const composeFile = compose.composeFile;
|
||||
const composeData = load(composeFile) as ComposeSpecification;
|
||||
const composeData = parse(composeFile) as ComposeSpecification;
|
||||
|
||||
const randomSuffix = suffix || generateRandomHash();
|
||||
|
||||
const newComposeFile = addSuffixToAllProperties(composeData, randomSuffix);
|
||||
|
||||
return dump(newComposeFile);
|
||||
return stringify(newComposeFile);
|
||||
};
|
||||
|
||||
export const randomizeSpecificationFile = (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { Compose } from "@dokploy/server/services/compose";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import {
|
||||
cloneRawBitbucketRepository,
|
||||
@@ -92,7 +92,7 @@ export const loadDockerCompose = async (
|
||||
|
||||
if (existsSync(path)) {
|
||||
const yamlStr = readFileSync(path, "utf8");
|
||||
const parsedConfig = load(yamlStr) as ComposeSpecification;
|
||||
const parsedConfig = parse(yamlStr) as ComposeSpecification;
|
||||
return parsedConfig;
|
||||
}
|
||||
return null;
|
||||
@@ -115,7 +115,7 @@ export const loadDockerComposeRemote = async (
|
||||
return null;
|
||||
}
|
||||
if (!stdout) return null;
|
||||
const parsedConfig = load(stdout) as ComposeSpecification;
|
||||
const parsedConfig = parse(stdout) as ComposeSpecification;
|
||||
return parsedConfig;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -141,7 +141,7 @@ export const writeDomainsToCompose = async (
|
||||
const composeConverted = await addDomainToCompose(compose, domains);
|
||||
|
||||
const path = getComposePath(compose);
|
||||
const composeString = dump(composeConverted, { lineWidth: 1000 });
|
||||
const composeString = stringify(composeConverted, { lineWidth: 1000 });
|
||||
try {
|
||||
await writeFile(path, composeString, "utf8");
|
||||
} catch (error) {
|
||||
@@ -169,7 +169,7 @@ exit 1;
|
||||
`;
|
||||
}
|
||||
if (compose.serverId) {
|
||||
const composeString = dump(composeConverted, { lineWidth: 1000 });
|
||||
const composeString = stringify(composeConverted, { lineWidth: 1000 });
|
||||
const encodedContent = encodeBase64(composeString);
|
||||
return `echo "${encodedContent}" | base64 -d > "${path}";`;
|
||||
}
|
||||
@@ -287,7 +287,7 @@ export const writeComposeFile = async (
|
||||
const path = getComposePath(compose);
|
||||
|
||||
try {
|
||||
const composeFile = dump(composeSpec, {
|
||||
const composeFile = stringify(composeSpec, {
|
||||
lineWidth: 1000,
|
||||
});
|
||||
fs.writeFileSync(path, composeFile, "utf8");
|
||||
|
||||
@@ -5,7 +5,10 @@ import type {
|
||||
apiBitbucketTestConnection,
|
||||
apiFindBitbucketBranches,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import { findBitbucketById } from "@dokploy/server/services/bitbucket";
|
||||
import {
|
||||
type Bitbucket,
|
||||
findBitbucketById,
|
||||
} from "@dokploy/server/services/bitbucket";
|
||||
import type { Compose } from "@dokploy/server/services/compose";
|
||||
import type { InferResultType } from "@dokploy/server/types/with";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -23,6 +26,39 @@ export type ComposeWithBitbucket = InferResultType<
|
||||
{ bitbucket: true }
|
||||
>;
|
||||
|
||||
export const getBitbucketCloneUrl = (
|
||||
bitbucketProvider: {
|
||||
apiToken?: string | null;
|
||||
bitbucketUsername?: string | null;
|
||||
appPassword?: string | null;
|
||||
} | null,
|
||||
repoClone: string,
|
||||
) => {
|
||||
if (!bitbucketProvider) {
|
||||
throw new Error("Bitbucket provider is required");
|
||||
}
|
||||
return bitbucketProvider.apiToken
|
||||
? `https://x-token-auth:${bitbucketProvider.apiToken}@${repoClone}`
|
||||
: `https://${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}@${repoClone}`;
|
||||
};
|
||||
|
||||
export const getBitbucketHeaders = (bitbucketProvider: Bitbucket) => {
|
||||
if (bitbucketProvider.apiToken) {
|
||||
// For API tokens, use HTTP Basic auth with email and token
|
||||
// According to Bitbucket docs: email:token for API calls
|
||||
const email =
|
||||
bitbucketProvider.bitbucketEmail || bitbucketProvider.bitbucketUsername;
|
||||
return {
|
||||
Authorization: `Basic ${Buffer.from(`${email}:${bitbucketProvider.apiToken}`).toString("base64")}`,
|
||||
};
|
||||
}
|
||||
|
||||
// For app passwords, use HTTP Basic auth with username and app password
|
||||
return {
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
||||
};
|
||||
};
|
||||
|
||||
export const cloneBitbucketRepository = async (
|
||||
entity: ApplicationWithBitbucket | ComposeWithBitbucket,
|
||||
logPath: string,
|
||||
@@ -51,7 +87,7 @@ export const cloneBitbucketRepository = async (
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
await recreateDirectory(outputPath);
|
||||
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
||||
const cloneUrl = `https://${bitbucket?.bitbucketUsername}:${bitbucket?.appPassword}@${repoclone}`;
|
||||
const cloneUrl = getBitbucketCloneUrl(bitbucket, repoclone);
|
||||
try {
|
||||
writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
||||
const cloneArgs = [
|
||||
@@ -103,7 +139,7 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
await recreateDirectory(outputPath);
|
||||
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||
const cloneUrl = getBitbucketCloneUrl(bitbucketProvider, repoclone);
|
||||
|
||||
try {
|
||||
const cloneArgs = [
|
||||
@@ -153,7 +189,7 @@ export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
|
||||
const basePath = COMPOSE_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||
const cloneUrl = getBitbucketCloneUrl(bitbucketProvider, repoclone);
|
||||
|
||||
try {
|
||||
const cloneCommand = `
|
||||
@@ -206,7 +242,7 @@ export const getBitbucketCloneCommand = async (
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
await recreateDirectory(outputPath);
|
||||
const repoclone = `bitbucket.org/${bitbucketOwner}/${bitbucketRepository}.git`;
|
||||
const cloneUrl = `https://${bitbucketProvider?.bitbucketUsername}:${bitbucketProvider?.appPassword}@${repoclone}`;
|
||||
const cloneUrl = getBitbucketCloneUrl(bitbucketProvider, repoclone);
|
||||
|
||||
const cloneCommand = `
|
||||
rm -rf ${outputPath};
|
||||
@@ -241,9 +277,7 @@ export const getBitbucketRepositories = async (bitbucketId?: string) => {
|
||||
while (url) {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
||||
},
|
||||
headers: getBitbucketHeaders(bitbucketProvider),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -279,35 +313,43 @@ export const getBitbucketBranches = async (
|
||||
}
|
||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
||||
const { owner, repo } = input;
|
||||
const url = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/refs/branches?pagelen=100`;
|
||||
let url = `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}/refs/branches?pagelen=1`;
|
||||
let allBranches: {
|
||||
name: string;
|
||||
commit: {
|
||||
sha: string;
|
||||
};
|
||||
}[] = [];
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `HTTP error! status: ${response.status}`,
|
||||
while (url) {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: getBitbucketHeaders(bitbucketProvider),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `HTTP error! status: ${response.status}`,
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const mappedData = data.values.map((branch: any) => {
|
||||
return {
|
||||
name: branch.name,
|
||||
commit: {
|
||||
sha: branch.target.hash,
|
||||
},
|
||||
};
|
||||
});
|
||||
allBranches = allBranches.concat(mappedData);
|
||||
url = data.next || null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const mappedData = data.values.map((branch: any) => {
|
||||
return {
|
||||
name: branch.name,
|
||||
commit: {
|
||||
sha: branch.target.hash,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return mappedData as {
|
||||
return allBranches as {
|
||||
name: string;
|
||||
commit: {
|
||||
sha: string;
|
||||
@@ -335,9 +377,7 @@ export const testBitbucketConnection = async (
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
||||
},
|
||||
headers: getBitbucketHeaders(bitbucketProvider),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -362,17 +362,22 @@ export const testGiteaConnection = async (input: { giteaId: string }) => {
|
||||
}
|
||||
|
||||
const baseUrl = provider.giteaUrl.replace(/\/+$/, "");
|
||||
const limit = 30;
|
||||
let allRepos = 0;
|
||||
let nextUrl = `${baseUrl}/api/v1/repos/search?limit=${limit}`;
|
||||
|
||||
while (nextUrl) {
|
||||
const response = await fetch(nextUrl, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `token ${provider.accessToken}`,
|
||||
// Use /user/repos to get authenticated user's repositories with pagination
|
||||
let allRepos = 0;
|
||||
let page = 1;
|
||||
const limit = 50; // Max per page
|
||||
|
||||
while (true) {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/api/v1/user/repos?page=${page}&limit=${limit}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `token ${provider.accessToken}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
@@ -381,22 +386,18 @@ export const testGiteaConnection = async (input: { giteaId: string }) => {
|
||||
}
|
||||
|
||||
const repos = await response.json();
|
||||
allRepos += repos.data.length;
|
||||
|
||||
const linkHeader = response.headers.get("link");
|
||||
nextUrl = "";
|
||||
|
||||
if (linkHeader) {
|
||||
const nextLink = linkHeader
|
||||
.split(",")
|
||||
.find((link) => link.includes('rel="next"'));
|
||||
if (nextLink) {
|
||||
const matches = nextLink.match(/<([^>]+)>/);
|
||||
if (matches?.[1]) {
|
||||
nextUrl = matches[1];
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(repos) || repos.length === 0) {
|
||||
break; // No more repositories
|
||||
}
|
||||
|
||||
allRepos += repos.length;
|
||||
|
||||
// Check if there are more pages
|
||||
if (repos.length < limit) {
|
||||
break; // Last page (fewer results than limit)
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
await updateGitea(giteaId, {
|
||||
@@ -418,17 +419,22 @@ export const getGiteaRepositories = async (giteaId?: string) => {
|
||||
const giteaProvider = await findGiteaById(giteaId);
|
||||
|
||||
const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, "");
|
||||
const limit = 30;
|
||||
let allRepositories: any[] = [];
|
||||
let nextUrl = `${baseUrl}/api/v1/repos/search?limit=${limit}`;
|
||||
|
||||
while (nextUrl) {
|
||||
const response = await fetch(nextUrl, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `token ${giteaProvider.accessToken}`,
|
||||
// Use /user/repos to get authenticated user's repositories with pagination
|
||||
let allRepositories: any[] = [];
|
||||
let page = 1;
|
||||
const limit = 50; // Max per page
|
||||
|
||||
while (true) {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/api/v1/user/repos?page=${page}&limit=${limit}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `token ${giteaProvider.accessToken}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new TRPCError({
|
||||
@@ -437,23 +443,19 @@ export const getGiteaRepositories = async (giteaId?: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
allRepositories = [...allRepositories, ...result.data];
|
||||
|
||||
const linkHeader = response.headers.get("link");
|
||||
nextUrl = "";
|
||||
|
||||
if (linkHeader) {
|
||||
const nextLink = linkHeader
|
||||
.split(",")
|
||||
.find((link) => link.includes('rel="next"'));
|
||||
if (nextLink) {
|
||||
const matches = nextLink.match(/<([^>]+)>/);
|
||||
if (matches?.[1]) {
|
||||
nextUrl = matches[1];
|
||||
}
|
||||
}
|
||||
const repos = await response.json();
|
||||
if (!Array.isArray(repos) || repos.length === 0) {
|
||||
break; // No more repositories
|
||||
}
|
||||
|
||||
allRepositories = [...allRepositories, ...repos];
|
||||
|
||||
// Check if there are more pages
|
||||
if (repos.length < limit) {
|
||||
break; // Last page (fewer results than limit)
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -482,25 +484,43 @@ export const getGiteaBranches = async (input: {
|
||||
const giteaProvider = await findGiteaById(input.giteaId);
|
||||
|
||||
const baseUrl = giteaProvider.giteaUrl.replace(/\/+$/, "");
|
||||
const url = `${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `token ${giteaProvider.accessToken}`,
|
||||
},
|
||||
});
|
||||
// Handle pagination for branches
|
||||
let allBranches: any[] = [];
|
||||
let page = 1;
|
||||
const limit = 50; // Max per page
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch branches: ${response.statusText}`);
|
||||
while (true) {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/api/v1/repos/${input.owner}/${input.repo}/branches?page=${page}&limit=${limit}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `token ${giteaProvider.accessToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch branches: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const branches = await response.json();
|
||||
if (!Array.isArray(branches) || branches.length === 0) {
|
||||
break; // No more branches
|
||||
}
|
||||
|
||||
allBranches = [...allBranches, ...branches];
|
||||
|
||||
// Check if there are more pages
|
||||
if (branches.length < limit) {
|
||||
break; // Last page (fewer results than limit)
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
const branches = await response.json();
|
||||
|
||||
if (!branches) {
|
||||
return [];
|
||||
}
|
||||
return branches?.map((branch: any) => ({
|
||||
return allBranches?.map((branch: any) => ({
|
||||
id: branch.name,
|
||||
name: branch.name,
|
||||
commit: {
|
||||
|
||||
@@ -401,7 +401,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
||||
const {
|
||||
appName,
|
||||
gitlabPathNamespace,
|
||||
branch,
|
||||
gitlabBranch,
|
||||
gitlabId,
|
||||
serverId,
|
||||
enableSubmodules,
|
||||
@@ -429,7 +429,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
||||
try {
|
||||
const command = `
|
||||
rm -rf ${outputPath};
|
||||
git clone --branch ${branch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
|
||||
git clone --branch ${gitlabBranch} --depth 1 ${enableSubmodules ? "--recurse-submodules" : ""} ${cloneUrl} ${outputPath}
|
||||
`;
|
||||
await execAsyncRemote(serverId, command);
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { createInterface } from "node:readline";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { encodeBase64 } from "../docker/utils";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import type { FileConfig, HttpLoadBalancerService } from "./file-types";
|
||||
@@ -40,7 +40,7 @@ export const createTraefikConfig = (appName: string) => {
|
||||
},
|
||||
},
|
||||
};
|
||||
const yamlStr = dump(config);
|
||||
const yamlStr = stringify(config);
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths();
|
||||
fs.mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
|
||||
writeFileSync(
|
||||
@@ -87,7 +87,7 @@ export const loadOrCreateConfig = (appName: string): FileConfig => {
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
if (fs.existsSync(configPath)) {
|
||||
const yamlStr = fs.readFileSync(configPath, "utf8");
|
||||
const parsedConfig = (load(yamlStr) as FileConfig) || {
|
||||
const parsedConfig = (parse(yamlStr) as FileConfig) || {
|
||||
http: { routers: {}, services: {} },
|
||||
};
|
||||
return parsedConfig;
|
||||
@@ -107,7 +107,7 @@ export const loadOrCreateConfigRemote = async (
|
||||
|
||||
if (!stdout) return fileConfig;
|
||||
|
||||
const parsedConfig = (load(stdout) as FileConfig) || {
|
||||
const parsedConfig = (parse(stdout) as FileConfig) || {
|
||||
http: { routers: {}, services: {} },
|
||||
};
|
||||
return parsedConfig;
|
||||
@@ -248,7 +248,7 @@ export const writeTraefikConfig = (
|
||||
try {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths();
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
const yamlStr = dump(traefikConfig);
|
||||
const yamlStr = stringify(traefikConfig);
|
||||
fs.writeFileSync(configPath, yamlStr, "utf8");
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
@@ -263,7 +263,7 @@ export const writeTraefikConfigRemote = async (
|
||||
try {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
const yamlStr = dump(traefikConfig);
|
||||
const yamlStr = stringify(traefikConfig);
|
||||
await execAsyncRemote(serverId, `echo '${yamlStr}' > ${configPath}`);
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { parse, stringify } from "yaml";
|
||||
import type { ApplicationNested } from "../builders";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { writeTraefikConfigRemote } from "./application";
|
||||
@@ -76,7 +76,7 @@ export const loadMiddlewares = <T>() => {
|
||||
throw new Error(`File not found: ${configPath}`);
|
||||
}
|
||||
const yamlStr = readFileSync(configPath, "utf8");
|
||||
const config = load(yamlStr) as T;
|
||||
const config = parse(yamlStr) as T;
|
||||
return config;
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ export const loadRemoteMiddlewares = async (serverId: string) => {
|
||||
console.error(`Error: ${stderr}`);
|
||||
throw new Error(`File not found: ${configPath}`);
|
||||
}
|
||||
const config = load(stdout) as FileConfig;
|
||||
const config = parse(stdout) as FileConfig;
|
||||
return config;
|
||||
} catch (_) {
|
||||
throw new Error(`File not found: ${configPath}`);
|
||||
@@ -103,7 +103,7 @@ export const loadRemoteMiddlewares = async (serverId: string) => {
|
||||
export const writeMiddleware = <T>(config: T) => {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths();
|
||||
const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
|
||||
const newYamlContent = dump(config);
|
||||
const newYamlContent = stringify(config);
|
||||
writeFileSync(configPath, newYamlContent, "utf8");
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { User } from "@dokploy/server/services/user";
|
||||
import { dump, load } from "js-yaml";
|
||||
import { parse, stringify } from "yaml";
|
||||
import {
|
||||
loadOrCreateConfig,
|
||||
removeTraefikConfig,
|
||||
@@ -79,13 +79,13 @@ export const updateLetsEncryptEmail = (newEmail: string | null) => {
|
||||
const { MAIN_TRAEFIK_PATH } = paths();
|
||||
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
|
||||
const configContent = readFileSync(configPath, "utf8");
|
||||
const config = load(configContent) as MainTraefikConfig;
|
||||
const config = parse(configContent) as MainTraefikConfig;
|
||||
if (config?.certificatesResolvers?.letsencrypt?.acme) {
|
||||
config.certificatesResolvers.letsencrypt.acme.email = newEmail;
|
||||
} else {
|
||||
throw new Error("Invalid Let's Encrypt configuration structure.");
|
||||
}
|
||||
const newYamlContent = dump(config);
|
||||
const newYamlContent = stringify(config);
|
||||
writeFileSync(configPath, newYamlContent, "utf8");
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
|
||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||
import path from "node:path";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import {
|
||||
createDeploymentVolumeBackup,
|
||||
updateDeploymentStatus,
|
||||
} from "@dokploy/server/services/deployment";
|
||||
import { findVolumeBackupById } from "@dokploy/server/services/volume-backups";
|
||||
import {
|
||||
execAsync,
|
||||
execAsyncRemote,
|
||||
} from "@dokploy/server/utils/process/execAsync";
|
||||
import { backupVolume } from "./backup";
|
||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||
import { getS3Credentials, normalizeS3Path } from "../backups/utils";
|
||||
import { backupVolume } from "./backup";
|
||||
|
||||
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
|
||||
const volumeBackup = await findVolumeBackupById(volumeBackupId);
|
||||
@@ -76,7 +78,20 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
|
||||
|
||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||
} catch (error) {
|
||||
const { VOLUME_BACKUPS_PATH } = paths(!!serverId);
|
||||
const volumeBackupPath = path.join(
|
||||
VOLUME_BACKUPS_PATH,
|
||||
volumeBackup.appName,
|
||||
);
|
||||
// delete all the .tar files
|
||||
const command = `rm -rf ${volumeBackupPath}/*.tar`;
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync(command);
|
||||
}
|
||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@@ -325,9 +325,6 @@ importers:
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
js-yaml:
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0
|
||||
lodash:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
@@ -448,6 +445,9 @@ importers:
|
||||
xterm-addon-fit:
|
||||
specifier: ^0.8.0
|
||||
version: 0.8.0(xterm@5.3.0)
|
||||
yaml:
|
||||
specifier: 2.8.1
|
||||
version: 2.8.1
|
||||
zod:
|
||||
specifier: ^3.25.32
|
||||
version: 3.25.32
|
||||
@@ -464,9 +464,6 @@ importers:
|
||||
'@types/js-cookie':
|
||||
specifier: ^3.0.6
|
||||
version: 3.0.6
|
||||
'@types/js-yaml':
|
||||
specifier: 4.0.9
|
||||
version: 4.0.9
|
||||
'@types/lodash':
|
||||
specifier: 4.17.4
|
||||
version: 4.17.4
|
||||
@@ -681,9 +678,6 @@ importers:
|
||||
hi-base32:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1
|
||||
js-yaml:
|
||||
specifier: 4.1.0
|
||||
version: 4.1.0
|
||||
lodash:
|
||||
specifier: 4.17.21
|
||||
version: 4.17.21
|
||||
@@ -747,6 +741,9 @@ importers:
|
||||
ws:
|
||||
specifier: 8.16.0
|
||||
version: 8.16.0
|
||||
yaml:
|
||||
specifier: 2.8.1
|
||||
version: 2.8.1
|
||||
zod:
|
||||
specifier: ^3.25.32
|
||||
version: 3.25.32
|
||||
@@ -760,9 +757,6 @@ importers:
|
||||
'@types/dockerode':
|
||||
specifier: 3.3.23
|
||||
version: 3.3.23
|
||||
'@types/js-yaml':
|
||||
specifier: 4.0.9
|
||||
version: 4.0.9
|
||||
'@types/lodash':
|
||||
specifier: 4.17.4
|
||||
version: 4.17.4
|
||||
@@ -3965,9 +3959,6 @@ packages:
|
||||
'@types/js-cookie@3.0.6':
|
||||
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||
|
||||
'@types/js-yaml@4.0.9':
|
||||
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
||||
|
||||
'@types/jsonwebtoken@9.0.9':
|
||||
resolution: {integrity: sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==}
|
||||
|
||||
@@ -7783,6 +7774,11 @@ packages:
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yaml@2.8.1:
|
||||
resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -11223,8 +11219,6 @@ snapshots:
|
||||
|
||||
'@types/js-cookie@3.0.6': {}
|
||||
|
||||
'@types/js-yaml@4.0.9': {}
|
||||
|
||||
'@types/jsonwebtoken@9.0.9':
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
@@ -14032,7 +14026,7 @@ snapshots:
|
||||
postcss-load-config@4.0.2(postcss@8.5.3):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
yaml: 2.8.0
|
||||
yaml: 2.8.1
|
||||
optionalDependencies:
|
||||
postcss: 8.5.3
|
||||
|
||||
@@ -15353,6 +15347,8 @@ snapshots:
|
||||
|
||||
yaml@2.8.0: {}
|
||||
|
||||
yaml@2.8.1: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
|
||||
Reference in New Issue
Block a user