dockup-cli 1.3.2 → 1.4.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,149 @@
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 = (target2, all) => {
9
+ for (var name in all)
10
+ __defProp(target2, 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, target2) => (target2 = 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(target2, "default", { value: mod, enumerable: true }) : target2,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var config_exports = {};
30
+ __export(config_exports, {
31
+ plan: () => plan,
32
+ up: () => up
33
+ });
34
+ module.exports = __toCommonJS(config_exports);
35
+ var import_chalk = __toESM(require("chalk"));
36
+ var fs = __toESM(require("fs"));
37
+ var path = __toESM(require("path"));
38
+ var import_js_yaml = __toESM(require("js-yaml"));
39
+ var import_config = require("../lib/config");
40
+ var import_flags = require("../lib/flags");
41
+ var import_output = require("../lib/output");
42
+ var import_target = require("../lib/target");
43
+ var import_api = require("../lib/api");
44
+ function applyJson(opts) {
45
+ (0, import_flags.setJson)(!!opts?.json);
46
+ }
47
+ function requireAuth() {
48
+ if (!(0, import_config.isLoggedIn)()) (0, import_output.fail)("Not logged in. Run `dockup login` first.", { code: "not_logged_in" });
49
+ }
50
+ function target(arg) {
51
+ try {
52
+ return (0, import_target.resolveTarget)(arg);
53
+ } catch (e) {
54
+ return (0, import_output.fail)(e.message, { code: "no_target" });
55
+ }
56
+ }
57
+ const SECRETY = /(secret|token|password|passwd|api[_-]?key|private[_-]?key|_pat\b|credential)/i;
58
+ function loadManifest(file) {
59
+ const p = path.resolve(process.cwd(), file || "dockup.yaml");
60
+ if (!fs.existsSync(p)) {
61
+ return (0, import_output.fail)(`No manifest at ${p}. Create a dockup.yaml or pass -f <path>.`, { code: "no_manifest" });
62
+ }
63
+ let parsed;
64
+ try {
65
+ parsed = import_js_yaml.default.load(fs.readFileSync(p, "utf8"));
66
+ } catch (e) {
67
+ return (0, import_output.fail)(`Invalid YAML: ${e.message}`, { code: "bad_yaml" });
68
+ }
69
+ const manifest = parsed?.service ? parsed : { service: parsed };
70
+ const env = manifest.service?.env || {};
71
+ const suspicious = Object.keys(env).filter((k) => SECRETY.test(k));
72
+ if (suspicious.length && !(0, import_flags.isJson)()) {
73
+ console.log(import_chalk.default.yellow(`\u26A0 Possible secret(s) inlined in dockup.yaml: ${suspicious.join(", ")}`));
74
+ console.log(import_chalk.default.dim(" Secrets should be set in the dashboard; yaml env are stored as non-secret.\n"));
75
+ }
76
+ return manifest;
77
+ }
78
+ const actionColor = (a) => a === "create" ? import_chalk.default.green : a === "update" ? import_chalk.default.yellow : a === "delete" ? import_chalk.default.red : a === "skip" ? import_chalk.default.gray : import_chalk.default.dim;
79
+ const sign = (a) => a === "create" ? "+" : a === "update" ? "~" : a === "delete" ? "-" : a === "skip" ? "!" : "=";
80
+ function printChanges(changes) {
81
+ if (!changes.length) {
82
+ console.log(import_chalk.default.dim(" (no changes)"));
83
+ return;
84
+ }
85
+ for (const c of changes) {
86
+ const col = actionColor(c.action);
87
+ const from = c.from === void 0 ? "" : typeof c.from === "object" ? JSON.stringify(c.from) : String(c.from);
88
+ const to = c.to === void 0 ? "" : typeof c.to === "object" ? JSON.stringify(c.to) : String(c.to);
89
+ const arrow = c.action === "update" ? `${import_chalk.default.dim(from)} \u2192 ${to}` : c.action === "delete" ? import_chalk.default.dim(from) : to;
90
+ const reason = c.reason ? import_chalk.default.dim(` (${c.reason})`) : "";
91
+ console.log(` ${col(sign(c.action))} ${c.field} ${arrow}${reason}`);
92
+ }
93
+ }
94
+ async function plan(arg, opts) {
95
+ applyJson(opts);
96
+ requireAuth();
97
+ const t = target(arg);
98
+ const manifest = loadManifest(opts?.file);
99
+ try {
100
+ const res = await import_api.api.planConfig(t.projectSlug, t.serviceSlug, manifest);
101
+ const effective = res.changes.filter((c) => c.action !== "noop");
102
+ (0, import_output.emit)({ target: res.target, changes: res.changes }, () => {
103
+ console.log(import_chalk.default.cyan(`
104
+ Plan \u2014 ${res.target}
105
+ `));
106
+ printChanges(effective.length ? effective : []);
107
+ if (!effective.length) console.log(import_chalk.default.green(" \u2713 up to date"));
108
+ console.log();
109
+ });
110
+ } catch (e) {
111
+ (0, import_output.fail)(e.message);
112
+ }
113
+ }
114
+ async function up(arg, opts) {
115
+ applyJson(opts);
116
+ requireAuth();
117
+ const t = target(arg);
118
+ const manifest = loadManifest(opts?.file);
119
+ try {
120
+ const res = await import_api.api.applyConfig(t.projectSlug, t.serviceSlug, {
121
+ manifest,
122
+ prune: !!opts?.prune,
123
+ deploy: !!opts?.deploy,
124
+ registryToken: opts?.registryToken
125
+ });
126
+ (0, import_output.ok)({ target: res.target, applied: res.applied, skipped: res.skipped, deploymentId: res.deploymentId || null, nextStep: res.nextStep }, () => {
127
+ console.log(import_chalk.default.cyan(`
128
+ Applied \u2014 ${res.target}
129
+ `));
130
+ printChanges(res.applied || []);
131
+ if ((res.skipped || []).length) {
132
+ console.log(import_chalk.default.dim("\n Skipped:"));
133
+ printChanges(res.skipped);
134
+ }
135
+ if (res.deploymentId) console.log(import_chalk.default.green(`
136
+ \u2713 deploy triggered (${res.deploymentId})`));
137
+ else if (res.nextStep) console.log(import_chalk.default.dim(`
138
+ ${res.nextStep}`));
139
+ console.log();
140
+ });
141
+ } catch (e) {
142
+ (0, import_output.fail)(e.message);
143
+ }
144
+ }
145
+ // Annotate the CommonJS export names for ESM import in node:
146
+ 0 && (module.exports = {
147
+ plan,
148
+ up
149
+ });
package/dist/index.js CHANGED
@@ -32,8 +32,9 @@ var import_agent = require("./commands/agent");
32
32
  var import_workspace = require("./commands/workspace");
33
33
  var import_database = require("./commands/database");
34
34
  var import_remote = require("./commands/remote");
35
+ var import_config = require("./commands/config");
35
36
  const program = new import_commander.Command();
36
- program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.3.2");
37
+ program.name("dockup").description("Dockup CLI - Deploy from your terminal").version("1.4.0");
37
38
  program.command("login").description("Authenticate with Dockup").option("-t, --token <token>", "Use API token directly").action(import_auth.login);
38
39
  program.command("logout").description("Log out from Dockup").action(import_auth.logout);
39
40
  program.command("whoami").description("Show current logged in user").action(import_auth.whoami);
@@ -55,6 +56,8 @@ registryCmd.command("set [project/service]").description("Set registry credentia
55
56
  registryCmd.command("clear [project/service]").description("Remove registry credentials").option("-j, --json", "Machine-readable JSON output").action(import_agent.registryClear);
56
57
  const resourcesCmd = program.command("resources [project/target]").alias("res").description("View reserved CPU/RAM (use `resources set` to change; --db for databases)").option("--db", "Target a database instead of a service").option("-j, --json", "Machine-readable JSON output").action(import_agent.resourcesView);
57
58
  resourcesCmd.command("set [project/target]").description("Set reserved CPU/RAM \u2014 PRO (max 24 vCPU / 24576 MB)").option("--cpu <vcpu>", "vCPU to reserve, e.g. 2").option("--memory <mb>", "RAM in MB to reserve, e.g. 4096").option("--db", "Target a database instead of a service").option("-j, --json", "Machine-readable JSON output").action(import_agent.resourcesSet);
59
+ program.command("plan [project/service]").description("Diff dockup.yaml against the live service (read-only)").option("-f, --file <path>", "Path to the manifest (default ./dockup.yaml)").option("-j, --json", "Machine-readable JSON output").action(import_config.plan);
60
+ program.command("up [project/service]").description("Apply dockup.yaml to the service (config as code)").option("-f, --file <path>", "Path to the manifest (default ./dockup.yaml)").option("--prune", "Delete env/domains not in the manifest (never secrets/services)").option("--deploy", "Trigger a deploy after applying").option("--registry-token <token>", "Registry token for registry.url/username in the manifest").option("-y, --yes", "Skip confirmation").option("-j, --json", "Machine-readable JSON output").action(import_config.up);
58
61
  program.command("deploy [project/service]").description("Trigger a deployment (no git push; use `push` for git+deploy)").option("-b, --branch <branch>", "Branch to deploy").option("-j, --json", "Machine-readable JSON output").action(import_agent.deployTrigger);
59
62
  program.command("open").description("Open service in browser").option("-d, --dashboard", "Open dashboard instead of live URL").action(import_open.open);
60
63
  const workspaceCmd = program.command("workspace").alias("ws").description("Manage workspaces");
package/dist/lib/api.js CHANGED
@@ -285,6 +285,16 @@ class ApiClient {
285
285
  async runTask(taskId) {
286
286
  return this.post(`/remote/tasks/${taskId}/run`);
287
287
  }
288
+ // ==================== CONFIG-AS-CODE (dockup.yaml) ====================
289
+ async planConfig(projectSlug, serviceSlug, manifest) {
290
+ return this.post(`/projects/${projectSlug}/services/${serviceSlug}/plan`, { manifest });
291
+ }
292
+ async applyConfig(projectSlug, serviceSlug, body) {
293
+ return this.post(`/projects/${projectSlug}/services/${serviceSlug}/apply`, body);
294
+ }
295
+ async getConfigStatus(projectSlug, serviceSlug) {
296
+ return this.get(`/projects/${projectSlug}/services/${serviceSlug}/config-status`);
297
+ }
288
298
  }
289
299
  const api = new ApiClient();
290
300
  // Annotate the CommonJS export names for ESM import in node:
package/dockupcli.md CHANGED
@@ -190,6 +190,49 @@ dockup server run <taskId> --json
190
190
  - `run` stops at the first non-zero exit and returns the combined output.
191
191
  - Manage the same servers/tasks visually in the dashboard → **Servers**.
192
192
 
193
+ ### Config as code — `dockup.yaml` (GitOps)
194
+
195
+ Declare a service's config in a `dockup.yaml` at the repo root. It becomes the
196
+ source of truth — apply it from the CLI, or it auto-applies on every deploy.
197
+
198
+ ```yaml
199
+ # dockup.yaml
200
+ service:
201
+ branch: main
202
+ port: 3000
203
+ dockerfile: Dockerfile
204
+ build: npm run build
205
+ start: npm start
206
+ resources: # PRO; clamped to 0.5–24 vCPU / 512–24576 MB
207
+ cpu: 2
208
+ memory: 4096
209
+ env: # plain values only — NEVER put secrets here
210
+ NODE_ENV: production
211
+ API_URL: https://api.myapp.com
212
+ registry: # token passed via --registry-token, not in yaml
213
+ url: ghcr.io
214
+ username: my-org
215
+ domains:
216
+ - api.myapp.com
217
+ - { domain: admin.myapp.com, port: 4000 }
218
+ ```
219
+
220
+ ```bash
221
+ dockup plan my-project/my-api # diff yaml vs live (read-only)
222
+ dockup up my-project/my-api # apply
223
+ dockup up --deploy # apply + trigger a deploy
224
+ dockup up --prune # also delete env/domains absent from yaml
225
+ dockup up --registry-token "$GHCR_PAT" # supply the registry token
226
+ dockup plan -f dockup.staging.yaml ... # use a different file
227
+ # plan/up output (--json): { changes:[{aspect,field,from,to,action}], applied, skipped, deploymentId }
228
+ ```
229
+
230
+ Safety guarantees:
231
+ - **Never deletes services, databases or volumes** — only settings/resources/env/domains.
232
+ - **Additive by default**: env/domains not in the yaml are left alone; `--prune` deletes them (never secrets).
233
+ - **Secrets never live in yaml**: existing secret env vars are never overwritten or pruned.
234
+ - **Auto-apply on deploy** is additive only (never prunes) and never blocks a deploy if the yaml is invalid.
235
+
193
236
  ---
194
237
 
195
238
  ## Agent recipes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dockup-cli",
3
- "version": "1.3.2",
3
+ "version": "1.4.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": {
@@ -26,12 +26,14 @@
26
26
  "commander": "^11.1.0",
27
27
  "conf": "^10.2.0",
28
28
  "inquirer": "^8.2.6",
29
+ "js-yaml": "^4.2.0",
29
30
  "node-fetch": "^2.7.0",
30
31
  "open": "^8.4.2",
31
32
  "ora": "^5.4.1"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/inquirer": "^8.2.10",
36
+ "@types/js-yaml": "^4.0.9",
35
37
  "@types/node": "^20.10.0",
36
38
  "@types/node-fetch": "^2.6.13",
37
39
  "ts-node": "^10.9.2",