Sfoglia il codice sorgente

修复旧源码回滚服务根路径

samien 2 mesi fa
parent
commit
decf3d0812
2 ha cambiato i file con 96 aggiunte e 17 eliminazioni
  1. 34 15
      bin/hermes-yaoyao-updater.mjs
  2. 62 2
      tests/updater.test.ts

+ 34 - 15
bin/hermes-yaoyao-updater.mjs

@@ -166,7 +166,6 @@ export function serviceInstallInvocation(
 ) {
   const cli = join(lifecycleRoot, 'bin', 'hermes-yaoyao.mjs')
   const env = serviceEnvironment(stableRoot ? join(plan.releaseRoot, 'current') : serviceRoot, plan)
-  if (!stableRoot) delete env.HERMES_YAOYAO_SERVICE_ROOT
   return { cli, env }
 }
 
@@ -176,6 +175,26 @@ function startService(serviceRoot, plan, stableRoot = true, lifecycleRoot = serv
   run(process.execPath, [cli, 'service', 'install'], { env, timeout: 30_000 })
 }
 
+export function restorePreviousService({
+  plan,
+  previousCurrentTarget,
+  previousServiceRoot,
+  lifecycleRoot,
+  token,
+  switchCurrentCommand = switchCurrent,
+  removeCurrentCommand = removeCurrent,
+  startServiceCommand = startService,
+}) {
+  if (!lifecycleRoot) fail('当前发布版本不可用,无法安全执行回滚')
+  if (previousCurrentTarget) {
+    switchCurrentCommand(plan.releaseRoot, previousCurrentTarget, token)
+    startServiceCommand(join(plan.releaseRoot, 'current'), plan, true, lifecycleRoot)
+  } else {
+    removeCurrentCommand(plan.releaseRoot)
+    startServiceCommand(previousServiceRoot, plan, false, lifecycleRoot)
+  }
+}
+
 async function responseJSON(url) {
   const response = await fetch(url, { signal: AbortSignal.timeout(3_000), cache: 'no-store' })
   if (!response.ok) fail(`${url} 返回 HTTP ${response.status}`)
@@ -262,13 +281,13 @@ async function runUpdate(jobPath, job) {
       updateJob(jobPath, { state: 'rolling_back', message: '升级失败,正在自动恢复上一版本', error: message })
       try {
         stopService()
-        if (previousCurrentTarget) {
-          switchCurrent(plan.releaseRoot, previousCurrentTarget, `${id}-rollback`)
-          startService(join(plan.releaseRoot, 'current'), plan, true, finalRoot)
-        } else {
-          removeCurrent(plan.releaseRoot)
-          startService(plan.previousServiceRoot, plan, false, finalRoot)
-        }
+        restorePreviousService({
+          plan,
+          previousCurrentTarget,
+          previousServiceRoot: plan.previousServiceRoot,
+          lifecycleRoot: finalRoot,
+          token: `${id}-rollback`,
+        })
         await verifyRuntime()
         updateJob(jobPath, { state: 'failed', message: '升级失败,已自动恢复上一版本', error: message })
       } catch (rollbackError) {
@@ -290,13 +309,13 @@ async function runRollback(jobPath, job) {
   if (!lifecycleRoot) fail('当前发布版本不可用,无法安全执行回滚')
   updateJob(jobPath, { state: 'rolling_back', message: '正在恢复上一版本' })
   stopService()
-  if (record.previousCurrentTarget) {
-    switchCurrent(plan.releaseRoot, record.previousCurrentTarget, id)
-    startService(join(plan.releaseRoot, 'current'), plan, true, lifecycleRoot)
-  } else {
-    removeCurrent(plan.releaseRoot)
-    startService(record.previousServiceRoot, plan, false, lifecycleRoot)
-  }
+  restorePreviousService({
+    plan,
+    previousCurrentTarget: record.previousCurrentTarget,
+    previousServiceRoot: record.previousServiceRoot,
+    lifecycleRoot,
+    token: id,
+  })
   await verifyRuntime()
   rmSync(recordPath, { force: true })
   updateJob(jobPath, { state: 'rolled_back', message: '已回滚 Web 服务;插件继续由 9119 管理' })

+ 62 - 2
tests/updater.test.ts

@@ -1,10 +1,12 @@
 import { lstatSync, mkdtempSync, rmSync } from 'node:fs'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
-import { afterEach, describe, expect, it } from 'vitest'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+import { launchAgentPlist } from '../bin/hermes-yaoyao.mjs'
 import {
   currentTarget,
   formatCommandFailure,
+  restorePreviousService,
   serviceInstallInvocation,
   switchCurrent,
   validateManifest,
@@ -76,7 +78,65 @@ describe('standalone updater primitives', () => {
       newRelease,
     )
     expect(sourceRollback.cli).toBe(join(newRelease, 'bin', 'hermes-yaoyao.mjs'))
-    expect(sourceRollback.env.HERMES_YAOYAO_SERVICE_ROOT).toBeUndefined()
+    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('当前发布版本不可用')
   })
 
 })