@kaynooo/cli-docker 0.7.0 → 0.8.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/dist/dc-cli.js CHANGED
@@ -627,7 +627,6 @@ var DASHES = /^-+/;
627
627
  function jsonOption(holds) {
628
628
  return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
629
629
  }
630
-
631
630
  class Exit extends Error {
632
631
  code;
633
632
  hint;
@@ -1032,6 +1031,8 @@ function composeError({ code, stderr }) {
1032
1031
  `).at(-1) ?? "";
1033
1032
  return last === "" ? `exit ${code}` : last;
1034
1033
  }
1034
+ // ../kit/src/folders.ts
1035
+ var SKIPPED_FOLDERS = new Set(["node_modules", "vendor", "dist", "build", "target", "coverage", "venv", "__pycache__"]);
1035
1036
  // ../kit/src/secrets.ts
1036
1037
  var SECRET_PART = /token|secret|passw|credential|private|dsn|apikey|accesskey|key$/i;
1037
1038
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
@@ -1404,13 +1405,12 @@ class Redactor {
1404
1405
  var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
1405
1406
  var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
1406
1407
  var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
1407
- var SKIPPED2 = new Set(["node_modules", "vendor", "dist", "build", "target", "venv", "__pycache__"]);
1408
1408
  var ACTIONS = ["shell", "logs", "restart", "up", "stop", "build"];
1409
1409
  function from(dir, command) {
1410
1410
  return dir === "." ? command : ["env", "-C", dir, ...command];
1411
1411
  }
1412
1412
  function children(dir = ".") {
1413
- return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
1413
+ return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED_FOLDERS.has(child.name)).map((child) => child.name).sort();
1414
1414
  }
1415
1415
  function up(dir) {
1416
1416
  const dirs = [];
package/dist/dlog-cli.js CHANGED
@@ -627,7 +627,6 @@ var DASHES = /^-+/;
627
627
  function jsonOption(holds) {
628
628
  return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
629
629
  }
630
-
631
630
  class Exit extends Error {
632
631
  code;
633
632
  hint;
@@ -1032,6 +1031,8 @@ function composeError({ code, stderr }) {
1032
1031
  `).at(-1) ?? "";
1033
1032
  return last === "" ? `exit ${code}` : last;
1034
1033
  }
1034
+ // ../kit/src/folders.ts
1035
+ var SKIPPED_FOLDERS = new Set(["node_modules", "vendor", "dist", "build", "target", "coverage", "venv", "__pycache__"]);
1035
1036
  // ../kit/src/secrets.ts
1036
1037
  var SECRET_PART = /token|secret|passw|credential|private|dsn|apikey|accesskey|key$/i;
1037
1038
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
@@ -1404,13 +1405,12 @@ class Redactor {
1404
1405
  var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
1405
1406
  var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
1406
1407
  var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
1407
- var SKIPPED2 = new Set(["node_modules", "vendor", "dist", "build", "target", "venv", "__pycache__"]);
1408
1408
  var ACTIONS = ["shell", "logs", "restart", "up", "stop", "build"];
1409
1409
  function from(dir, command) {
1410
1410
  return dir === "." ? command : ["env", "-C", dir, ...command];
1411
1411
  }
1412
1412
  function children(dir = ".") {
1413
- return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
1413
+ return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED_FOLDERS.has(child.name)).map((child) => child.name).sort();
1414
1414
  }
