dokku-compose 0.4.0 → 0.5.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.
Files changed (2) hide show
  1. package/dist/index.js +139 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -156,12 +156,10 @@ function createRunner(opts = {}) {
156
156
  await execDokku(args);
157
157
  },
158
158
  async query(...args) {
159
- if (opts.dryRun) return "";
160
159
  const { stdout } = await execDokku(args);
161
160
  return stdout.trim();
162
161
  },
163
162
  async check(...args) {
164
- if (opts.dryRun) return false;
165
163
  const { ok } = await execDokku(args);
166
164
  return ok;
167
165
  },
@@ -284,6 +282,40 @@ var Apps = {
284
282
  }
285
283
  };
286
284
 
285
+ // src/resources/parsers.ts
286
+ function parseReport(raw, namespace) {
287
+ const result = {};
288
+ const prefix = new RegExp(`^${namespace}\\s+`, "i");
289
+ for (const line of raw.split("\n")) {
290
+ if (line.trimStart().startsWith("=====>")) continue;
291
+ const colonIdx = line.indexOf(":");
292
+ if (colonIdx === -1) continue;
293
+ const rawKey = line.slice(0, colonIdx).trim();
294
+ if (!rawKey) continue;
295
+ const value = line.slice(colonIdx + 1).trim();
296
+ const stripped = rawKey.replace(prefix, "");
297
+ const key = stripped.toLowerCase().replace(/\s+/g, "-");
298
+ if (key.startsWith("computed-") || key.startsWith("global-") || key === "last-visited-at") continue;
299
+ if (!value) continue;
300
+ result[key] = value;
301
+ }
302
+ return result;
303
+ }
304
+ function parseBulkReport(raw, namespace) {
305
+ const result = /* @__PURE__ */ new Map();
306
+ const sections = raw.split(/(?=^=====> )/m).filter((s) => s.trim());
307
+ for (const section of sections) {
308
+ const headerEnd = section.indexOf("\n");
309
+ if (headerEnd === -1) continue;
310
+ const header = section.slice(0, headerEnd);
311
+ const match = header.match(/^=====> (.+?)\s+\S+\s+information/);
312
+ if (!match) continue;
313
+ const app = match[1];
314
+ result.set(app, parseReport(section, namespace));
315
+ }
316
+ return result;
317
+ }
318
+
287
319
  // src/resources/lists.ts
