chore: 移除 CI/Actions 相关设施(workflow、ci 脚本、tests 目录),审计脚本保留在 repro/ 供本地开发使用
This commit is contained in:
@@ -1,58 +0,0 @@
|
||||
# CI:每次推送自动运行完整测试套件(本地与云端同一入口:node scripts/ci.mjs)
|
||||
# 网络适配(服务器在国内):
|
||||
# 1) 不使用任何远程 action(actions/checkout 会被解析到 github.com 导致挂死),改用内置环境变量手动 clone
|
||||
# 2) job 容器镜像走腾讯内网镜像源(runner 标签已指向 mirror.ccs.tencentyun.com)
|
||||
# 3) npm/pnpm 走国内 registry
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: 测试套件(协议/用户管理/单测/双设备/实时链路/构建)
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mirror.ccs.tencentyun.com/library/node:22-bookworm
|
||||
steps:
|
||||
- name: 检出代码(内网闭环,不访问 github)
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
git clone --depth 1 "https://x-access-token:${GITHUB_TOKEN}@${GITHUB_SERVER_URL#https://}/root/daywork.git" .
|
||||
git checkout "$GITHUB_SHA"
|
||||
|
||||
- name: 安装 pnpm
|
||||
run: npm install -g pnpm@11 --registry=https://registry.npmmirror.com
|
||||
|
||||
- name: 安装依赖
|
||||
working-directory: intern-log
|
||||
run: pnpm install --frozen-lockfile --registry=https://registry.npmmirror.com
|
||||
|
||||
- name: 运行测试与构建
|
||||
env:
|
||||
NODE_ENV: production
|
||||
run: node scripts/ci.mjs
|
||||
|
||||
audit:
|
||||
name: 依赖漏洞扫描(软性检查)
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mirror.ccs.tencentyun.com/library/node:22-bookworm
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: 检出代码(内网闭环,不访问 github)
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
git clone --depth 1 "https://x-access-token:${GITHUB_TOKEN}@${GITHUB_SERVER_URL#https://}/root/daywork.git" .
|
||||
git checkout "$GITHUB_SHA"
|
||||
|
||||
- name: 安装 pnpm
|
||||
run: npm install -g pnpm@11 --registry=https://registry.npmmirror.com
|
||||
|
||||
- name: 安装依赖
|
||||
working-directory: intern-log
|
||||
run: pnpm install --frozen-lockfile --registry=https://registry.npmmirror.com
|
||||
|
||||
- name: pnpm audit(高危及以上)
|
||||
working-directory: intern-log
|
||||
run: pnpm audit --prod --audit-level=high --registry=https://registry.npmmirror.com
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
// 统一 CI 编排:本地与 Gitea Actions 共用同一入口
|
||||
// 用法:node scripts/ci.mjs
|
||||
// 步骤:起本地同步服务 → 协议审计 → 用户管理审计 → 单元测试 → 双设备回归 → 实时链路 → 网页构建 → 依赖审计(软性)
|
||||
// 任何硬性步骤失败即整体失败(依赖审计默认软性,CI 里单独放开)
|
||||
import { spawn, spawnSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const PORT = process.env.SYNC_PORT || '3780'
|
||||
const BASE = 'http://127.0.0.1:' + PORT
|
||||
const results = []
|
||||
let hardFailed = false
|
||||
let softWarned = false
|
||||
|
||||
function log(tag, line) { console.log('[' + tag + '] ' + line) }
|
||||
|
||||
function runStep(name, cmd, args, opts = {}) {
|
||||
log('RUN', name + ' (' + cmd + ' ' + args.join(' ') + ')')
|
||||
const started = Date.now()
|
||||
const r = spawnSync(cmd, args, { cwd: opts.cwd || ROOT, env: process.env, encoding: 'utf-8', shell: process.platform === 'win32' && cmd.endsWith('.cmd') })
|
||||
const out = ((r.stdout || '') + (r.stderr || '')).trim()
|
||||
const tail = out.split('\n').slice(-opts.tailLines || -40)
|
||||
for (const l of (opts.tail || out.split('\n').slice(-25))) if (l.trim()) console.log(' ' + l)
|
||||
const dur = ((Date.now() - started) / 1000).toFixed(1) + 's'
|
||||
const ok = r.status === 0
|
||||
results.push({ name, ok, dur })
|
||||
if (ok) log('OK ', name + ' (' + dur + ')')
|
||||
else {
|
||||
log('FAIL', name + ' (exit ' + r.status + ', ' + dur + ')')
|
||||
if (opts.soft) { softWarned = true; log('WARN', name + ' 为软性检查,不阻断 CI') }
|
||||
else hardFailed = true
|
||||
}
|
||||
return { ok, out }
|
||||
}
|
||||
|
||||
function findEsbuild() {
|
||||
const candidates = [
|
||||
path.join(ROOT, 'intern-log', 'node_modules', 'esbuild', 'bin', 'esbuild')
|
||||
]
|
||||
const pnpmDir = path.join(ROOT, 'intern-log', 'node_modules', '.pnpm')
|
||||
try {
|
||||
for (const d of fs.readdirSync(pnpmDir)) {
|
||||
if (d.startsWith('esbuild@')) candidates.push(path.join(pnpmDir, d, 'node_modules', 'esbuild', 'bin', 'esbuild'))
|
||||
}
|
||||
} catch { /* 无 .pnpm 目录 */ }
|
||||
for (const c of candidates) if (fs.existsSync(c)) return c
|
||||
throw new Error('找不到 esbuild,请先在 intern-log 下执行 pnpm install')
|
||||
}
|
||||
|
||||
function runBundled(name, tsPath) {
|
||||
const outfile = path.join(os.tmpdir(), 'daywork-' + name + '-' + Date.now() + '.cjs')
|
||||
const esbuildBin = findEsbuild()
|
||||
const b = runStep('bundle:' + name, process.execPath, [esbuildBin, tsPath, '--bundle', '--platform=node', '--format=cjs', '--outfile=' + outfile], { tail: [] })
|
||||
if (!b.ok) return
|
||||
runStep('test:' + name, process.execPath, [outfile], {})
|
||||
try { fs.rmSync(outfile, { force: true }) } catch { /* 忽略 */ }
|
||||
}
|
||||
|
||||
async function waitForServer(deadlineMs) {
|
||||
const started = Date.now()
|
||||
while (Date.now() - started < deadlineMs) {
|
||||
try {
|
||||
const r = await fetch(BASE + '/api/health')
|
||||
if (r.ok) return true
|
||||
} catch { /* 未就绪 */ }
|
||||
await new Promise(r => globalThis.setTimeout(r, 300))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ---------- 0) 清理历史测试数据,保证套件幂等 ----------
|
||||
fs.rmSync(path.join(ROOT, 'server', 'data'), { recursive: true, force: true })
|
||||
|
||||
// ---------- 1) 启动本地同步服务 ----------
|
||||
const serverProc = spawn(process.execPath, [path.join(ROOT, 'server', 'index.js')], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, PORT },
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
})
|
||||
let serverLog = ''
|
||||
serverProc.stdout.on('data', d => { serverLog += d })
|
||||
serverProc.stderr.on('data', d => { serverLog += d })
|
||||
|
||||
const up = await waitForServer(15000)
|
||||
if (!up) {
|
||||
console.error('[FATAL] 本地同步服务 15 秒内未就绪')
|
||||
console.error(serverLog.slice(-1500))
|
||||
process.exit(1)
|
||||
}
|
||||
log('OK', '同步服务已就绪 ' + BASE)
|
||||
|
||||
try {
|
||||
// ---------- 2) 服务端协议审计 ----------
|
||||
runStep('protocol-audit', process.execPath, [path.join(ROOT, 'tests', 'server', 'protocol.mjs')], {})
|
||||
// ---------- 3) 用户管理审计 ----------
|
||||
runStep('admin-audit', process.execPath, [path.join(ROOT, 'tests', 'server', 'admin.mjs')], {})
|
||||
// ---------- 4/5/6) 客户端引擎级测试 ----------
|
||||
runBundled('unit', 'intern-log/repro/unit-tests.ts')
|
||||
runBundled('two-device', 'intern-log/repro/two-device.ts')
|
||||
runBundled('realtime', 'intern-log/repro/engine-realtime.ts')
|
||||
|
||||
// ---------- 7) 网页构建(编译即检查) ----------
|
||||
const isWin = process.platform === 'win32'
|
||||
const pnpmCmd = isWin ? 'pnpm.cmd' : 'pnpm'
|
||||
runStep('build:web', pnpmCmd, ['run', 'build'], { cwd: path.join(ROOT, 'intern-log') })
|
||||
} finally {
|
||||
serverProc.kill()
|
||||
try { fs.rmSync(path.join(ROOT, 'server', 'data'), { recursive: true, force: true }) } catch { /* 忽略 */ }
|
||||
}
|
||||
|
||||
// ---------- 8) 依赖漏洞审计(软性:高危及以上才告警,CI 可单独放开为硬性) ----------
|
||||
const isWin = process.platform === 'win32'
|
||||
runStep('audit:deps', isWin ? 'pnpm.cmd' : 'pnpm', ['audit', '--prod', '--audit-level=high'], {
|
||||
cwd: path.join(ROOT, 'intern-log'),
|
||||
soft: true
|
||||
})
|
||||
|
||||
// ---------- 汇总 ----------
|
||||
console.log('')
|
||||
console.log('==================== CI 汇总 ====================')
|
||||
for (const r of results) console.log((r.ok ? ' PASS ' : ' FAIL ') + r.name.padEnd(18) + r.dur)
|
||||
console.log('================================================')
|
||||
if (hardFailed) {
|
||||
console.error('结果:存在失败步骤,CI 不通过')
|
||||
process.exit(1)
|
||||
}
|
||||
if (softWarned) console.log('结果:全部硬性步骤通过(存在软性告警,见上方 WARN)')
|
||||
else console.log('结果:全部通过')
|
||||
Reference in New Issue
Block a user