@kaynooo/cli-docker 0.5.0 → 0.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaynooo/cli-docker",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
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.8.1",
41
- "@kaynooo/cli-kit": "0.20.0"
40
+ "@kaynooo/cli-env": "0.10.0",
41
+ "@kaynooo/cli-kit": "0.21.1"
42
42
  }
43
43
  }
package/src/dc-cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env -S bun --no-env-file
2
2
 
3
3
  import process from 'node:process'
4
4
  import { completion, parse, runMain } from '@kaynooo/cli-kit'
package/src/dlog-cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env -S bun --no-env-file
2
2
 
3
3
  import process from 'node:process'
4
4
  import { runMain } from '@kaynooo/cli-kit'
package/src/dx-cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env -S bun --no-env-file
2
2
 
3
3
  import process from 'node:process'
4
4
  import { runMain } from '@kaynooo/cli-kit'
package/src/dx.ts CHANGED
@@ -4,14 +4,14 @@ import process from 'node:process'
4
4
  import { loadKnown } from '@kaynooo/cli-env/files'
5
5
  import { Redactor } from '@kaynooo/cli-env/redactor'
6
6
  import { closest, fail, jsonOption, paint, parse, printJson, wholeNumber } from '@kaynooo/cli-kit'
7
- import { byRecency, record, runs } from '@kaynooo/cli-kit/fzf'
7
+ import { byRecency, record, runs } from '@kaynooo/cli-kit/picker'
8
8
  import { KEY_LABELS, KEYS, pickService } from './picker'
9
9
  import { ACTIONS, allServices, commandFor, defaultAction, key, LOGS_TAIL, logsOf, serviceNamed, stateColor } from './stack'
10
10
 
11
11
  const usage = `dx [service] [action]
12
12
  dx -
13
13
 
14
- Without a terminal or without fzf (a script, a pipe, an agent) \`dx\` lists the services with their state, ports and image: that listing is the form to use there, \`--json\` giving the full record. On a terminal with fzf it picks one instead: 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.
14
+ Without a terminal (a script, a pipe, an agent) \`dx\` lists the services with their state, ports and image: that listing is the form to use there, \`--json\` giving the full record. On a terminal it picks one instead: 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.
15
15
  \`dx -\` does the last action again, \`dx <service> [action]\` acts without the picker: \`shell\` (bash when the container has it), \`logs\` (follows the last ${LOGS_TAIL} lines; \`--tail N\` changes the count and \`--no-follow\` prints them once and exits, for a script), \`restart\`, \`up\`, \`stop\` or \`build\` (up with a rebuild).
16
16
  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\`).`
17
17
 
@@ -141,12 +141,9 @@ export async function dx(args: string[]): Promise<number> {
141
141
  if (named === undefined) {
142
142
  const interactive = process.stdin.isTTY && process.stderr.isTTY && parsed.values.json !== true
143
143
 
144
- if (!interactive || Bun.which('fzf') === null) {
144
+ if (!interactive) {
145
145
  if (emit)
146
- fail('the picker needs a terminal and fzf', 1)
147
-
148
- if (interactive)
149
- console.error('fzf is not installed, listing the services instead')
146
+ fail('the picker needs a terminal', 1)
150
147
 
151
148
  if (parsed.values.json === true)
152
149
  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 })))
@@ -156,7 +153,7 @@ export async function dx(args: string[]): Promise<number> {
156
153
  return 0
157
154
  }
158
155
 
159
- const picked = pickService(byRecency(services, runs('dx'), key))
156
+ const picked = await pickService(byRecency(services, runs('dx'), key))
160
157
 
161
158
  if (picked === undefined)
162
159
  return 1
package/src/picker.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { Row } from '@kaynooo/cli-kit/fzf'
1
+ import type { Row } from '@kaynooo/cli-kit/picker'
2
2
  import type { Action, Service } from './stack'
3
3
  import process from 'node:process'
4
4
  import { paint } from '@kaynooo/cli-kit'
5
- import { fzf, heightFor } from '@kaynooo/cli-kit/fzf'
5
+ import { heightFor, pick } from '@kaynooo/cli-kit/picker'
6
6
  import { key, stateColor } from './stack'
7
7
 
8
8
  export const KEYS: Record<string, Action> = { 'ctrl-l': 'logs', 'ctrl-r': 'restart', 'ctrl-x': 'stop', 'ctrl-b': 'build' }
@@ -21,9 +21,9 @@ function row(service: Service): Row {
21
21
  }
22
22
  }
23
23
 
