Build and Release #61
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Product release CI (java-chains release repo; formerly web-chains). | |
| # Backend: Java-Chains/chains | SPA: in-tree java-chains-web-element (Vue Element Plus) | |
| name: Build and Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version without leading v (e.g. 2.0.0-beta3)" | |
| required: true | |
| type: string | |
| source_ref: | |
| description: "Branch or tag in Java-Chains/chains only (no commit SHA). Defaults to v<version>. Names containing 'internal' are rejected." | |
| required: false | |
| type: string | |
| publish: | |
| description: "Publish the GitHub Release immediately (skip draft). Default: draft only." | |
| required: false | |
| type: boolean | |
| default: false | |
| run_tests: | |
| description: "Run full backend (Maven / gate) and frontend (web + web-element) tests before packaging. Default: enabled. When enabled, product mvn does NOT pass -DskipTests." | |
| required: false | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| # ==================================================== | |
| # Job 0: 解析发布元信息 | |
| # ==================================================== | |
| resolve_release_meta: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_version: ${{ steps.meta.outputs.release_version }} | |
| release_tag: ${{ steps.meta.outputs.release_tag }} | |
| source_ref: ${{ steps.meta.outputs.source_ref }} | |
| is_prerelease: ${{ steps.meta.outputs.is_prerelease }} | |
| publish_release: ${{ steps.meta.outputs.publish_release }} | |
| run_tests: ${{ steps.meta.outputs.run_tests }} | |
| docker_tags: ${{ steps.meta.outputs.docker_tags }} | |
| chains_dev_jars_repository: ${{ steps.manifest.outputs.chains_dev_jars_repository }} | |
| chains_dev_jars_ref: ${{ steps.manifest.outputs.chains_dev_jars_ref }} | |
| java_echo_generator_repository: ${{ steps.manifest.outputs.java_echo_generator_repository }} | |
| java_echo_generator_ref: ${{ steps.manifest.outputs.java_echo_generator_ref }} | |
| java_memshell_generator_repository: ${{ steps.manifest.outputs.java_memshell_generator_repository }} | |
| java_memshell_generator_ref: ${{ steps.manifest.outputs.java_memshell_generator_ref }} | |
| chains_config_repository: ${{ steps.manifest.outputs.chains_config_repository }} | |
| chains_config_ref: ${{ steps.manifest.outputs.chains_config_ref }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Resolve Release Metadata | |
| id: meta | |
| env: | |
| DEPENDENCY_REPO_TOKEN: ${{ secrets.DEPENDENCY_REPO_TOKEN }} | |
| shell: bash | |
| run: | | |
| VERSION_INPUT="${{ inputs.version }}" | |
| VERSION="${VERSION_INPUT#v}" | |
| if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then | |
| echo "Invalid version: $VERSION_INPUT" | |
| echo "Use SemVer, for example: 2.0.0 or 2.0.0-beta3" | |
| exit 1 | |
| fi | |
| RELEASE_TAG="v$VERSION" | |
| SOURCE_REF="${{ inputs.source_ref }}" | |
| if [ -z "$SOURCE_REF" ]; then | |
| SOURCE_REF="$RELEASE_TAG" | |
| fi | |
| # Public release: branch/tag only; never commit SHA; never *internal* refs. | |
| if [[ "$SOURCE_REF" =~ ^[0-9a-fA-F]{7,40}$ ]]; then | |
| echo "source_ref must be a branch or tag name, not a commit SHA: $SOURCE_REF" | |
| exit 1 | |
| fi | |
| if [[ "$SOURCE_REF" == refs/* ]] || [[ "$SOURCE_REF" == *".."* ]] || [[ "$SOURCE_REF" == *" "* ]]; then | |
| echo "source_ref must be a plain branch or tag name: $SOURCE_REF" | |
| exit 1 | |
| fi | |
| SOURCE_REF_LC="$(printf '%s' "$SOURCE_REF" | tr '[:upper:]' '[:lower:]')" | |
| if [[ "$SOURCE_REF_LC" == *internal* ]]; then | |
| echo "Refusing public Build and Release for internal source_ref: $SOURCE_REF" | |
| echo "Use a public branch/tag (e.g. v2-dev), or the internal release workflow." | |
| exit 1 | |
| fi | |
| if [ -z "${DEPENDENCY_REPO_TOKEN:-}" ]; then | |
| echo "DEPENDENCY_REPO_TOKEN is required to validate source_ref against Java-Chains/chains" | |
| exit 1 | |
| fi | |
| # Private source repo: auth failures look like "Repository not found" — distinguish | |
| # that from a missing branch/tag so operators do not re-push tags for no reason. | |
| CHAINS_URL="https://x-access-token:${DEPENDENCY_REPO_TOKEN}@github.com/Java-Chains/chains.git" | |
| set +e | |
| LS_REMOTE_ERR="$(mktemp)" | |
| REF_LINES="$(git ls-remote "$CHAINS_URL" \ | |
| "refs/heads/${SOURCE_REF}" \ | |
| "refs/tags/${SOURCE_REF}" \ | |
| "refs/tags/${SOURCE_REF}^{}" 2>"$LS_REMOTE_ERR")" | |
| LS_EC=$? | |
| set -e | |
| if [ "$LS_EC" -ne 0 ] || grep -qiE 'Repository not found|Authentication failed|Invalid username|403|401' "$LS_REMOTE_ERR"; then | |
| echo "Cannot read Java-Chains/chains with secrets.DEPENDENCY_REPO_TOKEN (git exit=$LS_EC)." | |
| echo "This usually means the token expired, lost repo scope, or org SSO was not authorized —" | |
| echo "NOT that source_ref is missing. Tag/branch push to chains can still be fine." | |
| echo "Fix: update vulhub/java-chains secret DEPENDENCY_REPO_TOKEN (classic PAT with repo" | |
| echo "read on Java-Chains/chains, authorize SSO for the Java-Chains org if required)." | |
| sed 's#x-access-token:[^@]*@#x-access-token:***@#g' "$LS_REMOTE_ERR" >&2 || true | |
| rm -f "$LS_REMOTE_ERR" | |
| exit 1 | |
| fi | |
| rm -f "$LS_REMOTE_ERR" | |
| if [ -z "$(printf '%s' "$REF_LINES" | tr -d '[:space:]')" ]; then | |
| echo "source_ref must exist as refs/heads/* or refs/tags/* in Java-Chains/chains: $SOURCE_REF" | |
| echo "Commit SHAs and other object ids are not accepted." | |
| echo "Verified: DEPENDENCY_REPO_TOKEN can access the repo, but this ref was not found." | |
| exit 1 | |
| fi | |
| echo "Validated public source_ref as branch/tag:" | |
| printf '%s\n' "$REF_LINES" | |
| if [[ "$VERSION" == *-* ]]; then | |
| IS_PRERELEASE="true" | |
| else | |
| IS_PRERELEASE="false" | |
| fi | |
| # workflow_dispatch boolean → string "true"/"false"; default draft. | |
| PUBLISH_INPUT="${{ inputs.publish }}" | |
| if [ "$PUBLISH_INPUT" = "true" ]; then | |
| PUBLISH_RELEASE="true" | |
| else | |
| PUBLISH_RELEASE="false" | |
| fi | |
| # Default: run full frontend + backend tests (only skip when explicitly false). | |
| RUN_TESTS_INPUT="${{ inputs.run_tests }}" | |
| if [ "$RUN_TESTS_INPUT" = "false" ]; then | |
| RUN_TESTS="false" | |
| else | |
| RUN_TESTS="true" | |
| fi | |
| echo "release_version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT" | |
| echo "source_ref=$SOURCE_REF" >> "$GITHUB_OUTPUT" | |
| echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT" | |
| echo "publish_release=$PUBLISH_RELEASE" >> "$GITHUB_OUTPUT" | |
| echo "run_tests=$RUN_TESTS" >> "$GITHUB_OUTPUT" | |
| echo "run_tests=$RUN_TESTS publish=$PUBLISH_RELEASE" | |
| { | |
| echo "docker_tags<<EOF" | |
| echo "javachains/javachains:${VERSION}" | |
| if [ "$IS_PRERELEASE" != "true" ]; then | |
| echo "javachains/javachains:latest" | |
| fi | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Read Release Manifest | |
| id: manifest | |
| shell: bash | |
| run: | | |
| ruby <<'RUBY' | |
| require 'yaml' | |
| manifest = YAML.safe_load(File.read('.github/release-manifest.yml'), aliases: false) | |
| dependencies = manifest.fetch('dependencies') | |
| required = %w[ | |
| chains_dev_jars | |
| java_echo_generator | |
| java_memshell_generator | |
| chains_config | |
| ] | |
| File.open(ENV.fetch('GITHUB_OUTPUT'), 'a') do |out| | |
| required.each do |name| | |
| entry = dependencies[name] || abort("Missing dependency entry: #{name}") | |
| repository = entry['repository'].to_s.strip | |
| ref = entry['ref'].to_s.strip | |
| abort("Missing repository for #{name}") if repository.empty? | |
| abort("Missing ref for #{name}") if ref.empty? | |
| out.puts "#{name}_repository=#{repository}" | |
| out.puts "#{name}_ref=#{ref}" | |
| end | |
| end | |
| RUBY | |
| # ==================================================== | |
| # Job 1: 核心构建(适配 v2 模块与发行物) | |
| # ==================================================== | |
| build: | |
| needs: resolve_release_meta | |
| runs-on: ubuntu-latest | |
| # gate + frontend tests + package; allow headroom when run_tests=true | |
| timeout-minutes: 120 | |
| outputs: | |
| source_sha: ${{ steps.source_revision.outputs.source_sha }} | |
| frontend_sha: ${{ steps.frontend.outputs.frontend_sha }} | |
| env: | |
| JAVA_VERSION: '8' | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: Java-Chains/chains | |
| ref: ${{ needs.resolve_release_meta.outputs.source_ref }} | |
| token: ${{ secrets.DEPENDENCY_REPO_TOKEN }} | |
| fetch-depth: 0 | |
| - name: Checkout Release Packaging Script | |
| uses: actions/checkout@v4 | |
| with: | |
| path: _release_ci | |
| sparse-checkout: | | |
| .github/scripts | |
| sparse-checkout-cone-mode: false | |
| clean: false | |
| - name: Capture Source Revision | |
| id: source_revision | |
| shell: bash | |
| run: | | |
| echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Resolve Element Frontend Revision | |
| id: frontend | |
| shell: bash | |
| run: | | |
| test -f java-chains-web-element/package.json | |
| test -f java-chains-web-element/pnpm-lock.yaml | |
| FRONTEND_SHA="$(git log -1 --format=%H -- java-chains-web-element)" | |
| FRONTEND_CT="$(git log -1 --format=%ct -- java-chains-web-element)" | |
| echo "frontend_sha=${FRONTEND_SHA}" >> "$GITHUB_OUTPUT" | |
| echo "SOURCE_DATE_EPOCH=${FRONTEND_CT}" >> "$GITHUB_ENV" | |
| echo "Using in-tree java-chains-web-element @ ${FRONTEND_SHA}" | |
| - name: Validate Source Version | |
| shell: bash | |
| run: | | |
| EXPECTED_VERSION="${{ needs.resolve_release_meta.outputs.release_version }}" | |
| POM_VERSION=$(sed -n '0,/<version>/s/.*<version>\([^<]*\)<\/version>.*/\1/p' pom.xml | head -n 1) | |
| if [ -z "$POM_VERSION" ]; then | |
| echo "Failed to read version from pom.xml" | |
| exit 1 | |
| fi | |
| if [ "$POM_VERSION" != "$EXPECTED_VERSION" ]; then | |
| echo "Version mismatch:" | |
| echo " source_ref: ${{ needs.resolve_release_meta.outputs.source_ref }}" | |
| echo " pom.xml: $POM_VERSION" | |
| echo " input: $EXPECTED_VERSION" | |
| echo "Please bump/tag the source repo first, or pass the correct source_ref." | |
| exit 1 | |
| fi | |
| - name: Clone Pinned Dependency Repositories | |
| env: | |
| DEPENDENCY_REPO_TOKEN: ${{ secrets.DEPENDENCY_REPO_TOKEN }} | |
| CHAINS_DEV_JARS_REPOSITORY: ${{ needs.resolve_release_meta.outputs.chains_dev_jars_repository }} | |
| CHAINS_DEV_JARS_REF: ${{ needs.resolve_release_meta.outputs.chains_dev_jars_ref }} | |
| JAVA_ECHO_GENERATOR_REPOSITORY: ${{ needs.resolve_release_meta.outputs.java_echo_generator_repository }} | |
| JAVA_ECHO_GENERATOR_REF: ${{ needs.resolve_release_meta.outputs.java_echo_generator_ref }} | |
| JAVA_MEMSHELL_GENERATOR_REPOSITORY: ${{ needs.resolve_release_meta.outputs.java_memshell_generator_repository }} | |
| JAVA_MEMSHELL_GENERATOR_REF: ${{ needs.resolve_release_meta.outputs.java_memshell_generator_ref }} | |
| CHAINS_CONFIG_REPOSITORY: ${{ needs.resolve_release_meta.outputs.chains_config_repository }} | |
| CHAINS_CONFIG_REF: ${{ needs.resolve_release_meta.outputs.chains_config_ref }} | |
| run: | | |
| mkdir -p release-deps | |
| clone_repo() { | |
| local repository="$1" | |
| local directory="$2" | |
| local ref="$3" | |
| local url="https://x-access-token:${DEPENDENCY_REPO_TOKEN}@github.com/${repository}.git" | |
| echo "Cloning ${repository} @ ${ref}" | |
| git clone "$url" "$directory" | |
| git -C "$directory" checkout "$ref" | |
| echo "Resolved $(git -C "$directory" rev-parse HEAD) for ${repository}" | |
| } | |
| clone_repo "$CHAINS_DEV_JARS_REPOSITORY" "release-deps/chains-dev-jars" "$CHAINS_DEV_JARS_REF" | |
| clone_repo "$JAVA_ECHO_GENERATOR_REPOSITORY" "release-deps/java-echo-generator" "$JAVA_ECHO_GENERATOR_REF" | |
| clone_repo "$JAVA_MEMSHELL_GENERATOR_REPOSITORY" "release-deps/java-memshell-generator" "$JAVA_MEMSHELL_GENERATOR_REF" | |
| clone_repo "$CHAINS_CONFIG_REPOSITORY" "release-deps/chains-config" "$CHAINS_CONFIG_REF" | |
| - name: Set up Temurin JDK ${{ env.JAVA_VERSION }} | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: ${{ env.JAVA_VERSION }} | |
| cache: 'maven' | |
| - name: Set up Maven | |
| uses: stCarolas/setup-maven@v5 | |
| with: | |
| maven-version: 3.9.6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| # Match chains gate.yml / java-chains-server frontend.node.version | |
| node-version: '22.12.0' | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9.15.0 | |
| - name: Cache Maven dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.m2/repository | |
| key: ${{ runner.os }}-maven-v2-${{ hashFiles('**/pom.xml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-maven-v2- | |
| ${{ runner.os }}-maven- | |
| - name: Clone and Install Dev Jars | |
| run: | | |
| ( | |
| cd release-deps/chains-dev-jars | |
| bash mvn_install.sh | |
| ) | |
| - name: Build and Install Generators | |
| run: | | |
| ( | |
| cd release-deps/java-echo-generator | |
| mvn clean install -DskipTests | |
| ) | |
| ( | |
| cd release-deps/java-memshell-generator | |
| mvn clean install -DskipTests | |
| ) | |
| # ── Tests (default on via run_tests; set false only for emergency rebuilds) ── | |
| - name: Install local deps for backend gate | |
| if: needs.resolve_release_meta.outputs.run_tests == 'true' | |
| env: | |
| CHAINS_DEPS_TOKEN: ${{ secrets.DEPENDENCY_REPO_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| if [ -x scripts/ci/install-local-deps.sh ]; then | |
| scripts/ci/install-local-deps.sh | |
| else | |
| echo "WARNING: scripts/ci/install-local-deps.sh missing; relying on release-deps installs" | |
| fi | |
| - name: Frontend tests (java-chains-web-element) | |
| if: needs.resolve_release_meta.outputs.run_tests == 'true' | |
| working-directory: java-chains-web-element | |
| run: | | |
| set -euo pipefail | |
| pnpm install --frozen-lockfile | |
| pnpm typecheck | |
| pnpm test:run | |
| # Public RMI surface defaults (same checks as chains gate.yml web-element job) | |
| if [ -f tests/config/rmi-workbench.test.ts ]; then | |
| pnpm exec vitest run tests/config/rmi-workbench.test.ts tests/adapters/rmi-menu-routes.test.ts | |
| fi | |
| - name: Frontend tests (java-chains-web) | |
| if: needs.resolve_release_meta.outputs.run_tests == 'true' | |
| run: | | |
| set -euo pipefail | |
| if [ ! -f java-chains-web/package.json ]; then | |
| echo "java-chains-web absent; skip" | |
| exit 0 | |
| fi | |
| cd java-chains-web | |
| npm ci | |
| npm run typecheck | |
| npm run test | |
| if [ -f src/config/internal-features.test.ts ]; then | |
| npm test -- --run src/config/internal-features.test.ts | |
| fi | |
| - name: Backend tests (scripts/ci/gate.sh) | |
| if: needs.resolve_release_meta.outputs.run_tests == 'true' | |
| env: | |
| CHAINS_JDK8_HOME: ${{ env.JAVA_HOME }} | |
| run: | | |
| set -euo pipefail | |
| test -x scripts/ci/gate.sh | |
| scripts/ci/gate.sh | |
| - name: Upload surefire reports on test failure | |
| if: failure() && needs.resolve_release_meta.outputs.run_tests == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: surefire-reports-release | |
| path: '**/target/surefire-reports/**' | |
| retention-days: 7 | |
| - name: Build Ship Artifacts (Element SPA + CLI) | |
| env: | |
| RUN_TESTS: ${{ needs.resolve_release_meta.outputs.run_tests }} | |
| run: | | |
| set -euo pipefail | |
| # Product SPA = java-chains-web-element (pnpm → generated-resources/static). | |
| # Prefer in-tree ship script from chains; fall back to release-repo copy. | |
| if [ ! -f scripts/frontend/package-java-chains-web-element.sh ]; then | |
| install -m 0755 _release_ci/.github/scripts/package-java-chains-web-element.sh \ | |
| scripts/frontend/package-java-chains-web-element.sh | |
| fi | |
| # ONE Maven invocation: second `mvn -am package` without -Pfrontend reuses | |
| # server target/ but a new maven.build.timestamp → Vector A session mismatch. | |
| # Product surface: server + java-chains-cli (no java-chains-sdk). | |
| MVN_ARGS=( | |
| -pl java-chains-server,java-chains-cli | |
| -am | |
| clean | |
| package | |
| -Pfrontend,with-exploits | |
| "-Dfrontend.working.directory=${PWD}/java-chains-web-element" | |
| "-Dfrontend.package.script=${PWD}/scripts/frontend/package-java-chains-web-element.sh" | |
| ) | |
| if [ "${RUN_TESTS}" = "true" ]; then | |
| echo "run_tests=true: product mvn WITHOUT -DskipTests (test phase runs)" | |
| else | |
| echo "run_tests=false: product mvn WITH -DskipTests (tests skipped)" | |
| MVN_ARGS+=(-DskipTests) | |
| fi | |
| mvn "${MVN_ARGS[@]}" | |
| - name: Prepare Base Artifacts | |
| run: | | |
| mkdir -p release_base | |
| VERSION="${{ needs.resolve_release_meta.outputs.release_version }}" | |
| pick_first() { | |
| local candidate | |
| for candidate in "$@"; do | |
| if [ -f "$candidate" ]; then | |
| printf '%s\n' "$candidate" | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| # 1. Server Spring Boot fat jar (SPA embedded). Publish as java-chains.jar. | |
| SERVER_JAR="$(pick_first \ | |
| "java-chains-${VERSION}-exec.jar" \ | |
| "java-chains-server-${VERSION}-exec.jar" \ | |
| "java-chains-server/target/java-chains-server-${VERSION}-exec.jar" \ | |
| "java-chains-server/target/java-chains-${VERSION}-exec.jar" \ | |
| )" || true | |
| if [ -z "${SERVER_JAR:-}" ]; then | |
| SERVER_JAR="$(find . -maxdepth 2 -type f \( \ | |
| -name "java-chains-*-exec.jar" -o -name "java-chains-server-*-exec.jar" \ | |
| \) ! -name "*-sources.jar" ! -name "*-javadoc.jar" | sort | head -n 1 || true)" | |
| fi | |
| if [ -f "${SERVER_JAR:-}" ]; then | |
| echo "Found Server Jar: ${SERVER_JAR}" | |
| cp "$SERVER_JAR" release_base/java-chains.jar | |
| else | |
| echo "Error: java-chains server exec jar not found" | |
| find . -maxdepth 3 -type f -name "*.jar" | sort | head -n 80 | |
| exit 1 | |
| fi | |
| # 2. Product CLI fat jar (java-chains-cli). Keep a thin remote jar if present. | |
| CLI_JAR="$(pick_first \ | |
| "java-chains-cli/target/java-chains-cli-${VERSION}-jar-with-dependencies.jar" \ | |
| "java-chains-cli/target/java-chains-cli-${VERSION}.jar" \ | |
| )" || true | |
| if [ -z "${CLI_JAR:-}" ]; then | |
| CLI_JAR="$(find java-chains-cli/target -type f -name "java-chains-cli-*-jar-with-dependencies.jar" 2>/dev/null | sort | head -n 1 || true)" | |
| fi | |
| if [ -f "${CLI_JAR:-}" ]; then | |
| echo "Found CLI Jar: ${CLI_JAR}" | |
| cp "$CLI_JAR" release_base/java-chains-cli.jar | |
| else | |
| echo "Error: java-chains-cli fat jar not found" | |
| exit 1 | |
| fi | |
| CLI_REMOTE_JAR="$(pick_first \ | |
| "java-chains-cli-remote/target/java-chains-cli-remote-${VERSION}-jar-with-dependencies.jar" \ | |
| "java-chains-cli-remote/target/java-chains-cli-remote-${VERSION}.jar" \ | |
| )" || true | |
| if [ -f "${CLI_REMOTE_JAR:-}" ]; then | |
| echo "Found CLI remote Jar: ${CLI_REMOTE_JAR}" | |
| cp "$CLI_REMOTE_JAR" release_base/java-chains-cli-remote.jar | |
| fi | |
| # 3. Runtime config (plugins / third-libs / presets) | |
| cp -r release-deps/chains-config release_base/chains-config | |
| rm -rf release_base/chains-config/.git | |
| if [ -d chains-config ]; then | |
| # Overlay any in-tree config additions (e.g. presets) without wiping deps. | |
| cp -a chains-config/. release_base/chains-config/ 2>/dev/null || true | |
| rm -rf release_base/chains-config/.git | |
| fi | |
| # ── 源码防泄漏:销毁所有源码,确保 artifact 只含编译产物 ── | |
| - name: Purge Source Code | |
| if: always() | |
| run: | | |
| rm -rf release-deps | |
| rm -rf ~/.m2/repository | |
| if find release_base -type f \( \ | |
| -name "*.java" -o -name "*.go" -o -name "*.ts" -o -name "*.tsx" \ | |
| -o -name "*.vue" -o -name "*.jsx" -o -name "*.py" \ | |
| -o -name "*-sources.jar" -o -name "*-javadoc.jar" \ | |
| -o -name ".git" -o -name ".gitignore" \ | |
| -o -name "pom.xml" -o -name "package.json" -o -name "go.mod" \ | |
| \) | grep -q .; then | |
| echo "ERROR: Source code files detected in release artifacts!" | |
| find release_base -type f \( \ | |
| -name "*.java" -o -name "*.go" -o -name "*.ts" -o -name "*.tsx" \ | |
| -o -name "*.vue" -o -name "*.jsx" -o -name "*.py" \ | |
| -o -name "*-sources.jar" -o -name "*-javadoc.jar" \ | |
| -o -name ".git" -o -name ".gitignore" \ | |
| -o -name "pom.xml" -o -name "package.json" -o -name "go.mod" \ | |
| \) | |
| exit 1 | |
| fi | |
| find release_base -type d -name ".git" -exec rm -rf {} + 2>/dev/null || true | |
| echo "Artifact contents:" | |
| find release_base -type f | sort | |
| - name: Upload Base Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: base-artifacts | |
| path: release_base/ | |
| # ==================================================== | |
| # Job 2: Create Release | |
| # ==================================================== | |
| create_release: | |
| needs: [ resolve_release_meta, build ] | |
| runs-on: ubuntu-latest | |
| # No actions/* uses: — avoids GitHub "GetResolveActionsDownloadInfo" 503 during Set up job. | |
| # Notes are generated inline; gh is preinstalled on ubuntu-latest. | |
| steps: | |
| - name: Create GitHub Release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| RELEASE_TAG="${{ needs.resolve_release_meta.outputs.release_tag }}" | |
| RELEASE_VERSION="${{ needs.resolve_release_meta.outputs.release_version }}" | |
| SOURCE_REF="${{ needs.resolve_release_meta.outputs.source_ref }}" | |
| SOURCE_SHA="${{ needs.build.outputs.source_sha }}" | |
| FRONTEND_SHA="${{ needs.build.outputs.frontend_sha }}" | |
| IS_PRERELEASE="${{ needs.resolve_release_meta.outputs.is_prerelease }}" | |
| PUBLISH_RELEASE="${{ needs.resolve_release_meta.outputs.publish_release }}" | |
| if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then | |
| echo "Release $RELEASE_TAG already exists, skip creation." | |
| exit 0 | |
| fi | |
| cat > release-notes.txt <<EOF | |
| Auto-generated release for version ${RELEASE_VERSION} | |
| Backend repository: Java-Chains/chains | |
| Backend ref: ${SOURCE_REF} | |
| Backend commit: ${SOURCE_SHA} | |
| Frontend: java-chains-web-element (Vue3 + Element Plus, in-tree) | |
| Frontend commit: ${FRONTEND_SHA} | |
| Artifacts: | |
| - java-chains-\${VERSION}.tar.gz — Server (SPA from java-chains-web-element) + chains-config | |
| - java-chains-\${VERSION}-<platform>.tar.gz|zip — Server + bundled JDK 8 | |
| - java-chains-cli-\${VERSION}.tar.gz — Product CLI fat jar + chains-config | |
| - Docker: javachains/javachains:${RELEASE_VERSION} | |
| EOF | |
| ARGS=( | |
| "$RELEASE_TAG" | |
| --title "Release $RELEASE_TAG" | |
| --repo "$GITHUB_REPOSITORY" | |
| --notes-file release-notes.txt | |
| ) | |
| # Default: draft. Set workflow input publish=true to publish immediately. | |
| if [ "$PUBLISH_RELEASE" != "true" ]; then | |
| ARGS+=(--draft) | |
| echo "Creating DRAFT release $RELEASE_TAG" | |
| else | |
| echo "Creating PUBLISHED release $RELEASE_TAG (publish=true)" | |
| fi | |
| if [ "$IS_PRERELEASE" = "true" ]; then | |
| ARGS+=(--prerelease) | |
| fi | |
| gh release create "${ARGS[@]}" | |
| # ==================================================== | |
| # Job 3: 矩阵打包 (Server + JDK) | |
| # ==================================================== | |
| bundle_assets: | |
| needs: [ resolve_release_meta, build, create_release ] | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os_api: linux | |
| arch_api: x64 | |
| suffix: linux-amd64 | |
| ext: tar.gz | |
| - os_api: linux | |
| arch_api: aarch64 | |
| suffix: linux-aarch64 | |
| ext: tar.gz | |
| - os_api: windows | |
| arch_api: x64 | |
| suffix: windows-amd64 | |
| ext: zip | |
| - os_api: windows | |
| arch_api: arm64 | |
| suffix: windows-arm64 | |
| ext: zip | |
| - os_api: mac | |
| arch_api: x64 | |
| suffix: macos-intel | |
| ext: tar.gz | |
| - os_api: mac | |
| arch_api: aarch64 | |
| suffix: macos-m-series | |
| ext: tar.gz | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: base-artifacts | |
| path: base | |
| - name: Download JDK 8 | |
| run: | | |
| API_URL="https://api.adoptium.net/v3/binary/latest/8/ga/${{ matrix.os_api }}/${{ matrix.arch_api }}/jdk/hotspot/normal/eclipse" | |
| USE_AZUL="false" | |
| if [ "${{ matrix.os_api }}" = "mac" ] && [ "${{ matrix.arch_api }}" = "aarch64" ]; then | |
| USE_AZUL="true" | |
| fi | |
| if [ "${{ matrix.os_api }}" = "windows" ] && [ "${{ matrix.arch_api }}" = "arm64" ]; then | |
| API_URL="https://api.adoptium.net/v3/binary/latest/8/ga/windows/x64/jdk/hotspot/normal/eclipse" | |
| fi | |
| if [ "$USE_AZUL" = "true" ]; then | |
| AZUL_OS="${{ matrix.os_api }}" | |
| if [ "$AZUL_OS" = "mac" ]; then | |
| AZUL_OS="macos" | |
| fi | |
| API_URL="https://api.azul.com/zulu/download/community/v1.0/bundles/latest/binary/?jdk_version=8&os=$AZUL_OS&arch=${{ matrix.arch_api }}&bundle_type=jdk&ext=${{ matrix.ext }}" | |
| fi | |
| if [ "${{ matrix.ext }}" = "zip" ]; then | |
| curl -L "$API_URL" -o jdk_bundle.zip | |
| unzip -q jdk_bundle.zip -d jdk_extracted | |
| else | |
| curl -L "$API_URL" -o jdk_bundle.tar.gz | |
| mkdir jdk_extracted | |
| tar -xzf jdk_bundle.tar.gz -C jdk_extracted | |
| fi | |
| SUBDIR=$(ls jdk_extracted | head -n 1) | |
| mv "jdk_extracted/$SUBDIR" jdk_final | |
| if [ "${{ matrix.os_api }}" = "mac" ] && [ -d "jdk_final/Contents/Home" ]; then | |
| mv jdk_final/Contents/Home jdk_real | |
| rm -rf jdk_final | |
| mv jdk_real jdk_final | |
| fi | |
| - name: Create Distribution Structure | |
| run: | | |
| DIST_DIR="java-chains-${{ needs.resolve_release_meta.outputs.release_version }}-${{ matrix.suffix }}" | |
| mkdir -p "$DIST_DIR" | |
| cp base/java-chains.jar "$DIST_DIR/" | |
| cp -r base/chains-config "$DIST_DIR/" | |
| mv jdk_final "$DIST_DIR/jdk" | |
| echo "DIST_DIR=$DIST_DIR" >> "$GITHUB_ENV" | |
| - name: Generate Start Scripts | |
| run: | | |
| cd "${{ env.DIST_DIR }}" | |
| if [ "${{ matrix.os_api }}" != "windows" ]; then | |
| printf '%s\n' \ | |
| '#!/bin/bash' \ | |
| 'BASE_DIR=$(cd "$(dirname "$0")" && pwd)' \ | |
| 'cd "$BASE_DIR"' \ | |
| 'exec "$BASE_DIR/jdk/bin/java" -jar "$BASE_DIR/java-chains.jar"' \ | |
| > start.sh | |
| chmod +x start.sh | |
| else | |
| printf '%s\r\n' \ | |
| '@echo off' \ | |
| 'set "BASE_DIR=%~dp0"' \ | |
| 'cd /d "%BASE_DIR%"' \ | |
| '"%BASE_DIR%jdk\bin\java.exe" -jar "%BASE_DIR%java-chains.jar"' \ | |
| 'pause' \ | |
| > start.bat | |
| fi | |
| - name: Compress and Upload | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| RELEASE_TAG="${{ needs.resolve_release_meta.outputs.release_tag }}" | |
| ASSET_NAME="${{ env.DIST_DIR }}.${{ matrix.ext }}" | |
| if [ "${{ matrix.ext }}" = "zip" ]; then | |
| zip -r -q "$ASSET_NAME" "${{ env.DIST_DIR }}" | |
| else | |
| tar -czf "$ASSET_NAME" "${{ env.DIST_DIR }}" | |
| fi | |
| gh release upload "$RELEASE_TAG" "$ASSET_NAME" --clobber --repo "$GITHUB_REPOSITORY" | |
| # ==================================================== | |
| # Job 4: 上传通用包 / CLI / SDK | |
| # ==================================================== | |
| upload_standard_assets: | |
| needs: [ resolve_release_meta, build, create_release ] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: base-artifacts | |
| path: base | |
| - name: Create and Upload Assets | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| RELEASE_TAG="${{ needs.resolve_release_meta.outputs.release_tag }}" | |
| RELEASE_VERSION="${{ needs.resolve_release_meta.outputs.release_version }}" | |
| cd base | |
| tar -czvf "java-chains-${RELEASE_VERSION}.tar.gz" java-chains.jar chains-config | |
| gh release upload "$RELEASE_TAG" "java-chains-${RELEASE_VERSION}.tar.gz" --clobber --repo "$GITHUB_REPOSITORY" | |
| mkdir cli_dist | |
| cp java-chains-cli.jar cli_dist/ | |
| cp -r chains-config cli_dist/ | |
| if [ -f java-chains-cli-remote.jar ]; then | |
| cp java-chains-cli-remote.jar cli_dist/ | |
| fi | |
| tar -czvf "java-chains-cli-${RELEASE_VERSION}.tar.gz" -C cli_dist . | |
| gh release upload "$RELEASE_TAG" "java-chains-cli-${RELEASE_VERSION}.tar.gz" --clobber --repo "$GITHUB_REPOSITORY" | |
| # ==================================================== | |
| # Job 5: Docker Build | |
| # ==================================================== | |
| docker_build: | |
| needs: [ resolve_release_meta, build ] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: base-artifacts | |
| path: . | |
| - name: Prepare Docker context | |
| run: | | |
| # Docker image only needs the server jar + chains-config | |
| rm -f java-chains-cli.jar java-chains-cli-remote.jar | |
| test -f java-chains.jar | |
| test -d chains-config | |
| ls -la | |
| - uses: docker/setup-qemu-action@v3 | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| username: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | |
| - uses: docker/build-push-action@v6 | |
| with: | |
| file: Dockerfile | |
| context: . | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: ${{ needs.resolve_release_meta.outputs.docker_tags }} |