oche-cli 0.3.0 → 0.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.
Files changed (3) hide show
  1. package/README.md +9 -2
  2. package/dist/index.js +23 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -33,7 +33,7 @@ oche logs --stage prod # the running container's log: why it's crash-
33
33
  oche logs --stage prod --failures # just the errors and the tail
34
34
  oche logs --app api --build # the build log of the latest deployment instead
35
35
  oche deployments # recent deployments per app: status, commit, how long, when
36
- oche envs # which stages define each variable (names only)
36
+ oche envs # which stages define each variable, and which are empty (names only)
37
37
  oche envs --stage prod --key DATABASE_URL
38
38
  oche deploy --stage staging # redeploy (--force rebuilds without the cache); asks first
39
39
  oche rollback --app api # the images Coolify can go back to
@@ -44,7 +44,14 @@ oche preflight --to prod # missing variables, and whether staging is he
44
44
 
45
45
  Each one picks the app with `--app <name>` (or the start of its uuid) or `--stage prod|staging|dev`; with one app it needs neither, and with several it says which ones there are. `oche deploy` and `oche rollback` ask before they act unless you pass `-y`.
46
46
 
47
- `oche envs` never prints values. The server compares names, and whether staging and production hold the same value, and sends only that. Log lines Coolify marks as holding secrets are left out too.
47
+ `oche envs` marks each variable per stage: `✓` set, `∅` defined but empty, `·` not defined, `-` no app on that stage. A `∅` is counted as a problem, not a note: the key is there, so nothing complains, and the app reads it and gets nothing.
48
+
49
+ ```
50
+ VARIABLE dev staging prod
51
+ TURNSTILE_SECRET ∅ ∅ ∅ defined but empty
52
+ ```
53
+
54
+ `oche envs` never prints values. The server compares names, whether each key holds anything at all, and whether staging and production hold the same value, and sends only that. Log lines Coolify marks as holding secrets are left out too.
48
55
 
49
56
  ## Without a server: `oche pr`
50
57
 
package/dist/index.js CHANGED
@@ -3371,7 +3371,7 @@ var program = new Command();
3371
3371
  // package.json
