dokku-compose 0.13.0 → 0.13.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/README.md CHANGED
@@ -30,7 +30,7 @@ Like Docker Compose, but for Dokku.
30
30
  npm install -g dokku-compose
31
31
  ```
32
32
 
33
- Requires Node.js >= 20. See the [Installation Reference](docs/reference/install.md) for details.
33
+ Requires Node.js >= 20 and a server running Dokku >= 0.38.22 (every server command stops on an older one; dokku-compose never installs or upgrades Dokku). See the [Installation Reference](docs/reference/install.md) for details.
34
34
 
35
35
  ### 1. Export your existing server
36
36
 
@@ -0,0 +1,89 @@
1
+ // src/modules/version.ts
2
+ import { createRequire } from "module";
3
+
4
+ // src/core/logger.ts
5
+ import chalk from "chalk";
6
+ function logAction(context, message) {
7
+ process.stdout.write(chalk.blue(`[${context.padEnd(12)}]`) + ` ${message}`);
8
+ }
9
+ function logDone() {
10
+ console.log(`... ${chalk.green("done")}`);
11
+ }
12
+ function logSkip() {
13
+ console.log(`... ${chalk.yellow("already configured")}`);
14
+ }
15
+ function logError(context, message) {
16
+ console.error(chalk.red(`[${context.padEnd(12)}] ERROR: ${message}`));
17
+ }
18
+ function logWarn(context, message) {
19
+ console.warn(chalk.yellow(`[${context.padEnd(12)}] WARN: ${message}`));
20
+ }
21
+
22
+ // src/modules/version.ts
23
+ var MIN_DOKKU_VERSION = "0.38.22";
24
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
25
+ function parseSemver(input) {
26
+ const m = input.match(SEMVER_RE);
27
+ if (!m) throw new Error(`Cannot parse version: ${input}`);
28
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
29
+ }
30
+ function extractServerVersion(output) {
31
+ const match = output.match(/(\d+\.\d+\.\d+)/);
32
+ return match ? match[1] : null;
33
+ }
34
+ function compareSemver(a, b) {
35
+ const [aMaj, aMin, aPatch] = parseSemver(a);
36
+ const [bMaj, bMin, bPatch] = parseSemver(b);
37
+ if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
38
+ if (aMin !== bMin) return aMin < bMin ? -1 : 1;
39
+ if (aPatch !== bPatch) return aPatch < bPatch ? -1 : 1;
40
+ return 0;
41
+ }
42
+ function toolVersion() {
43
+ const require2 = createRequire(import.meta.url);
44
+ for (const p of ["../package.json", "../../package.json"]) {
45
+ try {
46
+ return require2(p).version;
47
+ } catch {
48
+ }
49
+ }
50
+ return "unknown";
51
+ }
52
+ async function assertDokkuSupported(ctx) {
53
+ const output = await ctx.query("version");
54
+ const server = extractServerVersion(output);
55
+ if (!server) {
56
+ throw new Error(`Cannot parse Dokku server version from output: ${output}`);
57
+ }
58
+ if (compareSemver(server, MIN_DOKKU_VERSION) === -1) {
59
+ throw new Error(
60
+ `dokku-compose ${toolVersion()} needs Dokku >= ${MIN_DOKKU_VERSION}; this server runs ${server}. Upgrade Dokku first.`
61
+ );
62
+ }
63
+ return server;
64
+ }
65
+ function compareToFile(server, file) {
66
+ if (!file) return "none";
67
+ const c = compareSemver(server, file);
68
+ return c === 0 ? "equal" : c < 0 ? "older" : "newer";
69
+ }
70
+ function enforceFileVersion(server, file) {
71
+ const rel = compareToFile(server, file);
72
+ if (rel === "older") {
73
+ throw new Error(`Dokku server is v${server} but the file requires >= v${file}. Upgrade Dokku or lower dokku.version.`);
74
+ }
75
+ if (rel === "newer") {
76
+ logWarn("dokku", `server is v${server}, newer than dokku.version ${file} in the file; update the file when ready`);
77
+ }
78
+ }
79
+
80
+ export {
81
+ logAction,
82
+ logDone,
83
+ logSkip,
84
+ logError,
85
+ logWarn,
86
+ assertDokkuSupported,
87
+ compareToFile,
88
+ enforceFileVersion
89
+ };
package/dist/index.js CHANGED
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ assertDokkuSupported,
4
+ compareToFile,
5
+ enforceFileVersion,
6
+ logAction,
7
+ logDone,
8
+ logError,
9
+ logSkip,
10
+ logWarn
11
+ } from "./chunk-3W53KPE4.js";
2
12
 
3
13
  // src/index.ts
4
14
  import { Command } from "commander";
@@ -388,24 +398,6 @@ function computeAdditiveChange(before, after) {
388
398
  return change;
389
399
  }
390
400
 
391
- // src/core/logger.ts
392
- import chalk from "chalk";
393
- function logAction(context, message) {
394
- process.stdout.write(chalk.blue(`[${context.padEnd(12)}]`) + ` ${message}`);
395
- }
396
- function logDone() {
397
- console.log(`... ${chalk.green("done")}`);
398
- }
399
- function logSkip() {
400
- console.log(`... ${chalk.yellow("already configured")}`);
401
- }
402
- function logError(context, message) {
403
- console.error(chalk.red(`[${context.padEnd(12)}] ERROR: ${message}`));
404
- }
405
- function logWarn(context, message) {
406
- console.warn(chalk.yellow(`[${context.padEnd(12)}] WARN: ${message}`));
407
- }
408
-
409
401
  // src/core/reconcile.ts
410
402
  async function reconcile(resource, ctx, target, desired, onRemovals, prune = false) {
411
403
  const canPrune = prune && !!resource.prune && !!resource.removals;
@@ -1460,40 +1452,6 @@ async function exportRedis(ctx) {
1460
1452
  return services;
1461
1453
  }
1462
1454
 
1463
- // src/modules/version.ts
1464
- var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
1465
- function parseSemver(input) {
1466
- const m = input.match(SEMVER_RE);
1467
- if (!m) throw new Error(`Cannot parse version: ${input}`);
1468
- return [Number(m[1]), Number(m[2]), Number(m[3])];
1469
- }
1470
- function extractServerVersion(output) {
1471
- const match = output.match(/(\d+\.\d+\.\d+)/);
1472
- return match ? match[1] : null;
1473
- }
1474
- function compareSemver(a, b) {
1475
- const [aMaj, aMin, aPatch] = parseSemver(a);
1476
- const [bMaj, bMin, bPatch] = parseSemver(b);
1477
- if (aMaj !== bMaj) return aMaj < bMaj ? -1 : 1;
1478
- if (aMin !== bMin) return aMin < bMin ? -1 : 1;
1479
- if (aPatch !== bPatch) return aPatch < bPatch ? -1 : 1;
1480
- return 0;
1481
- }
1482
- async function ensureDokkuVersion(ctx, pinned) {
1483
- if (!pinned) return;
1484
- const output = await ctx.query("version");
1485
- const server = extractServerVersion(output);
1486
- if (!server) {
1487
- throw new Error(`Cannot parse Dokku server version from output: ${output}`);
1488
- }
1489
- if (compareSemver(server, pinned) === -1) {
1490
- logWarn(
1491
- "dokku",
1492
- `server is v${server} but dokku-compose.yml pins >= v${pinned}. Some features may be unavailable.`
1493
- );
1494
- }
1495
- }
1496
-
1497
1455
  // src/modules/links.ts
1498
1456
  function resolveServicePlugin(name, config) {
1499
1457
  if (config.postgres?.[name]) return { plugin: "postgres", config: config.postgres[name] };
@@ -1633,7 +1591,7 @@ async function processApp(ctx, config, app, appConfig, failFast = false, prune =
1633
1591
  }
1634
1592
  async function runUp(ctx, config, appFilter, opts = {}) {
1635
1593
  const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
1636
- await ensureDokkuVersion(ctx, config.dokku?.version);
1594
+ enforceFileVersion(await assertDokkuSupported(ctx), config.dokku?.version);
1637
1595
  if (config.plugins) await ensurePlugins(ctx, config.plugins);
1638
1596
  if (config.docker_auth) await ensureDockerAuth(ctx, config.docker_auth);
1639
1597
  if (config.domains !== void 0) await ensureGlobalDomains(ctx, config.domains);
@@ -1693,6 +1651,7 @@ var DownFailedError = class extends Error {
1693
1651
  };
1694
1652
  async function runDown(ctx, config, appFilter, opts) {
1695
1653
  const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
1654
+ await assertDokkuSupported(ctx);
1696
1655
  const failures = [];
1697
1656
  for (const app of apps) {
1698
1657
  const appConfig = config.apps[app];
@@ -1762,8 +1721,7 @@ var ALL_APP_RESOURCES = [
1762
1721
  // src/commands/export.ts
1763
1722
  async function runExport(ctx, opts) {
1764
1723
  const config = { apps: {} };
1765
- const version2 = extractServerVersion(await ctx.query("version"));
1766
- if (version2) config.dokku = { version: version2 };
1724
+ config.dokku = { version: await assertDokkuSupported(ctx) };
1767
1725
  const apps = opts.appFilter?.length ? opts.appFilter : await exportApps(ctx);
1768
1726
  const networks = await exportNetworks(ctx);
1769
1727
  if (networks.length > 0) config.networks = networks;
@@ -1803,7 +1761,7 @@ async function runExport(ctx, opts) {
1803
1761
  }
1804
1762
 
1805
1763
  // src/commands/diff.ts
1806
- import chalk2 from "chalk";
1764
+ import chalk from "chalk";
1807
1765
  function diffSslStatus(desired, current) {
1808
1766
  if (typeof desired !== "object" || desired === null) return void 0;
1809
1767
  if (current === void 0) return void 0;
@@ -1823,7 +1781,12 @@ function isEmptyValue(value) {
1823
1781
  async function computeDiff(ctx, config) {
1824
1782
  const result = { apps: {}, services: {}, plugins: {}, inSync: true, readWarnings: [] };
1825
1783
  const appNames = Object.keys(config.apps);
1826
- await ensureDokkuVersion(ctx, config.dokku?.version);
1784
+ const serverVersion = await assertDokkuSupported(ctx);
1785
+ const fileVersion = config.dokku?.version;
1786
+ if (fileVersion && compareToFile(serverVersion, fileVersion) !== "equal") {
1787
+ result.dokku = { file: fileVersion, server: serverVersion };
1788
+ result.inSync = false;
1789
+ }
1827
1790
  const appExists = /* @__PURE__ */ new Map();
1828
1791
  await Promise.all(
1829
1792
  appNames.map(async (app) => {
@@ -1967,7 +1930,7 @@ async function computeDiff(ctx, config) {
1967
1930
  return result;
1968
1931
  }
1969
1932
  function maskDiffResult(diff) {
1970
- const masked = { apps: {}, services: diff.services, plugins: diff.plugins, inSync: diff.inSync, readWarnings: diff.readWarnings };
1933
+ const masked = { apps: {}, services: diff.services, plugins: diff.plugins, dokku: diff.dokku, inSync: diff.inSync, readWarnings: diff.readWarnings };
1971
1934
  for (const [app, appDiff] of Object.entries(diff.apps)) {
1972
1935
  const maskedAppDiff = {};
1973
1936
  for (const [feature, d] of Object.entries(appDiff)) {
@@ -1991,15 +1954,19 @@ function formatSummary(diff) {
1991
1954
  lines.push(` app: ${app}`);
1992
1955
  lines.push(` (in sync)`);
1993
1956
  } else {
1994
- lines.push(` app: ${chalk2.bold(app)}`);
1957
+ lines.push(` app: ${chalk.bold(app)}`);
1995
1958
  for (const [feature, d] of changes) {
1996
- const sym = d.status === "missing" ? chalk2.green("+") : d.status === "unknown" ? chalk2.red("?") : d.status === "extra" ? chalk2.red("-") : chalk2.yellow("~");
1959
+ const sym = d.status === "missing" ? chalk.green("+") : d.status === "unknown" ? chalk.red("?") : d.status === "extra" ? chalk.red("-") : chalk.yellow("~");
1997
1960
  lines.push(` ${sym} ${feature}: ${formatFeatureSummary(d)}`);
1998
1961
  }
1999
1962
  }
2000
1963
  }
1964
+ if (diff.dokku) {
1965
+ lines.push(` dokku`);
1966
+ lines.push(` ${chalk.yellow("~")} version: ${diff.dokku.file} (file) \u2192 ${diff.dokku.server} (server)`);
1967
+ }
2001
1968
  for (const [name, d] of Object.entries(diff.plugins)) {
2002
- const sym = d.status === "missing" ? chalk2.green("+") : d.status === "unknown" ? chalk2.red("?") : chalk2.yellow("~");
1969
+ const sym = d.status === "missing" ? chalk.green("+") : d.status === "unknown" ? chalk.red("?") : chalk.yellow("~");
2003
1970
  const detail = d.status === "missing" ? `not installed (want ${d.desired ?? "any"})` : `${d.current} \u2192 ${d.desired}`;
2004
1971
  lines.push(` plugin: ${name}`);
2005
1972
  lines.push(` ${sym} version: ${detail}${d.note ? ` (${d.note})` : ""}`);
@@ -2007,20 +1974,20 @@ function formatSummary(diff) {
2007
1974
  for (const [svc, d] of Object.entries(diff.services)) {
2008
1975
  if (d.status === "missing") {
2009
1976
  lines.push(` services:`);
2010
- lines.push(` ${chalk2.green("+")} ${svc}: not provisioned`);
1977
+ lines.push(` ${chalk.green("+")} ${svc}: not provisioned`);
2011
1978
  }
2012
1979
  }
2013
- const total = Object.values(diff.apps).flatMap((a) => Object.values(a)).filter((d) => d.status !== "in-sync").length + Object.values(diff.services).filter((d) => d.status !== "in-sync").length + Object.values(diff.plugins).filter((d) => d.status !== "unknown").length;
1980
+ const total = Object.values(diff.apps).flatMap((a) => Object.values(a)).filter((d) => d.status !== "in-sync").length + Object.values(diff.services).filter((d) => d.status !== "in-sync").length + Object.values(diff.plugins).filter((d) => d.status !== "unknown").length + (diff.dokku ? 1 : 0);
2014
1981
  if (diff.readWarnings.length > 0) {
2015
1982
  lines.push("");
2016
- lines.push(chalk2.red(" Could not read some server state \u2014 treated as unknown, not missing:"));
2017
- for (const w of diff.readWarnings) lines.push(chalk2.red(` - ${w}`));
1983
+ lines.push(chalk.red(" Could not read some server state \u2014 treated as unknown, not missing:"));
1984
+ for (const w of diff.readWarnings) lines.push(chalk.red(` - ${w}`));
2018
1985
  }
2019
1986
  lines.push("");
2020
1987
  if (total === 0) {
2021
- lines.push(chalk2.green(" Everything in sync."));
1988
+ lines.push(chalk.green(" Everything in sync."));
2022
1989
  } else {
2023
- lines.push(chalk2.yellow(` ${total} resource(s) out of sync.`));
1990
+ lines.push(chalk.yellow(` ${total} resource(s) out of sync.`));
2024
1991
  }
2025
1992
  lines.push("");
2026
1993
  return lines.join("\n");
@@ -2050,36 +2017,41 @@ function formatVerbose(diff) {
2050
2017
  for (const [feature, d] of changes) {
2051
2018
  lines.push(`@@ app: ${app} / ${feature} @@`);
2052
2019
  if (d.status === "unknown") {
2053
- lines.push(chalk2.red(` ? could not read server state \u2014 not reported as missing`));
2020
+ lines.push(chalk.red(` ? could not read server state \u2014 not reported as missing`));
2054
2021
  continue;
2055
2022
  }
2056
2023
  if (d.status === "extra") {
2057
- for (const r of d.removed ?? []) lines.push(chalk2.red(`- ${r}`));
2024
+ for (const r of d.removed ?? []) lines.push(chalk.red(`- ${r}`));
2058
2025
  if (d.note) lines.push(` (${d.note})`);
2059
2026
  continue;
2060
2027
  }
2061
2028
  const currentLines = d.current !== void 0 ? JSON.stringify(d.current, showUnset, 2).split("\n") : [];
2062
2029
  const desiredLines = JSON.stringify(d.desired, showUnset, 2).split("\n");
2063
- for (const line of currentLines) lines.push(chalk2.red(`- ${line}`));
2064
- for (const line of desiredLines) lines.push(chalk2.green(`+ ${line}`));
2065
- for (const r of d.removed ?? []) lines.push(chalk2.red(`- ${r} (not in the file)`));
2030
+ for (const line of currentLines) lines.push(chalk.red(`- ${line}`));
2031
+ for (const line of desiredLines) lines.push(chalk.green(`+ ${line}`));
2032
+ for (const r of d.removed ?? []) lines.push(chalk.red(`- ${r} (not in the file)`));
2066
2033
  }
2067
2034
  }
2035
+ if (diff.dokku) {
2036
+ lines.push(`@@ dokku version @@`);
2037
+ lines.push(chalk.red(`- ${diff.dokku.server} (server)`));
2038
+ lines.push(chalk.green(`+ ${diff.dokku.file} (file)`));
2039
+ }
2068
2040
  for (const [name, d] of Object.entries(diff.plugins)) {
2069
2041
  lines.push(`@@ plugin: ${name} @@`);
2070
- if (d.current !== void 0) lines.push(chalk2.red(`- ${d.current}`));
2071
- lines.push(chalk2.green(`+ ${d.desired ?? "(any version)"}`) + (d.note ? ` (${d.note})` : ""));
2042
+ if (d.current !== void 0) lines.push(chalk.red(`- ${d.current}`));
2043
+ lines.push(chalk.green(`+ ${d.desired ?? "(any version)"}`) + (d.note ? ` (${d.note})` : ""));
2072
2044
  }
2073
2045
  for (const [svc, d] of Object.entries(diff.services)) {
2074
2046
  if (d.status === "missing") {
2075
2047
  lines.push(`@@ services @@`);
2076
- lines.push(chalk2.green(`+ ${svc}`));
2048
+ lines.push(chalk.green(`+ ${svc}`));
2077
2049
  }
2078
2050
  }
2079
2051
  if (diff.readWarnings.length > 0) {
2080
2052
  lines.push("");
2081
- lines.push(chalk2.red("Could not read some server state \u2014 treated as unknown, not missing:"));
2082
- for (const w of diff.readWarnings) lines.push(chalk2.red(` - ${w}`));
2053
+ lines.push(chalk.red("Could not read some server state \u2014 treated as unknown, not missing:"));
2054
+ for (const w of diff.readWarnings) lines.push(chalk.red(` - ${w}`));
2083
2055
  }
2084
2056
  lines.push("");
2085
2057
  return lines.join("\n");
@@ -2136,7 +2108,7 @@ function validate(filePath) {
2136
2108
  }
2137
2109
 
2138
2110
  // src/index.ts
2139
- import chalk3 from "chalk";
2111
+ import chalk2 from "chalk";
2140
2112
  var require2 = createRequire(import.meta.url);
2141
2113
  var { version } = require2("../package.json");
2142
2114
  var program = new Command().name("dokku-compose").version(version);
@@ -2160,7 +2132,7 @@ program.command("up [apps...]").description("Create/update apps and services to
2160
2132
  }
2161
2133
  }
2162
2134
  } catch (error) {
2163
- console.error(chalk3.red(`
2135
+ console.error(chalk2.red(`
2164
2136
  ${error.message}`));
