mirror of
https://github.com/langgenius/dify.git
synced 2026-08-31 13:06:11 +08:00
231 lines
7.2 KiB
TypeScript
231 lines
7.2 KiB
TypeScript
import { execFileSync } from 'node:child_process'
|
|
import { mkdtempSync, rmSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { afterAll, describe, expect, it } from 'vite-plus/test'
|
|
import { BUILD_CHANNELS, resolveBuildInfo } from '../../scripts/lib/resolve-buildinfo.js'
|
|
import { ENV_CACHE_DIR, ENV_CONFIG_DIR } from '../../src/store/dir.js'
|
|
|
|
const CLI_ROOT = new URL('../../', import.meta.url)
|
|
const RELEASE_NAMING = fileURLToPath(new URL('scripts/release-naming.mjs', CLI_ROOT))
|
|
const DEV_ENTRY = fileURLToPath(new URL('bin/dev.js', CLI_ROOT))
|
|
|
|
const FIXED_DATE = new Date('2026-05-09T12:00:00.000Z')
|
|
const fixedNow = () => FIXED_DATE
|
|
const noGit = () => null
|
|
// Stub the package.json reader so tests exercise the "no sources" path
|
|
// without coupling to the live cli/package.json#difyctl.compat values.
|
|
const noPkg = () => ({})
|
|
|
|
describe('resolveBuildInfo', () => {
|
|
it('uses env values when fully populated', () => {
|
|
const info = resolveBuildInfo({
|
|
env: {
|
|
DIFYCTL_VERSION: '1.2.3',
|
|
DIFYCTL_COMMIT: 'abcdef0123456789',
|
|
DIFYCTL_BUILD_DATE: '2026-01-01T00:00:00.000Z',
|
|
DIFYCTL_CHANNEL: 'stable',
|
|
},
|
|
git: () => 'should-not-be-called',
|
|
now: fixedNow,
|
|
pkg: noPkg,
|
|
})
|
|
expect(info).toStrictEqual({
|
|
version: '1.2.3',
|
|
commit: 'abcdef0123456789',
|
|
buildDate: '2026-01-01T00:00:00.000Z',
|
|
channel: 'stable',
|
|
minDify: '0.0.0',
|
|
maxDify: '0.0.0',
|
|
})
|
|
})
|
|
|
|
it('falls back to git probes when env unset', () => {
|
|
const calls: string[] = []
|
|
const git = (cmd: string) => {
|
|
calls.push(cmd)
|
|
if (cmd.startsWith('git describe')) return 'v1.0.0-5-gabc1234-dirty'
|
|
if (cmd.startsWith('git rev-parse')) return '1234567890abcdef'
|
|
return null
|
|
}
|
|
const info = resolveBuildInfo({ env: {}, git, now: fixedNow, pkg: noPkg })
|
|
expect(info).toStrictEqual({
|
|
version: 'v1.0.0-5-gabc1234-dirty',
|
|
commit: '1234567890abcdef',
|
|
buildDate: '2026-05-09T12:00:00.000Z',
|
|
channel: 'dev',
|
|
minDify: '0.0.0',
|
|
maxDify: '0.0.0',
|
|
})
|
|
expect(calls).toStrictEqual(['git describe --tags --dirty --always', 'git rev-parse HEAD'])
|
|
})
|
|
|
|
it('uses string defaults when env unset, git unavailable, and package.json empty', () => {
|
|
const info = resolveBuildInfo({ env: {}, git: noGit, now: fixedNow, pkg: noPkg })
|
|
expect(info).toStrictEqual({
|
|
version: '0.0.0-dev',
|
|
commit: 'none',
|
|
buildDate: '2026-05-09T12:00:00.000Z',
|
|
channel: 'dev',
|
|
minDify: '0.0.0',
|
|
maxDify: '0.0.0',
|
|
})
|
|
})
|
|
|
|
it('throws on invalid channel', () => {
|
|
expect(() =>
|
|
resolveBuildInfo({ env: { DIFYCTL_CHANNEL: 'beta' }, git: noGit, now: fixedNow, pkg: noPkg }),
|
|
).toThrow(/invalid DIFYCTL_CHANNEL: beta/)
|
|
})
|
|
|
|
it('throws on removed nightly channel', () => {
|
|
expect(() =>
|
|
resolveBuildInfo({
|
|
env: { DIFYCTL_CHANNEL: 'nightly' },
|
|
git: noGit,
|
|
now: fixedNow,
|
|
pkg: noPkg,
|
|
}),
|
|
).toThrow(/invalid DIFYCTL_CHANNEL: nightly/)
|
|
})
|
|
|
|
it('accepts alpha channel', () => {
|
|
const info = resolveBuildInfo({
|
|
env: { DIFYCTL_CHANNEL: 'alpha' },
|
|
git: noGit,
|
|
now: fixedNow,
|
|
pkg: noPkg,
|
|
})
|
|
expect(info.channel).toBe('alpha')
|
|
})
|
|
|
|
it('accepts rc channel', () => {
|
|
const info = resolveBuildInfo({
|
|
env: {
|
|
DIFYCTL_VERSION: '0.1.0-rc.1',
|
|
DIFYCTL_CHANNEL: 'rc',
|
|
DIFYCTL_COMMIT: 'abc',
|
|
DIFYCTL_BUILD_DATE: '2026-01-01T00:00:00.000Z',
|
|
},
|
|
git: noGit,
|
|
now: fixedNow,
|
|
pkg: noPkg,
|
|
})
|
|
expect(info.channel).toBe('rc')
|
|
})
|
|
|
|
it('mixes env and git fallbacks per field', () => {
|
|
const git = (cmd: string) => (cmd.startsWith('git describe') ? 'v9.9.9' : null)
|
|
const info = resolveBuildInfo({
|
|
env: { DIFYCTL_COMMIT: 'pinned-sha' },
|
|
git,
|
|
now: fixedNow,
|
|
pkg: noPkg,
|
|
})
|
|
expect(info.version).toBe('v9.9.9')
|
|
expect(info.commit).toBe('pinned-sha')
|
|
expect(info.channel).toBe('dev')
|
|
})
|
|
|
|
it('reads minDify and maxDify from env', () => {
|
|
const info = resolveBuildInfo({
|
|
env: {
|
|
DIFYCTL_VERSION: '0.1.0-rc.1',
|
|
DIFYCTL_CHANNEL: 'rc',
|
|
DIFYCTL_COMMIT: 'abc',
|
|
DIFYCTL_BUILD_DATE: '2026-01-01T00:00:00.000Z',
|
|
DIFYCTL_MIN_DIFY: '1.6.0',
|
|
DIFYCTL_MAX_DIFY: '1.7.0',
|
|
},
|
|
git: noGit,
|
|
now: fixedNow,
|
|
pkg: noPkg,
|
|
})
|
|
expect(info.minDify).toBe('1.6.0')
|
|
expect(info.maxDify).toBe('1.7.0')
|
|
})
|
|
|
|
it('defaults minDify and maxDify to 0.0.0 when env and package.json are unset', () => {
|
|
const info = resolveBuildInfo({ env: {}, git: noGit, now: fixedNow, pkg: noPkg })
|
|
expect(info.minDify).toBe('0.0.0')
|
|
expect(info.maxDify).toBe('0.0.0')
|
|
})
|
|
|
|
it('falls back to package.json#difyctl.compat when env unset', () => {
|
|
const pkg = () => ({
|
|
difyctl: { compat: { minDify: '1.6.0', maxDify: '1.7.0' }, channel: 'rc' },
|
|
})
|
|
const info = resolveBuildInfo({ env: {}, git: noGit, now: fixedNow, pkg })
|
|
expect(info.minDify).toBe('1.6.0')
|
|
expect(info.maxDify).toBe('1.7.0')
|
|
expect(info.channel).toBe('rc')
|
|
})
|
|
|
|
it('env wins over package.json for compat range and channel', () => {
|
|
const pkg = () => ({
|
|
difyctl: { compat: { minDify: '1.6.0', maxDify: '1.7.0' }, channel: 'rc' },
|
|
})
|
|
const info = resolveBuildInfo({
|
|
env: {
|
|
DIFYCTL_MIN_DIFY: '2.0.0',
|
|
DIFYCTL_MAX_DIFY: '2.1.0',
|
|
DIFYCTL_CHANNEL: 'stable',
|
|
},
|
|
git: noGit,
|
|
now: fixedNow,
|
|
pkg,
|
|
})
|
|
expect(info.minDify).toBe('2.0.0')
|
|
expect(info.maxDify).toBe('2.1.0')
|
|
expect(info.channel).toBe('stable')
|
|
})
|
|
})
|
|
|
|
function releaseNamingChannels(): string[] {
|
|
return execFileSync('node', [RELEASE_NAMING, 'channels'], { encoding: 'utf8' })
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0)
|
|
}
|
|
|
|
const sorted = (names: readonly string[]) => [...names].sort()
|
|
|
|
describe('channel list parity', () => {
|
|
const LOCAL_ONLY_CHANNEL = 'dev'
|
|
|
|
it('released channels are the build channels minus the local-only one', () => {
|
|
expect(sorted(releaseNamingChannels())).toStrictEqual(
|
|
sorted(BUILD_CHANNELS.filter((name) => name !== LOCAL_ONLY_CHANNEL)),
|
|
)
|
|
})
|
|
})
|
|
|
|
type ClientVersionReport = { client: { channel: string } }
|
|
|
|
describe('bin/dev.js pins the local build channel', () => {
|
|
const ENV_CHANNEL = 'DIFYCTL_CHANNEL'
|
|
const stateDir = mkdtempSync(join(tmpdir(), 'difyctl-dev-channel-'))
|
|
afterAll(() => rmSync(stateDir, { recursive: true, force: true }))
|
|
|
|
function reportedChannel(channelOverride?: string): string {
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
[ENV_CONFIG_DIR]: stateDir,
|
|
[ENV_CACHE_DIR]: stateDir,
|
|
}
|
|
if (channelOverride === undefined) delete env[ENV_CHANNEL]
|
|
else env[ENV_CHANNEL] = channelOverride
|
|
const stdout = execFileSync('bun', [DEV_ENTRY, 'version', '--client', '--output', 'json'], {
|
|
cwd: fileURLToPath(CLI_ROOT),
|
|
encoding: 'utf8',
|
|
env,
|
|
})
|
|
return (JSON.parse(stdout) as ClientVersionReport).client.channel
|
|
}
|
|
|
|
it('reports dev when the env does not set a channel', { timeout: 30_000 }, () => {
|
|
expect(reportedChannel()).toBe('dev')
|
|
})
|
|
})
|