name: Sync & Release DeepSeek Harness

# 定期检测 npm 上的 @deepseek-ai/dsh 最新版本；发现新版本后自动构建四个平台、
# 发布 npm 平台包与 GitHub Release，并把版本号同步到 npm 分支。
# 上游 deepseek-ai/deepseek-harness 目前不发布 GitHub Release，同步信号以 npm
# 已发布版本为准：取 semver 最高者（覆盖 latest/next 等所有 dist-tag —— 上游可能
# 把新 rc 标在 next 上而 latest 仍是旧版），解析逻辑见 scripts/resolve-latest-dsh-version.mjs。
#
# 注意：GitHub 的 schedule 触发器只读取【默认分支】上的本文件，请确保默认分支
# （或将本文件同步到的分支）包含此 workflow。
on:
  schedule:
    - cron: '0 */6 * * *'
  workflow_dispatch:
    inputs:
      version:
        description: '手动指定要同步的 dsh 版本（留空自动取 npm 已发布版本中 semver 最高者）'
        required: false
        default: ''
      force:
        description: '即使版本未变化也强制重新构建'
        required: false
        type: boolean
        default: false

permissions:
  contents: write

# 同一目标版本同时只允许一个运行，防止定时与手动触发并发导致重复发布
concurrency:
  group: release-dsh-${{ github.event.inputs.version || 'auto' }}
  cancel-in-progress: false