24
- // The service picked with fzf and the key that picked it, enter's being empty; undefined on escape.
25
- export function pickService(services: Service[]): { service: Service, key: string } | undefined {
26
- const picked = fzf(services.map(row), {
24
+ // The service picked and the key that picked it, enter's being empty; undefined on escape.
25
+ export async function pickService(services: Service[]): Promise<{ service: Service, key: string } | undefined> {
26
+ const picked = await pick(services.map(row), {
27
27
  prompt: 'dx> ',
28
28
  header: 'enter shell or up, ctrl-l logs, ctrl-r restart, ctrl-x stop, ctrl-b rebuild',
29
29
  expect: Object.keys(KEYS),
package/src/sf-cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env -S bun --no-env-file
2
2
 
3
3
  import process from 'node:process'
4
4
  import { runMain } from '@kaynooo/cli-kit'
package/src/sf.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type { ExitRow } from '@kaynooo/cli-kit'
2
- import type { Arg, Row } from '@kaynooo/cli-kit/fzf'
2
+ import type { Arg, Row } from '@kaynooo/cli-kit/picker'
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
4
4
  import { homedir } from 'node:os'
5
5
  import { dirname, join, resolve } from 'node:path'
6
6
  import process from 'node:process'
7
7
  import { fail, paint, parse, printJson } from '@kaynooo/cli-kit'
8
- import { byRecency, emit, fzf, list, record, runs, table } from '@kaynooo/cli-kit/fzf'
8
+ import { byRecency, emit, list, pick, record, runs, table } from '@kaynooo/cli-kit/picker'
9
9
  import { input } from '@kaynooo/cli-kit/prompt'
10
10
  import { children, from, nearestStack, services, up } from './stack'
11
11
 
@@ -48,7 +48,7 @@ export interface Entry {
48
48
  const usage = `sf [command] [args...]
49
49
  sf -
50
50
 
51
- Without a terminal or without fzf (a script, a pipe, an agent) \`sf\` lists the commands, one per line with its description: that listing is the form to use there, and \`sf --list --json\` gives each with its arguments and options. On a terminal with fzf it picks a Symfony console command instead and runs it, asking for each required argument it needs. The commands last run come first, per folder, and the picker opens on the list cached from the last run while the console answers.
51
+ Without a terminal (a script, a pipe, an agent) \`sf\` lists the commands, one per line with its description: that listing is the form to use there, and \`sf --list --json\` gives each with its arguments and options. On a terminal it picks a Symfony console command instead and runs it, asking for each required argument it needs. The commands last run come first, per folder, and the picker opens on the list cached from the last run while the console answers.
52
52
  The console is the nearest bin/console up to the repository root, then those of the child folders (keyed \`api/cache:clear\`). It runs in the php service of the nearest compose stack when there is one, else with the local php; the container's working directory is taken to be the app.
53
53
  \`sf -\` runs the last command again with the arguments it ran with, \`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.
54
54
  \`sf --list --json\` (or \`sf --json\`) prints every command as JSON, each with its console folder, description, signature, arguments and options.
@@ -56,7 +56,7 @@ The hidden flags --list, --names, --rows, --show, --record and --emit are for te
56
56
 
57
57
  const EXITS: ExitRow[] = [['the code of the console', 'a command it ran'], [0, 'it listed the commands'], [1, 'no bin/console here, or it listed no command'], [2, 'a usage error']]
58
58
 
59
- /** Fzf paints its rows and previews itself, so these keep their colors whatever stream they are written to. */
59
+ /** Fzf paints its rows and previews itself, so these keep their colors whatever stream they are written to; `paint` still honours `NO_COLOR`. */
60
60
  const colors = paint({ isTTY: true })
61
61
  const MARKUP = /<(info|comment|question|error)>(.*?)<\/\1>/gs
62
62
  const TAGS = { info: 'green', comment: 'yellow', question: 'cyan', error: 'red' } as const
@@ -281,7 +281,7 @@ async function hidden(flag: string, rest: string[]): Promise<number | undefined>
281
281
  return 0
282
282
  }
283
283
 
284
- // Called back by fzf once the picker is open on the cached list.
284
+ // Brings the cache of the consoles up to date, which `--list` starts in the background for the next time.
285
285
  if (flag === '--rows') {
286
286
  console.log(table(rows(await entries(true))).join('\n'))
287
287
 
@@ -301,7 +301,7 @@ async function hidden(flag: string, rest: string[]): Promise<number | undefined>
301
301
  return 0
302
302
  }
303
303
 
304
- // From the cache, which a list run in the background brings up to date for the next time, as fzf's reload does.
304
+ // From the cache, which a list run in the background brings up to date for the next time, as the picker's reload does.
305
305
  if (refreshed)
306
306
  Bun.spawn([process.execPath, process.argv[1]!, '--rows'], { stdio: ['ignore', 'ignore', 'ignore'] }).unref()
307
307
 
@@ -375,27 +375,23 @@ export async function sf(args: string[]): Promise<number> {
375
375
  if (found.length === 0)
376
376
  fail('no bin/console here, or it listed no command', 1)
377
377
 
378
- if (!interactive() || Bun.which('fzf') === null) {
378
+ if (!interactive()) {
379
379
  if (emitting)
380
- fail('the picker needs a terminal and fzf', 1)
381
-
382
- if (interactive())
383
- console.error('fzf is not installed, listing the commands instead')
380
+ fail('the picker needs a terminal', 1)
384
381
 
385
382
  console.log(listing(found))
386
383
 
387
384
  return 0
388
385
  }
389
386
 
390
- const self = `${process.execPath} ${process.argv[1]}`
391
- const picked = fzf(rows(found), {
387
+ const picked = await pick(rows(found), {
392
388
  prompt: 'sf> ',
393
389
  searched: 1,
394
- preview: `${self} --show {1} {2}`,
395
- // Once the cached list is in, and only once, the reload firing load again.
396
- bind: refreshed ? [`load:unbind(load)+reload-sync(${self} --rows)`] : [],
390
+ preview: `${process.execPath} ${process.argv[1]} --show {1} {2}`,
391
+ // Opens on the cached list, which the consoles' own answer replaces once it is in.
392
+ reload: refreshed ? { onOpen: true, run: async () => ({ rows: rows(await entries(true)) }) } : undefined,
397
393
  })
398
- // Read again, the reload having maybe brought a command the cached list did not hold.
394
+ // Read again, the reload having maybe brought a command the first list did not hold.
399
395
  const entry = picked && (await entries(false)).find(candidate => candidate.app.dir === picked.hidden[0] && candidate.command.name === picked.hidden[1])
400
396
 
401
397
  return entry === undefined ? 1 : execute(emitting, key(entry), key(entry), commandFor(entry, []))