3372
3372
  var package_default = {
3373
3373
  name: "oche-cli",
3374
- version: "0.3.0",
3374
+ version: "0.4.0",
3375
3375
  description: "Command-line client for Oche: promote dev \u2192 staging \u2192 main from your terminal.",
3376
3376
  license: "MIT",
3377
3377
  repository: {
@@ -3880,9 +3880,16 @@ ${data.instance.url}`));
3880
3880
  var ISSUE_LABEL = {
3881
3881
  "missing-in-prod": "missing in production",
3882
3882
  "missing-in-staging": "missing in staging",
3883
+ "empty-value": "defined but empty",
3883
3884
  "same-secret": "same value in staging and production"
3884
3885
  };
3885
- var issueText = (issue) => issue === null ? "" : issue === "missing-in-prod" ? c.red(ISSUE_LABEL[issue]) : c.yellow(ISSUE_LABEL[issue]);
3886
+ var issueText = (issue) => issue === null ? "" : issue === "missing-in-prod" || issue === "empty-value" ? c.red(ISSUE_LABEL[issue]) : c.yellow(ISSUE_LABEL[issue]);
3887
+ function legend() {
3888
+ const set = `${c.green("\u2713")} ${c.dim("set")}`;
3889
+ const empty = `${c.red("\u2205")} ${c.dim("defined but empty, so the app sees nothing")}`;
3890
+ const absent = `${c.red("\xB7")} ${c.dim("not defined")}`;
3891
+ return `${set} ${empty} ${absent} ${c.dim("- no app on that stage")}`;
3892
+ }
3886
3893
  async function envsCommand(getClient, repoArg, opts) {
3887
3894
  const repo = await resolveRepo(repoArg);
3888
3895
  const { groups } = await (await getClient()).envs(repo);
@@ -3896,32 +3903,36 @@ async function envsCommand(getClient, repoArg, opts) {
3896
3903
  }
3897
3904
  kept.forEach((g, i) => {
3898
3905
  if (i) print();
3899
- printGroup(g, opts.stage, !key);
3906
+ envGroupLines(g, opts.stage, !key).forEach((l) => print(l));
3900
3907
  });
3901
- print(c.dim("\nOche compares names, and whether staging and production hold the same value. It never shows values."));
3908
+ print(`
3909
+ ${legend()}`);
3910
+ print(c.dim("Oche compares names, and whether staging and production hold the same value. It never shows values."));
3902
3911
  }
3903
- function printGroup(group, only, totals) {
3912
+ function envGroupLines(group, only, totals) {
3904
3913
  const stages = STAGES.filter((s) => group.apps[s] && (!only || s === only));
3905
3914
  const where = stages.map((s) => `${s}: ${group.apps[s].name}`).join(" \xB7 ");
3906
- print(`${c.bold(group.label)} ${c.dim(where)}`);
3907
3915
  const header = [["VARIABLE", c.dim("VARIABLE")], ...stages.map((s) => [s, c.dim(s)]), ""];
3908
3916
  const rows = group.rows.map((r) => [
3909
3917
  r.key,
3910
3918
  ...stages.map((s) => {
3911
3919
  if (r.present[s] === void 0) return ["-", c.dim("-")];
3912
- return r.present[s] ? ["\u2713", c.green("\u2713")] : ["\xB7", c.red("\xB7")];
3920
+ if (!r.present[s]) return ["\xB7", c.red("\xB7")];
3921
+ return r.hasValue?.[s] === false ? ["\u2205", c.red("\u2205")] : ["\u2713", c.green("\u2713")];
3913
3922
  }),
3914
3923
  issueText(r.issue)
3915
3924
  ]);
3916
- columns([header, ...rows]).forEach((l) => print(l));
3917
- if (!totals) return;
3918
- const { missingInProd, missingInStaging, sameSecret } = group.counts;
3925
+ const lines = [`${c.bold(group.label)} ${c.dim(where)}`, ...columns([header, ...rows])];
3926
+ if (!totals) return lines;
3927
+ const { missingInProd, missingInStaging, emptyValue, sameSecret } = group.counts;
3919
3928
  const issues = [
3929
+ emptyValue ? c.red(`${emptyValue} defined but empty`) : "",
3920
3930
  missingInProd ? c.red(`${missingInProd} missing in production`) : "",
3921
3931
  missingInStaging ? c.yellow(`${missingInStaging} missing in staging`) : "",
3922
3932
  sameSecret ? c.yellow(`${sameSecret} shared with staging`) : ""
3923
3933
  ].filter(Boolean);
3924
- print(issues.length ? ` ${issues.join(c.dim(" \xB7 "))}` : c.dim(" in step across environments"));
3934
+ lines.push(issues.length ? ` ${issues.join(c.dim(" \xB7 "))}` : c.dim(" in step across environments"));
3935
+ return lines;
3925
3936
  }
3926
3937
  async function deployCommand(getClient, repoArg, opts) {
3927
3938
  const repo = await resolveRepo(repoArg);
@@ -5276,7 +5287,7 @@ function buildProgram() {
5276
5287
  const npm = program2.command("npm").description("Publish this package to npm on every release");
5277
5288
  npm.command("setup").description("Add the publish workflow and let npm trust it (uses your npm login)").argument(repoArg, repoHelp).option("-y, --yes", "don't ask for confirmation").option("--keep-existing", "keep a workflow that already publishes to npm instead of replacing it").option("--skip-pr", "only do the npm side; don't open the workflow PR").action((repo, opts) => npmSetupCommand(client, repo, opts));
5278
5289
  npm.command("status", { isDefault: true }).description("Publishing setup, the npm version and the last run").argument(repoArg, repoHelp).option("--json", "print raw JSON").action((repo, opts) => npmStatusCommand(client, repo, opts));
5279
- program2.command("envs").description("Which stages define each environment variable (names only, never values)").argument(repoArg, repoHelp).addOption(stageOption("only this stage's column")).option("--key <name>", "only variables whose name contains this").option("--json", "print raw JSON").action((repo, opts) => envsCommand(client, repo, opts));
5290
+ program2.command("envs").description("Which stages define each environment variable, and which are defined but empty (names only, never values)").argument(repoArg, repoHelp).addOption(stageOption("only this stage's column")).option("--key <name>", "only variables whose name contains this").option("--json", "print raw JSON").action((repo, opts) => envsCommand(client, repo, opts));
5280
5291
  program2.command("deploy").description("Redeploy an app on Coolify").argument(repoArg, repoHelp).addOption(appOption).addOption(stageOption("the app deploying this stage")).option("--force", "rebuild without the build cache").option("-y, --yes", "don't ask for confirmation").option("--json", "print raw JSON").action((repo, opts) => deployCommand(client, repo, opts));
5281
5292
  program2.command("rollback").description("List the images Coolify can roll an app back to, and roll back to one").argument(repoArg, repoHelp).addOption(appOption).addOption(stageOption("the app deploying this stage")).option("--to <commit>", "roll back to this image (a commit SHA from the list)").option("-y, --yes", "don't ask for confirmation").option("--json", "print raw JSON").action((repo, opts) => rollbackCommand(client, repo, opts));
5282
5293
  program2.command("explain").description("Ask your Oche server's AI what broke a deployment").argument(repoArg, repoHelp).addOption(appOption).addOption(stageOption("the app deploying this stage")).option("--deployment <uuid>", "explain this deployment (default: the latest failed one)").option("--json", "print raw JSON").action((repo, opts) => explainCommand(client, repo, opts));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oche-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Command-line client for Oche: promote dev → staging → main from your terminal.",
5
5
  "license": "MIT",
6
6
  "repository": {