dockup-cli 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.2.0");
36
+ program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.3.0");
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
@@ -251,6 +251,35 @@ class ApiClient {
251
251
  async setDatabaseResources(projectSlug, dbSlug, memoryLimitMB, cpuLimit) {
252
252
  return this.post(`/projects/${projectSlug}/databases/${dbSlug}/resources`, { memoryLimitMB, cpuLimit });
253
253
  }
254
+ // ==================== REMOTE SERVERS (SSH deploy hooks) ====================
255
+ async listServers() {
256
+ const res = await this.get("/remote/servers");
257
+ return res.servers || [];
258
+ }
259
+ async createServer(body) {
260
+ const res = await this.post("/remote/servers", body);
261
+ return res.server;
262
+ }
263
+ async deleteServer(id) {
264
+ return this.delete(`/remote/servers/${id}`);
265
+ }
266
+ async testServer(id) {
267
+ return this.post(`/remote/servers/${id}/test`);
268
+ }
269
+ async listTasks(serverId) {
270
+ const res = await this.get(`/remote/servers/${serverId}/tasks`);
271
+ return res.tasks || [];
272
+ }
273
+ async createTask(serverId, name, steps) {
274
+ const res = await this.post(`/remote/servers/${serverId}/tasks`, { name, steps });
275
+ return res.task;
276
+ }
277
+ async deleteTask(taskId) {
278
+ return this.delete(`/remote/tasks/${taskId}`);
279
+ }
280
+ async runTask(taskId) {
281
+ return this.post(`/remote/tasks/${taskId}/run`);
282
+ }
254
283
  }
255
284
  const api = new ApiClient();
256
285
  // 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dockup-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Dockup CLI - deploy, env, logs, registry & more from your terminal (agent-friendly --json)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {