@lishugupta652/dokploy 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.githooks/pre-commit +10 -0
- package/README.md +77 -0
- package/dist/client.js +106 -0
- package/dist/commands/apply.js +38 -0
- package/dist/commands/deploy.js +17 -0
- package/dist/commands/destroy.js +73 -0
- package/dist/commands/init.js +51 -0
- package/dist/commands/projects.js +57 -0
- package/dist/commands/status.js +84 -0
- package/dist/config.js +288 -0
- package/dist/index.js +77 -0
- package/dist/output.js +113 -0
- package/dist/resources/application.js +173 -0
- package/dist/resources/backup.js +248 -0
- package/dist/resources/common.js +89 -0
- package/dist/resources/compose.js +136 -0
- package/dist/resources/database.js +161 -0
- package/dist/resources/domain.js +88 -0
- package/dist/resources/environment.js +88 -0
- package/dist/resources/project.js +52 -0
- package/dist/runtime.js +40 -0
- package/dist/state.js +49 -0
- package/dist/types.js +1 -0
- package/dist/version.js +14 -0
- package/examples/compose-stack.yaml +28 -0
- package/examples/fullstack.yaml +61 -0
- package/examples/simple-app.yaml +39 -0
- package/package.json +51 -0
- package/scripts/bump-version-on-commit.mjs +53 -0
- package/scripts/install-git-hooks.mjs +16 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
PACKAGE_DIR="dokploy-deploy-automation"
|
|
5
|
+
if [ ! -f "$PACKAGE_DIR/scripts/bump-version-on-commit.mjs" ]; then
|
|
6
|
+
PACKAGE_DIR="."
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
node "$PACKAGE_DIR/scripts/bump-version-on-commit.mjs"
|
|
10
|
+
git add "$PACKAGE_DIR/package.json" "$PACKAGE_DIR/package-lock.json"
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Dokploy
|
|
2
|
+
|
|
3
|
+
YAML-driven CLI for applying Dokploy projects through the Dokploy API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Run locally during development:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm run dev -- apply -f examples/simple-app.yaml --dry-run
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
After build, the CLI binary is `dokploy`.
|
|
19
|
+
|
|
20
|
+
For global install after publishing:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @lishugupta652/dokploy
|
|
24
|
+
dokploy --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Environment
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export DOKPLOY_API_KEY=your-api-key
|
|
31
|
+
export DOKPLOY_HOST=https://your-dokploy.example.com/api
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`DOKPLOY_HOST` is optional when `host` is set in the YAML file. API keys are never read from config.
|
|
35
|
+
|
|
36
|
+
## Commands
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
dokploy projects --host https://your-dokploy.example.com/api
|
|
40
|
+
dokploy projects --summary --host https://your-dokploy.example.com/api
|
|
41
|
+
dokploy projects --json --host https://your-dokploy.example.com/api
|
|
42
|
+
dokploy apply -f dokploy.yaml
|
|
43
|
+
dokploy apply -f dokploy.yaml --dry-run
|
|
44
|
+
dokploy deploy frontend -f dokploy.yaml
|
|
45
|
+
dokploy status -f dokploy.yaml
|
|
46
|
+
dokploy destroy frontend -f dokploy.yaml
|
|
47
|
+
dokploy destroy -f dokploy.yaml
|
|
48
|
+
dokploy init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
State is written next to the config as `dokploy-state.json` unless `stateFile` or `--state` is provided.
|
|
52
|
+
|
|
53
|
+
## Publishing
|
|
54
|
+
|
|
55
|
+
The npm package name is `@lishugupta652/dokploy`; the command is `dokploy`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm publish --access public
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`prepublishOnly` runs the TypeScript check and build before publishing.
|
|
62
|
+
|
|
63
|
+
## Commit Version Hook
|
|
64
|
+
|
|
65
|
+
Install the repo hook once:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run hooks:install
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The pre-commit hook bumps the package patch version and stages `package.json` plus `package-lock.json`. Set `SKIP_DOKPLOY_VERSION_BUMP=1` to skip it for a commit.
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
|
|
75
|
+
- `apply` is idempotent by name within the selected environment where Dokploy exposes search endpoints.
|
|
76
|
+
- Applications are configured in Dokploy's required order: create, source provider, build type, environment, domains, deploy.
|
|
77
|
+
- Database creation requires a password because the Dokploy API requires one on create.
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export class DokployApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
endpoint;
|
|
4
|
+
responseBody;
|
|
5
|
+
constructor(message, status, endpoint, responseBody) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.endpoint = endpoint;
|
|
9
|
+
this.responseBody = responseBody;
|
|
10
|
+
this.name = "DokployApiError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class DokployClient {
|
|
14
|
+
baseUrl;
|
|
15
|
+
apiKey;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.baseUrl = options.host.replace(/\/+$/, "");
|
|
18
|
+
this.apiKey = options.apiKey;
|
|
19
|
+
}
|
|
20
|
+
async get(endpoint, query) {
|
|
21
|
+
return this.request("GET", endpoint, undefined, query);
|
|
22
|
+
}
|
|
23
|
+
async post(endpoint, body) {
|
|
24
|
+
return this.request("POST", endpoint, body);
|
|
25
|
+
}
|
|
26
|
+
async request(method, endpoint, body, query) {
|
|
27
|
+
const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
28
|
+
const url = new URL(`${this.baseUrl}${normalizedEndpoint}`);
|
|
29
|
+
if (query) {
|
|
30
|
+
for (const [key, value] of Object.entries(query)) {
|
|
31
|
+
if (value !== undefined && value !== null) {
|
|
32
|
+
url.searchParams.set(key, String(value));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const headers = {
|
|
37
|
+
"x-api-key": this.apiKey,
|
|
38
|
+
accept: "application/json",
|
|
39
|
+
};
|
|
40
|
+
const init = {
|
|
41
|
+
method,
|
|
42
|
+
headers,
|
|
43
|
+
};
|
|
44
|
+
if (method === "POST") {
|
|
45
|
+
headers["content-type"] = "application/json";
|
|
46
|
+
init.body = JSON.stringify(body ?? {});
|
|
47
|
+
}
|
|
48
|
+
const response = await fetch(url, init);
|
|
49
|
+
const text = await response.text();
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new DokployApiError(this.formatErrorMessage(response.status, normalizedEndpoint, text), response.status, normalizedEndpoint, text);
|
|
52
|
+
}
|
|
53
|
+
if (!text.trim()) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const parsed = JSON.parse(text);
|
|
57
|
+
return this.unwrapResponse(parsed);
|
|
58
|
+
}
|
|
59
|
+
unwrapResponse(value) {
|
|
60
|
+
if (!isRecord(value))
|
|
61
|
+
return value;
|
|
62
|
+
const result = value.result;
|
|
63
|
+
if (isRecord(result)) {
|
|
64
|
+
const data = result.data;
|
|
65
|
+
if (isRecord(data) && "json" in data)
|
|
66
|
+
return data.json;
|
|
67
|
+
if (data !== undefined)
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
const data = value.data;
|
|
71
|
+
if (isRecord(data) && "json" in data)
|
|
72
|
+
return data.json;
|
|
73
|
+
if ("json" in value)
|
|
74
|
+
return value.json;
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
formatErrorMessage(status, endpoint, body) {
|
|
78
|
+
const detail = extractErrorDetail(body);
|
|
79
|
+
const hint = status === 401 || status === 403
|
|
80
|
+
? " Check DOKPLOY_API_KEY and the API key permissions."
|
|
81
|
+
: "";
|
|
82
|
+
return `Dokploy API ${endpoint} failed with HTTP ${status}${detail ? `: ${detail}` : ""}.${hint}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function extractErrorDetail(body) {
|
|
86
|
+
if (!body.trim())
|
|
87
|
+
return undefined;
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(body);
|
|
90
|
+
if (isRecord(parsed)) {
|
|
91
|
+
const message = parsed.message;
|
|
92
|
+
if (typeof message === "string")
|
|
93
|
+
return message;
|
|
94
|
+
const error = parsed.error;
|
|
95
|
+
if (isRecord(error) && typeof error.message === "string")
|
|
96
|
+
return error.message;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return body.slice(0, 400);
|
|
101
|
+
}
|
|
102
|
+
return body.slice(0, 400);
|
|
103
|
+
}
|
|
104
|
+
function isRecord(value) {
|
|
105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createRuntime } from "../runtime.js";
|
|
2
|
+
import { writeState } from "../state.js";
|
|
3
|
+
import { ensureBackupDestination, ensureScheduledBackup, ensureVolumeBackup } from "../resources/backup.js";
|
|
4
|
+
import { ensureApplication } from "../resources/application.js";
|
|
5
|
+
import { ensureCompose } from "../resources/compose.js";
|
|
6
|
+
import { ensureDatabase } from "../resources/database.js";
|
|
7
|
+
import { ensureEnvironment } from "../resources/environment.js";
|
|
8
|
+
import { ensureProject } from "../resources/project.js";
|
|
9
|
+
export async function applyCommand(options) {
|
|
10
|
+
const runtime = await createRuntime(options);
|
|
11
|
+
runtime.log.step("Applying Dokploy config");
|
|
12
|
+
const project = await ensureProject(runtime.config.project, runtime);
|
|
13
|
+
const environment = await ensureEnvironment(runtime.config.environment, project, runtime);
|
|
14
|
+
for (const database of runtime.config.databases) {
|
|
15
|
+
await ensureDatabase(database, environment, runtime);
|
|
16
|
+
}
|
|
17
|
+
for (const app of runtime.config.applications) {
|
|
18
|
+
await ensureApplication(app, environment, runtime);
|
|
19
|
+
}
|
|
20
|
+
for (const compose of runtime.config.compose) {
|
|
21
|
+
await ensureCompose(compose, environment, runtime);
|
|
22
|
+
}
|
|
23
|
+
for (const destination of runtime.config.backupDestinations) {
|
|
24
|
+
await ensureBackupDestination(destination, runtime);
|
|
25
|
+
}
|
|
26
|
+
for (const backup of runtime.config.backups) {
|
|
27
|
+
await ensureScheduledBackup(backup, runtime);
|
|
28
|
+
}
|
|
29
|
+
for (const backup of runtime.config.volumeBackups) {
|
|
30
|
+
await ensureVolumeBackup(backup, runtime);
|
|
31
|
+
}
|
|
32
|
+
if (runtime.dryRun) {
|
|
33
|
+
runtime.log.warn(`Dry run only. State was not written to ${runtime.statePath}.`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await writeState(runtime.statePath, runtime.state);
|
|
37
|
+
runtime.log.success(`State written to ${runtime.statePath}`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createRuntime } from "../runtime.js";
|
|
2
|
+
import { redeployApplication } from "../resources/application.js";
|
|
3
|
+
import { redeployCompose } from "../resources/compose.js";
|
|
4
|
+
export async function deployCommand(name, options) {
|
|
5
|
+
const runtime = await createRuntime(options);
|
|
6
|
+
const app = runtime.state.applications[name];
|
|
7
|
+
if (app) {
|
|
8
|
+
await redeployApplication(app, runtime);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const compose = runtime.state.compose[name];
|
|
12
|
+
if (compose) {
|
|
13
|
+
await redeployCompose(compose, runtime);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`No application or compose named ${name} found in ${runtime.statePath}. Run apply first.`);
|
|
17
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { destroyApplication } from "../resources/application.js";
|
|
2
|
+
import { destroyBackupDestination, destroyScheduledBackup, destroyVolumeBackup, } from "../resources/backup.js";
|
|
3
|
+
import { destroyCompose } from "../resources/compose.js";
|
|
4
|
+
import { destroyDatabase } from "../resources/database.js";
|
|
5
|
+
import { destroyEnvironment } from "../resources/environment.js";
|
|
6
|
+
import { destroyProject } from "../resources/project.js";
|
|
7
|
+
import { createRuntime } from "../runtime.js";
|
|
8
|
+
import { writeState } from "../state.js";
|
|
9
|
+
export async function destroyCommand(target, options) {
|
|
10
|
+
const runtime = await createRuntime(options);
|
|
11
|
+
const deleteVolumes = Boolean(options.deleteVolumes);
|
|
12
|
+
if (target) {
|
|
13
|
+
await destroyOne(target, runtime, deleteVolumes);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
await destroyAll(runtime, deleteVolumes);
|
|
17
|
+
}
|
|
18
|
+
if (runtime.dryRun) {
|
|
19
|
+
runtime.log.warn(`Dry run only. State was not written to ${runtime.statePath}.`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await writeState(runtime.statePath, runtime.state);
|
|
23
|
+
runtime.log.success(`State written to ${runtime.statePath}`);
|
|
24
|
+
}
|
|
25
|
+
async function destroyOne(target, runtime, deleteVolumes) {
|
|
26
|
+
if (runtime.state.volumeBackups[target]) {
|
|
27
|
+
await destroyVolumeBackup(target, runtime);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (runtime.state.backups[target]) {
|
|
31
|
+
await destroyScheduledBackup(target, runtime);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (runtime.state.applications[target]) {
|
|
35
|
+
await destroyApplication(target, runtime);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (runtime.state.compose[target]) {
|
|
39
|
+
await destroyCompose(target, runtime, deleteVolumes);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (runtime.state.databases[target]) {
|
|
43
|
+
await destroyDatabase(target, runtime);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (runtime.state.backupDestinations[target]) {
|
|
47
|
+
await destroyBackupDestination(target, runtime);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`No managed resource named ${target} found in ${runtime.statePath}.`);
|
|
51
|
+
}
|
|
52
|
+
async function destroyAll(runtime, deleteVolumes) {
|
|
53
|
+
for (const name of Object.keys(runtime.state.volumeBackups).reverse()) {
|
|
54
|
+
await destroyVolumeBackup(name, runtime);
|
|
55
|
+
}
|
|
56
|
+
for (const name of Object.keys(runtime.state.backups).reverse()) {
|
|
57
|
+
await destroyScheduledBackup(name, runtime);
|
|
58
|
+
}
|
|
59
|
+
for (const name of Object.keys(runtime.state.compose).reverse()) {
|
|
60
|
+
await destroyCompose(name, runtime, deleteVolumes);
|
|
61
|
+
}
|
|
62
|
+
for (const name of Object.keys(runtime.state.applications).reverse()) {
|
|
63
|
+
await destroyApplication(name, runtime);
|
|
64
|
+
}
|
|
65
|
+
for (const name of Object.keys(runtime.state.databases).reverse()) {
|
|
66
|
+
await destroyDatabase(name, runtime);
|
|
67
|
+
}
|
|
68
|
+
for (const name of Object.keys(runtime.state.backupDestinations).reverse()) {
|
|
69
|
+
await destroyBackupDestination(name, runtime);
|
|
70
|
+
}
|
|
71
|
+
await destroyEnvironment(runtime);
|
|
72
|
+
await destroyProject(runtime);
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { access, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function initCommand(options) {
|
|
4
|
+
const output = path.resolve(options.output ?? "dokploy.yaml");
|
|
5
|
+
if (!options.force && (await exists(output))) {
|
|
6
|
+
throw new Error(`${output} already exists. Pass --force to overwrite it.`);
|
|
7
|
+
}
|
|
8
|
+
await writeFile(output, starterConfig, "utf8");
|
|
9
|
+
console.log(`Wrote ${output}`);
|
|
10
|
+
}
|
|
11
|
+
async function exists(filePath) {
|
|
12
|
+
try {
|
|
13
|
+
await access(filePath);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const starterConfig = `host: https://your-dokploy.example.com/api
|
|
21
|
+
|
|
22
|
+
project:
|
|
23
|
+
name: My App
|
|
24
|
+
description: Deployed via dokploy
|
|
25
|
+
|
|
26
|
+
environment:
|
|
27
|
+
name: production
|
|
28
|
+
|
|
29
|
+
applications:
|
|
30
|
+
- name: frontend
|
|
31
|
+
source: github
|
|
32
|
+
github:
|
|
33
|
+
id: your-github-provider-id
|
|
34
|
+
owner: your-org
|
|
35
|
+
repository: your-repo
|
|
36
|
+
branch: main
|
|
37
|
+
buildPath: /
|
|
38
|
+
build:
|
|
39
|
+
type: dockerfile
|
|
40
|
+
dockerfile: Dockerfile
|
|
41
|
+
contextPath: .
|
|
42
|
+
env:
|
|
43
|
+
NODE_ENV: production
|
|
44
|
+
domains:
|
|
45
|
+
- host: app.example.com
|
|
46
|
+
port: 80
|
|
47
|
+
path: /
|
|
48
|
+
https: true
|
|
49
|
+
certificate: letsencrypt
|
|
50
|
+
deploy: true
|
|
51
|
+
`;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { asArray } from "../resources/common.js";
|
|
3
|
+
import { createClientRuntime } from "../runtime.js";
|
|
4
|
+
export async function projectsCommand(options) {
|
|
5
|
+
const runtime = await createClientRuntime(options);
|
|
6
|
+
const projects = await runtime.client.get("project.all");
|
|
7
|
+
if (options.json) {
|
|
8
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
runtime.log.header("Dokploy Projects", runtime.host);
|
|
12
|
+
const projectList = asArray(projects);
|
|
13
|
+
if (projectList.length === 0) {
|
|
14
|
+
runtime.log.warn("No projects returned by project.all.");
|
|
15
|
+
runtime.log.section("Raw Response");
|
|
16
|
+
runtime.log.json(projects);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
runtime.log.table(projectList.map((project) => ({
|
|
20
|
+
name: stringField(project, "name") ?? "unnamed",
|
|
21
|
+
id: chalk.gray(stringField(project, "projectId") ?? stringField(project, "id") ?? "unknown-id"),
|
|
22
|
+
environments: countNested(project, ["environments", "environment"]),
|
|
23
|
+
applications: countNested(project, ["applications", "application"]),
|
|
24
|
+
compose: countNested(project, ["compose", "composes"]),
|
|
25
|
+
databases: countNested(project, ["databases", "postgres", "mysql", "mariadb", "mongo", "redis"]),
|
|
26
|
+
description: stringField(project, "description") ?? "",
|
|
27
|
+
})), [
|
|
28
|
+
{ key: "name", label: "Project" },
|
|
29
|
+
{ key: "id", label: "ID" },
|
|
30
|
+
{ key: "environments", label: "Env" },
|
|
31
|
+
{ key: "applications", label: "Apps" },
|
|
32
|
+
{ key: "compose", label: "Compose" },
|
|
33
|
+
{ key: "databases", label: "DB" },
|
|
34
|
+
{ key: "description", label: "Description" },
|
|
35
|
+
]);
|
|
36
|
+
if (!options.summary) {
|
|
37
|
+
runtime.log.section("Full Response");
|
|
38
|
+
runtime.log.json(projects);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function stringField(project, key) {
|
|
42
|
+
const value = project[key];
|
|
43
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
function countNested(project, keys) {
|
|
46
|
+
let total = 0;
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
const value = project[key];
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
total += value.length;
|
|
51
|
+
}
|
|
52
|
+
else if (value && typeof value === "object") {
|
|
53
|
+
total += 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return total === 0 ? chalk.gray("-") : chalk.yellow(String(total));
|
|
57
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { DokployApiError } from "../client.js";
|
|
3
|
+
import { getOneById } from "../resources/common.js";
|
|
4
|
+
import { createRuntime } from "../runtime.js";
|
|
5
|
+
export async function statusCommand(options) {
|
|
6
|
+
const runtime = await createRuntime(options);
|
|
7
|
+
const rows = [];
|
|
8
|
+
runtime.log.header("Dokploy Status", runtime.statePath);
|
|
9
|
+
if (runtime.state.project) {
|
|
10
|
+
rows.push(await getStatus(runtime.state.project, "project", "project.one", "projectId", runtime));
|
|
11
|
+
}
|
|
12
|
+
if (runtime.state.environment) {
|
|
13
|
+
rows.push(await getStatus(runtime.state.environment, "environment", "environment.one", "environmentId", runtime));
|
|
14
|
+
}
|
|
15
|
+
for (const ref of Object.values(runtime.state.databases)) {
|
|
16
|
+
const idField = `${ref.type}Id`;
|
|
17
|
+
rows.push(await getStatus(ref, `${ref.type} database`, `${ref.type}.one`, idField, runtime));
|
|
18
|
+
}
|
|
19
|
+
for (const ref of Object.values(runtime.state.applications)) {
|
|
20
|
+
rows.push(await getStatus(ref, "application", "application.one", "applicationId", runtime));
|
|
21
|
+
}
|
|
22
|
+
for (const ref of Object.values(runtime.state.compose)) {
|
|
23
|
+
rows.push(await getStatus(ref, "compose", "compose.one", "composeId", runtime));
|
|
24
|
+
}
|
|
25
|
+
for (const ref of Object.values(runtime.state.backupDestinations)) {
|
|
26
|
+
rows.push(await getStatus(ref, "backup destination", "destination.one", "destinationId", runtime));
|
|
27
|
+
}
|
|
28
|
+
for (const ref of Object.values(runtime.state.backups)) {
|
|
29
|
+
rows.push(await getStatus(ref, "backup", "backup.one", "backupId", runtime));
|
|
30
|
+
}
|
|
31
|
+
for (const ref of Object.values(runtime.state.volumeBackups)) {
|
|
32
|
+
rows.push(await getStatus(ref, "volume backup", "volumeBackups.one", "volumeBackupId", runtime));
|
|
33
|
+
}
|
|
34
|
+
runtime.log.table(rows, [
|
|
35
|
+
{ key: "type", label: "Type" },
|
|
36
|
+
{ key: "name", label: "Name" },
|
|
37
|
+
{ key: "status", label: "Status" },
|
|
38
|
+
{ key: "id", label: "ID" },
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
async function getStatus(ref, label, endpoint, idField, runtime) {
|
|
42
|
+
try {
|
|
43
|
+
const remote = await getOneById(runtime.client, endpoint, idField, ref.id);
|
|
44
|
+
if (!remote) {
|
|
45
|
+
return statusRow(ref, label, "missing");
|
|
46
|
+
}
|
|
47
|
+
const status = pickStatus(remote);
|
|
48
|
+
return statusRow(ref, label, status ?? "exists");
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error instanceof DokployApiError && error.status === 404) {
|
|
52
|
+
return statusRow(ref, label, "missing");
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function pickStatus(remote) {
|
|
58
|
+
for (const key of ["applicationStatus", "composeStatus", "status"]) {
|
|
59
|
+
const value = remote[key];
|
|
60
|
+
if (typeof value === "string" && value.length > 0)
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function statusRow(ref, type, status) {
|
|
66
|
+
return {
|
|
67
|
+
type,
|
|
68
|
+
name: ref.name,
|
|
69
|
+
status: colorStatus(status),
|
|
70
|
+
id: chalk.gray(ref.id),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function colorStatus(status) {
|
|
74
|
+
if (status === "exists" || status === "done" || status === "running") {
|
|
75
|
+
return chalk.green(status);
|
|
76
|
+
}
|
|
77
|
+
if (status === "missing" || status === "error") {
|
|
78
|
+
return chalk.red(status);
|
|
79
|
+
}
|
|
80
|
+
if (status === "idle") {
|
|
81
|
+
return chalk.cyan(status);
|
|
82
|
+
}
|
|
83
|
+
return status;
|
|
84
|
+
}
|