288
320
  function splitWords(raw) {
289
321
  return raw.split(/\s+/).map((s) => s.trim()).filter(Boolean);
@@ -297,6 +329,15 @@ var Ports = {
297
329
  const raw = await ctx.query("ports:report", target, "--ports-map");
298
330
  return splitWords(raw);
299
331
  },
332
+ readAll: async (ctx) => {
333
+ const raw = await ctx.query("ports:report");
334
+ const bulk = parseBulkReport(raw, "ports");
335
+ const result = /* @__PURE__ */ new Map();
336
+ for (const [app, report] of bulk) {
337
+ result.set(app, report["map"] ? splitWords(report["map"]) : []);
338
+ }
339
+ return result;
340
+ },
300
341
  onChange: async (ctx, target, change) => {
301
342
  await ctx.run("ports:set", target, ...change.after);
302
343
  }
@@ -307,6 +348,15 @@ var Domains = {
307
348
  const raw = await ctx.query("domains:report", target, "--domains-app-vhosts");
308
349
  return splitLines(raw);
309
350
  },
351
+ readAll: async (ctx) => {
352
+ const raw = await ctx.query("domains:report");
353
+ const bulk = parseBulkReport(raw, "domains");
354
+ const result = /* @__PURE__ */ new Map();
355
+ for (const [app, report] of bulk) {
356
+ result.set(app, report["app-vhosts"] ? splitWords(report["app-vhosts"]) : []);
357
+ }
358
+ return result;
359
+ },
310
360
  onChange: async (ctx, target, { added, removed }) => {
311
361
  for (const d of removed) await ctx.run("domains:remove", target, d);
312
362
  for (const d of added) await ctx.run("domains:add", target, d);
@@ -318,32 +368,21 @@ var Storage = {
318
368
  const raw = await ctx.query("storage:report", target, "--storage-mounts");
319
369
  return splitLines(raw);
320
370
  },
371
+ readAll: async (ctx) => {
372
+ const raw = await ctx.query("storage:report");
373
+ const bulk = parseBulkReport(raw, "storage");
374
+ const result = /* @__PURE__ */ new Map();
375
+ for (const [app, report] of bulk) {
376
+ result.set(app, report["mounts"] ? splitLines(report["mounts"]) : []);
377
+ }
378
+ return result;
379
+ },
321
380
  onChange: async (ctx, target, { added, removed }) => {
322
381
  for (const m of removed) await ctx.run("storage:unmount", target, m);
323
382
  for (const m of added) await ctx.run("storage:mount", target, m);
324
383
  }
325
384
  };
326
385
 
327
- // src/resources/parsers.ts
328
- function parseReport(raw, namespace) {
329
- const result = {};
330
- const prefix = new RegExp(`^${namespace}\\s+`, "i");
331
- for (const line of raw.split("\n")) {
332
- if (line.trimStart().startsWith("=====>")) continue;
333
- const colonIdx = line.indexOf(":");
334
- if (colonIdx === -1) continue;
335
- const rawKey = line.slice(0, colonIdx).trim();
336
- if (!rawKey) continue;
337
- const value = line.slice(colonIdx + 1).trim();
338
- const stripped = rawKey.replace(prefix, "");
339
- const key = stripped.toLowerCase().replace(/\s+/g, "-");
340
- if (key.startsWith("computed-") || key.startsWith("global-") || key === "last-visited-at") continue;
341
- if (!value) continue;
342
- result[key] = value;
343
- }
344
- return result;
345
- }
346
-
347
386
  // src/resources/properties.ts
348
387
  function propertyResource(opts) {
349
388
  return {
@@ -352,6 +391,10 @@ function propertyResource(opts) {
352
391
  const raw = await ctx.query(`${opts.namespace}:report`, target);
353
392
  return parseReport(raw, opts.namespace);
354
393
  },
394
+ async readAll(ctx) {
395
+ const raw = await ctx.query(`${opts.namespace}:report`);
396
+ return parseBulkReport(raw, opts.namespace);
397
+ },
355
398
  async onChange(ctx, target, change) {
356
399
  for (const [key, value] of Object.entries({ ...change.added, ...change.modified })) {
357
400
  await ctx.run(opts.setCmd, target, key, String(value));
@@ -387,6 +430,15 @@ var Scheduler = {
387
430
  const report = parseReport(raw, "scheduler");
388
431
  return report["selected"] ?? "";
389
432
  },
433
+ async readAll(ctx) {
434
+ const raw = await ctx.query("scheduler:report");
435
+ const bulk = parseBulkReport(raw, "scheduler");
436
+ const result = /* @__PURE__ */ new Map();
437
+ for (const [app, report] of bulk) {
438
+ result.set(app, report["selected"] ?? "");
439
+ }
440
+ return result;
441
+ },
390
442
  async onChange(ctx, target, change) {
391
443
  await ctx.run("scheduler:set", target, "selected", change.after);
392
444
  }
@@ -399,6 +451,15 @@ var Proxy = {
399
451
  const raw = await ctx.query("proxy:report", target, "--proxy-enabled");
400
452
  return raw.trim() === "true";
401
453
  },
454
+ readAll: async (ctx) => {
455
+ const raw = await ctx.query("proxy:report");
456
+ const bulk = parseBulkReport(raw, "proxy");
457
+ const result = /* @__PURE__ */ new Map();
458
+ for (const [app, report] of bulk) {
459
+ result.set(app, report["enabled"] === "true");
460
+ }
461
+ return result;
462
+ },
402
463
  onChange: async (ctx, target, { after }) => {
403
464
  await ctx.run(after ? "proxy:enable" : "proxy:disable", target);
404
465
  }
@@ -451,6 +512,15 @@ var Certs = {
451
512
  const raw = await ctx.query("certs:report", target, "--ssl-enabled");
452
513
  return raw.trim() === "true";
453
514
  },
515
+ readAll: async (ctx) => {
516
+ const raw = await ctx.query("certs:report");
517
+ const bulk = parseBulkReport(raw, "ssl");
518
+ const result = /* @__PURE__ */ new Map();
519
+ for (const [app, report] of bulk) {
520
+ result.set(app, report["enabled"] === "true");
521
+ }
522
+ return result;
523
+ },
454
524
  onChange: async (ctx, target, { before, after }) => {
455
525
  if (after === false && before) {
456
526
  await ctx.run("certs:remove", target);
@@ -505,6 +575,15 @@ var Git = {
505
575
  const report = await ctx.query("git:report", target, "--git-deploy-branch");
506
576
  return { deploy_branch: report.trim() || void 0 };
507
577
  },
578
+ readAll: async (ctx) => {
579
+ const raw = await ctx.query("git:report");
580
+ const bulk = parseBulkReport(raw, "git");
581
+ const result = /* @__PURE__ */ new Map();
582
+ for (const [app, report] of bulk) {
583
+ result.set(app, { deploy_branch: report["deploy-branch"] || void 0 });
584
+ }
585
+ return result;
586
+ },
508
587
  onChange: async (ctx, target, { after }) => {
509
588
  if (after.deploy_branch) {
510
589
  await ctx.run("git:set", target, "deploy-branch", after.deploy_branch);
@@ -542,6 +621,15 @@ var Networks = {
542
621
  const raw = await ctx.query("network:report", target, "--network-attach-post-deploy");
543
622
  return raw.trim() ? raw.trim().split(/\s+/) : [];
544
623
  },
624
+ readAll: async (ctx) => {
625
+ const raw = await ctx.query("network:report");
626
+ const bulk = parseBulkReport(raw, "network");
627
+ const result = /* @__PURE__ */ new Map();
628
+ for (const [app, report] of bulk) {
629
+ result.set(app, report["attach-post-deploy"] ? report["attach-post-deploy"].split(/\s+/) : []);
630
+ }
631
+ return result;
632
+ },
545
633
  onChange: async (ctx, target, { after }) => {
546
634
  await ctx.run("network:set", target, "attach-post-deploy", ...after);
547
635
  }
@@ -858,12 +946,19 @@ async function runExport(ctx, opts) {
858
946
  if (networks.length > 0) config.networks = networks;
859
947
  const services = await exportServices(ctx);
860
948
  if (Object.keys(services).length > 0) config.services = services;
949
+ const prefetched = /* @__PURE__ */ new Map();
950
+ await Promise.all(
951
+ ALL_APP_RESOURCES.filter((r) => !r.forceApply && !r.key.startsWith("_") && r.readAll).map(async (r) => {
952
+ prefetched.set(r.key, await r.readAll(ctx));
953
+ })
954
+ );
861
955
  for (const app of apps) {
862
956
  const appConfig = {};
863
957
  for (const resource of ALL_APP_RESOURCES) {
864
958
  if (resource.key.startsWith("_")) continue;
865
959
  if (resource.forceApply) continue;
866
- const value = await resource.read(ctx, app);
960
+ const bulk = prefetched.get(resource.key);
961
+ const value = bulk ? bulk.get(app) : await resource.read(ctx, app);
867
962
  if (value === void 0 || value === null || value === "") continue;
868
963
  if (Array.isArray(value) && value.length === 0) continue;
869
964
  if (typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) continue;
@@ -886,6 +981,12 @@ async function runExport(ctx, opts) {
886
981
  import chalk2 from "chalk";
887
982
  async function computeDiff(ctx, config) {
888
983
  const result = { apps: {}, services: {}, inSync: true };
984
+ const prefetched = /* @__PURE__ */ new Map();
985
+ await Promise.all(
986
+ ALL_APP_RESOURCES.filter((r) => !r.forceApply && !r.key.startsWith("_") && r.readAll).map(async (r) => {
987
+ prefetched.set(r.key, await r.readAll(ctx));
988
+ })
989
+ );
889
990
  for (const [app, appConfig] of Object.entries(config.apps)) {
890
991
  const appDiff = {};
891
992
  for (const resource of ALL_APP_RESOURCES) {
@@ -898,7 +999,8 @@ async function computeDiff(ctx, config) {
898
999
  desired = appConfig[resource.key];
899
1000
  }
900
1001
  if (desired === void 0) continue;
901
- const current = await resource.read(ctx, app);
1002
+ const bulk = prefetched.get(resource.key);
1003
+ const current = bulk ? bulk.get(app) : await resource.read(ctx, app);
902
1004
  const change = computeChange(current, desired);
903
1005
  if (!change.changed) {
904
1006
  appDiff[resource.key] = { status: "in-sync", desired, current };
@@ -1027,6 +1129,14 @@ function validate(filePath) {
1027
1129
  return { errors, warnings };
1028
1130
  }
1029
1131
 
1132
+ // src/core/mask.ts
1133
+ function maskSensitiveArgs(cmd) {
1134
+ return cmd.replace(/(\S*(?:TOKEN|SECRET|PASSWORD|KEY|AUTH|CREDENTIAL)\S*)=(\S+)/gi, (_, key, value) => {
1135
+ if (value.length <= 4) return `${key}=****`;
1136
+ return `${key}=****${value.slice(-4)}`;
1137
+ });
1138
+ }
1139
+
1030
1140
  // src/index.ts
1031
1141
  var require2 = createRequire(import.meta.url);
1032
1142
  var { version } = require2("../package.json");
@@ -1037,7 +1147,7 @@ function makeRunner(opts) {
1037
1147
  dryRun: opts.dryRun ?? false
1038
1148
  });
1039
1149
  }
1040
- program.command("up [apps...]").description("Create/update apps and services to match config").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--dry-run", "Print commands without executing").option("--fail-fast", "Stop on first error").action(async (apps, opts) => {
1150
+ program.command("up [apps...]").description("Create/update apps and services to match config").option("-f, --file <path>", "Config file", "dokku-compose.yml").option("--dry-run", "Print commands without executing").option("--sensitive", "Show sensitive values in dry-run output (masked by default)").option("--fail-fast", "Stop on first error").action(async (apps, opts) => {
1041
1151
  const config = loadConfig(opts.file);
1042
1152
  const runner = makeRunner(opts);
1043
1153
  const ctx = createContext(runner);
@@ -1045,7 +1155,10 @@ program.command("up [apps...]").description("Create/update apps and services to
1045
1155
  await runUp(ctx, config, apps);
1046
1156
  if (opts.dryRun) {
1047
1157
  console.log("\n# Commands that would run:");
1048
- for (const cmd of runner.dryRunLog) console.log(`dokku ${cmd}`);
1158
+ for (const cmd of runner.dryRunLog) {
1159
+ const line = opts.sensitive ? cmd : maskSensitiveArgs(cmd);
1160
+ console.log(`dokku ${line}`);
1161
+ }
1049
1162
  }
1050
1163
  } finally {
1051
1164
  await ctx.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dokku-compose",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Docker Compose for Dokku — declare your entire server in a single YAML file.",
5
5
  "main": "dist/index.js",
6
6
  "exports": "./dist/index.js",