Selaa lähdekoodia

修复后台升级构建依赖缺失

LSamien 2 kuukautta sitten
vanhempi
sitoutus
5593f14d04
7 muutettua tiedostoa jossa 119 lisäystä ja 163 poistoa
  1. 1 1
      Dockerfile
  2. 47 10
      bin/hermes-yaoyao-updater.mjs
  3. 18 134
      package-lock.json
  4. 17 17
      package.json
  5. 12 0
      scripts/verify-release.mjs
  6. 17 1
      tests/updater.test.ts
  7. 7 0
      tsconfig.client.build.json

+ 1 - 1
Dockerfile

@@ -8,7 +8,7 @@ COPY package.json package-lock.json ./
 COPY release.json ./
 RUN npm ci
 
-COPY tsconfig.json tsconfig.client.json tsconfig.server.json vite.config.ts ./
+COPY tsconfig.json tsconfig.client.json tsconfig.client.build.json tsconfig.server.json vite.config.ts ./
 COPY index.html ./
 COPY public ./public
 COPY scripts ./scripts

+ 47 - 10
bin/hermes-yaoyao-updater.mjs

@@ -49,15 +49,51 @@ function updateJob(jobPath, patch) {
   writeJSON(jobPath, { ...current, ...patch, updatedAt: new Date().toISOString() })
 }
 
+const COMMAND_OUTPUT_LIMIT = 6_000
+
+function cleanCommandOutput(value) {
+  if (value === undefined || value === null) return ''
+  return String(value)
+    .replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, '')
+    .replace(/([a-z][a-z0-9+.-]*:\/\/)([^@\s/]+)@/gi, '$1***@')
+    .replace(/([?&](?:access_token|token|password)=)[^&\s]+/gi, '$1***')
+    .trim()
+}
+
+function outputTail(value) {
+  if (value.length <= COMMAND_OUTPUT_LIMIT) return value
+  return `…已省略前面的输出…\n${value.slice(-COMMAND_OUTPUT_LIMIT)}`
+}
+
+export function formatCommandFailure(command, args, error) {
+  const commandLine = cleanCommandOutput([command, ...args].join(' '))
+  const status = typeof error?.status === 'number'
+    ? `,退出码 ${error.status}`
+    : error?.signal ? `,信号 ${error.signal}` : ''
+  const stdout = cleanCommandOutput(error?.stdout)
+  const stderr = cleanCommandOutput(error?.stderr)
+  let detail = [stdout, stderr].filter(Boolean).join('\n')
+  if (!detail) {
+    detail = cleanCommandOutput(error instanceof Error ? error.message : error)
+      .replace(/^Command failed:[^\n]*(?:\n|$)/, '')
+      .trim()
+  }
+  return `${commandLine} 失败${status}${detail ? `\n${outputTail(detail)}` : ''}`
+}
+
 function run(command, args, options = {}) {
-  return execFileSync(command, args, {
-    cwd: options.cwd,
-    env: options.env ?? process.env,
-    encoding: 'utf8',
-    stdio: options.inherit ? 'inherit' : 'pipe',
-    timeout: options.timeout ?? 120_000,
-    maxBuffer: 8 * 1_024 * 1_024,
-  })
+  try {
+    return execFileSync(command, args, {
+      cwd: options.cwd,
+      env: options.env ?? process.env,
+      encoding: 'utf8',
+      stdio: 'pipe',
+      timeout: options.timeout ?? 120_000,
+      maxBuffer: 8 * 1_024 * 1_024,
+    })
+  } catch (error) {
+    throw new Error(formatCommandFailure(command, args, error))
+  }
 }
 
 function pathInside(path, parent) {
@@ -258,8 +294,9 @@ function stageRelease(job, jobPath) {
   }
 
   updateJob(jobPath, { state: 'building', message: `正在构建 Web ${target.webVersion}` })
-  run('npm', ['ci'], { cwd: staging, inherit: true, timeout: 600_000 })
-  run('npm', ['run', 'build'], { cwd: staging, inherit: true, timeout: 600_000 })
+  const buildEnvironment = { ...process.env, NODE_ENV: 'development' }
+  run('npm', ['ci', '--include=dev'], { cwd: staging, env: buildEnvironment, timeout: 600_000 })
+  run('npm', ['run', 'build'], { cwd: staging, env: buildEnvironment, timeout: 600_000 })
   const finalRoot = join(releasesRoot, `${target.releaseVersion}-${commit.slice(0, 12)}`)
   if (existsSync(finalRoot)) removeInside(staging, releasesRoot)
   else renameSync(staging, finalRoot)

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 18 - 134
package-lock.json


+ 17 - 17
package.json

@@ -13,7 +13,7 @@
   },
   "scripts": {
     "dev": "cross-env NODE_ENV=development tsx watch src/server/index.ts",
-    "build": "npm run brand:verify && npm run release:verify && vue-tsc -b && vite build && tsc -p tsconfig.server.json",
+    "build": "npm run brand:verify && npm run release:verify && vue-tsc -b tsconfig.client.build.json && vite build && tsc -p tsconfig.server.json",
     "start": "cross-env NODE_ENV=production node dist-server/server/index.js",
     "typecheck": "vue-tsc -b && tsc --noEmit -p tsconfig.server.json",
     "test": "vitest run",
@@ -27,8 +27,19 @@
   "dependencies": {
     "@koa/bodyparser": "^5.0.0",
     "@koa/router": "^15.4.0",
+    "@types/busboy": "^1.5.4",
+    "@types/koa": "^2.15.0",
+    "@types/koa-send": "^4.1.6",
+    "@types/koa-static": "^4.0.4",
+    "@types/markdown-it": "^14.1.2",
+    "@types/mime-types": "^2.1.4",
+    "@types/node": "^24.10.0",
+    "@types/qrcode": "^1.5.6",
+    "@types/ws": "^8.18.1",
     "@vicons/ionicons5": "^0.13.0",
     "@vicons/tabler": "^0.13.0",
+    "@vitejs/plugin-vue": "^6.0.5",
+    "@vue/tsconfig": "^0.9.1",
     "busboy": "^1.6.0",
     "cookie": "^1.0.2",
     "docx-preview": "^0.3.7",
@@ -45,36 +56,25 @@
     "pinia": "^3.0.4",
     "qrcode": "^1.5.4",
     "read-excel-file": "^7.0.3",
+    "sass": "^1.99.0",
+    "typescript": "~6.0.2",
+    "vite": "^8.0.4",
     "vue": "^3.5.32",
     "vue-router": "^4.6.4",
+    "vue-tsc": "^3.2.8",
     "ws": "^8.20.0",
     "zod": "^4.1.12"
   },
   "devDependencies": {
     "@playwright/test": "^1.60.0",
-    "@types/busboy": "^1.5.4",
     "@types/jsdom": "^21.1.7",
-    "@types/koa": "^2.15.0",
-    "@types/koa-send": "^4.1.6",
-    "@types/koa-static": "^4.0.4",
-    "@types/markdown-it": "^14.1.2",
-    "@types/mime-types": "^2.1.4",
-    "@types/node": "^24.10.0",
-    "@types/qrcode": "^1.5.6",
     "@types/supertest": "^6.0.3",
-    "@types/ws": "^8.18.1",
-    "@vitejs/plugin-vue": "^6.0.5",
     "@vitest/coverage-v8": "^3.2.4",
     "@vue/test-utils": "^2.4.6",
-    "@vue/tsconfig": "^0.9.1",
     "cross-env": "^10.1.0",
     "jsdom": "^27.0.1",
-    "sass": "^1.99.0",
     "supertest": "^7.1.4",
     "tsx": "^4.21.0",
-    "typescript": "~6.0.2",
-    "vite": "^8.0.4",
-    "vitest": "^3.2.4",
-    "vue-tsc": "^3.2.8"
+    "vitest": "^3.2.4"
   }
 }

