From ecb8217041697ef8115841bb6cfebf79e715d226 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 2 Jun 2025 23:19:08 +0100 Subject: [PATCH 1/4] ci: Build and test in CI --- .github/workflows/ci.yml | 346 +++++++++++++++++++++++++++++++++++++++ package.json | 6 +- test/e2e.test.mjs | 31 ++-- test/prepare.mjs | 8 +- test/yarn.lock | 16 +- yarn.lock | 12 +- 6 files changed, 387 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..894cfd2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,346 @@ + +name: "CI: Build & Test" +on: + push: + branches: + - main + - release/** + pull_request: + +jobs: + job_lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Check out current commit + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + - name: Install dependencies + run: yarn install --ignore-engines --ignore-scripts --frozen-lockfile + - name: Lint + run: yarn lint + + job_compile: + name: Compile Binary (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }} + runs-on: ${{ matrix.os }} + container: + image: ${{ matrix.container }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + # x64 glibc + - os: ubuntu-22.04 + node: 18 + binary: linux-x64-glibc-108 + - os: ubuntu-22.04 + node: 20 + binary: linux-x64-glibc-115 + - os: ubuntu-22.04 + node: 22 + binary: linux-x64-glibc-127 + - os: ubuntu-22.04 + node: 24 + binary: linux-x64-glibc-137 + + # x64 musl + - os: ubuntu-22.04 + container: node:18-alpine3.17 + node: 18 + binary: linux-x64-musl-108 + - os: ubuntu-22.04 + container: node:20-alpine3.17 + node: 20 + binary: linux-x64-musl-115 + - os: ubuntu-22.04 + container: node:22-alpine3.18 + node: 22 + binary: linux-x64-musl-127 + - os: ubuntu-22.04 + container: node:24-alpine3.20 + node: 24 + binary: linux-x64-musl-137 + + # arm64 glibc + - os: ubuntu-22.04 + arch: arm64 + node: 18 + binary: linux-arm64-glibc-108 + - os: ubuntu-22.04 + arch: arm64 + node: 20 + binary: linux-arm64-glibc-115 + - os: ubuntu-22.04 + arch: arm64 + node: 22 + binary: linux-arm64-glibc-127 + - os: ubuntu-22.04 + arch: arm64 + node: 24 + binary: linux-arm64-glibc-137 + + # arm64 musl + - os: ubuntu-22.04 + arch: arm64 + container: node:18-alpine3.17 + node: 18 + binary: linux-arm64-musl-108 + - os: ubuntu-22.04 + arch: arm64 + container: node:20-alpine3.17 + node: 20 + binary: linux-arm64-musl-115 + - os: ubuntu-22.04 + arch: arm64 + container: node:22-alpine3.18 + node: 22 + binary: linux-arm64-musl-127 + - os: ubuntu-22.04 + arch: arm64 + container: node:24-alpine3.20 + node: 24 + binary: linux-arm64-musl-137 + + # macos x64 + - os: macos-13 + node: 18 + arch: x64 + binary: darwin-x64-108 + - os: macos-13 + node: 20 + arch: x64 + binary: darwin-x64-115 + - os: macos-13 + node: 22 + arch: x64 + binary: darwin-x64-127 + - os: macos-13 + node: 24 + arch: x64 + binary: darwin-x64-137 + + # macos arm64 + - os: macos-13 + arch: arm64 + node: 18 + target_platform: darwin + binary: darwin-arm64-108 + - os: macos-13 + arch: arm64 + node: 20 + target_platform: darwin + binary: darwin-arm64-115 + - os: macos-13 + arch: arm64 + node: 22 + target_platform: darwin + binary: darwin-arm64-127 + - os: macos-13 + arch: arm64 + node: 24 + target_platform: darwin + binary: darwin-arm64-137 + + # windows x64 + - os: windows-2022 + node: 18 + arch: x64 + binary: win32-x64-108 + - os: windows-2022 + node: 20 + arch: x64 + binary: win32-x64-115 + - os: windows-2022 + node: 22 + arch: x64 + binary: win32-x64-127 + - os: windows-2022 + node: 24 + arch: x64 + binary: win32-x64-137 + + steps: + - name: Setup (alpine) + if: contains(matrix.container, 'alpine') + run: | + apk add --no-cache build-base git g++ make curl python3 + ln -sf python3 /usr/bin/python + + - name: Check out current commit + uses: actions/checkout@v4 + + # Note: On alpine images, this does nothing + # The node version will be the one that is installed in the image + # If you want to change the node version, you need to change the image + # For non-alpine images, this will install the correct version of node + - name: Setup Node + uses: actions/setup-node@v4 + if: contains(matrix.container, 'alpine') == false + with: + node-version: ${{ matrix.node }} + + - name: Increase yarn network timeout on Windows + if: contains(matrix.os, 'windows') + run: yarn config set network-timeout 600000 -g + + - name: Install dependencies + run: yarn install --ignore-engines --ignore-scripts --frozen-lockfile + + - name: Configure safe directory + run: | + git config --global --add safe.directory "*" + + - name: Setup python + uses: actions/setup-python@v5 + if: ${{ !contains(matrix.container, 'alpine') }} + id: python-setup + with: + python-version: "3.9.13" + + - name: Setup (arm64| ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' + run: | + sudo apt-get update + sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + + - name: Setup Musl + if: contains(matrix.container, 'alpine') + run: | + curl -OL https://musl.cc/aarch64-linux-musl-cross.tgz + tar -xzvf aarch64-linux-musl-cross.tgz + $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version + + # configure node-gyp + - name: Configure node-gyp + if: matrix.arch != 'arm64' + run: yarn build:bindings:configure + + - name: Configure node-gyp (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && matrix.target_platform != 'darwin' + run: yarn build:bindings:configure:arm64 + + - name: Configure node-gyp (arm64, darwin) + if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' + run: yarn build:bindings:configure:arm64 + + # build bindings + - name: Build Bindings + if: matrix.arch != 'arm64' + run: | + yarn build:bindings + + - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' + run: | + CC=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc \ + CXX=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ \ + BUILD_ARCH=arm64 \ + yarn build:bindings + + - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' + run: | + CC=aarch64-linux-gnu-gcc \ + CXX=aarch64-linux-gnu-g++ \ + BUILD_ARCH=arm64 \ + yarn build:bindings:arm64 + + - name: Build Bindings (arm64, darwin) + if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' + run: | + BUILD_PLATFORM=darwin \ + BUILD_ARCH=arm64 \ + yarn build:bindings:arm64 + + - name: Build + run: yarn build:lib + + - name: Archive Binary + uses: actions/upload-artifact@v4 + with: + name: stack-trace-${{ matrix.binary }} + path: ${{ github.workspace }}/lib/stack-trace-${{matrix.binary}}.node + if-no-files-found: error + + job_build: + name: Build Package + needs: [job_compile] + runs-on: ubuntu-latest + steps: + - name: Check out current commit + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + + - name: Install dependencies + run: yarn install --ignore-engines --ignore-scripts --frozen-lockfile + + - name: Build TypeScript + run: yarn build:lib + + - name: Extract Prebuilt Binaries + uses: actions/download-artifact@v4 + with: + pattern: stack-trace-* + path: ${{ github.workspace }}/lib/ + merge-multiple: true + + - name: Pack tarball + run: yarn build:tarball + + - name: Archive artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.sha }} + retention-days: 90 + path: ${{ github.workspace }}/*.tgz + + job_test_bindings: + name: Test (v${{ matrix.node }}) ${{ matrix.os }} + needs: [job_build] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ + ubuntu-24.04, + ubuntu-22.04, + ubuntu-22.04-arm, + macos-latest, # macOS arm64 + macos-13, # macOS x64 + windows-latest, + ] + node: [18, 20, 22, 24] + steps: + - name: Check out current commit + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Install dependencies + run: yarn install --ignore-engines --ignore-scripts --frozen-lockfile + - name: Download Tarball + uses: actions/download-artifact@v4 + with: + name: ${{ github.sha }} + - name: Run tests + run: yarn test + + job_required_jobs_passed: + name: All required jobs passed + needs: [job_lint, job_test_bindings] + # Always run this, even if a dependent job failed + if: always() + runs-on: ubuntu-latest + steps: + - name: Check for failures + if: contains(needs.*.result, 'failure') + run: | + echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 diff --git a/package.json b/package.json index 21325d8..5ae2ea3 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,14 @@ "build:dev": "yarn clean && yarn build:bindings:configure && yarn build", "build:tarball": "npm pack", "clean": "node-gyp clean && rm -rf lib && rm -rf build", - "test": "vitest run --silent=false --disable-console-intercept" + "test": "node ./test/prepare.mjs && vitest run --silent=false --disable-console-intercept" }, "volta": { "node": "24.1.0" }, "dependencies": { "detect-libc": "^2.0.4", - "node-abi": "^4.9.0" + "node-abi": "^3.73.0" }, "devDependencies": { "@sentry-internal/eslint-config-sdk": "^9.22.0", @@ -49,4 +49,4 @@ "/scripts/check-build.mjs", "/scripts/copy-target.mjs" ] -} +} \ No newline at end of file diff --git a/test/e2e.test.mjs b/test/e2e.test.mjs index fba60ee..82bf050 100644 --- a/test/e2e.test.mjs +++ b/test/e2e.test.mjs @@ -1,22 +1,23 @@ import { spawnSync } from 'node:child_process'; import { join } from 'node:path'; -import { beforeAll, describe, expect, test } from 'vitest'; -import { installTarballAsDependency } from './prepare.mjs'; +import { describe, expect, test } from 'vitest'; const __dirname = import.meta.dirname || new URL('.', import.meta.url).pathname; describe('e2e Tests', { timeout: 20000 }, () => { - beforeAll(() => { - installTarballAsDependency(__dirname); - }); - test('Capture stack trace from multiple threads', () => { const testFile = join(__dirname, 'stack-traces.js'); const result = spawnSync('node', [testFile]) - expect(result.status).toBe(0); - - const stacks = JSON.parse(result.stdout.toString()); + let stacks; + try { + stacks = JSON.parse(result.stdout.toString()); + } catch (e) { + console.log('status', result.status); + console.log('stdout', result.stdout.toString()); + console.log('stderr', result.stderr.toString()); + throw e; + } expect(stacks['0']).toEqual(expect.arrayContaining([ { @@ -65,9 +66,15 @@ describe('e2e Tests', { timeout: 20000 }, () => { const testFile = join(__dirname, 'stalled.js'); const result = spawnSync('node', [testFile]); - expect(result.status).toBe(0); - - const stacks = JSON.parse(result.stdout.toString()); + let stacks; + try { + stacks = JSON.parse(result.stdout.toString()); + } catch (e) { + console.log('status', result.status); + console.log('stdout', result.stdout.toString()); + console.log('stderr', result.stderr.toString()); + throw e; + } expect(stacks['0']).toEqual(expect.arrayContaining([ { diff --git a/test/prepare.mjs b/test/prepare.mjs index e146c5f..eb2ca66 100644 --- a/test/prepare.mjs +++ b/test/prepare.mjs @@ -9,7 +9,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); const env = {...process.env, NODE_OPTIONS: '--no-deprecation'}; -export function installTarballAsDependency(root) { +function installTarballAsDependency(root) { const pkgJson = require('../package.json'); const normalizedName = pkgJson.name.replace('@', '').replace('/', '-'); @@ -17,7 +17,7 @@ export function installTarballAsDependency(root) { if (!existsSync(tarball)) { console.error(`Tarball not found: '${tarball}'`); - console.error(`Run 'yarn build && yarn build:tarball' first`); + console.error('Run \'yarn build && yarn build:tarball\' first'); process.exit(1); } @@ -41,5 +41,7 @@ export function installTarballAsDependency(root) { writeFileSync(join(root, 'package.json'), modified); console.log('Installing dependencies...'); - execSync('yarn install', { cwd: root }); + execSync('yarn install', { cwd: root, stdio: 'inherit' }); } + +installTarballAsDependency(__dirname); diff --git a/test/yarn.lock b/test/yarn.lock index 1779b92..10be108 100644 --- a/test/yarn.lock +++ b/test/yarn.lock @@ -4,24 +4,24 @@ "@sentry-internal/node-native-stacktrace@file:../sentry-internal-node-native-stacktrace-0.1.0.tgz": version "0.1.0" - resolved "file:../sentry-internal-node-native-stacktrace-0.1.0.tgz#9f528856b2eecdefad847e171435f0d33d2375f5" + resolved "file:../sentry-internal-node-native-stacktrace-0.1.0.tgz#9c96a02e580c905dcdc99058b4ac067b02d8468e" dependencies: detect-libc "^2.0.4" - node-abi "^4.9.0" + node-abi "^3.73.0" detect-libc@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8" integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== -node-abi@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-4.9.0.tgz#ca6dabf7991e54bf3ba6d8d32641e1b84f305263" - integrity sha512-0isb3h+AXUblx5Iv0mnYy2WsErH+dk2e9iXJXdKAtS076Q5hP+scQhp6P4tvDeVlOBlG3ROKvkpQHtbORllq2A== +node-abi@^3.73.0: + version "3.75.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764" + integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg== dependencies: - semver "^7.6.3" + semver "^7.3.5" -semver@^7.6.3: +semver@^7.3.5: version "7.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== diff --git a/yarn.lock b/yarn.lock index 4f2c2ad..3b5dba9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2284,12 +2284,12 @@ negotiator@^1.0.0: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== -node-abi@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-4.9.0.tgz#ca6dabf7991e54bf3ba6d8d32641e1b84f305263" - integrity sha512-0isb3h+AXUblx5Iv0mnYy2WsErH+dk2e9iXJXdKAtS076Q5hP+scQhp6P4tvDeVlOBlG3ROKvkpQHtbORllq2A== +node-abi@^3.73.0: + version "3.75.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764" + integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg== dependencies: - semver "^7.6.3" + semver "^7.3.5" node-gyp@^11.2.0: version "11.2.0" @@ -2656,7 +2656,7 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4, semver@^7.6.3: +semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: version "7.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== From aa570936b1c40696fb484a079d44defe32439cc6 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 3 Jun 2025 01:33:04 +0200 Subject: [PATCH 2/4] Use my CF proxy --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 894cfd2..9bc66ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -210,7 +210,7 @@ jobs: - name: Setup Musl if: contains(matrix.container, 'alpine') run: | - curl -OL https://musl.cc/aarch64-linux-musl-cross.tgz + curl -OL https://musl.cc.timfish.dev//aarch64-linux-musl-cross.tgz tar -xzvf aarch64-linux-musl-cross.tgz $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version From a91dfdc1f119735accd2c784fae6f00e37f190cc Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 3 Jun 2025 01:36:43 +0200 Subject: [PATCH 3/4] oops url --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bc66ba..19f4066 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -210,7 +210,7 @@ jobs: - name: Setup Musl if: contains(matrix.container, 'alpine') run: | - curl -OL https://musl.cc.timfish.dev//aarch64-linux-musl-cross.tgz + curl -OL https://musl.cc.timfish.dev/aarch64-linux-musl-cross.tgz tar -xzvf aarch64-linux-musl-cross.tgz $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version From fe775820a8b0fcedada80734064f464802fbd194 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 3 Jun 2025 11:59:03 +0200 Subject: [PATCH 4/4] expect status to be 0 --- test/e2e.test.mjs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/test/e2e.test.mjs b/test/e2e.test.mjs index 82bf050..7b23c92 100644 --- a/test/e2e.test.mjs +++ b/test/e2e.test.mjs @@ -9,15 +9,9 @@ describe('e2e Tests', { timeout: 20000 }, () => { const testFile = join(__dirname, 'stack-traces.js'); const result = spawnSync('node', [testFile]) - let stacks; - try { - stacks = JSON.parse(result.stdout.toString()); - } catch (e) { - console.log('status', result.status); - console.log('stdout', result.stdout.toString()); - console.log('stderr', result.stderr.toString()); - throw e; - } + expect(result.status).toEqual(0); + + const stacks = JSON.parse(result.stdout.toString()); expect(stacks['0']).toEqual(expect.arrayContaining([ { @@ -66,15 +60,9 @@ describe('e2e Tests', { timeout: 20000 }, () => { const testFile = join(__dirname, 'stalled.js'); const result = spawnSync('node', [testFile]); - let stacks; - try { - stacks = JSON.parse(result.stdout.toString()); - } catch (e) { - console.log('status', result.status); - console.log('stdout', result.stdout.toString()); - console.log('stderr', result.stderr.toString()); - throw e; - } + expect(result.status).toEqual(0); + + const stacks = JSON.parse(result.stdout.toString()); expect(stacks['0']).toEqual(expect.arrayContaining([ {