mirror of
https://github.com/langgenius/dify.git
synced 2026-06-23 12:31:13 +08:00
fix(cli): make auth devices revoke --yes a real flag (#37740)
This commit is contained in:
parent
7aa20d6d94
commit
1d74bff311
@ -146,6 +146,43 @@ describe('runDevicesRevoke', () => {
|
||||
expect(saved?.hosts[mock.url]).toBeUndefined()
|
||||
})
|
||||
|
||||
it('TTY without --yes: prompts and aborts on decline (no revoke)', async () => {
|
||||
const base = bufferStreams('n\n')
|
||||
const io = { ...base, isErrTTY: true }
|
||||
const store = new MemStore()
|
||||
const { reg, active } = buildRegistry(mock.url, 'tester@dify.ai', 'tok-1')
|
||||
const http = testHttpClient(mock.url, 'dfoa_test')
|
||||
|
||||
await expect(runDevicesRevoke({ io, reg, active, store, http, all: true }))
|
||||
.rejects
|
||||
.toThrow(/aborted by user/)
|
||||
expect(base.errBuf()).toContain('Revoke 2 session(s)? [y/N]')
|
||||
expect(base.outBuf()).not.toContain('Revoked')
|
||||
})
|
||||
|
||||
it('TTY without --yes: proceeds on accept', async () => {
|
||||
const base = bufferStreams('y\n')
|
||||
const io = { ...base, isErrTTY: true }
|
||||
const store = new MemStore()
|
||||
const { reg, active } = buildRegistry(mock.url, 'tester@dify.ai', 'tok-1')
|
||||
const http = testHttpClient(mock.url, 'dfoa_test')
|
||||
|
||||
await runDevicesRevoke({ io, reg, active, store, http, target: 'tok-2', all: false })
|
||||
expect(base.outBuf()).toContain('Revoked 1 session(s)')
|
||||
})
|
||||
|
||||
it('TTY with --yes: skips prompt entirely', async () => {
|
||||
const base = bufferStreams()
|
||||
const io = { ...base, isErrTTY: true }
|
||||
const store = new MemStore()
|
||||
const { reg, active } = buildRegistry(mock.url, 'tester@dify.ai', 'tok-1')
|
||||
const http = testHttpClient(mock.url, 'dfoa_test')
|
||||
|
||||
await runDevicesRevoke({ io, reg, active, store, http, target: 'tok-2', all: false, yes: true })
|
||||
expect(base.errBuf()).not.toContain('[y/N]')
|
||||
expect(base.outBuf()).toContain('Revoked 1 session(s)')
|
||||
})
|
||||
|
||||
it('no target + no --all: throws UsageMissingArg', async () => {
|
||||
const io = bufferStreams()
|
||||
const store = new MemStore()
|
||||
|
||||
@ -8,6 +8,7 @@ import { BaseError } from '@/errors/base'
|
||||
import { ErrorCode } from '@/errors/codes'
|
||||
import { LIMIT_DEFAULT, LIMIT_MAX, parseLimit } from '@/limit/limit'
|
||||
import { colorEnabled, colorScheme } from '@/sys/io/color'
|
||||
import { promptConfirm } from '@/sys/io/prompt'
|
||||
import { runWithSpinner } from '@/sys/io/spinner'
|
||||
|
||||
export type DevicesListOptions = {
|
||||
@ -96,6 +97,17 @@ export async function runDevicesRevoke(opts: DevicesRevokeOptions): Promise<void
|
||||
return
|
||||
}
|
||||
|
||||
if (opts.yes !== true && opts.io.isErrTTY) {
|
||||
const confirmed = await promptConfirm(opts.io, `Revoke ${ids.length} session(s)? [y/N] `)
|
||||
if (!confirmed) {
|
||||
throw new BaseError({
|
||||
code: ErrorCode.UsageMissingArg,
|
||||
message: 'aborted by user',
|
||||
hint: 'pass --yes to skip confirmation',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of ids)
|
||||
await sessions.revoke(id)
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ export default class DevicesRevoke extends DifyCommand {
|
||||
static override flags = {
|
||||
'all': Flags.boolean({ description: 'revoke every session except the current one', default: false }),
|
||||
'http-retry': httpRetryFlag,
|
||||
'yes': Flags.boolean({ description: 'skip confirmation prompt', default: false }),
|
||||
'yes': Flags.boolean({ char: 'y', description: 'skip confirmation prompt', default: false }),
|
||||
}
|
||||
|
||||
async run(argv: string[]): Promise<void> {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { ActiveContext } from '@/auth/hosts'
|
||||
import type { HttpClient } from '@/http/types'
|
||||
import type { IOStreams } from '@/sys/io/streams'
|
||||
import * as readline from 'node:readline'
|
||||
import { MembersClient } from '@/api/members'
|
||||
import { BaseError } from '@/errors/base'
|
||||
import { ErrorCode } from '@/errors/codes'
|
||||
import { colorEnabled, colorScheme } from '@/sys/io/color'
|
||||
import { promptConfirm } from '@/sys/io/prompt'
|
||||
import { runWithSpinner } from '@/sys/io/spinner'
|
||||
import { nullStreams } from '@/sys/io/streams'
|
||||
import { resolveWorkspaceId } from '@/workspace/resolver'
|
||||
@ -76,15 +76,3 @@ export async function runDeleteMember(
|
||||
workspaceId: wsId,
|
||||
}
|
||||
}
|
||||
|
||||
async function promptConfirm(io: IOStreams, message: string): Promise<boolean> {
|
||||
io.err.write(message)
|
||||
const rl = readline.createInterface({ input: io.in, output: io.err, terminal: false })
|
||||
try {
|
||||
const line: string = await new Promise(resolve => rl.once('line', resolve))
|
||||
return line.trim().toLowerCase() === 'y'
|
||||
}
|
||||
finally {
|
||||
rl.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,18 @@ function normalize(raw: string, opts: Pick<PromptTextOptions<unknown>, 'default'
|
||||
return trimmed
|
||||
}
|
||||
|
||||
export async function promptConfirm(io: IOStreams, message: string): Promise<boolean> {
|
||||
io.err.write(message)
|
||||
const rl = readline.createInterface({ input: io.in, output: io.err, terminal: false })
|
||||
try {
|
||||
const line = await new Promise<string>(resolve => rl.once('line', resolve))
|
||||
return line.trim().toLowerCase() === 'y'
|
||||
}
|
||||
finally {
|
||||
rl.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function promptText<T>(opts: PromptTextOptions<T>): Promise<T> {
|
||||
const prompt = buildPromptLine(opts)
|
||||
const cs = colorScheme(colorEnabled(opts.io.isErrTTY))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user