From e8458df680326cefdd3da8a3f9c4dfaa95e30d8b Mon Sep 17 00:00:00 2001 From: alboped Date: Sun, 12 Apr 2026 01:29:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=EF=BC=8C=E6=96=B0=E5=A2=9Eweb=E9=A1=B9=E7=9B=AEci?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + ci/web-spa-deploy.yml | 158 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 README.md create mode 100644 ci/web-spa-deploy.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..783384f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# CI脚本项目 + +## ci/web-spa-deploy.yml: web项目构建、部署; diff --git a/ci/web-spa-deploy.yml b/ci/web-spa-deploy.yml new file mode 100644 index 0000000..1591bab --- /dev/null +++ b/ci/web-spa-deploy.yml @@ -0,0 +1,158 @@ +# 公共可复用工作流(workflow_call)。调用方传入:node_version、yarn_version、project_dir。 +# 部署主机/用户/父路径、SSH 私钥从「调用方」仓库的 vars / secrets 读取(同仓 uses: ./... 时即本仓库配置)。 +# +# 调用方 ci.yml 示例: +# jobs: +# build: +# uses: ./.gitea/workflows/node-spa-tar-deploy.reusable.yml@main +# with: +# node_version: "22.14.0" +# yarn_version: "1.22.22" +# project_dir: "chat-one-web" +# secrets: inherit +# +# 调用方「工作流 → 变量」:DEPLOY_PATH、DEPLOY_HOST、DEPLOY_USER +# 调用方「工作流 → 密钥」:SSH_PRIVATE_KEY +# 远端发布目录 = ${DEPLOY_PATH}/${project_dir} + +name: Reusable Node SPA + tar deploy + +on: + workflow_call: + inputs: + node_version: + description: "Node 完整版本号(如 22.14.0),npmmirror linux-x64" + required: true + type: string + yarn_version: + description: "Yarn 1 版本号(如 1.22.22)" + required: true + type: string + project_dir: + description: "站点子目录(相对 DEPLOY_PATH,如 chat-one-web)" + required: true + type: string + +jobs: + build: + runs-on: ubuntu-latest + env: + DEPLOY_PATH: ${{ vars.DEPLOY_PATH }} + DEPLOY_HOST: ${{ vars.DEPLOY_HOST }} + DEPLOY_USER: ${{ vars.DEPLOY_USER }} + + steps: + - name: Checkout + env: + TOKEN: ${{ github.token }} + run: | + set -e + export GIT_TERMINAL_PROMPT=0 + if ! command -v git >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + shopt -s nullglob + for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list; do + [ -f "$f" ] || continue + sed -i \ + -e 's/deb.debian.org/mirrors.aliyun.com/g' \ + -e 's/security.debian.org/mirrors.aliyun.com/g' \ + "$f" + done + apt-get -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 -o Acquire::Retries=2 update + apt-get install -y --no-install-recommends git + fi + SERVER="${{ github.server_url }}" + SERVER="${SERVER%/}" + REPO="${{ github.repository }}" + ORIGIN="${SERVER/https:\/\//https:\/\/oauth2:${TOKEN}@}" + ORIGIN="${ORIGIN/http:\/\//http:\/\/oauth2:${TOKEN}@}/${REPO}.git" + git init + git remote add origin "$ORIGIN" + git fetch --depth 1 origin "${{ github.sha }}" + git checkout -f "${{ github.sha }}" + + - name: Install Node ${{ inputs.node_version }} + run: | + set -euo pipefail + NODE_VER="${{ inputs.node_version }}" + WANT="${NODE_VER#v}" + HAVE="" + if command -v node >/dev/null 2>&1; then + HAVE="$(node -v | sed 's/^v//')" + fi + if [ -n "$HAVE" ] && [ "$HAVE" = "$WANT" ]; then + echo "Node 已是 ${WANT}(node -v: v${HAVE}),跳过下载安装" + node -v + exit 0 + fi + ARCH="linux-x64" + NAME="node-v${WANT}-${ARCH}" + URL="https://npmmirror.com/mirrors/node/v${WANT}/${NAME}.tar.xz" + curl -fsSL "$URL" -o /tmp/node.tar.xz + mkdir -p /opt/node-ci + tar -xJf /tmp/node.tar.xz -C /opt/node-ci + BINDIR="/opt/node-ci/${NAME}/bin" + if [ -x "$BINDIR/node" ]; then + [ -n "${GITHUB_PATH:-}" ] && echo "$BINDIR" >> "$GITHUB_PATH" + [ -n "${GITHUB_ENV:-}" ] && echo "PATH=$BINDIR:$PATH" >> "$GITHUB_ENV" + export PATH="$BINDIR:$PATH" + node -v + else + echo "Install Node failed: $BINDIR/node missing" >&2 + exit 1 + fi + + - name: Setup Yarn + run: | + corepack enable + corepack prepare yarn@${{ inputs.yarn_version }} --activate + echo "node -v: $(node -v); yarn -v: $(yarn -v)" + + - name: Install + run: yarn install --frozen-lockfile + + - name: Prettier check + run: yarn format:check + + - name: ESLint + run: yarn lint + + - name: Build + run: yarn build + + - name: Install OpenSSH client + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + run: | + set -e + export DEBIAN_FRONTEND=noninteractive + shopt -s nullglob + for f in /etc/apt/sources.list /etc/apt/sources.list.d/debian.sources /etc/apt/sources.list.d/*.sources /etc/apt/sources.list.d/*.list; do + [ -f "$f" ] || continue + sed -i \ + -e 's/deb.debian.org/mirrors.aliyun.com/g' \ + -e 's/security.debian.org/mirrors.aliyun.com/g' \ + "$f" + done + apt-get -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 -o Acquire::Retries=2 update + apt-get install -y --no-install-recommends openssh-client + + - name: Deploy to Nginx (tar over SSH) + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + PROJECT_DIR: ${{ inputs.project_dir }} + run: | + set -e + : "${DEPLOY_PATH:?DEPLOY_PATH (vars) is not set}" + : "${DEPLOY_HOST:?DEPLOY_HOST (vars) is not set}" + : "${DEPLOY_USER:?DEPLOY_USER (vars) is not set}" + : "${PROJECT_DIR:?project_dir is not set}" + REMOTE_ROOT="${DEPLOY_PATH}/${PROJECT_DIR}" + mkdir -p ~/.ssh + chmod 700 ~/.ssh + echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_deploy + chmod 600 ~/.ssh/id_deploy + ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts + SSH=(ssh -i ~/.ssh/id_deploy -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes) + "${SSH[@]}" "${DEPLOY_USER}@${DEPLOY_HOST}" "mkdir -p '${REMOTE_ROOT}' && find '${REMOTE_ROOT}' -mindepth 1 -delete" + tar czf - -C dist . | "${SSH[@]}" "${DEPLOY_USER}@${DEPLOY_HOST}" "tar xzf - -C '${REMOTE_ROOT}'"