import type { ExecSyncOptions } from 'node:child_process' import { execSync } from 'node:child_process' import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' export const BUILD_CHANNELS = ['dev', 'rc', 'stable'] as const export type BuildChannel = (typeof BUILD_CHANNELS)[number] export type BuildInfo = { version: string commit: string buildDate: string channel: BuildChannel minDify: string maxDify: string } export type Env = Record export type GitProbe = (cmd: string) => string | null const GIT_PROBE_OPTS: ExecSyncOptions = { stdio: ['ignore', 'pipe', 'ignore'], } export const defaultGitProbe: GitProbe = (cmd) => { try { return execSync(cmd, GIT_PROBE_OPTS).toString().trim() || null } catch { return null } } type PackageManifest = { difyctl?: { channel?: string compat?: { minDify?: string, maxDify?: string } } } export type PackageReader = () => PackageManifest // Default reader resolves cli/package.json relative to this file so the same // helper works whether invoked from vite.config.ts, bin/dev.js, or release.sh. const defaultPackageReader: PackageReader = () => { try { const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json') return JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageManifest } catch { return {} } } export type ResolveOptions = { env?: Env git?: GitProbe now?: () => Date pkg?: PackageReader } export function resolveBuildInfo(opts: ResolveOptions = {}): BuildInfo { const env = opts.env ?? process.env const git = opts.git ?? defaultGitProbe const now = opts.now ?? (() => new Date()) const pkg = (opts.pkg ?? defaultPackageReader)() const channel = env.DIFYCTL_CHANNEL ?? pkg.difyctl?.channel ?? 'dev' if (!(BUILD_CHANNELS as readonly string[]).includes(channel)) { throw new Error( `invalid DIFYCTL_CHANNEL: ${channel} (expected ${BUILD_CHANNELS.join(' | ')})`, ) } const version = env.DIFYCTL_VERSION ?? git('git describe --tags --dirty --always') ?? '0.0.0-dev' const commit = env.DIFYCTL_COMMIT ?? git('git rev-parse HEAD') ?? 'none' const buildDate = env.DIFYCTL_BUILD_DATE ?? now().toISOString() const minDify = env.DIFYCTL_MIN_DIFY ?? pkg.difyctl?.compat?.minDify ?? '0.0.0' const maxDify = env.DIFYCTL_MAX_DIFY ?? pkg.difyctl?.compat?.maxDify ?? '0.0.0' return { version, commit, buildDate, channel: channel as BuildChannel, minDify, maxDify } }