2165
2137
  process.exitCode = 1;
2166
2138
  } finally {
@@ -2178,7 +2150,7 @@ program.command("down [apps...]").description("Destroy apps and services (requir
2178
2150
  try {
2179
2151
  await runDown(ctx, config, apps, { force: true, failFast: opts.failFast ?? false });
2180
2152
  } catch (error) {
2181
- console.error(chalk3.red(`
2153
+ console.error(chalk2.red(`
2182
2154
  ${error.message}`));
2183
2155
  process.exitCode = 1;
2184
2156
  } finally {
@@ -2216,6 +2188,10 @@ program.command("export").description("Export server state to dokku-compose.yml
2216
2188
  } else {
2217
2189
  process.stdout.write(out);
2218
2190
  }
2191
+ } catch (error) {
2192
+ console.error(chalk2.red(`
2193
+ ${error.message}`));
2194
+ process.exitCode = 1;
2219
2195
  } finally {
2220
2196
  await ctx.close();
2221
2197
  }
@@ -2230,6 +2206,10 @@ program.command("diff").description("Show what is out of sync between config and
2230
2206
  const output = opts.verbose ? formatVerbose(diff) : formatSummary(diff);
2231
2207
  process.stdout.write(output);
2232
2208
  process.exitCode = rawDiff.inSync ? 0 : 1;
2209
+ } catch (error) {
2210
+ console.error(chalk2.red(`
2211
+ ${error.message}`));
2212
+ process.exitCode = 1;
2233
2213
  } finally {
2234
2214
  await ctx.close();
2235
2215
  }
@@ -2238,8 +2218,12 @@ program.command("ps [apps...]").description("Show status of configured apps").op
2238
2218
  const config = loadConfig(opts.file);
2239
2219
  const runner = makeRunner({});
2240
2220
  try {
2241
- const { runPs } = await import("./ps-33II4UU3.js");
2221
+ const { runPs } = await import("./ps-IWBYZRDO.js");
2242
2222
  await runPs(runner, config, apps);
2223
+ } catch (error) {
2224
+ console.error(chalk2.red(`
2225
+ ${error.message}`));
2226
+ process.exitCode = 1;
2243
2227
  } finally {
2244
2228
  await runner.close();
2245
2229
  }
@@ -1,6 +1,11 @@
1
+ import {
2
+ assertDokkuSupported
3
+ } from "./chunk-3W53KPE4.js";
4
+
1
5
  // src/commands/ps.ts
2
6
  async function runPs(runner, config, appFilter) {
3
7
  const apps = appFilter.length > 0 ? appFilter : Object.keys(config.apps);
8
+ await assertDokkuSupported(runner);
4
9
  for (const app of apps) {
5
10
  const exists = await runner.check("apps:exists", app);
6
11
  if (!exists) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dokku-compose",
3
- "version": "0.13.0",
3
+ "version": "0.13.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",