mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 06:11:12 +08:00
223 lines
7.9 KiB
TypeScript
223 lines
7.9 KiB
TypeScript
import { spawnSync } from 'node:child_process'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { describe, expect, it } from 'vite-plus/test'
|
|
|
|
const SCRIPT = fileURLToPath(new URL('./install-r2.sh', import.meta.url))
|
|
|
|
const MANIFEST = [
|
|
'{',
|
|
' "schema": 1,',
|
|
' "name": "difyctl",',
|
|
' "channel": "edge",',
|
|
' "version": "0.1.0-edge.2fd7b82",',
|
|
' "commit": "abc1234",',
|
|
' "buildDate": "2026-06-14T12:00:00Z",',
|
|
' "baseUrl": "https://pub.example.r2.dev/difyctl/edge/0.1.0-edge.2fd7b82",',
|
|
' "targets": {',
|
|
' "linux-x64": { "asset": "difyctl-v0.1.0-edge.2fd7b82-linux-x64", "sha256": "deadbeef" },',
|
|
' "darwin-arm64": { "asset": "difyctl-v0.1.0-edge.2fd7b82-darwin-arm64", "sha256": "cafef00d" }',
|
|
' }',
|
|
'}',
|
|
].join('\n')
|
|
|
|
const INDEX = [
|
|
'{',
|
|
' "schema": 1,',
|
|
' "channel": "edge",',
|
|
' "updated": "2026-06-15T00:00:00Z",',
|
|
' "builds": [',
|
|
' {',
|
|
' "version": "0.1.0-edge.ce4af868",',
|
|
' "commit": "ce4af868d653f405070fabb3be3303430cc030ad",',
|
|
' "buildDate": "2026-06-15T00:00:00Z",',
|
|
' "dir": "0.1.0-edge.ce4af868"',
|
|
' },',
|
|
' {',
|
|
' "version": "0.1.0-edge.aaaa111",',
|
|
' "commit": "aaaa111bbbbcccc000011112222333344445555",',
|
|
' "buildDate": "2026-06-14T00:00:00Z",',
|
|
' "dir": "0.1.0-edge.aaaa111"',
|
|
' }',
|
|
' ]',
|
|
'}',
|
|
].join('\n')
|
|
|
|
const CHECKSUMS = [
|
|
'deadbeef difyctl-v0.1.0-edge.ce4af868-linux-x64',
|
|
'cafef00d difyctl-v0.1.0-edge.ce4af868-darwin-arm64',
|
|
'beadc0de difyctl-v0.1.0-edge.ce4af868-windows-x64.exe',
|
|
].join('\n')
|
|
|
|
function lib(
|
|
program: string,
|
|
env: Record<string, string> = {},
|
|
): { code: number; stdout: string; stderr: string } {
|
|
const full = `. "${SCRIPT}"\n${program}`
|
|
const r = spawnSync('sh', ['-c', full], {
|
|
encoding: 'utf8',
|
|
env: { ...process.env, DIFYCTL_INSTALL_LIB: '1', ...env },
|
|
})
|
|
return { code: r.status ?? 1, stdout: (r.stdout ?? '').trim(), stderr: r.stderr ?? '' }
|
|
}
|
|
|
|
describe('install-r2 manifest parsing', () => {
|
|
// install-r2.sh is POSIX-only; under git-bash on Windows `uname -s` is MINGW*,
|
|
// so detect_target intentionally dies (Windows installs go through install-r2.ps1).
|
|
it.skipIf(process.platform === 'win32')('detect_target maps to one of the 5 ids', () => {
|
|
const { stdout } = lib('detect_target')
|
|
expect(['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64', 'windows-x64']).toContain(
|
|
stdout,
|
|
)
|
|
})
|
|
|
|
it('manifest_str reads a top-level string field', () => {
|
|
const { stdout } = lib(
|
|
`printf '%s' '${MANIFEST}' > "$tmp_m"; manifest_str "$tmp_m" channel`,
|
|
{},
|
|
)
|
|
expect(stdout).toBe('edge')
|
|
})
|
|
|
|
it('manifest_target_field extracts per-target values from a single line', () => {
|
|
const prog =
|
|
`printf '%s' '${MANIFEST}' > "$tmp_m"\n` +
|
|
'manifest_target_field "$tmp_m" darwin-arm64 asset\n' +
|
|
'manifest_target_field "$tmp_m" darwin-arm64 sha256'
|
|
const { stdout } = lib(prog)
|
|
expect(stdout).toBe('difyctl-v0.1.0-edge.2fd7b82-darwin-arm64\ncafef00d')
|
|
})
|
|
|
|
it('requires DIFYCTL_R2_BASE when run as the installer (not lib)', () => {
|
|
const r = spawnSync('sh', [SCRIPT], {
|
|
encoding: 'utf8',
|
|
env: { ...process.env, DIFYCTL_R2_BASE: '' },
|
|
})
|
|
expect(r.status).not.toBe(0)
|
|
expect(r.stderr).toMatch(/DIFYCTL_R2_BASE/)
|
|
})
|
|
|
|
it('sha256_check aborts on a checksum mismatch', () => {
|
|
const r = lib('f="$(mktemp)"; printf \'hello\' > "$f"; sha256_check "$f" deadbeef')
|
|
expect(r.code).not.toBe(0)
|
|
expect(r.stderr).toMatch(/checksum mismatch/)
|
|
})
|
|
|
|
it('sha256_check passes on the correct hash', () => {
|
|
// sha256('hello') = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
|
|
const r = lib(
|
|
'f="$(mktemp)"; printf \'hello\' > "$f"; sha256_check "$f" 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 && echo OK',
|
|
)
|
|
expect(r.stdout).toBe('OK')
|
|
})
|
|
})
|
|
|
|
describe('install-r2 version/commit pin', () => {
|
|
it('index_resolve matches a build by exact version', () => {
|
|
const r = lib(
|
|
`printf '%s' '${INDEX}' > "$tmp_m"; index_resolve "$tmp_m" version 0.1.0-edge.aaaa111`,
|
|
)
|
|
expect(r.stdout).toBe('0.1.0-edge.aaaa111\t0.1.0-edge.aaaa111')
|
|
})
|
|
|
|
it('index_resolve matches a build by commit prefix', () => {
|
|
const r = lib(`printf '%s' '${INDEX}' > "$tmp_m"; index_resolve "$tmp_m" commit ce4af868`)
|
|
expect(r.stdout).toBe('0.1.0-edge.ce4af868\t0.1.0-edge.ce4af868')
|
|
})
|
|
|
|
it('index_resolve matches the full 40-char commit too', () => {
|
|
const r = lib(
|
|
`printf '%s' '${INDEX}' > "$tmp_m"; index_resolve "$tmp_m" commit aaaa111bbbbcccc000011112222333344445555`,
|
|
)
|
|
expect(r.stdout).toBe('0.1.0-edge.aaaa111\t0.1.0-edge.aaaa111')
|
|
})
|
|
|
|
it('index_resolve prints nothing when no build matches', () => {
|
|
const r = lib(`printf '%s' '${INDEX}' > "$tmp_m"; index_resolve "$tmp_m" commit ffffff`)
|
|
expect(r.stdout).toBe('')
|
|
})
|
|
|
|
it('checksums_target extracts sha and asset for a posix target', () => {
|
|
const r = lib(`printf '%s' '${CHECKSUMS}' > "$tmp_m"; checksums_target "$tmp_m" darwin-arm64`)
|
|
expect(r.stdout).toBe('cafef00d\tdifyctl-v0.1.0-edge.ce4af868-darwin-arm64')
|
|
})
|
|
|
|
it('checksums_target does not bleed x64 into arm64', () => {
|
|
const r = lib(`printf '%s' '${CHECKSUMS}' > "$tmp_m"; checksums_target "$tmp_m" linux-x64`)
|
|
expect(r.stdout).toBe('deadbeef\tdifyctl-v0.1.0-edge.ce4af868-linux-x64')
|
|
})
|
|
})
|
|
|
|
describe('install-r2.sh install location', () => {
|
|
const where = (env: Record<string, string>) =>
|
|
lib('bin_dir_for', { DIFYCTL_PREFIX: '', DIFYCTL_INSTALL_DIR: '', HOME: '/home/u', ...env })
|
|
.stdout
|
|
|
|
it('defaults to ~/.local/bin', () => {
|
|
expect(where({})).toBe('/home/u/.local/bin')
|
|
})
|
|
|
|
it('accepts DIFYCTL_PREFIX like the GitHub installer', () => {
|
|
expect(where({ DIFYCTL_PREFIX: '/opt/dify' })).toBe('/opt/dify/bin')
|
|
})
|
|
|
|
it('keeps DIFYCTL_INSTALL_DIR as the bin folder itself, over DIFYCTL_PREFIX', () => {
|
|
expect(where({ DIFYCTL_PREFIX: '/opt/dify', DIFYCTL_INSTALL_DIR: '/usr/local/bin' })).toBe(
|
|
'/usr/local/bin',
|
|
)
|
|
})
|
|
})
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'install-r2.sh points Windows users at install-r2.ps1',
|
|
() => {
|
|
const r = lib('uname() { printf MINGW64_NT; }; detect_target')
|
|
expect(r.code).not.toBe(0)
|
|
expect(r.stderr).toMatch(/use install-r2\.ps1 on Windows/)
|
|
},
|
|
)
|
|
|
|
describe.skipIf(process.platform === 'win32')('install-r2.sh install_binary', () => {
|
|
it('installs a 0600 temp file as mode 0755 next to and then over the target', () => {
|
|
const r = lib(
|
|
[
|
|
'd="$(mktemp -d)"',
|
|
'src="$(mktemp)"; chmod 0600 "$src"; printf new > "$src"',
|
|
'printf old > "$d/difyctl"',
|
|
'install_binary "$src" "$d/difyctl"',
|
|
'ls -l "$d/difyctl" | cut -c1-10; cat "$d/difyctl"; echo; ls -A "$d"',
|
|
'rm -rf "$d" "$src"',
|
|
].join('\n'),
|
|
)
|
|
expect(r.code).toBe(0)
|
|
expect(r.stdout.split('\n')).toEqual(['-rwxr-xr-x', 'new', 'difyctl'])
|
|
})
|
|
})
|
|
|
|
// A v2 binary answers `install skills --help`; a v1 one exits 1 with "unknown command".
|
|
describe.skipIf(process.platform === 'win32')('install-r2.sh print_skill_hint', () => {
|
|
const fakeBins = [
|
|
'd="$(mktemp -d)"',
|
|
'printf \'#!/bin/sh\\n[ "$*" = "install skills --help" ]\\n\' > "$d/v2"',
|
|
'printf \'#!/bin/sh\\necho "unknown command: $*" >&2\\nexit 1\\n\' > "$d/v1"',
|
|
'chmod 0755 "$d/v2" "$d/v1"',
|
|
]
|
|
|
|
it('prints the hint for a binary that has install skills', () => {
|
|
const r = lib([...fakeBins, 'print_skill_hint "$d/v2"', 'rm -rf "$d"'].join('\n'))
|
|
expect(r.code).toBe(0)
|
|
expect(r.stdout).toMatch(/difyctl install skills <skills root>/)
|
|
})
|
|
|
|
it('prints nothing for a binary without install skills', () => {
|
|
const r = lib([...fakeBins, 'print_skill_hint "$d/v1"', 'rm -rf "$d"'].join('\n'))
|
|
expect(r.code).toBe(0)
|
|
expect(r.stdout).toBe('')
|
|
})
|
|
|
|
it('prints nothing when the binary cannot run', () => {
|
|
const r = lib('print_skill_hint /nonexistent/difyctl')
|
|
expect(r.code).toBe(0)
|
|
expect(r.stdout).toBe('')
|
|
})
|
|
})
|