Compare commits
18 Commits
35aa7fb6bc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 47f92ac7e1 | |||
| 48a20c800d | |||
| 3b7545dd52 | |||
| 120b06a50c | |||
| 989ea68f7e | |||
| 59c947f2e0 | |||
| 9f27e4db51 | |||
| f2eb3f5cb1 | |||
| 00683e51c4 | |||
| 5b5ecd1b29 | |||
| 8ab8557ef1 | |||
| 91dcb735cd | |||
| 57dd743fc1 | |||
| a61081fca9 | |||
| 6490cd068e | |||
| 303e32f599 | |||
| b117a3694d | |||
| 0b28c94762 |
@@ -0,0 +1,84 @@
|
||||
name: CI and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_full_e2e:
|
||||
description: 'Run full E2E tests after deploy'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
ci-gate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git clone http://172.17.0.1:3000/kurihada/no-whatever.git .
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Generate Prisma
|
||||
run: npx prisma generate
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Unit tests
|
||||
run: npm run test
|
||||
|
||||
build-and-deploy:
|
||||
needs: ci-gate
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git clone http://172.17.0.1:3000/kurihada/no-whatever.git .
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build \
|
||||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="${{ secrets.AMAP_API_KEY }}" \
|
||||
-t no-whatever:latest .
|
||||
|
||||
- name: Deploy
|
||||
run: |
|
||||
docker stop no-whatever || true
|
||||
docker rm no-whatever || true
|
||||
mkdir -p /data/no-whatever && chown 1001:1001 /data/no-whatever
|
||||
docker run -d \
|
||||
--name no-whatever \
|
||||
--network nginx \
|
||||
-p 3721:3721 \
|
||||
-v /data/no-whatever:/app/data \
|
||||
-e DATABASE_URL=file:/app/data/prod.db \
|
||||
-e AMAP_API_KEY="${{ secrets.AMAP_API_KEY }}" \
|
||||
-e DEEPSEEK_API_KEY="${{ secrets.DEEPSEEK_API_KEY }}" \
|
||||
--restart unless-stopped \
|
||||
no-whatever:latest
|
||||
|
||||
full-e2e:
|
||||
needs: build-and-deploy
|
||||
if: ${{ inputs.run_full_e2e }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
git clone http://172.17.0.1:3000/kurihada/no-whatever.git .
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Full E2E
|
||||
run: |
|
||||
npx prisma generate
|
||||
npm run test:e2e
|
||||
@@ -0,0 +1,8 @@
|
||||
ARG CI_IMAGE=mcr.microsoft.com/playwright:v1.51.1-jammy
|
||||
FROM ${CI_IMAGE}
|
||||
|
||||
ARG NPM_REGISTRY=https://registry.npmmirror.com
|
||||
WORKDIR /workspace
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm config set registry ${NPM_REGISTRY} && npm ci --prefer-offline --no-audit --progress=false
|
||||
Vendored
+137
-16
@@ -1,8 +1,20 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
options {
|
||||
skipDefaultCheckout(true)
|
||||
}
|
||||
|
||||
parameters {
|
||||
booleanParam(name: 'RUN_FULL_E2E', defaultValue: false, description: '是否在部署后额外执行全量 E2E(失败仅标记 UNSTABLE)')
|
||||
booleanParam(name: 'RUN_COVERAGE', defaultValue: false, description: '是否在主链路执行覆盖率测试(关闭可提速)')
|
||||
}
|
||||
|
||||
environment {
|
||||
APP_NAME = 'no-whatever'
|
||||
CI_IMAGE = 'mcr.microsoft.com/playwright:v1.51.1-jammy'
|
||||
NPM_REGISTRY = 'https://registry.npmmirror.com'
|
||||
BUILDX_CACHE_DIR = '.buildx-cache'
|
||||
}
|
||||
|
||||
triggers {
|
||||
@@ -23,39 +35,67 @@ pipeline {
|
||||
}
|
||||
|
||||
stage('Prepare Test Images') {
|
||||
steps {
|
||||
sh '''
|
||||
docker pull node:20-bookworm
|
||||
docker pull mcr.microsoft.com/playwright:v1.51.1-jammy
|
||||
'''
|
||||
options {
|
||||
timeout(time: 15, unit: 'MINUTES')
|
||||
}
|
||||
}
|
||||
|
||||
stage('Quality Gate') {
|
||||
steps {
|
||||
retry(2) {
|
||||
sh '''
|
||||
set -e
|
||||
cid=$(docker create node:20-bookworm sh -lc "cd /workspace && npm ci && npm run lint && npx tsc --noEmit && npm run test:coverage")
|
||||
trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
|
||||
docker cp . "$cid":/workspace
|
||||
docker start -a "$cid"
|
||||
|
||||
if docker image inspect ${CI_IMAGE} >/dev/null 2>&1; then
|
||||
echo "${CI_IMAGE} already exists, skip pull"
|
||||
else
|
||||
docker pull ${CI_IMAGE}
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('E2E Gate') {
|
||||
stage('Prepare CI Deps Image') {
|
||||
options {
|
||||
timeout(time: 20, unit: 'MINUTES')
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
env.LOCK_HASH = sh(script: "sha256sum package-lock.json | cut -d ' ' -f1", returnStdout: true).trim()
|
||||
env.CI_DEPS_IMAGE = "${APP_NAME}-ci-deps:${env.LOCK_HASH}"
|
||||
}
|
||||
sh '''
|
||||
set -e
|
||||
cid=$(docker create --ipc=host mcr.microsoft.com/playwright:v1.51.1-jammy sh -lc "cd /workspace && npm ci && npm run test:e2e")
|
||||
if docker image inspect "${CI_DEPS_IMAGE}" >/dev/null 2>&1; then
|
||||
echo "deps image cache hit: ${CI_DEPS_IMAGE}"
|
||||
else
|
||||
echo "deps image cache miss, building: ${CI_DEPS_IMAGE}"
|
||||
docker build \
|
||||
--build-arg CI_IMAGE=${CI_IMAGE} \
|
||||
--build-arg NPM_REGISTRY=${NPM_REGISTRY} \
|
||||
-f Dockerfile.ci-deps \
|
||||
-t "${CI_DEPS_IMAGE}" .
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('CI Gate (Lint + Unit + Smoke E2E)') {
|
||||
options {
|
||||
timeout(time: 30, unit: 'MINUTES')
|
||||
}
|
||||
steps {
|
||||
sh '''
|
||||
set -e
|
||||
rm -rf playwright-report test-results
|
||||
cid=$(docker create --ipc=host \
|
||||
-e HOME=/tmp \
|
||||
-e RUN_COVERAGE=${RUN_COVERAGE} \
|
||||
${CI_DEPS_IMAGE} \
|
||||
sh -lc 'set -e; cd /workspace; npx prisma generate; npm run lint; if [ "$RUN_COVERAGE" = "true" ]; then npm run test:coverage; else npm run test; fi; npm run test:e2e:smoke')
|
||||
cleanup() {
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
docker cp . "$cid":/workspace
|
||||
git archive --format=tar HEAD | docker cp - "$cid":/workspace
|
||||
set +e
|
||||
docker start -a "$cid"
|
||||
status=$?
|
||||
@@ -77,7 +117,50 @@ pipeline {
|
||||
withCredentials([
|
||||
string(credentialsId: 'amap-api-key', variable: 'AMAP_KEY')
|
||||
]) {
|
||||
sh "docker build --build-arg NEXT_PUBLIC_AMAP_API_KEY=${AMAP_KEY} -t ${APP_NAME}:${BUILD_NUMBER} -t ${APP_NAME}:latest ."
|
||||
sh '''
|
||||
set -e
|
||||
CACHE_DIR="${BUILDX_CACHE_DIR}"
|
||||
CACHE_NEW_DIR="${BUILDX_CACHE_DIR}-new"
|
||||
rm -rf "$CACHE_NEW_DIR"
|
||||
|
||||
if docker buildx version >/dev/null 2>&1; then
|
||||
echo "using docker buildx with local cache"
|
||||
docker buildx create --name "${APP_NAME}-builder" --use >/dev/null 2>&1 || true
|
||||
docker buildx use "${APP_NAME}-builder" >/dev/null 2>&1 || true
|
||||
mkdir -p "$CACHE_DIR"
|
||||
|
||||
set +e
|
||||
docker buildx build \
|
||||
--load \
|
||||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
|
||||
--cache-from type=local,src="$CACHE_DIR" \
|
||||
--cache-to type=local,dest="$CACHE_NEW_DIR",mode=max \
|
||||
-t "${APP_NAME}:${BUILD_NUMBER}" \
|
||||
-t "${APP_NAME}:latest" \
|
||||
.
|
||||
bx_status=$?
|
||||
set -e
|
||||
|
||||
if [ $bx_status -eq 0 ]; then
|
||||
rm -rf "$CACHE_DIR"
|
||||
mv "$CACHE_NEW_DIR" "$CACHE_DIR"
|
||||
else
|
||||
echo "buildx build failed, fallback to classic docker build"
|
||||
docker build \
|
||||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
|
||||
-t "${APP_NAME}:${BUILD_NUMBER}" \
|
||||
-t "${APP_NAME}:latest" \
|
||||
.
|
||||
fi
|
||||
else
|
||||
echo "docker buildx unavailable, fallback to classic docker build"
|
||||
docker build \
|
||||
--build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
|
||||
-t "${APP_NAME}:${BUILD_NUMBER}" \
|
||||
-t "${APP_NAME}:latest" \
|
||||
.
|
||||
fi
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +190,44 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Full E2E (Optional, Non-Blocking)') {
|
||||
when {
|
||||
expression { return params.RUN_FULL_E2E }
|
||||
}
|
||||
options {
|
||||
timeout(time: 30, unit: 'MINUTES')
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
|
||||
sh '''
|
||||
set -e
|
||||
rm -rf playwright-report-full test-results-full
|
||||
cid=$(docker create --ipc=host \
|
||||
-e HOME=/tmp \
|
||||
${CI_DEPS_IMAGE} \
|
||||
sh -lc 'set -e; cd /workspace; npx prisma generate; npm run test:e2e')
|
||||
cleanup() {
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
git archive --format=tar HEAD | docker cp - "$cid":/workspace
|
||||
set +e
|
||||
docker start -a "$cid"
|
||||
status=$?
|
||||
set -e
|
||||
docker cp "$cid":/workspace/playwright-report ./playwright-report-full 2>/dev/null || true
|
||||
docker cp "$cid":/workspace/test-results ./test-results-full 2>/dev/null || true
|
||||
exit $status
|
||||
'''
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts artifacts: 'playwright-report-full/**,test-results-full/**', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("首页模式卡片可正确导航", async ({ page }) => {
|
||||
test("@smoke 首页模式卡片可正确导航", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "NoWhatever" })).toBeVisible();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("Panic 手动加入房间会规范化房间号并发起 join 请求", async ({ page }) => {
|
||||
test("@smoke Panic 手动加入房间会规范化房间号并发起 join 请求", async ({ page }) => {
|
||||
const joinBodies: Array<{ userId?: string }> = [];
|
||||
|
||||
await page.route("**/api/room/*/join", async (route) => {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"test:watch": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"test:e2e:install": "playwright install chromium",
|
||||
"test:e2e:smoke": "playwright test --grep @smoke",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:headed": "playwright test --headed"
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ const shared = {
|
||||
env: {
|
||||
AMAP_API_KEY: "test-amap-key",
|
||||
DEEPSEEK_API_KEY: "test-deepseek-key",
|
||||
JWT_SECRET: "test-jwt-secret",
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user