@kaynooo/cli-docker 0.2.0 → 0.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # docker
2
2
 
3
- Two docker compose helpers: `dc` runs compose on the stack found here, `dx` lists a stack's services and opens a shell in one, follows its logs, restarts, stops or rebuilds it.
3
+ Three helpers: `dc` runs compose on the stack found here, `dx` lists a stack's services and opens a shell in one, follows its logs, restarts, stops or rebuilds it, `sf` picks a Symfony console command and runs it.
4
4
 
5
5
  ```sh
6
6
  bun install -g @kaynooo/cli-docker
@@ -15,6 +15,11 @@ bun install -g @kaynooo/cli-docker
15
15
  | `dx` | picks a service with fzf: enter opens its shell (or brings it up), ctrl-l follows its logs, ctrl-r restarts, ctrl-x stops, ctrl-b rebuilds; the ones acted on last come first. Without fzf or a terminal it lists them with their state, ports and image (`--json` for the full record) |
16
16
  | `dx -` | the last action again |
17
17
  | `dx <service> [action]` | `shell` (the default, `up` for a stopped service), `logs`, `restart`, `up`, `stop` or `build` |
18
+ | `sf` | picks a Symfony console command with fzf and runs it, asking for each required argument; the commands run last come first, and the picker opens on the list cached from the last run. It runs in the php service of the nearest compose stack, else with the local php. Without fzf or a terminal it lists the commands |
19
+ | `sf -` | the last command again, with the arguments it ran with |
20
+ | `sf <command> [args...]` | runs without the picker; a name the list does not hold goes to the console as is, which resolves `c:c` to `cache:clear` itself |
21
+
22
+ `sf` finds the nearest `bin/console` up to the repository root, then those of the child folders (keyed `api/cache:clear`).
18
23
 
19
24
  `dc` takes no option of its own, every word reaches compose as typed. With several stacks that only have containers here and no name, it asks with `pick` when that is installed and a terminal is attached, and otherwise lists the projects. When it picked the stack through its containers it prints the compose command it stands for on stderr.
20
25
 
package/dist/dc-cli.js CHANGED
@@ -745,7 +745,7 @@ var {Glob } = globalThis.Bun;
745
745
  var SESSION_FILES = new Glob("*.json");
746
746
  var ACCOUNT_DIRS = new Glob("*/sessions");
747
747
  // package.json
748
- var version = "0.2.0";
748
+ var version = "0.3.0";
749
749
 
750
750
  // src/dc.ts
751
751
  import process8 from "process";
package/dist/dx-cli.js CHANGED
@@ -745,34 +745,86 @@ var {Glob } = globalThis.Bun;
745
745
  var SESSION_FILES = new Glob("*.json");
746
746
  var ACCOUNT_DIRS = new Glob("*/sessions");
747
747
  // package.json
748
- var version = "0.2.0";
748
+ var version = "0.3.0";
749
749
 
750
750
  // src/dx.ts
751
751
  import process10 from "process";
752
752
 
753
- // src/history.ts
753
+ // ../kit/src/fzf.ts
754
754
  import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
755
755
  import { homedir as homedir2 } from "os";
756
756
  import { dirname as dirname2, join as join2 } from "path";
757
757
  import process8 from "process";
758
758
  var REMEMBERED = 20;
759
- function historyFile() {
760
- return join2(process8.env.XDG_CACHE_HOME ?? join2(homedir2(), ".cache"), "dx", "history.json");
759
+ function table(rows) {
760
+ const widths = rows[0]?.cells.map((_, index) => Math.max(...rows.map((row) => Bun.stringWidth(row.cells[index])))) ?? [];
761
+ return rows.map((row) => [
762
+ ...row.hidden,
763
+ ...row.cells.map((cell, index) => index === row.cells.length - 1 ? cell.trimEnd() : `${cell}${" ".repeat(widths[index] + 1 - Bun.stringWidth(cell))}`)
764
+ ].join("\t"));
765
+ }
766
+ function fzf(rows, options) {
767
+ const hidden = options.hidden ?? rows[0]?.hidden.length ?? 0;
768
+ const proc = Bun.spawnSync([
769
+ "fzf",
770
+ "--prompt",
771
+ options.prompt,
772
+ ...options.header === undefined ? [] : ["--header", options.header],
773
+ ...(options.expect ?? []).length > 0 ? ["--expect", (options.expect ?? []).join(",")] : [],
774
+ "--ansi",
775
+ "--reverse",
776
+ "--height",
777
+ options.height ?? "100%",
778
+ "--delimiter",
779
+ "\t",
780
+ "--with-nth",
781
+ `${hidden + 1}..`,
782
+ "--nth",
783
+ `1..${options.searched}`,
784
+ "--tabstop",
785
+ "1",
786
+ "--tiebreak",
787
+ "chunk,index",
788
+ ...options.preview === undefined ? [] : ["--preview", options.preview, "--preview-window", "down,40%"],
789
+ ...(options.bind ?? []).flatMap((bind) => ["--bind", bind]),
790
+ ...options.query === true ? ["--print-query"] : []
791
+ ], {
792
+ stdin: new TextEncoder().encode(table(rows).join(`
793
+ `)),
794
+ stdout: "pipe",
795
+ stderr: "inherit"
796
+ });
797
+ return picked(proc.stdout.toString(), hidden, options);
761
798
  }
762
- function history() {
799
+ function picked(output, hidden, options) {
800
+ const lines = output.split(`
801
+ `);
802
+ const query = options.query === true ? lines.shift() : "";
803
+ const [key, line] = (options.expect ?? []).length > 0 ? [lines[0], lines[1]] : ["", lines[0]];
804
+ if (line !== undefined && line !== "")
805
+ return { key, hidden: line.split("\t").slice(0, hidden), query };
806
+ return options.query === true && key !== "" ? { key, hidden: [], query } : undefined;
807
+ }
808
+ function heightFor(count) {
809
+ return String(Math.max(15, Math.ceil((count + 4) / 0.6)));
810
+ }
811
+ function historyFile(name) {
812
+ return join2(process8.env.XDG_CACHE_HOME ?? join2(homedir2(), ".cache"), name, "history.json");
813
+ }
814
+ function history(name) {
763
815
  try {
764
- const file = historyFile();
816
+ const file = historyFile(name);
765
817
  return existsSync(file) ? JSON.parse(readFileSync(file, "utf-8")) : {};
766
818
  } catch {
767
819
  return {};
768
820
  }
769
821
  }
770
- function runs() {
771
- return history()[process8.cwd()] ?? [];
822
+ function runs(name) {
823
+ return history(name)[process8.cwd()] ?? [];
772
824
  }
773
- function record(run) {
774
- const all = history();
775
- const file = historyFile();
825
+ function record(name, run) {
826
+ const all = history(name);
827
+ const file = historyFile(name);
776
828
  all[process8.cwd()] = [run, ...(all[process8.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
777
829
  mkdirSync2(dirname2(file), { recursive: true });
778
830
  writeFileSync(file, JSON.stringify(all));
@@ -782,6 +834,19 @@ function byRecency(items, previous, idOf) {
782
834
  const recent = items.filter((item) => rank.has(idOf(item))).sort((a, b) => rank.get(idOf(a)) - rank.get(idOf(b)));
783
835
  return [...recent, ...items.filter((item) => !rank.has(idOf(item)))];
784
836
  }
837
+ function word(arg) {
838
+ if (typeof arg === "string")
839
+ return `=${arg}`;
840
+ return "given" in arg ? `+${arg.given}` : `?${arg.ask}`;
841
+ }
842
+ function emit(mode, typed, recorded, command) {
843
+ const words = command.map(word);
844
+ console.log([mode, typed, recorded, ...words].join(`
845
+ `));
846
+ }
847
+ function list(rows) {
848
+ console.log(JSON.stringify(rows));
849
+ }
785
850
 
786
851
  // src/picker.ts
787
852
  import process9 from "process";
@@ -955,47 +1020,17 @@ function row(service) {
955
1020
  cells: [dir === "." ? name : `${colors.dim(`${dir}/`)}${name}`, colors[stateColor(service.state)](service.state), colors.cyan(service.ports), colors.dim(service.image)]
956
1021
  };
957
1022
  }
958
- function table(rows) {
959
- const widths = rows[0]?.cells.map((_, index) => Math.max(...rows.map((each) => Bun.stringWidth(each.cells[index])))) ?? [];
960
- return rows.map((each) => [
961
- ...each.hidden,
962
- ...each.cells.map((cell, index) => index === each.cells.length - 1 ? cell.trimEnd() : `${cell}${" ".repeat(widths[index] + 1 - Bun.stringWidth(cell))}`)
963
- ].join("\t")).join(`
964
- `);
965
- }
966
1023
  function pickService(services) {
967
- const proc = Bun.spawnSync([
968
- "fzf",
969
- "--prompt",
970
- "dx> ",
971
- "--header",
972
- "enter shell or up, ctrl-l logs, ctrl-r restart, ctrl-x stop, ctrl-b rebuild",
973
- "--expect",
974
- Object.keys(KEYS).join(","),
975
- "--ansi",
976
- "--reverse",
977
- "--height",
978
- String(Math.max(15, Math.ceil((services.length + 4) / 0.6))),
979
- "--delimiter",
980
- "\t",
981
- "--with-nth",
982
- "5..",
983
- "--nth",
984
- "1",
985
- "--tabstop",
986
- "1",
987
- "--tiebreak",
988
- "chunk,index",
989
- "--preview",
990
- `${process9.execPath} ${process9.argv[1]} --show {1} {2} {4}`,
991
- "--preview-window",
992
- "down,40%"
993
- ], { stdin: new TextEncoder().encode(table(services.map(row))), stdout: "pipe", stderr: "inherit" });
994
- const [pressed = "", line] = proc.stdout.toString().split(`
995
- `);
996
- const keyed = line?.split("\t")[2];
997
- const service = services.find((candidate) => key(candidate) === keyed);
998
- return service === undefined ? undefined : { service, key: pressed };
1024
+ const picked = fzf(services.map(row), {
1025
+ prompt: "dx> ",
1026
+ header: "enter shell or up, ctrl-l logs, ctrl-r restart, ctrl-x stop, ctrl-b rebuild",
1027
+ expect: Object.keys(KEYS),
1028
+ searched: 1,
1029
+ height: heightFor(services.length),
1030
+ preview: `${process9.execPath} ${process9.argv[1]} --show {1} {2} {4}`
1031
+ });
1032
+ const service = services.find((candidate) => key(candidate) === picked?.hidden[2]);
1033
+ return picked === undefined || service === undefined ? undefined : { service, key: picked.key };
999
1034
  }
1000
1035
 
1001
1036
  // src/dx.ts
@@ -1031,7 +1066,7 @@ function find(services, named) {
1031
1066
  return fail(`no service named ${named}${nearest === undefined ? "" : `, did you mean ${nearest}?`}`, 1);
1032
1067
  }
1033
1068
  function listed2(services) {
1034
- return byRecency(services, runs(), key).map((service) => ({
1069
+ return byRecency(services, runs("dx"), key).map((service) => ({
1035
1070
  label: key(service),
1036
1071
  hint: [service.state, service.ports, service.image].filter(Boolean).join(" \xB7 "),
1037
1072
  typed: key(service),
@@ -1091,13 +1126,13 @@ async function dx(args) {
1091
1126
  console.log(table2(services));
1092
1127
  return 0;
1093
1128
  }
1094
- const picked = pickService(byRecency(services, runs(), key));
1129
+ const picked = pickService(byRecency(services, runs("dx"), key));
1095
1130
  if (picked === undefined)
1096
1131
  return 1;
1097
1132
  service = picked.service;
1098
1133
  action = KEYS[picked.key];
1099
1134
  } else if (named === "-") {
1100
- const last = runs()[0];
1135
+ const last = runs("dx")[0];
1101
1136
  service = last && services.find((candidate) => key(candidate) === last.id);
1102
1137
  if (service === undefined)
1103
1138
  fail(last ? `${last.id} is gone` : "nothing done here yet", 1);
@@ -1110,7 +1145,7 @@ async function dx(args) {
1110
1145
  }
1111
1146
  const chosen = action ?? defaultAction(service);
1112
1147
  const command = await commandFor(service, chosen);
1113
- record({ id: key(service), args: [chosen] });
1148
+ record("dx", { id: key(service), args: [chosen] });
1114
1149
  if (emit) {
1115
1150
  console.log(["run", action === undefined ? key(service) : `${key(service)} ${action}`, "", ...command.map((word) => `=${word}`)].join(`
1116
1151
  `));