diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae2d613..18ba304 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,23 +20,35 @@ jobs: include: - os: macos-14 target: aarch64-apple-darwin + platform: eventum-darwin-arm64 + - os: macos-latest target: x86_64-apple-darwin + platform: eventum-darwin-x64 - os: windows-latest target: x86_64-pc-windows-msvc + platform: eventum-win32-x64 + - os: windows-latest target: aarch64-pc-windows-msvc + platform: eventum-win32-arm64 - os: ubuntu-latest target: x86_64-unknown-linux-gnu + platform: eventum-linux-x64-gnu + - os: ubuntu-latest target: aarch64-unknown-linux-gnu + platform: eventum-linux-arm64-gnu - os: ubuntu-latest target: x86_64-unknown-linux-musl + platform: eventum-linux-x64-musl + - os: ubuntu-latest target: aarch64-unknown-linux-musl + platform: eventum-linux-arm64-musl steps: - uses: actions/checkout@v4 @@ -85,12 +97,52 @@ jobs: - name: Build native addon run: npx napi build --release --target ${{ matrix.target }} - - - name: Upload artifacts + + - name: Create platform package + shell: bash + run: | + # Extract version from tag (v0.1.0-alpha.2 -> 0.1.0-alpha.2) + VERSION="${GITHUB_REF#refs/tags/v}" + PKG_NAME="${{ matrix.platform }}" + mkdir -p "platform-packages/$PKG_NAME" + cp *.node "platform-packages/$PKG_NAME/" + + case "${{ matrix.target }}" in + *"darwin"*) OS="darwin" ;; + *"windows"*) OS="win32" ;; + *"linux"*) OS="linux" ;; + esac + + case "${{ matrix.target }}" in + x86_64*) CPU="x64" ;; + aarch64*) CPU="arm64" ;; + esac + + cat > "platform-packages/$PKG_NAME/package.json" << EOF + { + "name": "$PKG_NAME", + "version": "$VERSION", + "os": ["$OS"], + "cpu": ["$CPU"], + "main": "$(basename *.node)", + "files": ["*.node"], + "license": "MIT", + "description": "Eventum native binding for ${{ matrix.target }}", + "repository": { + "type": "git", + "url": "git+https://github.com/dmytroPolhul/eventum.git" + } + } + EOF + + echo "Created platform package: $PKG_NAME" + ls -la "platform-packages/$PKG_NAME/" + + - name: Upload platform package artifact uses: actions/upload-artifact@v4 with: - name: bindings-${{ matrix.target }} - path: "*.node" + name: ${{ matrix.platform }} + path: platform-packages/${{ matrix.platform }} if-no-files-found: error retention-days: 7 @@ -98,32 +150,41 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: "https://registry.npmjs.org" - cache: npm - - - run: npm ci - - - name: Download all artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts - pattern: bindings-* - merge-multiple: false - - - name: List artifacts (debug) - run: | - echo "Artifacts directory:" - ls -R artifacts || echo "No artifacts directory found" - - - name: Move bindings to npm package - run: npx napi artifacts -d artifacts - - - name: Publish to npm with provenance - run: npm publish --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: "https://registry.npmjs.org" + cache: npm + + - name: Download all platform packages + uses: actions/download-artifact@v4 + with: + path: platform-packages + + - name: List downloaded packages (debug) + run: | + echo "Platform packages:" + ls -R platform-packages/ + + - name: Publish platform packages + run: | + for dir in platform-packages/*/; do + if [ -f "$dir/package.json" ]; then + echo "Publishing $(basename $dir)" + cd "$dir" + npm publish --access public --provenance || echo "Failed to publish $(basename $dir)" + cd - > /dev/null + fi + done + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish main package + run: | + echo "Publishing main eventum package" + npm ci + npm publish --access public --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/test.yml similarity index 100% rename from .github/workflows/ci.yml rename to .github/workflows/test.yml diff --git a/.gitignore b/.gitignore index 2b1a93b..4a78f89 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ node_modules/ /target index.node +eventum.node .idea/ *.log diff --git a/.npmignore b/.npmignore index 2354697..735a8c4 100644 --- a/.npmignore +++ b/.npmignore @@ -53,3 +53,11 @@ yarn-debug.log* yarn-error.log* .npm/ .yarn/ + +npm/ +*.node +darwin-*/ +linux-*/ +win32-*/ +x86_64-*/ +aarch64-*/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a771a..c2a4f1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.0-alpha.2] - 2026-02-09 + +### Added +- Multi-platform binary distribution - No longer requires Rust toolchain for installation + - Separate platform-specific packages for 8 platforms + - Automatic platform detection and binary loading + - Users only download binaries for their platform +- Platform package support: + - `eventum-darwin-x64` (macOS Intel) + - `eventum-darwin-arm64` (macOS Apple Silicon) + - `eventum-win32-x64` (Windows x64) + - `eventum-win32-arm64` (Windows ARM) + - `eventum-linux-x64-gnu` (Linux x64 with glibc - Ubuntu, Debian, etc.) + - `eventum-linux-arm64-gnu` (Linux ARM with glibc) + - `eventum-linux-x64-musl` (Linux x64 with musl - Alpine, etc.) + - `eventum-linux-arm64-musl` (Linux ARM with musl) +- Platform detection tests to verify correct binary selection +- Improved musl vs glibc detection on Linux systems + +### Changed +- Binary loading now tries platform-specific package first, falls back to local build for development +- Removed bundled binaries from main package (now distributed via platform packages) +- Updated release workflow to build and publish platform-specific packages + +### Fixed +- Platform detection for Alpine Linux (musl) vs standard Linux (glibc) + +### Performance +- Faster npm install times due to smaller package sizes + ## [0.1.0-alpha.1] - 2026-02-07 @@ -69,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Security policy and code of conduct - GitHub issue templates for bug reports and feature requests -[Unreleased]: https://github.com/dmytroPolhul/eventum/compare/v0.1.0-alpha.1...HEAD +[Unreleased]: https://github.com/dmytroPolhul/eventum/compare/v0.1.0-alpha.2...HEAD +[0.1.0-alpha.2]: https://github.com/dmytroPolhul/eventum/compare/v0.1.0-alpha.1...v0.1.0-alpha.2 [0.1.0-alpha.1]: https://github.com/dmytroPolhul/eventum/compare/v0.1.0-alpha.0...v0.1.0-alpha.1 [0.1.0-alpha.0]: https://github.com/dmytroPolhul/eventum/releases/tag/v0.1.0-alpha.0 diff --git a/Cargo.lock b/Cargo.lock index e1510f5..1698feb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,7 +110,7 @@ dependencies = [ [[package]] name = "eventum" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" dependencies = [ "chrono", "colored", diff --git a/Cargo.toml b/Cargo.toml index bb60c61..db47e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eventum" -version = "0.1.0-alpha.1" +version = "0.1.0-alpha.2" edition = "2021" build = "build.rs" diff --git a/README.md b/README.md index 57f0c40..b166cc7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ > ⚠️ Eventum is currently in **alpha**. > APIs may change, and platform support is still evolving. -> Prebuilt binaries are currently limited. > If you encounter build issues, please open an issue. @@ -42,14 +41,6 @@ npm install eventum yarn add eventum ``` -### Build Requirements - -The native module is built automatically during installation. Ensure you have: -- **Node.js** 18.0.0 or higher -- **Rust toolchain** (will be installed via `npm install` if missing) - -> Under the hood: compiled Rust binary with no runtime dependencies. - --- ## Quick Start @@ -311,7 +302,7 @@ MIT ## Roadmap - [ ] Async file writing support (currently uses synchronous I/O) -- [ ] Prebuilt binaries for common platforms (npm, arm64, x64) +- [x] Prebuilt binaries for common platforms (npm, arm64, x64) - [ ] External transport targets (HTTP, sockets, Kafka, etc.) - [ ] WebAssembly support - [ ] File compression on rotation @@ -321,7 +312,6 @@ MIT ## Known Limitations (Alpha) - **File I/O is synchronous**: File writes may block the event loop on slow disks. Use batching to mitigate. -- **No prebuilt binaries**: Native module builds locally during installation (requires Rust toolchain). - **Log loss on crash**: Buffered logs are lost if process crashes without calling `shutdown()`. - **Single config warning**: Logger only warns once if used before `setConfig()`, then silently discards logs. diff --git a/index.cjs b/index.cjs index 0a676a9..ed01fea 100644 --- a/index.cjs +++ b/index.cjs @@ -1,21 +1,42 @@ -const path = require("node:path"); +const { platform, arch } = require('os') +const { join } = require('path') +const { readFileSync } = require('fs') -const nativePath = path.join(__dirname, "index.node"); +function isMusl() { + if (process.report?.getReport) { + const report = process.report.getReport() + return !report.header?.glibcVersionRuntime + } + + try { + const ldd = readFileSync('/usr/bin/ldd', 'utf8') + return ldd.includes('musl') + } catch { + return false + } +} -let native; -try { - native = require(nativePath); -} catch (err) { - const buildCommand = process.platform === "win32" ? "npm.cmd run build" : "npm run build"; - const message = err && err.message ? err.message : String(err); +function getPlatformPackage() { + const plat = platform() + const cpu = arch() + + if (plat === 'linux') { + const libc = isMusl() ? 'musl' : 'gnu' + return `eventum-${plat}-${cpu}-${libc}` + } + + return `eventum-${plat}-${cpu}` +} - throw new Error( - [ - `Failed to load native module at: ${nativePath}`, - `Build it first using: \`${buildCommand}\``, - `Original error: ${message}`, - ].join("\n") - ); +let native +try { + native = require(getPlatformPackage()) +} catch (e) { + try { + native = require(join(__dirname, 'eventum.node')) + } catch (err) { + throw new Error(`Failed to load native binding: ${err.message}`) + } } /** diff --git a/index.js b/index.js index 63fd574..71da553 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,47 @@ -import { createRequire } from "node:module"; -import { fileURLToPath } from "node:url"; -import { dirname, join } from "node:path"; +import { platform, arch } from 'os' +import { createRequire } from 'node:module' +import { fileURLToPath } from 'node:url' +import { dirname, join } from 'node:path' +import { readFileSync } from 'node:fs' -const require = createRequire(import.meta.url); -const __dirname = dirname(fileURLToPath(import.meta.url)); -const nativePath = join(__dirname, "index.node"); +const __dirname = dirname(fileURLToPath(import.meta.url)) +const require = createRequire(import.meta.url) -let native; +function isMusl() { + if (process.report?.getReport) { + const report = process.report.getReport() + return !report.header?.glibcVersionRuntime + } + + try { + const ldd = readFileSync('/usr/bin/ldd', 'utf8') + return ldd.includes('musl') + } catch { + return false + } +} + +function getPlatformPackage() { + const plat = platform() + const cpu = arch() + + if (plat === 'linux') { + const libc = isMusl() ? 'musl' : 'gnu' + return `eventum-${plat}-${cpu}-${libc}` + } + + return `eventum-${plat}-${cpu}` +} + +let native try { - native = require(nativePath); -} catch (err) { - const buildCommand = process.platform === "win32" ? "npm.cmd run build" : "npm run build"; - const message = err && err.message ? err.message : String(err); - - throw new Error( - [ - `Failed to load native module at: ${nativePath}`, - `Build it first using: \`${buildCommand}\``, - `Original error: ${message}`, - ].join("\n") - ); + native = require(getPlatformPackage()) +} catch (e) { + try { + native = require(join(__dirname, 'eventum.node')) + } catch (err) { + throw new Error(`Failed to load native binding: ${err.message}`) + } } /** diff --git a/package-lock.json b/package-lock.json index d584928..fafcf2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "eventum", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "eventum", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "cpu": [ "x64", "arm64" @@ -29,6 +29,16 @@ }, "engines": { "node": ">=18.0.0" + }, + "optionalDependencies": { + "eventum-darwin-arm64": "0.1.0-alpha.2", + "eventum-darwin-x64": "0.1.0-alpha.2", + "eventum-linux-arm64-gnu": "0.1.0-alpha.2", + "eventum-linux-arm64-musl": "0.1.0-alpha.2", + "eventum-linux-x64-gnu": "0.1.0-alpha.2", + "eventum-linux-x64-musl": "0.1.0-alpha.2", + "eventum-win32-arm64": "0.1.0-alpha.2", + "eventum-win32-x64": "0.1.0-alpha.2" } }, "node_modules/@babel/helper-string-parser": { @@ -1203,6 +1213,30 @@ "@types/estree": "^1.0.0" } }, + "node_modules/eventum-darwin-arm64": { + "optional": true + }, + "node_modules/eventum-darwin-x64": { + "optional": true + }, + "node_modules/eventum-linux-arm64-gnu": { + "optional": true + }, + "node_modules/eventum-linux-arm64-musl": { + "optional": true + }, + "node_modules/eventum-linux-x64-gnu": { + "optional": true + }, + "node_modules/eventum-linux-x64-musl": { + "optional": true + }, + "node_modules/eventum-win32-arm64": { + "optional": true + }, + "node_modules/eventum-win32-x64": { + "optional": true + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", diff --git a/package.json b/package.json index 1d5ad2f..164b470 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eventum", - "version": "0.1.0-alpha.1", + "version": "0.1.0-alpha.2", "private": false, "description": "Ultra-fast, low-overhead logger for Node.js powered by Rust and napi-rs", "homepage": "https://github.com/dmytroPolhul/eventum#readme", @@ -16,6 +16,16 @@ "x64", "arm64" ], + "optionalDependencies": { + "eventum-darwin-arm64": "0.1.0-alpha.2", + "eventum-darwin-x64": "0.1.0-alpha.2", + "eventum-linux-arm64-gnu": "0.1.0-alpha.2", + "eventum-linux-arm64-musl": "0.1.0-alpha.2", + "eventum-linux-x64-gnu": "0.1.0-alpha.2", + "eventum-linux-x64-musl": "0.1.0-alpha.2", + "eventum-win32-arm64": "0.1.0-alpha.2", + "eventum-win32-x64": "0.1.0-alpha.2" + }, "bugs": { "url": "https://github.com/dmytroPolhul/eventum/issues" }, @@ -44,6 +54,10 @@ "bench": "node benchmarks/compare-targets-bench.js" }, "napi": { + "name": "eventum", + "package": { + "name": "eventum" + }, "triples": { "defaults": false, "additional": [ @@ -62,7 +76,6 @@ "index.js", "index.cjs", "index.d.ts", - "index.node", "README.md", "LICENSE", "examples/images/" diff --git a/test/define-system.test.js b/test/define-system.test.js new file mode 100644 index 0000000..977fa79 --- /dev/null +++ b/test/define-system.test.js @@ -0,0 +1,171 @@ +import { describe, it, expect } from 'vitest'; +import { platform, arch } from 'os'; +import { readFileSync } from 'fs'; + +describe('Platform Detection for Binary Selection', () => { + it('should detect correct platform and architecture', () => { + const detectedPlatform = platform(); + const detectedArch = arch(); + + expect(detectedPlatform).toBeDefined(); + expect(detectedArch).toBeDefined(); + expect(['darwin', 'linux', 'win32']).toContain(detectedPlatform); + expect(['x64', 'arm64']).toContain(detectedArch); + }); + + it('should generate correct platform package name for non-Linux systems', () => { + const plat = platform(); + const cpu = arch(); + + if (plat !== 'linux') { + const packageName = `eventum-${plat}-${cpu}`; + expect(packageName).toMatch(/^eventum-(darwin|win32)-(x64|arm64)$/); + const supportedPackages = [ + 'eventum-darwin-x64', + 'eventum-darwin-arm64', + 'eventum-win32-x64', + 'eventum-win32-arm64' + ]; + expect(supportedPackages).toContain(packageName); + } + }); + + it('should detect musl vs gnu on Linux', () => { + const plat = platform(); + + if (plat === 'linux') { + function isMusl() { + if (process.report?.getReport) { + const report = process.report.getReport(); + return !report.header?.glibcVersionRuntime; + } + + try { + const ldd = readFileSync('/usr/bin/ldd', 'utf8'); + return ldd.includes('musl'); + } catch { + return false; + } + } + + const detected = isMusl(); + expect(typeof detected).toBe('boolean'); + } else { + expect(true).toBe(true); + } + }); + + it('should generate correct platform package name for Linux', () => { + const plat = platform(); + const cpu = arch(); + + if (plat === 'linux') { + function isMusl() { + if (process.report?.getReport) { + const report = process.report.getReport(); + return !report.header?.glibcVersionRuntime; + } + + try { + const ldd = readFileSync('/usr/bin/ldd', 'utf8'); + return ldd.includes('musl'); + } catch { + return false; + } + } + + const libc = isMusl() ? 'musl' : 'gnu'; + const packageName = `eventum-${plat}-${cpu}-${libc}`; + + expect(packageName).toMatch(/^eventum-linux-(x64|arm64)-(gnu|musl)$/); + + const supportedPackages = [ + 'eventum-linux-x64-gnu', + 'eventum-linux-arm64-gnu', + 'eventum-linux-x64-musl', + 'eventum-linux-arm64-musl' + ]; + expect(supportedPackages).toContain(packageName); + } else { + expect(true).toBe(true); + } + }); + + it('should match platform package name with optionalDependencies', () => { + const plat = platform(); + const cpu = arch(); + + function isMusl() { + if (process.report?.getReport) { + const report = process.report.getReport(); + return !report.header?.glibcVersionRuntime; + } + + try { + const ldd = readFileSync('/usr/bin/ldd', 'utf8'); + return ldd.includes('musl'); + } catch { + return false; + } + } + + let packageName; + if (plat === 'linux') { + const libc = isMusl() ? 'musl' : 'gnu'; + packageName = `eventum-${plat}-${cpu}-${libc}`; + } else { + packageName = `eventum-${plat}-${cpu}`; + } + + const allPlatformPackages = [ + 'eventum-darwin-x64', + 'eventum-darwin-arm64', + 'eventum-win32-x64', + 'eventum-win32-arm64', + 'eventum-linux-x64-gnu', + 'eventum-linux-arm64-gnu', + 'eventum-linux-x64-musl', + 'eventum-linux-arm64-musl' + ]; + + expect(allPlatformPackages).toContain(packageName); + }); + + it('should have consistent detection logic with index.js', () => { + const plat = platform(); + const cpu = arch(); + + function getPlatformPackage() { + function isMusl() { + if (process.report?.getReport) { + const report = process.report.getReport(); + return !report.header?.glibcVersionRuntime; + } + + try { + const ldd = readFileSync('/usr/bin/ldd', 'utf8'); + return ldd.includes('musl'); + } catch { + return false; + } + } + + if (plat === 'linux') { + const libc = isMusl() ? 'musl' : 'gnu'; + return `eventum-${plat}-${cpu}-${libc}`; + } + + return `eventum-${plat}-${cpu}`; + } + + const detectedPackage = getPlatformPackage(); + + expect(typeof detectedPackage).toBe('string'); + expect(detectedPackage.length).toBeGreaterThan(0); + + expect(detectedPackage).toMatch(/^eventum-/); + + expect(detectedPackage).not.toContain('undefined'); + expect(detectedPackage).not.toContain('null'); + }); +}); \ No newline at end of file