jobs:
  sync:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.resolve.outputs.version }}
      changed: ${{ steps.resolve.outputs.changed }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: npm

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Resolve target version
        id: resolve
        shell: bash
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          set -euo pipefail

          # 手动指定优先；留空时取 npm 已发布版本中 semver 最高者，
          # 覆盖 latest/next 等所有 dist-tag（见 scripts/resolve-latest-dsh-version.mjs）。
          target="${{ github.event.inputs.version }}"
          if [ -z "$target" ]; then
            target="$(node scripts/resolve-latest-dsh-version.mjs)"
          fi
          echo "target version: $target"

          # 上次已发布的版本，从最新 release tag `dsh-<version>-<run_id>` 解析。
          last=""
          if gh release list --limit 1 --json tagName --jq '.[0].tagName' >/tmp/last_release.txt 2>/tmp/last_release.err; then
            tag="$(cat /tmp/last_release.txt)"
            last="${tag#dsh-}"
            last="${last%-*}"
          else
            echo "gh release list failed: $(cat /tmp/last_release.err)"
          fi
          echo "last released version: ${last:-<none>}"

          changed="false"
          if [ "$target" != "$last" ] || [ "${{ github.event.inputs.force }}" = "true" ]; then
            changed="true"
          fi
          echo "changed: $changed"

          echo "version=$target" >> "$GITHUB_OUTPUT"
          echo "changed=$changed" >> "$GITHUB_OUTPUT"

  build:
    name: Build on ${{ matrix.os }}
    needs: sync
    if: needs.sync.outputs.changed == 'true'
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [windows-latest, macos-latest, macos-15-intel, ubuntu-latest]
        include:
          - os: windows-latest
            artifact_name: deepseek-harness-pkg-windows.zip
            npm_name: core-win32-x64
          - os: macos-latest
            artifact_name: deepseek-harness-pkg-macos-arm64.zip
            npm_name: core-darwin-arm64
          - os: macos-15-intel
            artifact_name: deepseek-harness-pkg-macos-x64.zip
            npm_name: core-darwin-x64
          - os: ubuntu-latest
            artifact_name: deepseek-harness-pkg-linux.zip
            npm_name: core-linux-x64

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: npm

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Install pnpm
        uses: pnpm/action-setup@v4

      - name: Pin dsh version and install dependencies
        shell: bash
        run: |
          pnpm add "@deepseek-ai/dsh@${{ needs.sync.outputs.version }}" --save-exact
          pnpm install

      - name: Deploy package
        shell: bash
        run: |
          pnpm deploy --filter . --prod build_dir --legacy
          cd build_dir
          npm install --omit=dev

      - name: Apply dsh-web-app patch
        shell: bash
        working-directory: build_dir
        run: |
          node ../scripts/apply-dsh-web-app-patch.mjs

      - name: Verify patch applied
        shell: bash
        working-directory: build_dir
        run: |
          grep -q 'DSH_PKG_ALLOW_LAN' node_modules/@deepseek-ai/dsh-web-app/lib/startup.js && echo "patch OK" || (echo "patch missing" && exit 1)

      - name: Cleanup unnecessary files
        shell: bash
        working-directory: build_dir
        run: |
          # 统一瘦身逻辑 (跨平台支持)
          find node_modules -type f \( -name "*.map" -o -name "*.ts" -o -name "*.mts" \) -delete || true
          find node_modules -type d \( -name "test" -o -name "tests" -o -name "benchmark" -o -name "example" -o -name "demo" \) -exec rm -rf {} + || true

      - name: Create archive
        shell: bash
        working-directory: build_dir
        run: |
          # 根据不同平台使用对应的压缩命令
          if [ "${{ matrix.os }}" = "windows-latest" ]; then
            7z a ../${{ matrix.artifact_name }} node_modules package.json package-lock.json patches
          else
            zip -r ../${{ matrix.artifact_name }} node_modules package.json package-lock.json patches
          fi

      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact_name }}
          path: ${{ matrix.artifact_name }}

      # npm 分发（esbuild 模式）：每个平台 runner 把 build_dir 直接发布为
      # @dsh-pkg/core-<platform> 包，国内用户可经 npmmirror 镜像高速下载。
      # 放在 Upload Artifact 之后：prepare 会删除 .bin 并 dereference symlink，
      # 不影响已打包的 GitHub Release zip 内容。
      - name: Prepare npm package
        shell: bash
        working-directory: build_dir
        run: |
          node ../scripts/prepare-npm-package.mjs "@dsh-pkg/${{ matrix.npm_name }}"

      - name: Publish to npm
        shell: bash
        working-directory: build_dir
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: |
          npm config set //registry.npmjs.org/:_authToken "$NODE_AUTH_TOKEN"
          # npm 要求 prerelease 版本必须显式指定 dist-tag（不能落到 latest）：
          # 从版本号提取标识，0.1.1-rc.2 -> --tag rc，正式版不加 tag
          tag_args() {
            local ver
            ver=$(node -p "require('./package.json').version")
            if [[ "$ver" == *-* ]]; then
              echo "--tag ${ver#*-}" | sed -E 's/([a-zA-Z-]+)\..*/\1/'
            fi
          }
          if npm publish --access public $(tag_args); then
            exit 0
          fi
          # npm 不允许覆盖已发布版本：同版本强制重建时追加 build 后缀重试一次
          echo "::warning::base version already published, retrying with build suffix"
          node ../scripts/prepare-npm-package.mjs "@dsh-pkg/${{ matrix.npm_name }}" "build.${{ github.run_id }}"
          npm publish --access public $(tag_args) || echo "::warning::npm publish failed (GitHub Release 不受影响)"

  release:
    needs: [sync, build]
    if: needs.sync.outputs.changed == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Download all artifacts
        uses: actions/download-artifact@v4
        with:
          path: ./artifacts

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dsh-${{ needs.sync.outputs.version }}-${{ github.run_id }}
          name: Release-dsh-${{ needs.sync.outputs.version }}
          body: |
            # DeepSeek Harness Desktop Packages
            - Version: ${{ needs.sync.outputs.version }}
            - Build ID: ${{ github.run_id }}
            - 使用说明见仓库 README.md
          draft: false
          prerelease: false
          # 自动匹配下载目录下的 zip 文件
          files: |
            ./artifacts/**/*.zip

      # 主动触发 npmmirror 同步刚发布的 4 个平台包（自动同步有分钟~小时级
      # 延迟，主动推一次让国内用户尽快可走 registry.npmmirror.com 下载）。
      - name: Trigger npmmirror sync
        run: |
          for pkg in core-win32-x64 core-darwin-arm64 core-darwin-x64 core-linux-x64; do
            curl -s -X PUT "https://registry-direct.npmmirror.com/-/package/@dsh-pkg/$pkg/syncs" || true
          done

  # 构建完成后，把 npm 分支上的版本号（@deepseek-ai/dsh 依赖以及本仓库自身
  # version）同步为本次发布的版本，并实时推送到 npm 分支。本仓库以 npm 分支
  # 为唯一工作分支，不回合并到上游/main。
  update-branch:
    needs: [sync, build]
    if: needs.sync.outputs.changed == 'true'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout npm branch
        uses: actions/checkout@v4
        with:
          ref: npm

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 24

      - name: Install pnpm
        uses: pnpm/action-setup@v4

      - name: Configure git
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

      - name: Update version on npm branch
        env:
          TARGET: ${{ needs.sync.outputs.version }}
        run: |
          set -euo pipefail
          # 更新 @deepseek-ai/dsh 依赖为本次同步的版本（save-exact）
          npm pkg set "dependencies.@deepseek-ai/dsh=$TARGET"
          # 本仓库自身 version 同步为该版本
          npm version "$TARGET" --no-git-tag-version --allow-same-version
          # 同步 lockfile（--lockfile-only 只重算解析，不构建；allowUnusedPatches 忽略旧版本补丁）
          pnpm install --lockfile-only --ignore-scripts

      - name: Commit and push to npm branch
        run: |
          if git diff --quiet && git diff --cached --quiet; then
            echo "no version changes to commit"
          else
            git add package.json pnpm-lock.yaml
            git commit -m "chore: sync version to ${{ needs.sync.outputs.version }}"
            git push origin npm
            echo "pushed version ${{ needs.sync.outputs.version }} to npm branch"
          fi