+ 12 - 0
scripts/verify-release.mjs

@@ -11,6 +11,13 @@ const lock = await json('package-lock.json')
 const pluginManifest = await json('hermes-plugins/yaoyao/dashboard/manifest.json')
 const pluginYaml = await readFile(resolve(root, 'hermes-plugins/yaoyao/plugin.yaml'), 'utf8')
 const pluginYamlVersion = pluginYaml.match(/^version:\s*["']?([^"'\s]+)["']?\s*$/m)?.[1]
+const updaterBuildPackages = [
+  '@vitejs/plugin-vue',
+  '@vue/tsconfig',
+  'typescript',
+  'vite',
+  'vue-tsc',
+]
 
 const failures = []
 if (release.schemaVersion !== 1) failures.push('release.json schemaVersion 必须为 1')
@@ -22,6 +29,11 @@ if (lock.version !== release.webVersion || lock.packages?.['']?.version !== rele
 }
 if (pluginManifest.version !== release.pluginVersion) failures.push('插件 manifest.json 版本与 release.json 不一致')
 if (pluginYamlVersion !== release.pluginVersion) failures.push('plugin.yaml 版本与 release.json 不一致')
+for (const dependency of updaterBuildPackages) {
+  if (typeof pkg.dependencies?.[dependency] !== 'string') {
+    failures.push(`升级构建依赖 ${dependency} 必须位于 dependencies,以兼容旧版后台升级器`)
+  }
+}
 
 if (failures.length) {
   for (const failure of failures) process.stderr.write(`- ${failure}\n`)

+ 17 - 1
tests/updater.test.ts

@@ -2,7 +2,7 @@ import { lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { afterEach, describe, expect, it } from 'vitest'
-import { backupPlugin, currentTarget, restorePlugin, switchCurrent, validateManifest } from '../bin/hermes-yaoyao-updater.mjs'
+import { backupPlugin, currentTarget, formatCommandFailure, restorePlugin, switchCurrent, validateManifest } from '../bin/hermes-yaoyao-updater.mjs'
 
 const roots: string[] = []
 afterEach(() => {
@@ -32,6 +32,22 @@ describe('standalone updater primitives', () => {
     })).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('backs up and restores plugin code without touching durable plugin data', () => {
     const root = mkdtempSync(join(tmpdir(), 'hermes-updater-plugin-'))
     roots.push(root)

+ 7 - 0
tsconfig.client.build.json

@@ -0,0 +1,7 @@
+{
+  "extends": "./tsconfig.client.json",
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.client.build.tsbuildinfo"
+  },
+  "include": ["src/client/**/*.ts", "src/client/**/*.vue", "src/shared/**/*.ts"]
+}

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä