@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 +6 -1
- package/dist/dc-cli.js +1 -1
- package/dist/dx-cli.js +90 -55
- package/dist/sf-cli.js +1328 -0
- package/package.json +7 -5
- package/src/dx.ts +5 -5
- package/src/picker.ts +15 -45
- package/src/sf-cli.ts +8 -0
- package/src/sf.ts +368 -0
- package/src/stack.ts +3 -3
- package/src/history.ts +0 -48
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# docker
|
|
2
2
|
|
|
3
|
-
|
|
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
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.
|
|
748
|
+
var version = "0.3.0";
|
|
749
749
|
|
|
750
750
|
// src/dx.ts
|
|
751
751
|
import process10 from "process";
|
|
752
752
|
|
|
753
|
-
// src/
|
|
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
|
|
760
|
-
|
|
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
|
|
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
|
|
968
|
-
"
|
|
969
|
-
"
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
`));
|