feat: louvain community detection + fix complexity build regression (… #120
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
| name: Publish | |
| on: | |
| push: | |
| branches: [main] | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version to publish (e.g. 2.1.0, without v prefix). Used to retry a failed stable release.' | |
| required: true | |
| type: string | |
| concurrency: | |
| group: publish-${{ github.event_name }} | |
| cancel-in-progress: true | |
| permissions: {} | |
| jobs: | |
| preflight: | |
| name: Preflight checks | |
| runs-on: ubuntu-latest | |
| # Skip dev publish when the push is a stable release version bump (direct push or merged PR) | |
| if: "${{ github.event_name != 'push' || (!startsWith(github.event.head_commit.message, 'chore: release v') && !contains(github.event.head_commit.message, 'release/v')) }}" | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - run: npm install | |
| - run: npm test | |
| compute-version: | |
| needs: preflight | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.compute.outputs.version }} | |
| npm_tag: ${{ steps.compute.outputs.npm_tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compute version | |
| id: compute | |
| run: | | |
| CURRENT=$(node -p "require('./package.json').version") | |
| if [ "${{ github.event_name }}" = "release" ]; then | |
| TAG="${{ github.event.release.tag_name }}" | |
| VERSION="${TAG#v}" | |
| NPM_TAG="latest" | |
| echo "Stable release: $VERSION (from tag $TAG)" | |
| elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| VERSION="${{ inputs.version }}" | |
| VERSION="${VERSION#v}" | |
| if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then | |
| echo "::error::Invalid version '$VERSION'. Expected semver (e.g. 2.1.0)." | |
| exit 1 | |
| fi | |
| NPM_TAG="latest" | |
| echo "Stable release (manual retry): $VERSION" | |
| else | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" | |
| DEV_PATCH=$((PATCH + 1)) | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| VERSION="${MAJOR}.${MINOR}.${DEV_PATCH}-dev.${SHORT_SHA}" | |
| NPM_TAG="dev" | |
| echo "Dev release: $VERSION" | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=$NPM_TAG" >> "$GITHUB_OUTPUT" | |
| build-native: | |
| needs: preflight | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| node_arch: x64 | |
| node_os: linux | |
| - os: macos-latest | |
| target: aarch64-apple-darwin | |
| node_arch: arm64 | |
| node_os: darwin | |
| - os: macos-14 | |
| target: x86_64-apple-darwin | |
| node_arch: x64 | |
| node_os: darwin | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| node_arch: x64 | |
| node_os: win32 | |
| runs-on: ${{ matrix.os }} | |
| name: Build ${{ matrix.target }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: crates/codegraph-core | |
| - name: Install napi-rs CLI | |
| run: npm install -g @napi-rs/cli@3 | |
| - name: Build native addon | |
| working-directory: crates/codegraph-core | |
| run: napi build --release --target ${{ matrix.target }} | |
| - name: Upload native binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: native-${{ matrix.node_os }}-${{ matrix.node_arch }} | |
| path: crates/codegraph-core/*.node | |
| if-no-files-found: error | |
| # ── Dev builds: GitHub pre-release with tarballs ── | |
| publish-dev: | |
| if: github.event_name == 'push' | |
| needs: [compute-version, build-native] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| - run: npm install | |
| - name: Set version | |
| env: | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| npm version "$VERSION" --no-git-tag-version --allow-same-version | |
| node scripts/sync-native-versions.js | |
| echo "Packaging version $VERSION" | |
| - name: Disable prepublishOnly | |
| run: npm pkg delete scripts.prepublishOnly | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: ${{ runner.temp }}/artifacts/ | |
| - name: Pack main package | |
| run: npm pack | |
| - name: Pack platform packages | |
| env: | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| shell: bash | |
| run: | | |
| declare -A PACKAGES=( | |
| ["linux-x64"]="@optave/codegraph-linux-x64-gnu" | |
| ["darwin-arm64"]="@optave/codegraph-darwin-arm64" | |
| ["darwin-x64"]="@optave/codegraph-darwin-x64" | |
| ["win32-x64"]="@optave/codegraph-win32-x64-msvc" | |
| ) | |
| ARTIFACTS="${RUNNER_TEMP}/artifacts" | |
| PKG_DIR="${RUNNER_TEMP}/pkg" | |
| for artifact_dir in "${ARTIFACTS}"/native-*/; do | |
| platform=$(basename "$artifact_dir" | sed 's/^native-//') | |
| pkg_name=${PACKAGES[$platform]} | |
| node_os=${platform%%-*} | |
| node_arch=${platform##*-} | |
| mkdir -p "${PKG_DIR}/$platform" | |
| cp "$artifact_dir"/*.node "${PKG_DIR}/$platform/codegraph-core.node" | |
| cat > "${PKG_DIR}/$platform/package.json" <<PKGJSON | |
| { | |
| "name": "${pkg_name}", | |
| "version": "${VERSION}", | |
| "description": "Native codegraph-core binary for ${node_os}-${node_arch}", | |
| "os": ["${node_os}"], | |
| "cpu": ["${node_arch}"], | |
| "main": "codegraph-core.node", | |
| "files": ["codegraph-core.node"], | |
| "license": "Apache-2.0", | |
| "repository": { | |
| "type": "git", | |
| "url": "https://github.com/optave/codegraph.git" | |
| } | |
| } | |
| PKGJSON | |
| npm pack "${PKG_DIR}/$platform" | |
| done | |
| - name: Create GitHub pre-release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| TAG="dev-v${VERSION}" | |
| gh release create "$TAG" \ | |
| --prerelease \ | |
| --title "Dev build ${VERSION}" \ | |
| --notes "Dev build from commit \`${{ github.sha }}\` on \`main\`." \ | |
| *.tgz | |
| - name: Prune old dev releases | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # List dev releases sorted newest-first, skip the first 5, delete the rest | |
| # Non-critical: failures here should not fail the workflow | |
| TAGS=$(gh release list --limit 100 --json tagName,isPrerelease,createdAt 2>&1) || { | |
| echo "::warning::Failed to list releases for pruning: ${TAGS}" | |
| exit 0 | |
| } | |
| OLD_TAGS=$(echo "$TAGS" | jq -r ' | |
| [ .[] | select(.isPrerelease and (.tagName | startswith("dev-v"))) ] | |
| | sort_by(.createdAt) | reverse | |
| | .[5:] | |
| | .[].tagName | |
| ' 2>&1) || { | |
| echo "::warning::Failed to parse release list for pruning: ${OLD_TAGS}" | |
| exit 0 | |
| } | |
| # When fewer than 5 dev releases exist, OLD_TAGS is empty and the loop is a no-op | |
| echo "$OLD_TAGS" | while read -r tag; do | |
| [ -z "$tag" ] && continue | |
| echo "Deleting old dev release: $tag" | |
| gh release delete "$tag" --yes --cleanup-tag || echo "::warning::Failed to delete release ${tag}" | |
| done | |
| - name: Summary | |
| env: | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| TAG="dev-v${VERSION}" | |
| cat >> "$GITHUB_STEP_SUMMARY" <<EOF | |
| ## Dev Build Published | |
| **Version:** \`${VERSION}\` | |
| **Commit:** \`${{ github.sha }}\` | |
| ### Download | |
| Tarballs attached to [GitHub release \`${TAG}\`](${{ github.server_url }}/${{ github.repository }}/releases/tag/${TAG}). | |
| \`\`\`bash | |
| # Install the main package (uses WASM fallback): | |
| npm install ${{ github.server_url }}/${{ github.repository }}/releases/download/${TAG}/optave-codegraph-${VERSION}.tgz | |
| # For native performance, also install your platform package: | |
| # Linux x64: | |
| npm install ${{ github.server_url }}/${{ github.repository }}/releases/download/${TAG}/optave-codegraph-linux-x64-gnu-${VERSION}.tgz | |
| # macOS arm64: | |
| npm install ${{ github.server_url }}/${{ github.repository }}/releases/download/${TAG}/optave-codegraph-darwin-arm64-${VERSION}.tgz | |
| \`\`\` | |
| EOF | |
| # ── Stable releases: publish to npm ── | |
| publish: | |
| if: github.event_name != 'push' | |
| needs: [compute-version, build-native] | |
| runs-on: ubuntu-latest | |
| environment: npm-publish | |
| permissions: | |
| contents: write | |
| id-token: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: main | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Upgrade npm for OIDC trusted publishing | |
| run: npm install -g npm@latest | |
| - run: npm install | |
| - name: Set version | |
| env: | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| git checkout -- package-lock.json | |
| npm version "$VERSION" --no-git-tag-version --allow-same-version | |
| node scripts/sync-native-versions.js | |
| echo "Publishing version $VERSION" | |
| - name: Disable prepublishOnly | |
| run: npm pkg delete scripts.prepublishOnly | |
| - name: Download native artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: ${{ runner.temp }}/artifacts/ | |
| - name: Check if main package already published | |
| id: check-main | |
| env: | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| PKG="@optave/codegraph" | |
| echo "Checking if $PKG@$VERSION already exists on npm..." | |
| if npm view "$PKG@$VERSION" version 2>/dev/null; then | |
| echo "⚠️ $PKG@$VERSION is already published — will skip publish steps" | |
| echo "already_published=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "$PKG@$VERSION is not yet published — proceeding" | |
| echo "already_published=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish platform packages | |
| if: steps.check-main.outputs.already_published == 'false' | |
| env: | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| NPM_TAG: ${{ needs.compute-version.outputs.npm_tag }} | |
| shell: bash | |
| run: | | |
| declare -A PACKAGES=( | |
| ["linux-x64"]="@optave/codegraph-linux-x64-gnu" | |
| ["darwin-arm64"]="@optave/codegraph-darwin-arm64" | |
| ["darwin-x64"]="@optave/codegraph-darwin-x64" | |
| ["win32-x64"]="@optave/codegraph-win32-x64-msvc" | |
| ) | |
| ARTIFACTS="${RUNNER_TEMP}/artifacts" | |
| PKG_DIR="${RUNNER_TEMP}/pkg" | |
| for artifact_dir in "${ARTIFACTS}"/native-*/; do | |
| platform=$(basename "$artifact_dir" | sed 's/^native-//') | |
| pkg_name=${PACKAGES[$platform]} | |
| node_os=${platform%%-*} | |
| node_arch=${platform##*-} | |
| mkdir -p "${PKG_DIR}/$platform" | |
| cp "$artifact_dir"/*.node "${PKG_DIR}/$platform/codegraph-core.node" | |
| cat > "${PKG_DIR}/$platform/package.json" <<PKGJSON | |
| { | |
| "name": "${pkg_name}", | |
| "version": "${VERSION}", | |
| "description": "Native codegraph-core binary for ${node_os}-${node_arch}", | |
| "os": ["${node_os}"], | |
| "cpu": ["${node_arch}"], | |
| "main": "codegraph-core.node", | |
| "files": ["codegraph-core.node"], | |
| "license": "Apache-2.0", | |
| "repository": { | |
| "type": "git", | |
| "url": "https://github.com/optave/codegraph.git" | |
| } | |
| } | |
| PKGJSON | |
| # Skip if this exact version is already published (idempotent re-runs) | |
| if npm view "${pkg_name}@${VERSION}" version 2>/dev/null; then | |
| echo "⚠️ ${pkg_name}@${VERSION} already published — skipping" | |
| continue | |
| fi | |
| echo "Publishing ${pkg_name}@${VERSION} with --tag ${NPM_TAG}" | |
| npm publish "${PKG_DIR}/$platform" --access public --provenance --tag "$NPM_TAG" | |
| done | |
| - name: Publish main package | |
| if: steps.check-main.outputs.already_published == 'false' | |
| env: | |
| NPM_TAG: ${{ needs.compute-version.outputs.npm_tag }} | |
| run: npm publish --access public --provenance --tag "$NPM_TAG" | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Generate DEPENDENCIES.json | |
| run: mkdir -p generated && npm ls --json --all --omit=dev > generated/DEPENDENCIES.json 2>/dev/null || true | |
| - name: Push version bump via PR | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| TAG="v${VERSION}" | |
| BRANCH="release/v${VERSION}" | |
| # Check if there are version bump changes to push | |
| if git diff --quiet HEAD -- package.json package-lock.json CHANGELOG.md generated/DEPENDENCIES.json; then | |
| echo "No version bump commit to push — skipping PR" | |
| else | |
| git add -f package.json package-lock.json CHANGELOG.md generated/DEPENDENCIES.json | |
| git commit -m "chore: release v${VERSION}" | |
| git push origin "HEAD:refs/heads/${BRANCH}" | |
| gh pr create \ | |
| --base main \ | |
| --head "$BRANCH" \ | |
| --title "chore: release v${VERSION}" \ | |
| --body "Automated version bump to \`${VERSION}\` and CHANGELOG update from publish workflow run [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." | |
| echo "::notice::Version bump PR created for ${BRANCH} → main" | |
| fi | |
| # Push tag (skip if it already exists on remote) | |
| if git ls-remote --tags origin "refs/tags/$TAG" | grep -q .; then | |
| echo "Tag $TAG already exists on remote — skipping tag push" | |
| else | |
| git tag -a "$TAG" -m "release: $TAG" | |
| git push origin "$TAG" | |
| fi |