Przeglądaj źródła

确保跨版本回滚使用新版生命周期

samien 2 miesięcy temu
rodzic
commit
2651f0f6a9
3 zmienionych plików z 52 dodań i 8 usunięć
  1. 19 7
      bin/hermes-yaoyao-updater.mjs
  2. 1 0
      tests/serviceCli.test.ts
  3. 32 1
      tests/updater.test.ts

+ 19 - 7
bin/hermes-yaoyao-updater.mjs

@@ -158,11 +158,21 @@ function serviceEnvironment(serviceRoot, plan) {
   }
 }
 
-function startService(serviceRoot, plan, stableRoot = true) {
-  const cli = join(serviceRoot, 'bin', 'hermes-yaoyao.mjs')
-  if (!existsSync(cli)) fail(`服务入口不存在:${cli}`)
+export function serviceInstallInvocation(
+  serviceRoot,
+  plan,
+  stableRoot = true,
+  lifecycleRoot = serviceRoot,
+) {
+  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 }
+}
+
+function startService(serviceRoot, plan, stableRoot = true, lifecycleRoot = serviceRoot) {
+  const { cli, env } = serviceInstallInvocation(serviceRoot, plan, stableRoot, lifecycleRoot)
+  if (!existsSync(cli)) fail(`服务入口不存在:${cli}`)
   run(process.execPath, [cli, 'service', 'install'], { env, timeout: 30_000 })
 }
 
@@ -254,10 +264,10 @@ async function runUpdate(jobPath, job) {
         stopService()
         if (previousCurrentTarget) {
           switchCurrent(plan.releaseRoot, previousCurrentTarget, `${id}-rollback`)
-          startService(join(plan.releaseRoot, 'current'), plan, true)
+          startService(join(plan.releaseRoot, 'current'), plan, true, finalRoot)
         } else {
           removeCurrent(plan.releaseRoot)
-          startService(plan.previousServiceRoot, plan, false)
+          startService(plan.previousServiceRoot, plan, false, finalRoot)
         }
         await verifyRuntime()
         updateJob(jobPath, { state: 'failed', message: '升级失败,已自动恢复上一版本', error: message })
@@ -276,14 +286,16 @@ async function runRollback(jobPath, job) {
   const recordPath = join(dirname(jobPath), 'last-success.json')
   if (!existsSync(recordPath)) fail('没有可回滚的上一版本')
   const record = readJSON(recordPath)
+  const lifecycleRoot = currentTarget(plan.releaseRoot)
+  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)
+    startService(join(plan.releaseRoot, 'current'), plan, true, lifecycleRoot)
   } else {
     removeCurrent(plan.releaseRoot)
-    startService(record.previousServiceRoot, plan, false)
+    startService(record.previousServiceRoot, plan, false, lifecycleRoot)
   }
   await verifyRuntime()
   rmSync(recordPath, { force: true })

+ 1 - 0
tests/serviceCli.test.ts

@@ -65,6 +65,7 @@ describe('hermes-yaoyao LaunchAgent plist', () => {
     })).resolves.toBe('')
     expect(elapsedMs).toBeGreaterThanOrEqual(5_000)
     expect(run).toHaveBeenCalledTimes(6)
+    expect(wait.mock.calls.map(([waitMs]) => waitMs)).toEqual([250, 500, 1_000, 2_000, 2_000])
   })
 
   it('stops retrying a transient launchctl error at the deadline', async () => {

+ 32 - 1
tests/updater.test.ts

@@ -2,7 +2,13 @@ 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 { currentTarget, formatCommandFailure, switchCurrent, validateManifest } from '../bin/hermes-yaoyao-updater.mjs'
+import {
+  currentTarget,
+  formatCommandFailure,
+  serviceInstallInvocation,
+  switchCurrent,
+  validateManifest,
+} from '../bin/hermes-yaoyao-updater.mjs'
 
 const roots: string[] = []
 afterEach(() => {
@@ -48,4 +54,29 @@ describe('standalone updater primitives', () => {
     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')
+
+    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).toBeUndefined()
+  })
+
 })