1415
1415
  function up(dir) {
1416
1416
  const dirs = [];
package/dist/dx-cli.js CHANGED
@@ -627,7 +627,6 @@ var DASHES = /^-+/;
627
627
  function jsonOption(holds) {
628
628
  return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
629
629
  }
630
-
631
630
  class Exit extends Error {
632
631
  code;
633
632
  hint;
@@ -1032,6 +1031,8 @@ function composeError({ code, stderr }) {
1032
1031
  `).at(-1) ?? "";
1033
1032
  return last === "" ? `exit ${code}` : last;
1034
1033
  }
1034
+ // ../kit/src/folders.ts
1035
+ var SKIPPED_FOLDERS = new Set(["node_modules", "vendor", "dist", "build", "target", "coverage", "venv", "__pycache__"]);
1035
1036
  // ../kit/src/secrets.ts
1036
1037
  var SECRET_PART = /token|secret|passw|credential|private|dsn|apikey|accesskey|key$/i;
1037
1038
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
@@ -1503,6 +1504,14 @@ var KEYS = {
1503
1504
  "\x1B[B": "down",
1504
1505
  "\x1BOB": "down",
1505
1506
  "\x0E": "down",
1507
+ "\x1B[5~": "pageup",
1508
+ "\x1B[6~": "pagedown",
1509
+ "\x1B[H": "home",
1510
+ "\x1B[1~": "home",
1511
+ "\x1BOH": "home",
1512
+ "\x1B[F": "end",
1513
+ "\x1B[4~": "end",
1514
+ "\x1BOF": "end",
1506
1515
  "\r": "enter",
1507
1516
  "\n": "enter",
1508
1517
  "\x03": "cancel",
@@ -1511,7 +1520,10 @@ var KEYS = {
1511
1520
  "\b": "erase",
1512
1521
  "\x15": "clear",
1513
1522
  "\x17": "word",
1514
- "\t": "tab"
1523
+ "\t": "tab",
1524
+ "\x1F": "help",
1525
+ "\x1BOP": "help",
1526
+ "\x1B[11~": "help"
1515
1527
  };
1516
1528
  var CTRL_KEY = /^ctrl-[abd-gkln-z]$/;
1517
1529
  function repeat(value, count) {
@@ -1581,7 +1593,7 @@ function parseKeys(input, paste = { active: false }, ctrls = []) {
1581
1593
  });
1582
1594
  }
1583
1595
  function createState(rows, { multi = false, prompt = "> ", query = "", header = "", expect = [], searched = Number.POSITIVE_INFINITY } = {}) {
1584
- const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, marked: new Set, multi, prompt, done: undefined };
1596
+ const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, help: false, page: 10, marked: new Set, multi, prompt, done: undefined };
1585
1597
  return withRows(state, rows);
1586
1598
  }
1587
1599
  function withRows(state, rows, header = state.header) {
@@ -1603,6 +1615,8 @@ function highlighted(state) {
1603
1615
  function press(state, key) {
1604
1616
  if (state.done !== undefined)
1605
1617
  return state;
1618
+ if (state.help)
1619
+ return { ...state, help: false };
1606
1620
  if (typeof key === "object") {
1607
1621
  if ("text" in key)
1608
1622
  return withQuery(state, state.query + key.text);
@@ -1613,8 +1627,18 @@ function press(state, key) {
1613
1627
  return move(state, -1);
1614
1628
  case "down":
1615
1629
  return move(state, 1);
1630
+ case "pageup":
1631
+ return move(state, -state.page);
1632
+ case "pagedown":
1633
+ return move(state, state.page);
1634
+ case "home":
1635
+ return move(state, -state.matches.length);
1636
+ case "end":
1637
+ return move(state, state.matches.length);
1616
1638
  case "cancel":
1617
1639
  return { ...state, done: "cancel" };
1640
+ case "help":
1641
+ return { ...state, help: true };
1618
1642
  case "erase":
1619
1643
  return withQuery(state, [...state.query].slice(0, -1).join(""));
1620
1644
  case "clear":
@@ -1794,7 +1818,18 @@ function fitStyled(line, width, plain = identity) {
1794
1818
  function caretColumn(state, layout) {
1795
1819
  return Math.min(layout.listWidth, Bun.stringWidth(state.prompt) + Bun.stringWidth(state.query) + 1);
1796
1820
  }
1821
+ function helpLines(state, layout, theme) {
1822
+ const lines = [
1823
+ "type to filter, up, down, ctrl-p, ctrl-n, pgup, pgdn, home, end move",
1824
+ `enter picks${state.multi ? ", tab marks" : ""}, ctrl-u clears the query, ctrl-w a word, esc or ctrl-c cancels`,
1825
+ ...state.expect.length > 0 ? [`${state.expect.join(", ")} end the pick as this command asks`] : []
1826
+ ];
1827
+ const title = "keys, any key closes this help";
1828
+ return [{ text: theme.accent(title), width: title.length }, ...lines.map((line) => fit(` ${line}`, layout.listWidth, theme.label))];
1829
+ }
1797
1830
  function listLines(state, layout, theme) {
1831
+ if (state.help)
1832
+ return helpLines(state, layout, theme);
1798
1833
  const header = state.header === "" ? [] : state.header.split(`
1799
1834
  `).map((line) => fitStyled(line, layout.listWidth, theme.label));
1800
1835
  const rows = Math.max(1, layout.listHeight - 2 - header.length);
@@ -1803,7 +1838,8 @@ function listLines(state, layout, theme) {
1803
1838
  const marks = (state.marked.size > 0 ? ` (${state.marked.size} marked)` : "") + (state.status === "" ? "" : ` ${state.status}`);
1804
1839
  const promptWidth = Bun.stringWidth(state.prompt);
1805
1840
  const query = fit(state.query, Math.max(1, layout.listWidth - promptWidth), identity);
1806
- const count = fit(` ${state.matches.length}/${state.items.length}${marks}`, layout.listWidth, theme.label);
1841
+ const place = state.matches.length > rows ? ` (${state.cursor + 1} of ${state.matches.length})` : "";
1842
+ const count = fit(` ${state.matches.length}/${state.items.length}${place}${marks}`, layout.listWidth, theme.label);
1807
1843
  const lines = [{ text: theme.accent(state.prompt) + query.text, width: promptWidth + query.width }, count, ...header];
1808
1844
  if (state.matches.length === 0)
1809
1845
  lines.push(fit(" no match", layout.listWidth, theme.label));
@@ -1830,6 +1866,7 @@ function render(state, layout, theme, preview = []) {
1830
1866
 
1831
1867
  // ../kit/src/picker/terminal.ts
1832
1868
  var PREVIEW_DELAY_MS = 120;
1869
+ var ESCAPE_WAIT_MS = 30;
1833
1870
  var OPEN = { ctrl: "open" };
1834
1871
  var HIDE_CURSOR = "\x1B[?25l";
1835
1872
  var SHOW_CURSOR = "\x1B[?25h";
@@ -1841,14 +1878,24 @@ function up(lines) {
1841
1878
  return lines > 0 ? `\x1B[${lines}A` : "";
1842
1879
  }
1843
1880
  function openTerminal() {
1844
- let input;
1881
+ let mode;
1845
1882
  try {
1846
- input = new tty.ReadStream(openSync("/dev/tty", "r"));
1883
+ mode = new tty.ReadStream(openSync("/dev/tty", "r"));
1847
1884
  } catch {
1848
1885
  return fail("no terminal to read keys from (/dev/tty)");
1849
1886
  }
1887
+ const keys = Bun.file("/dev/tty").stream().getReader();
1850
1888
  const out = process14.stderr;
1851
1889
  let resize;
1890
+ async function read(onChunk) {
1891
+ const decoder = new TextDecoder;
1892
+ try {
1893
+ for (let chunk = await keys.read();!chunk.done; chunk = await keys.read())
1894
+ onChunk(decoder.decode(chunk.value, { stream: true }));
1895
+ } catch {
1896
+ onChunk("\x03");
1897
+ }
1898
+ }
1852
1899
  return {
1853
1900
  get columns() {
1854
1901
  return out.columns || 80;
@@ -1860,17 +1907,18 @@ function openTerminal() {
1860
1907
  write: (text) => void out.write(text),
1861
1908
  listen(onChunk, onResize) {
1862
1909
  resize = onResize;
1863
- input.setRawMode(true);
1910
+ mode.setRawMode(true);
1864
1911
  out.on("resize", onResize);
1865
- input.on("data", (chunk) => onChunk(chunk.toString()));
1912
+ read(onChunk);
1866
1913
  },
1867
1914
  close() {
1868
1915
  if (resize !== undefined)
1869
1916
  out.off("resize", resize);
1870
1917
  try {
1871
- input.setRawMode(false);
1918
+ mode.setRawMode(false);
1872
1919
  } finally {
1873
- input.destroy();
1920
+ mode.destroy();
1921
+ keys.cancel();
1874
1922
  }
1875
1923
  }
1876
1924
  };
@@ -1885,20 +1933,29 @@ async function select(rows, settings, terminal = openTerminal()) {
1885
1933
  let shownLine;
1886
1934
  let finish;
1887
1935
  let restored = false;
1936
+ let escape;
1937
+ let caret;
1888
1938
  function draw(clear = false) {
1889
1939
  if (restored)
1890
1940
  return;
1891
1941
  const layout = layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition);
1892
1942
  let frame = HIDE_CURSOR;
1893
1943
  if (reserved !== layout.height || clear) {
1944
+ frame += clear && caret !== undefined ? up(Math.floor((caret - 1) / terminal.columns)) : "";
1894
1945
  frame += CLEAR_BELOW + `
1895
1946
  `.repeat(layout.height - 1) + up(layout.height - 1);
1896
1947
  reserved = layout.height;
1897
1948
  }
1949
+ caret = caretColumn(state, layout);
1898
1950
  frame += `\r${render(state, layout, theme, preview).map((line) => `\x1B[2K${line}`).join(`
1899
- `)}${up(layout.height - 1)}\r\x1B[${caretColumn(state, layout)}G${SHOW_CURSOR}`;
1951
+ `)}${up(layout.height - 1)}\r\x1B[${caret}G${SHOW_CURSOR}`;
1900
1952
  terminal.write(frame);
1901
1953
  }
1954
+ function pageSize() {
1955
+ const headerLines = state.header === "" ? 0 : state.header.split(`
1956
+ `).length;
1957
+ return Math.max(1, layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition).listHeight - 2 - headerLines);
1958
+ }
1902
1959
  function refreshPreview() {
1903
1960
  const at = highlighted(state);
1904
1961
  if (settings.preview === undefined || at === shownLine)
@@ -1928,6 +1985,7 @@ async function select(rows, settings, terminal = openTerminal()) {
1928
1985
  return;
1929
1986
  restored = true;
1930
1987
  clearTimeout(previewTimer);
1988
+ clearTimeout(escape);
1931
1989
  previewing.abort();
1932
1990
  terminal.write(`${CLEAR_BELOW}${PASTE_OFF}${SHOW_CURSOR}`);
1933
1991
  terminal.close();
@@ -1966,7 +2024,7 @@ async function select(rows, settings, terminal = openTerminal()) {
1966
2024
  if (key === OPEN || typeof key === "object" && "ctrl" in key && key.ctrl === reload?.key)
1967
2025
  await reloadRows();
1968
2026
  else
1969
- state = press(state, key);
2027
+ state = press({ ...state, page: pageSize() }, key);
1970
2028
  }
1971
2029
  } finally {
1972
2030
  draining = false;
@@ -1979,6 +2037,20 @@ async function select(rows, settings, terminal = openTerminal()) {
1979
2037
  draw();
1980
2038
  }
1981
2039
  function onChunk(chunk) {
2040
+ if (escape !== undefined) {
2041
+ clearTimeout(escape);
2042
+ escape = undefined;
2043
+ chunk = `\x1B${chunk}`;
2044
+ } else if (chunk === "\x1B") {
2045
+ escape = setTimeout(() => {
2046
+ escape = undefined;
2047
+ takeKeys("\x1B");
2048
+ }, ESCAPE_WAIT_MS);
2049
+ return;
2050
+ }
2051
+ takeKeys(chunk);
2052
+ }
2053
+ function takeKeys(chunk) {
1982
2054
  const keys = parseKeys(chunk, paste, [...settings.expect ?? [], ...reload?.key === undefined ? [] : [reload.key]]);
1983
2055
  if (busy && keys.includes("cancel")) {
1984
2056
  stop();
@@ -2092,13 +2164,12 @@ import { dirname as dirname6, join as join8, relative, resolve as resolve3 } fro
2092
2164
  var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
2093
2165
  var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
2094
2166
  var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
2095
- var SKIPPED2 = new Set(["node_modules", "vendor", "dist", "build", "target", "venv", "__pycache__"]);
2096
2167
  var ACTIONS = ["shell", "logs", "restart", "up", "stop", "build"];
2097
2168
  function from(dir, command) {
2098
2169
  return dir === "." ? command : ["env", "-C", dir, ...command];
2099
2170
  }
2100
2171
  function children(dir = ".") {
2101
- return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
2172
+ return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED_FOLDERS.has(child.name)).map((child) => child.name).sort();
2102
2173
  }
2103
2174
  function up2(dir) {
2104
2175
  const dirs = [];
package/dist/sf-cli.js CHANGED
@@ -627,7 +627,6 @@ var DASHES = /^-+/;
627
627
  function jsonOption(holds) {
628
628
  return { type: "boolean", description: holds === undefined ? "print JSON" : `print JSON: ${holds}` };
629
629
  }
630
-
631
630
  class Exit extends Error {
632
631
  code;
633
632
  hint;
@@ -1032,6 +1031,8 @@ function composeError({ code, stderr }) {
1032
1031
  `).at(-1) ?? "";
1033
1032
  return last === "" ? `exit ${code}` : last;
1034
1033
  }
1034
+ // ../kit/src/folders.ts
1035
+ var SKIPPED_FOLDERS = new Set(["node_modules", "vendor", "dist", "build", "target", "coverage", "venv", "__pycache__"]);
1035
1036
  // ../kit/src/secrets.ts
1036
1037
  var SECRET_PART = /token|secret|passw|credential|private|dsn|apikey|accesskey|key$/i;
1037
1038
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
@@ -1156,6 +1157,14 @@ var KEYS = {
1156
1157
  "\x1B[B": "down",
1157
1158
  "\x1BOB": "down",
1158
1159
  "\x0E": "down",
1160
+ "\x1B[5~": "pageup",
1161
+ "\x1B[6~": "pagedown",
1162
+ "\x1B[H": "home",
1163
+ "\x1B[1~": "home",
1164
+ "\x1BOH": "home",
1165
+ "\x1B[F": "end",
1166
+ "\x1B[4~": "end",
1167
+ "\x1BOF": "end",
1159
1168
  "\r": "enter",
1160
1169
  "\n": "enter",
1161
1170
  "\x03": "cancel",
@@ -1164,7 +1173,10 @@ var KEYS = {
1164
1173
  "\b": "erase",
1165
1174
  "\x15": "clear",
1166
1175
  "\x17": "word",
1167
- "\t": "tab"
1176
+ "\t": "tab",
1177
+ "\x1F": "help",
1178
+ "\x1BOP": "help",
1179
+ "\x1B[11~": "help"
1168
1180
  };
1169
1181
  var CTRL_KEY = /^ctrl-[abd-gkln-z]$/;
1170
1182
  function repeat(value, count) {
@@ -1234,7 +1246,7 @@ function parseKeys(input, paste = { active: false }, ctrls = []) {
1234
1246
  });
1235
1247
  }
1236
1248
  function createState(rows, { multi = false, prompt = "> ", query = "", header = "", expect = [], searched = Number.POSITIVE_INFINITY } = {}) {
1237
- const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, marked: new Set, multi, prompt, done: undefined };
1249
+ const state = { rows: [], items: [], styles: [], searchable: [], searched, header, expect, key: "", status: "", query, matches: [], cursor: 0, help: false, page: 10, marked: new Set, multi, prompt, done: undefined };
1238
1250
  return withRows(state, rows);
1239
1251
  }
1240
1252
  function withRows(state, rows, header = state.header) {
@@ -1256,6 +1268,8 @@ function highlighted(state) {
1256
1268
  function press(state, key) {
1257
1269
  if (state.done !== undefined)
1258
1270
  return state;
1271
+ if (state.help)
1272
+ return { ...state, help: false };
1259
1273
  if (typeof key === "object") {
1260
1274
  if ("text" in key)
1261
1275
  return withQuery(state, state.query + key.text);
@@ -1266,8 +1280,18 @@ function press(state, key) {
1266
1280
  return move(state, -1);
1267
1281
  case "down":
1268
1282
  return move(state, 1);
1283
+ case "pageup":
1284
+ return move(state, -state.page);
1285
+ case "pagedown":
1286
+ return move(state, state.page);
1287
+ case "home":
1288
+ return move(state, -state.matches.length);
1289
+ case "end":
1290
+ return move(state, state.matches.length);
1269
1291
  case "cancel":
1270
1292
  return { ...state, done: "cancel" };
1293
+ case "help":
1294
+ return { ...state, help: true };
1271
1295
  case "erase":
1272
1296
  return withQuery(state, [...state.query].slice(0, -1).join(""));
1273
1297
  case "clear":
@@ -1447,7 +1471,18 @@ function fitStyled(line, width, plain = identity) {
1447
1471
  function caretColumn(state, layout) {
1448
1472
  return Math.min(layout.listWidth, Bun.stringWidth(state.prompt) + Bun.stringWidth(state.query) + 1);
1449
1473
  }
1474
+ function helpLines(state, layout, theme) {
1475
+ const lines = [
1476
+ "type to filter, up, down, ctrl-p, ctrl-n, pgup, pgdn, home, end move",
1477
+ `enter picks${state.multi ? ", tab marks" : ""}, ctrl-u clears the query, ctrl-w a word, esc or ctrl-c cancels`,
1478
+ ...state.expect.length > 0 ? [`${state.expect.join(", ")} end the pick as this command asks`] : []
1479
+ ];
1480
+ const title = "keys, any key closes this help";
1481
+ return [{ text: theme.accent(title), width: title.length }, ...lines.map((line) => fit(` ${line}`, layout.listWidth, theme.label))];
1482
+ }
1450
1483
  function listLines(state, layout, theme) {
1484
+ if (state.help)
1485
+ return helpLines(state, layout, theme);
1451
1486
  const header = state.header === "" ? [] : state.header.split(`
1452
1487
  `).map((line) => fitStyled(line, layout.listWidth, theme.label));
1453
1488
  const rows = Math.max(1, layout.listHeight - 2 - header.length);
@@ -1456,7 +1491,8 @@ function listLines(state, layout, theme) {
1456
1491
  const marks = (state.marked.size > 0 ? ` (${state.marked.size} marked)` : "") + (state.status === "" ? "" : ` ${state.status}`);
1457
1492
  const promptWidth = Bun.stringWidth(state.prompt);
1458
1493
  const query = fit(state.query, Math.max(1, layout.listWidth - promptWidth), identity);
1459
- const count = fit(` ${state.matches.length}/${state.items.length}${marks}`, layout.listWidth, theme.label);
1494
+ const place = state.matches.length > rows ? ` (${state.cursor + 1} of ${state.matches.length})` : "";
1495
+ const count = fit(` ${state.matches.length}/${state.items.length}${place}${marks}`, layout.listWidth, theme.label);
1460
1496
  const lines = [{ text: theme.accent(state.prompt) + query.text, width: promptWidth + query.width }, count, ...header];
1461
1497
  if (state.matches.length === 0)
1462
1498
  lines.push(fit(" no match", layout.listWidth, theme.label));
@@ -1483,6 +1519,7 @@ function render(state, layout, theme, preview = []) {
1483
1519
 
1484
1520
  // ../kit/src/picker/terminal.ts
1485
1521
  var PREVIEW_DELAY_MS = 120;
1522
+ var ESCAPE_WAIT_MS = 30;
1486
1523
  var OPEN = { ctrl: "open" };
1487
1524
  var HIDE_CURSOR = "\x1B[?25l";
1488
1525
  var SHOW_CURSOR = "\x1B[?25h";
@@ -1494,14 +1531,24 @@ function up(lines) {
1494
1531
  return lines > 0 ? `\x1B[${lines}A` : "";
1495
1532
  }
1496
1533
  function openTerminal() {
1497
- let input;
1534
+ let mode;
1498
1535
  try {
1499
- input = new tty.ReadStream(openSync("/dev/tty", "r"));
1536
+ mode = new tty.ReadStream(openSync("/dev/tty", "r"));
1500
1537
  } catch {
1501
1538
  return fail("no terminal to read keys from (/dev/tty)");
1502
1539
  }
1540
+ const keys = Bun.file("/dev/tty").stream().getReader();
1503
1541
  const out = process14.stderr;
1504
1542
  let resize;
1543
+ async function read(onChunk) {
1544
+ const decoder = new TextDecoder;
1545
+ try {
1546
+ for (let chunk = await keys.read();!chunk.done; chunk = await keys.read())
1547
+ onChunk(decoder.decode(chunk.value, { stream: true }));
1548
+ } catch {
1549
+ onChunk("\x03");
1550
+ }
1551
+ }
1505
1552
  return {
1506
1553
  get columns() {
1507
1554
  return out.columns || 80;
@@ -1513,17 +1560,18 @@ function openTerminal() {
1513
1560
  write: (text) => void out.write(text),
1514
1561
  listen(onChunk, onResize) {
1515
1562
  resize = onResize;
1516
- input.setRawMode(true);
1563
+ mode.setRawMode(true);
1517
1564
  out.on("resize", onResize);
1518
- input.on("data", (chunk) => onChunk(chunk.toString()));
1565
+ read(onChunk);
1519
1566
  },
1520
1567
  close() {
1521
1568
  if (resize !== undefined)
1522
1569
  out.off("resize", resize);
1523
1570
  try {
1524
- input.setRawMode(false);
1571
+ mode.setRawMode(false);
1525
1572
  } finally {
1526
- input.destroy();
1573
+ mode.destroy();
1574
+ keys.cancel();
1527
1575
  }
1528
1576
  }
1529
1577
  };
@@ -1538,20 +1586,29 @@ async function select(rows, settings, terminal = openTerminal()) {
1538
1586
  let shownLine;
1539
1587
  let finish;
1540
1588
  let restored = false;
1589
+ let escape;
1590
+ let caret;
1541
1591
  function draw(clear = false) {
1542
1592
  if (restored)
1543
1593
  return;
1544
1594
  const layout = layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition);
1545
1595
  let frame = HIDE_CURSOR;
1546
1596
  if (reserved !== layout.height || clear) {
1597
+ frame += clear && caret !== undefined ? up(Math.floor((caret - 1) / terminal.columns)) : "";
1547
1598
  frame += CLEAR_BELOW + `
1548
1599
  `.repeat(layout.height - 1) + up(layout.height - 1);
1549
1600
  reserved = layout.height;
1550
1601
  }
1602
+ caret = caretColumn(state, layout);
1551
1603
  frame += `\r${render(state, layout, theme, preview).map((line) => `\x1B[2K${line}`).join(`
1552
- `)}${up(layout.height - 1)}\r\x1B[${caretColumn(state, layout)}G${SHOW_CURSOR}`;
1604
+ `)}${up(layout.height - 1)}\r\x1B[${caret}G${SHOW_CURSOR}`;
1553
1605
  terminal.write(frame);
1554
1606
  }
1607
+ function pageSize() {
1608
+ const headerLines = state.header === "" ? 0 : state.header.split(`
1609
+ `).length;
1610
+ return Math.max(1, layoutFor({ columns: terminal.columns, rows: terminal.rows }, settings.height, settings.preview !== undefined, settings.previewPosition).listHeight - 2 - headerLines);
1611
+ }
1555
1612
  function refreshPreview() {
1556
1613
  const at = highlighted(state);
1557
1614
  if (settings.preview === undefined || at === shownLine)
@@ -1581,6 +1638,7 @@ async function select(rows, settings, terminal = openTerminal()) {
1581
1638
  return;
1582
1639
  restored = true;
1583
1640
  clearTimeout(previewTimer);
1641
+ clearTimeout(escape);
1584
1642
  previewing.abort();
1585
1643
  terminal.write(`${CLEAR_BELOW}${PASTE_OFF}${SHOW_CURSOR}`);
1586
1644
  terminal.close();
@@ -1619,7 +1677,7 @@ async function select(rows, settings, terminal = openTerminal()) {
1619
1677
  if (key === OPEN || typeof key === "object" && "ctrl" in key && key.ctrl === reload?.key)
1620
1678
  await reloadRows();
1621
1679
  else
1622
- state = press(state, key);
1680
+ state = press({ ...state, page: pageSize() }, key);
1623
1681
  }
1624
1682
  } finally {
1625
1683
  draining = false;
@@ -1632,6 +1690,20 @@ async function select(rows, settings, terminal = openTerminal()) {
1632
1690
  draw();
1633
1691
  }
1634
1692
  function onChunk(chunk) {
1693
+ if (escape !== undefined) {
1694
+ clearTimeout(escape);
1695
+ escape = undefined;
1696
+ chunk = `\x1B${chunk}`;
1697
+ } else if (chunk === "\x1B") {
1698
+ escape = setTimeout(() => {
1699
+ escape = undefined;
1700
+ takeKeys("\x1B");
1701
+ }, ESCAPE_WAIT_MS);
1702
+ return;
1703
+ }
1704
+ takeKeys(chunk);
1705
+ }
1706
+ function takeKeys(chunk) {
1635
1707
  const keys = parseKeys(chunk, paste, [...settings.expect ?? [], ...reload?.key === undefined ? [] : [reload.key]]);
1636
1708
  if (busy && keys.includes("cancel")) {
1637
1709
  stop();
@@ -1738,11 +1810,9 @@ function list(rows) {
1738
1810
 
1739
1811
  // ../kit/src/prompt.ts
1740
1812
  import process16 from "process";
1741
- var KEY_SEQUENCES2 = /\x1B\[[0-9;]*[A-Z~]|\x1BO[A-Z]|[\s\S]/giu;
1742
- var CONTROL3 = /^[\x00-\x1F\x7F]/;
1743
- var KEYS2 = { "\x1B[A": "up", "\x1BOA": "up", "\x1B[B": "down", "\x1BOB": "down", "\r": "enter", "\n": "enter", "\x03": "cancel", "\x1B": "cancel", "\x7F": "erase", "\b": "erase" };
1744
- function readKeys(input) {
1745
- return (input.match(KEY_SEQUENCES2) ?? []).flatMap((sequence) => KEYS2[sequence] ?? (CONTROL3.test(sequence) ? [] : [{ text: sequence }]));
1813
+ var PROMPT_KEYS = new Set(["up", "down", "pageup", "pagedown", "home", "end", "enter", "cancel", "erase"]);
1814
+ function readKeys(input, paste = { active: false }) {
1815
+ return parseKeys(input, paste).filter((key) => typeof key === "object" ? "text" in key : PROMPT_KEYS.has(key));
1746
1816
  }
1747
1817
  async function input2(question, { default: fallback, mask = false, validate, ...settings } = {}) {
1748
1818
  let value = "";
@@ -1781,22 +1851,29 @@ async function ask(question, { hint, terminal = process16 }, render, press) {
1781
1851
  if (!isInteractive(terminal))
1782
1852
  fail(`cannot ask "${question}" off a terminal`, 2, hint);
1783
1853
  const colors = paint(stderr);
1784
- let drawn = 0;
1854
+ let drawn = [];
1855
+ let current = "asking";
1785
1856
  let outcome;
1857
+ const columns = () => stderr.columns || 80;
1786
1858
  const draw = (state) => {
1787
- const frame = render(colors, state, { columns: stderr.columns || 80, rows: stderr.rows || 24 });
1788
- stderr.write(`${drawn > 1 ? `\x1B[${drawn - 1}A` : ""}\r\x1B[0J${frame.join(`
1859
+ const frame = render(colors, state, { columns: columns(), rows: stderr.rows || 24 });
1860
+ const rows = drawn.reduce((sum, line) => sum + Math.max(1, Math.ceil(Bun.stringWidth(line) / columns())), 0);
1861
+ stderr.write(`${rows > 1 ? `\x1B[${rows - 1}A` : ""}\r\x1B[0J${frame.join(`
1789
1862
  `)}`);
1790
- drawn = frame.length;
1863
+ drawn = frame;
1864
+ current = state;
1791
1865
  };
1866
+ const redraw = () => draw(current);
1867
+ const paste = { active: false };
1792
1868
  try {
1869
+ stderr.on?.("resize", redraw);
1793
1870
  stdin.setRawMode(true);
1794
1871
  stdin.resume();
1795
- stderr.write("\x1B[?25l");
1872
+ stderr.write("\x1B[?25l\x1B[?2004h");
1796
1873
  draw("asking");
1797
1874
  return await new Promise((resolve) => {
1798
1875
  function onData(data) {
1799
- for (const key of readKeys(data.toString())) {
1876
+ for (const key of readKeys(data.toString(), paste)) {
1800
1877
  const pressed = press(key);
1801
1878
  if (pressed !== undefined) {
1802
1879
  settle(pressed);
@@ -1818,9 +1895,10 @@ async function ask(question, { hint, terminal = process16 }, render, press) {
1818
1895
  stdin.on("end", onEnd);
1819
1896
  });
1820
1897
  } finally {
1898
+ stderr.off?.("resize", redraw);
1821
1899
  draw(outcome ?? "cancel");
1822
1900
  stderr.write(`
1823
- \x1B[?25h`);
1901
+ \x1B[?25h\x1B[?2004l`);
1824
1902
  stdin.setRawMode(false);
1825
1903
  stdin.pause();
1826
1904
  }
@@ -2184,13 +2262,12 @@ class Redactor {
2184
2262
  var LABEL_FORMAT = ["project", "project.config_files", "project.environment_file", "project.working_dir"].map((label) => `{{.Label "com.docker.compose.${label}"}}`).join("\t");
2185
2263
  var COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m;
2186
2264
  var COMPOSE_FILES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
2187
- var SKIPPED2 = new Set(["node_modules", "vendor", "dist", "build", "target", "venv", "__pycache__"]);
2188
2265
  var ACTIONS = ["shell", "logs", "restart", "up", "stop", "build"];
2189
2266
  function from(dir, command) {
2190
2267
  return dir === "." ? command : ["env", "-C", dir, ...command];
2191
2268
  }
2192
2269
  function children(dir = ".") {
2193
- return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED2.has(child.name)).map((child) => child.name).sort();
2270
+ return readdirSync(dir, { withFileTypes: true }).filter((child) => child.isDirectory() && !child.name.startsWith(".") && !SKIPPED_FOLDERS.has(child.name)).map((child) => child.name).sort();
2194
2271
  }
2195
2272
  function up2(dir) {
2196
2273
  const dirs = [];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaynooo/cli-docker",
3
3
  "type": "module",
4
- "version": "0.7.0",
4
+ "version": "0.8.1",
5
5
  "description": "dc, dx, dlog and sf: docker compose on the stack found here, including one only its containers tell of, a compose service's shell, logs, restart, stop or rebuild, every service's health and recent errors in one call, and a Symfony console command picked and run.",
6
6
  "author": "Kaynooo <contact@kaynooo.fr>",
7
7
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "sf": "$npm_execpath src/sf-cli.ts"
38
38
  },
39
39
  "devDependencies": {
40
- "@kaynooo/cli-env": "0.11.0",
41
- "@kaynooo/cli-kit": "0.22.0"
40
+ "@kaynooo/cli-env": "0.12.0",
41
+ "@kaynooo/cli-kit": "0.27.1"
42
42
  }
43
43
  }
package/src/stack.ts CHANGED
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'
3
3
  import { dirname, join, relative, resolve } from 'node:path'
4
4
  import { loadKnown } from '@kaynooo/cli-env/files'
5
5
  import { Redactor } from '@kaynooo/cli-env/redactor'
6
- import { captureOutput, composeError } from '@kaynooo/cli-kit'
6
+ import { captureOutput, composeError, SKIPPED_FOLDERS } from '@kaynooo/cli-kit'
7
7
 
8
8
  /** How to reach one compose project: the command a user runs, and the one this package lists it with. */
9
9
  export interface Stack {
@@ -54,7 +54,6 @@ export interface Container {
54
54
  const LABEL_FORMAT = ['project', 'project.config_files', 'project.environment_file', 'project.working_dir'].map(label => `{{.Label "com.docker.compose.${label}"}}`).join('\t')
55
55
  const COMPOSE_FILE_VAR = /^\s*(?:export\s+)?COMPOSE_FILE\s*=\s*\S/m
56
56
  const COMPOSE_FILES = ['compose.yaml', 'compose.yml', 'docker-compose.yaml', 'docker-compose.yml']
57
- const SKIPPED = new Set(['node_modules', 'vendor', 'dist', 'build', 'target', 'venv', '__pycache__'])
58
57
 
59
58
  export const ACTIONS: Action[] = ['shell', 'logs', 'restart', 'up', 'stop', 'build']
60
59
 
@@ -66,7 +65,7 @@ export function from(dir: string, command: string[]): string[] {
66
65
  /** The direct child folders, hidden and built ones aside. */
67
66
  export function children(dir = '.'): string[] {
68
67
  return readdirSync(dir, { withFileTypes: true })
69
- .filter(child => child.isDirectory() && !child.name.startsWith('.') && !SKIPPED.has(child.name))
68
+ .filter(child => child.isDirectory() && !child.name.startsWith('.') && !SKIPPED_FOLDERS.has(child.name))
70
69
  .map(child => child.name)
71
70
  .sort()
72
71
  }