@kaynooo/cli-docker 0.5.1 → 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/README.md +2 -2
- package/dist/dc-cli.js +246 -58
- package/dist/dlog-cli.js +246 -58
- package/dist/dx-cli.js +903 -128
- package/dist/sf-cli.js +910 -136
- package/package.json +3 -3
- package/src/dx.ts +5 -8
- package/src/picker.ts +5 -5
- package/src/sf.ts +13 -17
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaynooo/cli-docker",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "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.
|
|
41
|
-
"@kaynooo/cli-kit": "0.21.
|
|
40
|
+
"@kaynooo/cli-env": "0.10.0",
|
|
41
|
+
"@kaynooo/cli-kit": "0.21.1"
|
|
42
42
|
}
|
|
43
43
|
}
|
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/
|
|
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
|
|
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
|
|
144
|
+
if (!interactive) {
|
|
145
145
|
if (emit)
|
|
146
|
-
fail('the picker needs a terminal
|
|
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/
|
|
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 {
|
|
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
|
|
25
|
-
export function pickService(services: Service[]): { service: Service, key: string } | undefined {
|
|
26
|
-
const picked =
|
|
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.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ExitRow } from '@kaynooo/cli-kit'
|
|
2
|
-
import type { Arg, Row } from '@kaynooo/cli-kit/
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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()
|
|
378
|
+
if (!interactive()) {
|
|
379
379
|
if (emitting)
|
|
380
|
-
fail('the picker needs a terminal
|
|
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
|
|
391
|
-
const picked = fzf(rows(found), {
|
|
387
|
+
const picked = await pick(rows(found), {
|
|
392
388
|
prompt: 'sf> ',
|
|
393
389
|
searched: 1,
|
|
394
|
-
preview: `${
|
|
395
|
-
//
|
|
396
|
-
|
|
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
|
|
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, []))
|