Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 124 additions & 20 deletions .github/workflows/code-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:

permissions:
contents: read
id-token: write
pull-requests: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down Expand Up @@ -61,27 +61,13 @@ jobs:
- name: Validate
id: validate
run: bun run validate:coverage
- name: Upload coverage to Codecov
- name: Upload coverage
if: always() && steps.validate.outcome == 'success'
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
use_oidc: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
token: ${{ secrets.CODECOV_TOKEN }}
slug: archgate/cli
files: coverage/lcov.info
disable_search: true
fail_ci_if_error: false
- name: Upload test results to Codecov
if: always() && steps.validate.outcome != 'skipped'
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
use_oidc: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
token: ${{ secrets.CODECOV_TOKEN }}
slug: archgate/cli
files: coverage/junit.xml
disable_search: true
report_type: test_results
fail_ci_if_error: false
name: coverage-linux
path: coverage/lcov.info
retention-days: 1
- name: Save Bun Cache
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
if: steps.validate.outcome == 'success' && steps.restore-bun-cache.outputs.cache-hit != 'true'
Expand All @@ -99,6 +85,124 @@ jobs:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
uses: ./.github/workflows/smoke-test-linux.yml

coverage:
name: Coverage Report
runs-on: ubuntu-latest
if: always() && needs.validate.result == 'success'
needs: [validate, smoke-windows]
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Download Linux coverage
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: coverage-linux
path: coverage-linux
- name: Download Windows coverage
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: coverage-windows
path: coverage-windows
continue-on-error: true
- name: Merge coverage and generate report
env:
GH_TOKEN: ${{ github.token }}
run: |
sudo apt-get install -y -qq lcov > /dev/null 2>&1

# Start with Linux coverage
cp coverage-linux/lcov.info merged.info

# Add Windows coverage if available (normalize backslash paths first)
if [ -f coverage-windows/lcov.info ]; then
sed -i 's|\\|/|g' coverage-windows/lcov.info
lcov --add-tracefile coverage-linux/lcov.info \
--add-tracefile coverage-windows/lcov.info \
--output-file merged.info --quiet
fi

# Filter to src/ only (excludes test temp dirs)
lcov --extract merged.info 'src/*' --output-file coverage.info --quiet

# Generate HTML report
genhtml coverage.info --output-directory coverage-html --quiet

# Parse summary stats
LINES_FOUND=$(awk -F: '/^LF:/{s+=$2} END{print s+0}' coverage.info)
LINES_HIT=$(awk -F: '/^LH:/{s+=$2} END{print s+0}' coverage.info)
if [ "$LINES_FOUND" -gt 0 ]; then
COVERAGE=$(awk "BEGIN{printf \"%.1f\", ($LINES_HIT/$LINES_FOUND)*100}")
else
COVERAGE="0.0"
fi

