dify/cli/src/version/nudge.ts
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

65 lines
2.6 KiB
TypeScript

import type { ServerVersionResponse } from '@dify/contracts/api/openapi/types.gen'
import type { NudgeStore } from '@/cache/nudge-store'
import { colorScheme } from '@/sys/io/color'
import { difyCompat, evaluateCompat } from './compat'
// Formats whose stdout is structured data (json/yaml) or a single name token —
// any stderr banner from us would pollute machine parsing. Default text format
// (the empty string) intentionally falls through and is allowed to warn.
const SUPPRESSED_FORMATS: ReadonlySet<string> = new Set(['json', 'yaml', 'name'])
export type NudgeDeps = {
readonly store: NudgeStore
// /openapi/v1/_version is intentionally unauthenticated (mirrors _health),
// so the probe does not need a bearer.
readonly probe: (host: string) => Promise<ServerVersionResponse>
readonly emit: (line: string) => void
readonly isTty: boolean
readonly format: string
readonly clientVersion: string
readonly color?: boolean
readonly now?: () => Date
}
// Public guarantee: never throws. Every internal failure is silenced so the
// calling authed command continues regardless of probe / disk errors.
//
// Order matters: cheap suppression checks (format, TTY, throttle window) run
// before any I/O so the happy path costs nothing in steady state.
export async function maybeNudgeCompat(host: string, deps: NudgeDeps): Promise<void> {
try {
if (!deps.isTty) return
if (SUPPRESSED_FORMATS.has(deps.format)) return
if (!deps.store.canWarn(host, deps.now?.())) return
let server: ServerVersionResponse
try {
server = await deps.probe(host)
} catch {
return
}
const verdict = evaluateCompat(server.version)
// Only "too new" is a soft nudge here; "too old" is hard-failed up front by
// enforceDifyVersion, so the command never reaches this path for it.
if (verdict.status !== 'too_new') return
deps.emit(formatBanner(deps.clientVersion, server.version, deps.color === true))
await deps.store.markWarned(host, deps.now?.()).catch(() => {
// disk failure must not propagate; the user already saw the banner.
})
} catch {
// belt-and-braces: any unexpected throw must not affect the business command
}
}
function formatBanner(clientVersion: string, serverVersion: string, color: boolean): string {
const { yellow } = colorScheme(color)
const { minDify, maxDify } = difyCompat
const line =
`warning: difyctl ${clientVersion} may be incompatible with server ` +
`${serverVersion} (tested: ${minDify}..${maxDify}). ` +
'Run `difyctl version` for details.'
return `${yellow(line)}\n`
}