@kaynooo/cli-docker 0.1.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 +8 -2
- package/dist/dc-cli.js +16 -1
- package/dist/dx-cli.js +236 -30
- package/dist/sf-cli.js +1328 -0
- package/package.json +7 -5
- package/src/dx.ts +117 -24
- package/src/picker.ts +37 -0
- package/src/sf-cli.ts +8 -0
- package/src/sf.ts +368 -0
- package/src/stack.ts +27 -3
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
|
|
@@ -12,8 +12,14 @@ bun install -g @kaynooo/cli-docker
|
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| `dc <compose args>` | `docker compose` on the nearest compose file (or `.env` setting `COMPOSE_FILE`) up to the repository root |
|
|
14
14
|
| `dc <project> <compose args>` | the same on a stack that came up from other files (`-f docker-compose.prod.yaml --env-file .env.prod`), found through the labels compose put on its containers |
|
|
15
|
-
| `dx` |
|
|
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
|
+
| `dx -` | the last action again |
|
|
16
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`).
|
|
17
23
|
|
|
18
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.
|
|
19
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.
|
|
748
|
+
var version = "0.3.0";
|
|
749
749
|
|
|
750
750
|
// src/dc.ts
|
|
751
751
|
import process8 from "process";
|
|
@@ -890,6 +890,21 @@ async function commandFor(service, action) {
|
|
|
890
890
|
function defaultAction(service) {
|
|
891
891
|
return service.state === "running" ? "shell" : "up";
|
|
892
892
|
}
|
|
893
|
+
function stateColor(state) {
|
|
894
|
+
if (state === "running")
|
|
895
|
+
return "green";
|
|
896
|
+
if (state === "exited" || state === "dead")
|
|
897
|
+
return "red";
|
|
898
|
+
return state === "none" ? "dim" : "yellow";
|
|
899
|
+
}
|
|
900
|
+
async function serviceNamed(dir, name, project) {
|
|
901
|
+
const [recorded] = project === "" ? [] : await labels(project);
|
|
902
|
+
const stack = recorded === undefined ? stackAt(dir) : labelledStack(dir, recorded);
|
|
903
|
+
return (await services(stack)).find((candidate) => candidate.name === name);
|
|
904
|
+
}
|
|
905
|
+
async function logsOf(stack, name) {
|
|
906
|
+
return output(stack, ["logs", "--tail=30", "--no-log-prefix", name]);
|
|
907
|
+
}
|
|
893
908
|
|
|
894
909
|
// src/dc.ts
|
|
895
910
|
var COMMANDS = new Set(["alpha", "attach", "bridge", "build", "commit", "config", "cp", "create", "down", "events", "exec", "export", "help", "images", "kill", "logs", "ls", "pause", "port", "ps", "publish", "pull", "push", "restart", "rm", "run", "scale", "start", "stats", "stop", "top", "unpause", "up", "version", "volumes", "wait", "watch"]);
|
package/dist/dx-cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/dx-cli.ts
|
|
5
|
-
import
|
|
5
|
+
import process11 from "process";
|
|
6
6
|
|
|
7
7
|
// ../kit/src/args.ts
|
|
8
8
|
import process4 from "process";
|
|
@@ -745,14 +745,115 @@ 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
|
+
import process10 from "process";
|
|
752
|
+
|
|
753
|
+
// ../kit/src/fzf.ts
|
|
754
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
755
|
+
import { homedir as homedir2 } from "os";
|
|
756
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
751
757
|
import process8 from "process";
|
|
758
|
+
var REMEMBERED = 20;
|
|
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);
|
|
798
|
+
}
|
|
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) {
|
|
815
|
+
try {
|
|
816
|
+
const file = historyFile(name);
|
|
817
|
+
return existsSync(file) ? JSON.parse(readFileSync(file, "utf-8")) : {};
|
|
818
|
+
} catch {
|
|
819
|
+
return {};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function runs(name) {
|
|
823
|
+
return history(name)[process8.cwd()] ?? [];
|
|
824
|
+
}
|
|
825
|
+
function record(name, run) {
|
|
826
|
+
const all = history(name);
|
|
827
|
+
const file = historyFile(name);
|
|
828
|
+
all[process8.cwd()] = [run, ...(all[process8.cwd()] ?? []).filter((earlier) => earlier.id !== run.id)].slice(0, REMEMBERED);
|
|
829
|
+
mkdirSync2(dirname2(file), { recursive: true });
|
|
830
|
+
writeFileSync(file, JSON.stringify(all));
|
|
831
|
+
}
|
|
832
|
+
function byRecency(items, previous, idOf) {
|
|
833
|
+
const rank = new Map(previous.map((run, index) => [run.id, index]));
|
|
834
|
+
const recent = items.filter((item) => rank.has(idOf(item))).sort((a, b) => rank.get(idOf(a)) - rank.get(idOf(b)));
|
|
835
|
+
return [...recent, ...items.filter((item) => !rank.has(idOf(item)))];
|
|
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
|
+
}
|
|
850
|
+
|
|
851
|
+
// src/picker.ts
|
|
852
|
+
import process9 from "process";
|
|
752
853
|
|
|
753
854
|
// src/stack.ts
|
|
754
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
755
|
-
import { dirname as
|
|
855
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
856
|
+
import { dirname as dirname3, join as join3, relative, resolve } from "path";
|
|
756
857
|
var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
|
|
757
858
|
var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
|
|
758
859
|
var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
|
|
@@ -766,10 +867,10 @@ function children(dir = ".") {
|
|
|
766
867
|
}
|
|
767
868
|
function up(dir) {
|
|
768
869
|
const dirs = [];
|
|
769
|
-
for (let current = dir;; current =
|
|
870
|
+
for (let current = dir;; current = join3(current, "..")) {
|
|
770
871
|
dirs.push(current);
|
|
771
872
|
const absolute = resolve(current);
|
|
772
|
-
if (
|
|
873
|
+
if (existsSync2(join3(current, ".git")) || dirname3(absolute) === absolute)
|
|
773
874
|
return dirs;
|
|
774
875
|
}
|
|
775
876
|
}
|
|
@@ -777,10 +878,10 @@ function stackAt(dir) {
|
|
|
777
878
|
return { dir, run: from(dir, ["docker", "compose"]), list: { argv: ["docker", "compose"], cwd: resolve(dir), env: process.env } };
|
|
778
879
|
}
|
|
779
880
|
function holdsStack(dir) {
|
|
780
|
-
if (COMPOSE_FILES.some((name) =>
|
|
881
|
+
if (COMPOSE_FILES.some((name) => existsSync2(join3(dir, name))))
|
|
781
882
|
return true;
|
|
782
883
|
try {
|
|
783
|
-
return COMPOSE_FILE_VAR.test(
|
|
884
|
+
return COMPOSE_FILE_VAR.test(readFileSync2(join3(dir, ".env"), "utf-8"));
|
|
784
885
|
} catch {
|
|
785
886
|
return false;
|
|
786
887
|
}
|
|
@@ -809,7 +910,7 @@ async function labels(project) {
|
|
|
809
910
|
const proc = Bun.spawn(["docker", "ps", "--all", ...filter, "--format", LABEL_FORMAT], { stdout: "pipe", stderr: "ignore" });
|
|
810
911
|
if (await proc.exited !== 0)
|
|
811
912
|
return [];
|
|
812
|
-
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) =>
|
|
913
|
+
return labelled(await new Response(proc.stdout).text()).filter((stack) => [...stack.files, ...stack.envFiles].every((file) => existsSync2(file)));
|
|
813
914
|
}
|
|
814
915
|
function labelledStack(dir, { project, files, envFiles, workdir }) {
|
|
815
916
|
const options = (paths) => ["-p", project, ...files.flatMap((file) => ["-f", paths(file)]), ...envFiles.flatMap((file) => ["--env-file", paths(file)])];
|
|
@@ -890,26 +991,62 @@ async function commandFor(service, action) {
|
|
|
890
991
|
function defaultAction(service) {
|
|
891
992
|
return service.state === "running" ? "shell" : "up";
|
|
892
993
|
}
|
|
994
|
+
function stateColor(state) {
|
|
995
|
+
if (state === "running")
|
|
996
|
+
return "green";
|
|
997
|
+
if (state === "exited" || state === "dead")
|
|
998
|
+
return "red";
|
|
999
|
+
return state === "none" ? "dim" : "yellow";
|
|
1000
|
+
}
|
|
1001
|
+
async function serviceNamed(dir, name, project) {
|
|
1002
|
+
const [recorded] = project === "" ? [] : await labels(project);
|
|
1003
|
+
const stack = recorded === undefined ? stackAt(dir) : labelledStack(dir, recorded);
|
|
1004
|
+
return (await services(stack)).find((candidate) => candidate.name === name);
|
|
1005
|
+
}
|
|
1006
|
+
async function logsOf(stack, name) {
|
|
1007
|
+
return output(stack, ["logs", "--tail=30", "--no-log-prefix", name]);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/picker.ts
|
|
1011
|
+
var KEYS = { "ctrl-l": "logs", "ctrl-r": "restart", "ctrl-x": "stop", "ctrl-b": "build" };
|
|
1012
|
+
var KEY_LABELS = { "ctrl-l": "Logs", "ctrl-r": "Restart", "ctrl-x": "Stop", "ctrl-b": "Rebuild" };
|
|
1013
|
+
function row(service) {
|
|
1014
|
+
const colors = paint(process9.stderr);
|
|
1015
|
+
const dir = service.stack.dir;
|
|
1016
|
+
const project = service.stack.project;
|
|
1017
|
+
const name = project === undefined ? service.name : `${colors.dim(`${project}:`)}${service.name}`;
|
|
1018
|
+
return {
|
|
1019
|
+
hidden: [dir, service.name, key(service), project ?? ""],
|
|
1020
|
+
cells: [dir === "." ? name : `${colors.dim(`${dir}/`)}${name}`, colors[stateColor(service.state)](service.state), colors.cyan(service.ports), colors.dim(service.image)]
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function pickService(services) {
|
|
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 };
|
|
1034
|
+
}
|
|
893
1035
|
|
|
894
1036
|
// src/dx.ts
|
|
895
1037
|
var usage2 = `dx [service] [action]
|
|
1038
|
+
dx -
|
|
896
1039
|
|
|
897
|
-
Without a service,
|
|
898
|
-
|
|
1040
|
+
Without a service, picks one with fzf when it is installed and a terminal is attached: enter opens its shell (or brings a stopped service up), ctrl-l follows its logs, ctrl-r restarts, ctrl-x stops and ctrl-b rebuilds it. The services last acted on come first, per folder. Without fzf or a terminal it lists them instead.
|
|
1041
|
+
\`dx -\` does the last action again, \`dx <service> [action]\` acts without the picker: \`shell\` (bash when the container has it), \`logs\` (follows the last 200 lines), \`restart\`, \`up\`, \`stop\` or \`build\` (up with a rebuild).
|
|
1042
|
+
The services are the stack of this folder or its nearest parent, the ones of the child folders (keyed \`api/php\`), and stacks found only through their containers (keyed \`<project>:php\`).
|
|
899
1043
|
|
|
900
1044
|
Exits with the code of the command it ran, 1 when there is no compose service here or none by that name.`;
|
|
901
1045
|
var options = {
|
|
902
1046
|
json: jsonOption("[{ key, project, name, state, status, ports, image }]")
|
|
903
1047
|
};
|
|
904
|
-
function
|
|
905
|
-
|
|
906
|
-
return "green";
|
|
907
|
-
if (state === "exited" || state === "dead")
|
|
908
|
-
return "red";
|
|
909
|
-
return state === "none" ? "dim" : "yellow";
|
|
910
|
-
}
|
|
911
|
-
function table(services) {
|
|
912
|
-
const colors = paint(process8.stdout);
|
|
1048
|
+
function table2(services) {
|
|
1049
|
+
const colors = paint(process10.stdout);
|
|
913
1050
|
const keys = services.map(key);
|
|
914
1051
|
const width = Math.max(...keys.map((name) => name.length));
|
|
915
1052
|
const states = Math.max(...services.map((service) => service.state.length));
|
|
@@ -928,25 +1065,94 @@ function find(services, named) {
|
|
|
928
1065
|
const nearest = closest(named, services.map(key));
|
|
929
1066
|
return fail(`no service named ${named}${nearest === undefined ? "" : `, did you mean ${nearest}?`}`, 1);
|
|
930
1067
|
}
|
|
1068
|
+
function listed2(services) {
|
|
1069
|
+
return byRecency(services, runs("dx"), key).map((service) => ({
|
|
1070
|
+
label: key(service),
|
|
1071
|
+
hint: [service.state, service.ports, service.image].filter(Boolean).join(" \xB7 "),
|
|
1072
|
+
typed: key(service),
|
|
1073
|
+
preview: ["--show", service.stack.dir, service.name, service.stack.project ?? ""],
|
|
1074
|
+
actions: Object.entries(KEYS).map(([pressed, action]) => ({ label: KEY_LABELS[pressed] ?? action, key: pressed, typed: `${key(service)} ${action}` }))
|
|
1075
|
+
}));
|
|
1076
|
+
}
|
|
1077
|
+
async function hidden(flag, rest, services) {
|
|
1078
|
+
if (flag === "--list") {
|
|
1079
|
+
console.log(JSON.stringify(listed2(services)));
|
|
1080
|
+
return 0;
|
|
1081
|
+
}
|
|
1082
|
+
if (flag === "--names") {
|
|
1083
|
+
console.log(services.map((service) => `${key(service)} ${service.state}`).join(`
|
|
1084
|
+
`));
|
|
1085
|
+
return 0;
|
|
1086
|
+
}
|
|
1087
|
+
if (flag === "--show") {
|
|
1088
|
+
const [dir = ".", name = "", project = ""] = rest;
|
|
1089
|
+
const service = await serviceNamed(dir, name, project);
|
|
1090
|
+
if (service === undefined)
|
|
1091
|
+
return 0;
|
|
1092
|
+
const colors = paint(process10.stdout);
|
|
1093
|
+
console.log(`${colors[stateColor(service.state)](service.status || service.state)} ${colors.dim(service.image)}${service.ports ? ` ${colors.cyan(service.ports)}` : ""}
|
|
1094
|
+
`);
|
|
1095
|
+
console.log(await logsOf(service.stack, name));
|
|
1096
|
+
return 0;
|
|
1097
|
+
}
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
931
1100
|
async function dx(args) {
|
|
932
|
-
const
|
|
1101
|
+
const [flag = ""] = args;
|
|
1102
|
+
if (flag === "--show" || flag === "--list" || flag === "--names") {
|
|
1103
|
+
const services = flag === "--show" ? [] : await allServices();
|
|
1104
|
+
if (services.length === 0 && flag !== "--show")
|
|
1105
|
+
fail("no compose service here", 1);
|
|
1106
|
+
return await hidden(flag, args.slice(1), services) ?? 0;
|
|
1107
|
+
}
|
|
1108
|
+
const emit = flag === "--emit";
|
|
1109
|
+
const parsed = parse(emit ? args.slice(1) : args, usage2, options, { replaces: "docker compose exec <service> bash, logs -f and restart typed per service", examples: ["dx", ["dx php", "a shell in php, or php brought up when stopped"], ["dx php logs", "follow its logs"], ["dx -", "the last action again"]] });
|
|
933
1110
|
const [named, asked] = parsed.positionals;
|
|
934
1111
|
const services = await allServices();
|
|
935
1112
|
if (services.length === 0)
|
|
936
1113
|
fail("no compose service here", 1);
|
|
1114
|
+
let service;
|
|
1115
|
+
let action;
|
|
937
1116
|
if (named === undefined) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1117
|
+
const interactive = process10.stdin.isTTY && process10.stderr.isTTY && parsed.values.json !== true;
|
|
1118
|
+
if (!interactive || Bun.which("fzf") === null) {
|
|
1119
|
+
if (emit)
|
|
1120
|
+
fail("the picker needs a terminal and fzf", 1);
|
|
1121
|
+
if (interactive)
|
|
1122
|
+
console.error("fzf is not installed, listing the services instead");
|
|
1123
|
+
if (parsed.values.json === true)
|
|
1124
|
+
printJson(services.map((each) => ({ key: key(each), project: each.project, name: each.name, state: each.state, status: each.status, ports: each.ports, image: each.image })));
|
|
1125
|
+
else
|
|
1126
|
+
console.log(table2(services));
|
|
1127
|
+
return 0;
|
|
1128
|
+
}
|
|
1129
|
+
const picked = pickService(byRecency(services, runs("dx"), key));
|
|
1130
|
+
if (picked === undefined)
|
|
1131
|
+
return 1;
|
|
1132
|
+
service = picked.service;
|
|
1133
|
+
action = KEYS[picked.key];
|
|
1134
|
+
} else if (named === "-") {
|
|
1135
|
+
const last = runs("dx")[0];
|
|
1136
|
+
service = last && services.find((candidate) => key(candidate) === last.id);
|
|
1137
|
+
if (service === undefined)
|
|
1138
|
+
fail(last ? `${last.id} is gone` : "nothing done here yet", 1);
|
|
1139
|
+
action = last.args[0];
|
|
1140
|
+
} else {
|
|
1141
|
+
service = find(services, named);
|
|
1142
|
+
if (asked !== undefined && !ACTIONS.includes(asked))
|
|
1143
|
+
fail(`no action ${asked}, one of ${ACTIONS.join(", ")}`);
|
|
1144
|
+
action = asked;
|
|
1145
|
+
}
|
|
1146
|
+
const chosen = action ?? defaultAction(service);
|
|
1147
|
+
const command = await commandFor(service, chosen);
|
|
1148
|
+
record("dx", { id: key(service), args: [chosen] });
|
|
1149
|
+
if (emit) {
|
|
1150
|
+
console.log(["run", action === undefined ? key(service) : `${key(service)} ${action}`, "", ...command.map((word) => `=${word}`)].join(`
|
|
1151
|
+
`));
|
|
942
1152
|
return 0;
|
|
943
1153
|
}
|
|
944
|
-
const service = find(services, named);
|
|
945
|
-
if (asked !== undefined && !ACTIONS.includes(asked))
|
|
946
|
-
fail(`no action ${asked}, one of ${ACTIONS.join(", ")}`);
|
|
947
|
-
const command = await commandFor(service, asked ?? defaultAction(service));
|
|
948
1154
|
return Bun.spawn(command, { stdio: ["inherit", "inherit", "inherit"] }).exited;
|
|
949
1155
|
}
|
|
950
1156
|
|
|
951
1157
|
// src/dx-cli.ts
|
|
952
|
-
|
|
1158
|
+
process11.exitCode = await runMain({ name: "dx", version, run: dx });
|