# Per-directory coverage table
DIR_TABLE=$(awk -F: '
/^SF:/ {
file = $2
n = split(file, p, "/")
dir = (n >= 3) ? p[1] "/" p[2] : (n == 2 ? p[1] : file)
}
/^LF:/ { lf[dir] += $2 }
/^LH:/ { lh[dir] += $2 }
END {
for (d in lf) {
pct = (lf[d] > 0) ? (lh[d] / lf[d]) * 100 : 0
printf "| `%s/` | %.1f%% | %d / %d |\n", d, pct, lh[d], lf[d]
}
}
' coverage.info | sort)

# Check if Windows coverage was merged
if [ -f coverage-windows/lcov.info ]; then
PLATFORMS="Linux + Windows"
else
PLATFORMS="Linux only"
fi

RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"

# Build report markdown
{
echo "## Code Coverage"
echo ""
echo "| Metric | Value |"
echo "|--------|-------|"
echo "| **Lines** | **${COVERAGE}%** (${LINES_HIT} / ${LINES_FOUND}) |"
echo "| **Platforms** | ${PLATFORMS} |"
echo ""
echo "Full HTML report available in [workflow artifacts](${RUN_URL}#artifacts)."
echo ""
echo "<details>"
echo "<summary>Per-directory breakdown</summary>"
echo ""
echo "| Directory | Coverage | Lines |"
echo "|-----------|----------|-------|"
echo "${DIR_TABLE}"
echo ""
echo "</details>"
} > /tmp/coverage-report.md

# Write to GitHub Actions job summary
cat /tmp/coverage-report.md >> "$GITHUB_STEP_SUMMARY"

# Post/update PR comment (skipped for fork PRs and non-PR events)
if [ "${{ github.event_name }}" = "pull_request" ] && \
[ "${{ github.event.pull_request.head.repo.fork }}" != "true" ]; then
PR_NUM="${{ github.event.pull_request.number }}"
BODY="<!-- archgate-coverage -->
$(cat /tmp/coverage-report.md)"

gh pr comment "${PR_NUM}" --body "${BODY}" --edit-last 2>/dev/null || \
gh pr comment "${PR_NUM}" --body "${BODY}"
fi
- name: Upload coverage report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-report
path: coverage-html
retention-days: 30

# Gate job — single required status check for branch protection.
status:
name: Validate Code
Expand Down
23 changes: 1 addition & 22 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,28 +152,7 @@ jobs:
run: bun install --frozen-lockfile
- name: Validate
id: validate
run: bun run validate:coverage
- name: Upload coverage to Codecov
if: always() && steps.validate.outcome == 'success'
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
use_oidc: true
token: ${{ secrets.CODECOV_TOKEN }}
slug: archgate/cli
files: coverage/lcov.info
disable_search: true
fail_ci_if_error: false
- name: Upload test results to Codecov
if: always() && steps.validate.outcome != 'skipped'
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
use_oidc: true
token: ${{ secrets.CODECOV_TOKEN }}
slug: archgate/cli
files: coverage/junit.xml
disable_search: true
report_type: test_results
fail_ci_if_error: false
run: bun run validate
- name: Ensure main is up-to-date before release
run: git pull --rebase origin main
- name: Release
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/smoke-test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ jobs:
run: bun run format:check

- name: Tests
run: bun test --timeout 60000
id: tests
run: bun test --timeout 60000 --coverage
- name: Upload coverage
if: always() && steps.tests.outcome == 'success'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-windows
path: coverage/lcov.info
retention-days: 1

- name: Build Windows binary
run: bun build src/cli.ts --compile --bytecode --outfile dist/archgate-smoke-test
Expand Down
2 changes: 1 addition & 1 deletion .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"perf": "error"
},
"rules": {
"max-lines": ["error", { "max": 500 }],
"max-lines": ["error", { "max": 500, "skipComments": true }],
"max-lines-per-function": "off",
"max-nested-callbacks": "off",
"max-depth": "off",
Expand Down
25 changes: 0 additions & 25 deletions codecov.yml

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"knip": "knip",
"lint": "oxlint --deny-warnings .",
"test": "bun test --timeout 60000",
"test:coverage": "bun test --timeout 60000 --coverage --reporter=junit --reporter-outfile=coverage/junit.xml",
"test:coverage": "bun test --timeout 60000 --coverage",
"test:watch": "bun test --watch --timeout 60000",
"typecheck": "tsc --build",
"validate": "bun run lint && bun run typecheck && bun run format:check && bun run test && bun run check && bun run knip && bun run build:check",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/upgrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ export function registerUpgradeCommand(program: Command) {
});
}

/** @internal test hooks — consumed via dynamic import() in upgrade.test.ts */
/** Test hooks — exported for unit tests in upgrade.test.ts */
export {
isBinaryInstall as _isBinaryInstall,
isProtoInstall as _isProtoInstall,
Expand Down
6 changes: 5 additions & 1 deletion src/helpers/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ let distinctId = "";
let repoContextSnapshot: RepoContext | null = null;

// ---------------------------------------------------------------------------
// Environment enrichment
// Environment enrichment — intentionally uncovered in unit tests.
// These private functions only run when the PostHog client is live
// (ARCHGATE_TELEMETRY=0 in tests disables init). Validated via dashboard.
// ---------------------------------------------------------------------------

/**
Expand Down Expand Up @@ -198,6 +200,8 @@ export async function initTelemetry(): Promise<void> {
try {
// Lazy-load the PostHog SDK so the `ARCHGATE_TELEMETRY=0` path never pays
// the module-parse cost (noticeable on cold starts / WSL).
// SDK init + custom fetch wrapper below are intentionally uncovered —
// validated via PostHog dashboard, not by mocking the SDK constructor.
const { PostHog } = await import("posthog-node");
client = new PostHog(POSTHOG_API_KEY, {
host: POSTHOG_HOST,
Expand Down
Loading
Loading