dockup-cli 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/remote.js +203 -0
- package/dist/index.js +11 -1
- package/dist/lib/api.js +33 -1
- package/dockupcli.md +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var remote_exports = {};
|
|
30
|
+
__export(remote_exports, {
|
|
31
|
+
serverAdd: () => serverAdd,
|
|
32
|
+
serverList: () => serverList,
|
|
33
|
+
serverRemove: () => serverRemove,
|
|
34
|
+
serverTest: () => serverTest,
|
|
35
|
+
taskAdd: () => taskAdd,
|
|
36
|
+
taskList: () => taskList,
|
|
37
|
+
taskRemove: () => taskRemove,
|
|
38
|
+
taskRun: () => taskRun
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(remote_exports);
|
|
41
|
+
var import_chalk = __toESM(require("chalk"));
|
|
42
|
+
var fs = __toESM(require("fs"));
|
|
43
|
+
var import_config = require("../lib/config");
|
|
44
|
+
var import_flags = require("../lib/flags");
|
|
45
|
+
var import_output = require("../lib/output");
|
|
46
|
+
var import_api = require("../lib/api");
|
|
47
|
+
function applyJson(opts) {
|
|
48
|
+
(0, import_flags.setJson)(!!opts?.json);
|
|
49
|
+
}
|
|
50
|
+
function requireAuth() {
|
|
51
|
+
if (!(0, import_config.isLoggedIn)()) (0, import_output.fail)("Not logged in. Run `dockup login` first.", { code: "not_logged_in" });
|
|
52
|
+
}
|
|
53
|
+
async function serverList(opts) {
|
|
54
|
+
applyJson(opts);
|
|
55
|
+
requireAuth();
|
|
56
|
+
try {
|
|
57
|
+
const servers = await import_api.api.listServers();
|
|
58
|
+
(0, import_output.emit)({ count: servers.length, servers }, () => {
|
|
59
|
+
console.log(import_chalk.default.cyan(`
|
|
60
|
+
${servers.length} server(s)
|
|
61
|
+
`));
|
|
62
|
+
for (const s of servers) {
|
|
63
|
+
console.log(` ${import_chalk.default.bold(s.name.padEnd(20))} ${import_chalk.default.dim(s.username + "@" + s.host + ":" + s.port)} ${import_chalk.default.dim(s.authType)} ${import_chalk.default.dim(s.taskCount + " task(s)")}`);
|
|
64
|
+
console.log(` ${import_chalk.default.dim(" id: " + s.id)}`);
|
|
65
|
+
}
|
|
66
|
+
console.log();
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
(0, import_output.fail)(e.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function serverAdd(opts) {
|
|
73
|
+
applyJson(opts);
|
|
74
|
+
requireAuth();
|
|
75
|
+
if (!opts?.name || !opts?.host) return (0, import_output.fail)("--name and --host are required.", { code: "bad_args" });
|
|
76
|
+
let authType = "password";
|
|
77
|
+
let secret = opts?.password;
|
|
78
|
+
if (opts?.keyFile) {
|
|
79
|
+
authType = "key";
|
|
80
|
+
try {
|
|
81
|
+
secret = fs.readFileSync(opts.keyFile, "utf-8");
|
|
82
|
+
} catch {
|
|
83
|
+
return (0, import_output.fail)(`Cannot read key file: ${opts.keyFile}`, { code: "file_not_found" });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!secret) return (0, import_output.fail)("Provide --password or --key-file.", { code: "bad_args" });
|
|
87
|
+
try {
|
|
88
|
+
const server = await import_api.api.createServer({
|
|
89
|
+
name: opts.name,
|
|
90
|
+
host: opts.host,
|
|
91
|
+
port: opts.port ? parseInt(opts.port, 10) : 22,
|
|
92
|
+
username: opts.user || "root",
|
|
93
|
+
authType,
|
|
94
|
+
secret,
|
|
95
|
+
passphrase: opts.passphrase
|
|
96
|
+
});
|
|
97
|
+
(0, import_output.ok)({ id: server.id, name: server.name, host: server.host }, () => {
|
|
98
|
+
console.log(import_chalk.default.green(`\u2713 server added`) + import_chalk.default.dim(` ${server.id}`));
|
|
99
|
+
});
|
|
100
|
+
} catch (e) {
|
|
101
|
+
(0, import_output.fail)(e.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function serverRemove(id, opts) {
|
|
105
|
+
applyJson(opts);
|
|
106
|
+
requireAuth();
|
|
107
|
+
try {
|
|
108
|
+
await import_api.api.deleteServer(id);
|
|
109
|
+
(0, import_output.ok)({ id, deleted: true }, () => console.log(import_chalk.default.green("\u2713 server removed")));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
(0, import_output.fail)(e.message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function serverTest(id, opts) {
|
|
115
|
+
applyJson(opts);
|
|
116
|
+
requireAuth();
|
|
117
|
+
try {
|
|
118
|
+
const r = await import_api.api.testServer(id);
|
|
119
|
+
(0, import_output.emit)(r, () => {
|
|
120
|
+
if (r.ok) console.log(import_chalk.default.green("\u2713 connection ok") + import_chalk.default.dim(`
|
|
121
|
+
${r.message}`));
|
|
122
|
+
else console.log(import_chalk.default.red("\u2717 connection failed") + import_chalk.default.dim(`
|
|
123
|
+
${r.message}`));
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
(0, import_output.fail)(e.message);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function taskList(serverId, opts) {
|
|
130
|
+
applyJson(opts);
|
|
131
|
+
requireAuth();
|
|
132
|
+
try {
|
|
133
|
+
const tasks = await import_api.api.listTasks(serverId);
|
|
134
|
+
(0, import_output.emit)({ serverId, count: tasks.length, tasks }, () => {
|
|
135
|
+
console.log(import_chalk.default.cyan(`
|
|
136
|
+
${tasks.length} task(s)
|
|
137
|
+
`));
|
|
138
|
+
for (const t of tasks) {
|
|
139
|
+
console.log(` ${import_chalk.default.bold(t.name)} ${import_chalk.default.dim("id: " + t.id)}`);
|
|
140
|
+
for (const s of t.steps || []) console.log(` ${import_chalk.default.dim("-")} ${s.cwd ? import_chalk.default.dim("(" + s.cwd + ") ") : ""}${s.command}`);
|
|
141
|
+
}
|
|
142
|
+
console.log();
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
(0, import_output.fail)(e.message);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function taskAdd(serverId, opts) {
|
|
149
|
+
applyJson(opts);
|
|
150
|
+
requireAuth();
|
|
151
|
+
if (!opts?.name) return (0, import_output.fail)("--name is required.", { code: "bad_args" });
|
|
152
|
+
const raw = Array.isArray(opts?.step) ? opts.step : opts?.step ? [opts.step] : [];
|
|
153
|
+
if (!raw.length) return (0, import_output.fail)('Provide at least one --step "command" (use "cwd::command" to set a directory).', { code: "bad_args" });
|
|
154
|
+
const steps = raw.map((r) => {
|
|
155
|
+
const idx = r.indexOf("::");
|
|
156
|
+
return idx === -1 ? { command: r } : { cwd: r.slice(0, idx), command: r.slice(idx + 2) };
|
|
157
|
+
});
|
|
158
|
+
try {
|
|
159
|
+
const task = await import_api.api.createTask(serverId, opts.name, steps);
|
|
160
|
+
(0, import_output.ok)({ id: task.id, name: task.name, steps: task.steps }, () => {
|
|
161
|
+
console.log(import_chalk.default.green(`\u2713 task created`) + import_chalk.default.dim(` ${task.id}`));
|
|
162
|
+
});
|
|
163
|
+
} catch (e) {
|
|
164
|
+
(0, import_output.fail)(e.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function taskRemove(taskId, opts) {
|
|
168
|
+
applyJson(opts);
|
|
169
|
+
requireAuth();
|
|
170
|
+
try {
|
|
171
|
+
await import_api.api.deleteTask(taskId);
|
|
172
|
+
(0, import_output.ok)({ taskId, deleted: true }, () => console.log(import_chalk.default.green("\u2713 task removed")));
|
|
173
|
+
} catch (e) {
|
|
174
|
+
(0, import_output.fail)(e.message);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function taskRun(taskId, opts) {
|
|
178
|
+
applyJson(opts);
|
|
179
|
+
requireAuth();
|
|
180
|
+
try {
|
|
181
|
+
const r = await import_api.api.runTask(taskId);
|
|
182
|
+
(0, import_output.emit)({ runId: r.runId, ok: r.ok, status: r.status, exitCode: r.exitCode, steps: r.steps, output: r.output }, () => {
|
|
183
|
+
console.log(r.output);
|
|
184
|
+
console.log(r.ok ? import_chalk.default.green(`
|
|
185
|
+
\u2713 ${r.status}`) : import_chalk.default.red(`
|
|
186
|
+
\u2717 ${r.status} (exit ${r.exitCode})`));
|
|
187
|
+
});
|
|
188
|
+
if (!r.ok) process.exitCode = 1;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
(0, import_output.fail)(e.message);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
194
|
+
0 && (module.exports = {
|
|
195
|
+
serverAdd,
|
|
196
|
+
serverList,
|
|
197
|
+
serverRemove,
|
|
198
|
+
serverTest,
|
|
199
|
+
taskAdd,
|
|
200
|
+
taskList,
|
|
201
|
+
taskRemove,
|
|
202
|
+
taskRun
|
|
203
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -31,8 +31,9 @@ var import_open = require("./commands/open");
|
|
|
31
31
|
var import_agent = require("./commands/agent");
|
|
32
32
|
var import_workspace = require("./commands/workspace");
|
|
33
33
|
var import_database = require("./commands/database");
|
|
34
|
+
var import_remote = require("./commands/remote");
|
|
34
35
|
const program = new import_commander.Command();
|
|
35
|
-
program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.
|
|
36
|
+
program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.3.1");
|
|
36
37
|
program.command("login").description("Authenticate with Dockup").option("-t, --token <token>", "Use API token directly").action(import_auth.login);
|
|
37
38
|
program.command("logout").description("Log out from Dockup").action(import_auth.logout);
|
|
38
39
|
program.command("whoami").description("Show current logged in user").action(import_auth.whoami);
|
|
@@ -70,6 +71,15 @@ dbCmd.command("rename [id]").description("Rename a database").action(import_data
|
|
|
70
71
|
dbCmd.command("delete [id]").alias("rm").description("Delete a database").action(import_database.databaseDelete);
|
|
71
72
|
dbCmd.command("credentials [id]").alias("creds").description("Get database credentials").action(import_database.databaseCredentials);
|
|
72
73
|
dbCmd.command("info [id]").description("Get database details").action(import_database.databaseInfo);
|
|
74
|
+
const serverCmd = program.command("server").alias("srv").description("Remote servers & deploy tasks over SSH");
|
|
75
|
+
serverCmd.command("list").alias("ls").description("List remote servers").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverList);
|
|
76
|
+
serverCmd.command("add").description("Add a remote server (use --password or --key-file)").requiredOption("--name <name>", "Friendly name").requiredOption("--host <host>", "IP or hostname").option("--port <port>", "SSH port", "22").option("--user <user>", "SSH username", "root").option("--password <password>", "SSH password").option("--key-file <path>", "Path to a private key file (PEM)").option("--passphrase <passphrase>", "Passphrase for the key, if any").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverAdd);
|
|
77
|
+
serverCmd.command("remove <serverId>").alias("rm").description("Remove a remote server").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverRemove);
|
|
78
|
+
serverCmd.command("test <serverId>").description("Test the SSH connection to a server").option("-j, --json", "Machine-readable JSON output").action(import_remote.serverTest);
|
|
79
|
+
serverCmd.command("tasks <serverId>").description("List deploy tasks on a server").option("-j, --json", "Machine-readable JSON output").action(import_remote.taskList);
|
|
80
|
+
serverCmd.command("task-add <serverId>").description('Create a deploy task. Repeat --step; use "cwd::command" to set a directory').requiredOption("--name <name>", "Task name").option("--step <command>", 'A step. Repeat for multiple. "cwd::command" sets the working dir', (v, acc) => acc.concat([v]), []).option("-j, --json", "Machine-readable JSON output").action(import_remote.taskAdd);
|
|
81
|
+
serverCmd.command("task-remove <taskId>").description("Delete a deploy task").option("-j, --json", "Machine-readable JSON output").action(import_remote.taskRemove);
|
|
82
|
+
serverCmd.command("run <taskId>").description("Run a deploy task over SSH (stops at the first failing step)").option("-j, --json", "Machine-readable JSON output").action(import_remote.taskRun);
|
|
73
83
|
program.parse();
|
|
74
84
|
if (!process.argv.slice(2).length) {
|
|
75
85
|
console.log(import_chalk.default.cyan(`
|
package/dist/lib/api.js
CHANGED
|
@@ -61,7 +61,10 @@ class ApiClient {
|
|
|
61
61
|
const response = await (0, import_node_fetch.default)(url, {
|
|
62
62
|
method: "POST",
|
|
63
63
|
headers: this.getHeaders(),
|
|
64
|
-
|
|
64
|
+
// getHeaders() always sets Content-Type: application/json, so the body
|
|
65
|
+
// must be valid JSON even for no-body POSTs (test/run) — else Fastify
|
|
66
|
+
// rejects the empty body with 400.
|
|
67
|
+
body: JSON.stringify(body ?? {})
|
|
65
68
|
});
|
|
66
69
|
if (!response.ok) {
|
|
67
70
|
const error = await response.json().catch(() => ({}));
|
|
@@ -251,6 +254,35 @@ class ApiClient {
|
|
|
251
254
|
async setDatabaseResources(projectSlug, dbSlug, memoryLimitMB, cpuLimit) {
|
|
252
255
|
return this.post(`/projects/${projectSlug}/databases/${dbSlug}/resources`, { memoryLimitMB, cpuLimit });
|
|
253
256
|
}
|
|
257
|
+
// ==================== REMOTE SERVERS (SSH deploy hooks) ====================
|
|
258
|
+
async listServers() {
|
|
259
|
+
const res = await this.get("/remote/servers");
|
|
260
|
+
return res.servers || [];
|
|
261
|
+
}
|
|
262
|
+
async createServer(body) {
|
|
263
|
+
const res = await this.post("/remote/servers", body);
|
|
264
|
+
return res.server;
|
|
265
|
+
}
|
|
266
|
+
async deleteServer(id) {
|
|
267
|
+
return this.delete(`/remote/servers/${id}`);
|
|
268
|
+
}
|
|
269
|
+
async testServer(id) {
|
|
270
|
+
return this.post(`/remote/servers/${id}/test`);
|
|
271
|
+
}
|
|
272
|
+
async listTasks(serverId) {
|
|
273
|
+
const res = await this.get(`/remote/servers/${serverId}/tasks`);
|
|
274
|
+
return res.tasks || [];
|
|
275
|
+
}
|
|
276
|
+
async createTask(serverId, name, steps) {
|
|
277
|
+
const res = await this.post(`/remote/servers/${serverId}/tasks`, { name, steps });
|
|
278
|
+
return res.task;
|
|
279
|
+
}
|
|
280
|
+
async deleteTask(taskId) {
|
|
281
|
+
return this.delete(`/remote/tasks/${taskId}`);
|
|
282
|
+
}
|
|
283
|
+
async runTask(taskId) {
|
|
284
|
+
return this.post(`/remote/tasks/${taskId}/run`);
|
|
285
|
+
}
|
|
254
286
|
}
|
|
255
287
|
const api = new ApiClient();
|
|
256
288
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dockupcli.md
CHANGED
|
@@ -161,6 +161,35 @@ dockup resources set my-project/my-db --db --cpu 1 --memory 2048 --json
|
|
|
161
161
|
it applies on the next deploy (service) / start (database).
|
|
162
162
|
- `dockup info --json` also surfaces a service's current `memoryLimit`/`cpuLimit`.
|
|
163
163
|
|
|
164
|
+
### Remote servers (SSH deploy hooks)
|
|
165
|
+
|
|
166
|
+
Run deploy steps on **your own** servers over SSH — `git pull`, `npm run build`,
|
|
167
|
+
`pm2 restart`, `plesk ext git --deploy`. Credentials are stored encrypted.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Register a server (password or --key-file)
|
|
171
|
+
dockup server add --name "plesk-prod" --host 94.130.67.75 --user root --password "$PW" --json
|
|
172
|
+
dockup server add --name "prod" --host 1.2.3.4 --key-file ~/.ssh/id_ed25519 --json
|
|
173
|
+
dockup server list --json
|
|
174
|
+
dockup server test <serverId> --json # opens an SSH connection and runs whoami
|
|
175
|
+
|
|
176
|
+
# Create a deploy task — each --step is a command; "cwd::command" sets the directory
|
|
177
|
+
dockup server task-add <serverId> --name "Deploy frontend" \
|
|
178
|
+
--step "plesk ext git --deploy -domain new.dockup.ai -name deploy-new-frontend.git" \
|
|
179
|
+
--step "/var/www/vhosts/dockup.ai/new.dockup.ai::npm install && npm run build && pm2 restart doknew" \
|
|
180
|
+
--json
|
|
181
|
+
dockup server tasks <serverId> --json
|
|
182
|
+
|
|
183
|
+
# Run it (executes steps in order, stops at the first failing step)
|
|
184
|
+
dockup server run <taskId> --json
|
|
185
|
+
# → {"runId","ok":true,"status":"success","exitCode":0,"steps":[...],"output":"...full log..."}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- Steps run via `bash -lc` so `npm`/`pm2`/`plesk` are on PATH; `cwd` fixes the
|
|
189
|
+
"ran in /root" problem — set it per step.
|
|
190
|
+
- `run` stops at the first non-zero exit and returns the combined output.
|
|
191
|
+
- Manage the same servers/tasks visually in the dashboard → **Servers**.
|
|
192
|
+
|
|
164
193
|
---
|
|
165
194
|
|
|
166
195
|
## Agent recipes
|