name: Publish to npm

# 推 tag 后发布到 npm，走 npm Trusted Publishing（OIDC）：不需要 npm token，也不需要 OTP。
# npmjs.com 上 aaden-cli 的 Trusted Publisher 必须登记这个文件名（publish.yml）并允许 npm publish，改名要同步改那边。
#
# - `x.y.z`（`npm run update` 打的）→ npm `latest`。
#   ⚠️ 全网约 650 台设备每天 04:55 `npm install aaden-cli -g` 装 latest：发出去就等于全网发布。
# - `x.y.z-任意后缀`（如 1.6.103-rc.0，任何分支都可以打）→ npm `next`，设备不会自动装。
#   用来灰度：在某台设备上 `sudo npm i -g aaden-cli@next` 先试；那台设备第二天 04:55 会自己回到 latest。
#
# 私有通道（POS-46 第 2 期）：正式版另外签名发到私有桶（scripts/cli-release/publish-private.sh），设备经云端
# /device-cli/latest 领取。npm 和私有通道发的是同一个 `npm pack` 出来的 tgz（逐字节相同）。
# 私有通道是单独的 job（private），npm 发布成功后才跑，tgz 用 artifact 传过去：
#   - KMS / S3 出错只需要在 Actions 页面对这个 job 点「Re-run failed jobs」，不会重复发 npm（npm 不允许重发同一版本）；
#   - 只有 private job 拿 AWS 角色（id-token + contents: read），npm job 不碰 AWS。
#   - private job 跑在 environment「cli-release」里（只允许 x.y.z tag 部署），AWS 角色只信任这个 environment。
# 只在仓库变量 AWS_CLI_RELEASE_ROLE_ARN、CLI_SIGNING_KEY_ARN 都设好之后才跑；没设就整个 job 跳过，npm 发布照旧。
# 第三方 action 一律钉 40 位 commit（后面注释写对应的版本）：private job 拿的是能签名的角色。
on:
  push:
    tags:
      - '[0-9]+.[0-9]+.[0-9]+'
      - '[0-9]+.[0-9]+.[0-9]+-*'

permissions: {}

concurrency:
  group: npm-publish
  cancel-in-progress: false

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write # 发布成功后建 GitHub Release
      id-token: write # Trusted Publishing 用 OIDC 换 npm 发布权限（这个 job 不拿 AWS）
    outputs:
      dist_tag: ${{ steps.channel.outputs.dist_tag }}
      tgz: ${{ steps.pack.outputs.tgz }}
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
        with:
          fetch-depth: 0

      - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
          package-manager-cache: false

      - name: 决定发布渠道
        id: channel
        run: |
          if [[ "$GITHUB_REF_NAME" == *-* ]]; then
            echo "dist_tag=next" >> "$GITHUB_OUTPUT"
          else
            echo "dist_tag=latest" >> "$GITHUB_OUTPUT"
          fi

      - name: 校验 tag（latest）
        if: steps.channel.outputs.dist_tag == 'latest'
        run: |
          VERSION=$(node -p "require('./package.json').version")
          if [ "$GITHUB_REF_NAME" != "$VERSION" ]; then
            echo "::error::tag $GITHUB_REF_NAME 和 package.json 的版本 $VERSION 不一致"
            exit 1
          fi
          # 只发 master 上的提交：从别的分支推个 tag 不能直接发到全网
          if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/master; then
            echo "::error::tag $GITHUB_REF_NAME 指向的提交不在 master 上"
            exit 1
          fi

      # 预发布的版本号以 tag 为准，不用为了灰度去提交一次版本号
      - name: 写入预发布版本号（next）
        if: steps.channel.outputs.dist_tag == 'next'
        run: npm version "$GITHUB_REF_NAME" --no-git-tag-version

      - run: npm ci

      # 单测不过不发（以前发布前一个检查都没有）
      - name: 单测
        run: npm test

      - name: 打包
        run: npx ncc build index.js -o dist -C

      - name: 冒烟检查
        run: |
          OUT=$(node dist/index.js --version | tail -n 1)
          if [ "$OUT" != "$GITHUB_REF_NAME" ]; then
            echo "::error::dist/index.js --version 输出「$OUT」，期望 $GITHUB_REF_NAME"
            exit 1
          fi
          npm pack --dry-run 2>&1 | grep -q 'dist/index.js' || {
            echo "::error::npm 包里没有 dist/index.js"
            exit 1
          }

      # 打一次包，npm 和私有通道发同一个文件
      - name: 打 tgz
        id: pack
        run: |
          TGZ=$(npm pack --silent | tail -n 1)
          [ -f "$TGZ" ] || { echo "::error::npm pack 没产出 tgz"; exit 1; }
          echo "tgz=$TGZ" >> "$GITHUB_OUTPUT"
          echo "$TGZ sha256 $(sha256sum "$TGZ" | cut -d' ' -f1)"

      # 交给 private job 的就是 npm 发出去的这个文件
      # 只在私有通道要跑时才传（变量没设时不传，免得 artifact 出错挡住 npm 发布）；放在 npm publish 前面
      - name: 保存 tgz
        if: steps.channel.outputs.dist_tag == 'latest' && vars.AWS_CLI_RELEASE_ROLE_ARN != '' && vars.CLI_SIGNING_KEY_ARN != ''
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: npm-tgz
          path: ${{ steps.pack.outputs.tgz }}
          if-no-files-found: error
          retention-days: 30

      # 私有仓库不支持 provenance，显式关掉免得 publish 失败
      - name: 发布到 npm
        env:
          NPM_CONFIG_PROVENANCE: 'false'
        run: npm publish "./${{ steps.pack.outputs.tgz }}" --tag "${{ steps.channel.outputs.dist_tag }}"

      - name: GitHub Release
        if: steps.channel.outputs.dist_tag == 'latest'
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create "$GITHUB_REF_NAME" \
            --title "aaden-cli: release v$GITHUB_REF_NAME" \
            --generate-notes \
            --verify-tag

  # 私有通道（POS-46）：npm 那步成功后才跑；失败了单独重跑这个 job 即可（npm 不会重发）
  private:
    needs: publish
    if: needs.publish.outputs.dist_tag == 'latest' && vars.AWS_CLI_RELEASE_ROLE_ARN != '' && vars.CLI_SIGNING_KEY_ARN != ''
    runs-on: ubuntu-latest
    # 只允许 x.y.z tag 部署到这个 environment；AWS 角色的信任条件是 repo:innerken-gmbh/aaden-cli:environment:cli-release
    environment: cli-release
    permissions:
      contents: read
      id-token: write # 只用来换 AWS 角色
    concurrency:
      group: cli-release-manifest
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
        with:
          name: npm-tgz

      - uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
        with:
          role-to-assume: ${{ vars.AWS_CLI_RELEASE_ROLE_ARN }}
          aws-region: eu-central-1

      # npm 扫描可能拖几个小时；私有通道不等它
      - name: 发布到私有通道
        env:
          BUCKET: ${{ vars.CLI_RELEASE_BUCKET || 'aaden-cli-releases' }}
          KMS_KEY_ID: ${{ vars.CLI_SIGNING_KEY_ARN }}
          TGZ: ${{ needs.publish.outputs.tgz }}
        run: bash scripts/cli-release/publish-private.sh "./$TGZ" "$GITHUB_REF_NAME"
