name: Reusable Prepare Workspace on: workflow_call: inputs: repo_url: description: "Git 仓库地址" required: true type: string git_sha: description: "目标提交 SHA" required: true type: string node_major: description: "Node 主版本(例如 22)" required: false type: string default: "22" node_version: description: "Node 精确版本(例如 22.12.0,优先于 node_major)" required: false type: string default: "" yarn_version: description: "Yarn 版本(例如 stable 或 1.22.22)" required: false type: string default: "stable" nodejs_mirror: description: "Node 二进制镜像地址" required: false type: string default: "https://npmmirror.com/mirrors/node" npm_registry: description: "NPM 镜像地址" required: false type: string default: "https://registry.npmmirror.com" node_install_root: description: "Node 安装缓存目录" required: false type: string default: "" run_commands: description: "准备完成后执行的额外命令(多行 shell)" required: false type: string default: "" secrets: SSH_PRIVATE_KEY: required: false REGISTRY_USERNAME: required: false REGISTRY_PASSWORD: required: false jobs: prepare: runs-on: ubuntu-latest env: REPO_URL: ${{ inputs.repo_url }} GIT_SHA: ${{ inputs.git_sha }} NODE_MAJOR: ${{ inputs.node_major }} NODE_VERSION: ${{ inputs.node_version }} YARN_VERSION: ${{ inputs.yarn_version }} NODEJS_MIRROR: ${{ inputs.nodejs_mirror }} NPM_REGISTRY: ${{ inputs.npm_registry }} NODE_INSTALL_ROOT: ${{ inputs.node_install_root }} RUN_COMMANDS: ${{ inputs.run_commands }} steps: - name: Prepare workspace (checkout + node/yarn) run: | set -euo pipefail resolve_node_version() { if [ -n "${NODE_VERSION}" ]; then echo "${NODE_VERSION#v}" return fi local resolved resolved="$(curl -fsSL "${NODEJS_MIRROR}/index.tab" | awk -F'\t' -v major="${NODE_MAJOR}" ' NR > 1 && $1 ~ ("^v" major "\\.") { print substr($1, 2); exit } ')" if [ -z "${resolved}" ]; then echo "failed to resolve latest Node ${NODE_MAJOR}.x from ${NODEJS_MIRROR}" exit 1 fi echo "${resolved}" } if [ -z "${NODE_INSTALL_ROOT}" ]; then NODE_INSTALL_ROOT="${HOME}/.local/node-ci" fi detect_arch() { case "$(uname -m)" in x86_64) echo "x64" ;; aarch64 | arm64) echo "arm64" ;; *) echo "unsupported architecture: $(uname -m)" exit 1 ;; esac } install_node() { local target_version="$1" local arch name url tarball install_dir bindir arch="$(detect_arch)" name="node-v${target_version}-linux-${arch}" url="${NODEJS_MIRROR}/v${target_version}/${name}.tar.xz" tarball="/tmp/${name}.tar.xz" install_dir="${NODE_INSTALL_ROOT}/${name}" bindir="${install_dir}/bin" if [ ! -x "${bindir}/node" ]; then mkdir -p "${NODE_INSTALL_ROOT}" curl -fsSL "${url}" -o "${tarball}" tar -xJf "${tarball}" -C "${NODE_INSTALL_ROOT}" rm -f "${tarball}" fi export PATH="${bindir}:${PATH}" } ensure_node() { local target_version node_version target_version="$(resolve_node_version)" if command -v node >/dev/null 2>&1; then node_version="$(node -v | sed 's/^v//')" if [ "${node_version}" = "${target_version}" ]; then return fi fi install_node "${target_version}" node_version="$(node -v | sed 's/^v//')" if [ "${node_version}" != "${target_version}" ]; then echo "failed to switch to Node ${target_version}, current=${node_version}" exit 1 fi } if [ ! -d .git ]; then git clone "${REPO_URL}" . fi git fetch --all --tags --prune git checkout -f "${GIT_SHA}" ensure_node npm config set registry "${NPM_REGISTRY}" >/dev/null 2>&1 || true if ! command -v yarn >/dev/null 2>&1; then corepack enable export COREPACK_NPM_REGISTRY="${NPM_REGISTRY}" corepack prepare "yarn@${YARN_VERSION}" --activate fi echo "Node: $(node -v)" echo "Yarn: $(yarn -v)" - name: Run extra commands if: ${{ inputs.run_commands != '' }} env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} run: | set -euo pipefail printf '%s\n' "${RUN_COMMANDS}" > /tmp/reusable-prepare-run.sh chmod +x /tmp/reusable-prepare-run.sh /tmp/reusable-prepare-run.sh