| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- import { lstatSync, mkdtempSync, rmSync } from 'node:fs'
- import { tmpdir } from 'node:os'
- import { join } from 'node:path'
- import { afterEach, describe, expect, it, vi } from 'vitest'
- import { launchAgentPlist } from '../bin/hermes-yaoyao.mjs'
- import {
- currentTarget,
- formatCommandFailure,
- restorePreviousService,
- serviceInstallInvocation,
- switchCurrent,
- validateManifest,
- } from '../bin/hermes-yaoyao-updater.mjs'
- const roots: string[] = []
- afterEach(() => {
- for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
- })
- describe('standalone updater primitives', () => {
- it('atomically switches the stable current symlink', () => {
- const root = mkdtempSync(join(tmpdir(), 'hermes-updater-'))
- roots.push(root)
- const first = join(root, 'releases', '0.2.0-a')
- const second = join(root, 'releases', '0.3.0-b')
- switchCurrent(root, first, 'first')
- expect(currentTarget(root)).toBe(first)
- switchCurrent(root, second, 'second')
- expect(currentTarget(root)).toBe(second)
- expect(lstatSync(join(root, 'current')).isSymbolicLink()).toBe(true)
- })
- it('rejects a mismatched release manifest before any service transition', () => {
- expect(() => validateManifest({
- schemaVersion: 1,
- releaseVersion: '0.3.0',
- webVersion: '0.3.1',
- pluginVersion: '1.8.0',
- gitTag: 'v0.3.0',
- })).toThrow('版本组合不一致')
- })
- it('keeps useful command output while redacting credentials', () => {
- const error = Object.assign(new Error('Command failed'), {
- status: 127,
- stdout: '\u001B[31m发布版本校验通过\u001B[0m',
- stderr: 'https://secret@example.test/release.git\nsh: vue-tsc: command not found',
- })
- const message = formatCommandFailure('npm', ['run', 'build'], error)
- expect(message).toContain('npm run build 失败,退出码 127')
- expect(message).toContain('发布版本校验通过')
- expect(message).toContain('https://***@example.test/release.git')
- expect(message).toContain('vue-tsc: command not found')
- expect(message).not.toContain('secret')
- expect(message).not.toContain('\u001B')
- })
- it('uses the new lifecycle code while starting a rolled-back release', () => {
- const releaseRoot = '/Users/test/.local/share/hermes-yaoyao'
- const oldRelease = join(releaseRoot, 'releases', '0.2.11-old')
- const newRelease = join(releaseRoot, 'releases', '0.2.12-new')
- const invocation = serviceInstallInvocation(
- oldRelease,
- { releaseRoot, source: 'https://example.test/hermes-yaoyao.git' },
- true,
- newRelease,
- )
- expect(invocation.cli).toBe(join(newRelease, 'bin', 'hermes-yaoyao.mjs'))
- expect(invocation.env.HERMES_YAOYAO_SERVICE_ROOT).toBe(join(releaseRoot, 'current'))
- expect(invocation.env.HERMES_YAOYAO_RELEASE_SOURCE).toBe('https://example.test/hermes-yaoyao.git')
- expect(invocation.env.HERMES_YAOYAO_HOST).toBe('0.0.0.0')
- expect(invocation.env.HERMES_YAOYAO_ALLOW_INSECURE_LAN).toBe('1')
- const sourceRollback = serviceInstallInvocation(
- oldRelease,
- { releaseRoot, source: 'https://example.test/hermes-yaoyao.git' },
- false,
- newRelease,
- )
- expect(sourceRollback.cli).toBe(join(newRelease, 'bin', 'hermes-yaoyao.mjs'))
- expect(sourceRollback.env.HERMES_YAOYAO_SERVICE_ROOT).toBe(oldRelease)
- const rollbackPlist = launchAgentPlist({ serviceRoot: sourceRollback.env.HERMES_YAOYAO_SERVICE_ROOT })
- expect(rollbackPlist).toContain(`<string>${join(oldRelease, 'dist-server', 'server', 'index.js')}</string>`)
- expect(rollbackPlist).toContain(`<key>WorkingDirectory</key><string>${oldRelease}</string>`)
- })
- it('routes release and source rollbacks through the new lifecycle root', () => {
- const plan = {
- releaseRoot: '/Users/test/.local/share/hermes-yaoyao',
- source: 'https://example.test/hermes-yaoyao.git',
- }
- const lifecycleRoot = join(plan.releaseRoot, 'releases', '0.2.12-new')
- const previousRelease = join(plan.releaseRoot, 'releases', '0.2.11-old')
- const previousSource = '/Users/test/git/hermes-yaoyao'
- const switchCurrentCommand = vi.fn()
- const removeCurrentCommand = vi.fn()
- const startServiceCommand = vi.fn()
- restorePreviousService({
- plan,
- previousCurrentTarget: previousRelease,
- previousServiceRoot: previousSource,
- lifecycleRoot,
- token: 'release-rollback',
- switchCurrentCommand,
- removeCurrentCommand,
- startServiceCommand,
- })
- expect(switchCurrentCommand).toHaveBeenCalledWith(plan.releaseRoot, previousRelease, 'release-rollback')
- expect(removeCurrentCommand).not.toHaveBeenCalled()
- expect(startServiceCommand).toHaveBeenCalledWith(
- join(plan.releaseRoot, 'current'), plan, true, lifecycleRoot,
- )
- switchCurrentCommand.mockClear()
- startServiceCommand.mockClear()
- restorePreviousService({
- plan,
- previousCurrentTarget: undefined,
- previousServiceRoot: previousSource,
- lifecycleRoot,
- token: 'source-rollback',
- switchCurrentCommand,
- removeCurrentCommand,
- startServiceCommand,
- })
- expect(switchCurrentCommand).not.toHaveBeenCalled()
- expect(removeCurrentCommand).toHaveBeenCalledWith(plan.releaseRoot)
- expect(startServiceCommand).toHaveBeenCalledWith(previousSource, plan, false, lifecycleRoot)
- })
- it('refuses rollback without a usable lifecycle root', () => {
- expect(() => restorePreviousService({
- plan: { releaseRoot: '/tmp/releases', source: 'https://example.test/repo.git' },
- previousCurrentTarget: '/tmp/releases/old',
- previousServiceRoot: '/tmp/source',
- lifecycleRoot: undefined,
- token: 'missing-lifecycle',
- })).toThrow('当前发布版本不可用')
- })
- })
|