diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0cab524..d0f55e9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,8 +1,8 @@ name: CI env: DEBUG: napi:* - APP_NAME: cel-typescript MACOSX_DEPLOYMENT_TARGET: '10.13' + RUST_BACKTRACE: 1 on: push: @@ -13,54 +13,27 @@ on: pull_request: jobs: + # Builds native Node.js bindings for each target platform using napi-rs. + # The build artifacts are uploaded and used by the test job. build: + name: Build - ${{ matrix.target }} if: "!contains(github.event.head_commit.message, 'skip ci')" strategy: fail-fast: false matrix: - settings: - - host: macos-latest - target: x86_64-apple-darwin - build: | - npm install - npx @napi-rs/cli build --target x86_64-apple-darwin - strip -x *.node - - host: macos-latest - target: aarch64-apple-darwin - build: | - npm install - npx @napi-rs/cli build --target aarch64-apple-darwin - strip -x *.node - - host: windows-latest - target: x86_64-pc-windows-msvc - build: | - npm install - npx @napi-rs/cli build --target x86_64-pc-windows-msvc - - host: windows-latest - target: aarch64-pc-windows-msvc - build: | - npm install - npx @napi-rs/cli build --target aarch64-pc-windows-msvc - - host: ubuntu-latest - target: x86_64-unknown-linux-gnu - docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian - build: | - set -e && - npm install && - npx @napi-rs/cli build --target x86_64-unknown-linux-gnu && - strip *.node - - host: ubuntu-latest - target: aarch64-unknown-linux-gnu - docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 - build: | - set -e && - npm install && - npx @napi-rs/cli build --target aarch64-unknown-linux-gnu && - aarch64-unknown-linux-gnu-strip *.node - - - name: stable - ${{ matrix.settings.target }} - node@18 - runs-on: ${{ matrix.settings.host }} + target: [aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-pc-windows-msvc] + include: + - target: aarch64-apple-darwin + os: macos-latest + - target: x86_64-apple-darwin + os: macos-latest + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + - target: x86_64-pc-windows-msvc + os: windows-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -68,133 +41,81 @@ jobs: submodules: recursive fetch-depth: 0 - - name: Debug - List submodules - run: | - git submodule status - ls -la - ls -la cel-rust/ - ls -la cel-rust/interpreter/ - shell: bash - - name: Setup node uses: actions/setup-node@v4 - if: ${{ !matrix.settings.docker }} with: node-version: 18 - check-latest: true cache: npm - - name: Install + - name: Install Rust uses: dtolnay/rust-toolchain@stable - if: ${{ !matrix.settings.docker }} with: - targets: ${{ matrix.settings.target }} + toolchain: stable + targets: ${{ matrix.target }} - - name: Cache cargo + - name: Install Zig + uses: mlugg/setup-zig@v1 + if: ${{ runner.os == 'Linux' }} + + - name: Cache NPM dependencies + uses: actions/cache@v4 + with: + path: node_modules + key: npm-cache-${{ matrix.target }}-node@18-${{ hashFiles('package-lock.json') }} + + - name: Cache Rust dependencies uses: actions/cache@v4 with: path: | ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ - .cargo-cache target/ - key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} - - - uses: goto-bus-stop/setup-zig@v2 - if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }} - with: - version: 0.11.0 - - - name: Setup toolchain - if: ${{ matrix.settings.docker }} - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: ${{ matrix.settings.target }} - override: true - - - name: Cache NPM dependencies - uses: actions/cache@v4 - with: - path: node_modules - key: npm-cache-${{ matrix.settings.target }}-${{ hashFiles('package-lock.json') }} + key: rust-cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - name: Install dependencies - run: npm ci - - - name: Build in docker - uses: addnab/docker-run-action@v3 - if: ${{ matrix.settings.docker }} - with: - image: ${{ matrix.settings.docker }} - options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build - run: ${{ matrix.settings.build }} - - - name: Build - if: ${{ !matrix.settings.docker }} - run: ${{ matrix.settings.build }} + run: npm install shell: bash - - name: Debug - List files after build - run: | - echo "Current directory:" - pwd - echo "\nFiles in current directory:" - ls -la - echo "\nNode files:" - find . -name "*.node" + - name: Build + run: npx nx build:native -- --target ${{ matrix.target }} + env: + USE_ZIG: ${{ runner.os == 'Linux' }} shell: bash - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: bindings-${{ matrix.settings.target }} + name: bindings-${{ matrix.target }} path: | - *.node + **/*.node + !node_modules/**/*.node !target/**/*.node if-no-files-found: error retention-days: 1 + # Downloads the built native modules and runs the test suite on each platform + # to ensure the bindings work correctly across different architectures. test: - name: Test ${{ matrix.platform.name }} - node@${{ matrix.node }} + name: Test - ${{ matrix.target }} needs: - build strategy: fail-fast: false matrix: - node: - - 18 - - 20 - platform: - # For non-tag pushes, only test on x64 platforms - - name: Linux x64 - runner: ubuntu-latest - target: x86_64-unknown-linux-gnu - - name: macOS x64 - runner: macos-latest - target: x86_64-apple-darwin - - name: Windows x64 - runner: windows-latest - target: x86_64-pc-windows-msvc - # Include all platforms for tags + target: [aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-pc-windows-msvc] include: - - if: startsWith(github.ref, 'refs/tags/v') - node: 18 - platform: - name: Linux ARM64 - runner: ubuntu-latest - target: aarch64-unknown-linux-gnu - docker: true - qemu: arm64 - - if: startsWith(github.ref, 'refs/tags/v') - node: 18 - platform: - name: macOS ARM64 - runner: macos-latest - target: aarch64-apple-darwin - - runs-on: ${{ matrix.platform.runner }} + - target: aarch64-apple-darwin + os: macos-latest + - target: x86_64-apple-darwin + os: macos-latest + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + - target: x86_64-pc-windows-msvc + os: windows-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -205,86 +126,27 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node }} - check-latest: true + node-version: 18 cache: npm - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - node_modules - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - .cargo-cache - target/ - key: > - deps-${{ matrix.platform.target }}-${{ matrix.node }}- - ${{ contains(matrix.platform.target, 'aarch64') && 'npm-install' || 'npm-ci' }}- - ${{ hashFiles('package-lock.json', '**/Cargo.lock') }} - - - name: Install dependencies - run: npm ci - - - name: Setup QEMU - if: matrix.platform.qemu - uses: docker/setup-qemu-action@v3 - with: - platforms: ${{ matrix.platform.qemu }} - - - name: Install Android NDK - if: matrix.platform.android - run: | - wget https://dl.google.com/android/repository/android-ndk-r25c-linux.zip - unzip android-ndk-r25c-linux.zip - echo "ANDROID_NDK_HOME=$PWD/android-ndk-r25c" >> $GITHUB_ENV - - name: Download artifacts uses: actions/download-artifact@v4 with: - name: bindings-${{ matrix.platform.target }} - path: . + name: bindings-${{ matrix.target }} - - name: List files - run: ls -R . - shell: bash + - name: Install dependencies + run: npm install - - name: Test bindings (native) - if: "!matrix.platform.docker" + - name: Test run: npm test - - name: Test bindings (Docker) - if: matrix.platform.docker - run: | - docker run --rm ${{ matrix.platform.qemu && format('--platform linux/{0}', matrix.platform.qemu) || '' }} \ - -v $(pwd):/build -w /build node:${{ matrix.node }}-slim bash -c ' - # Install Rust - apt-get update && apt-get install -y curl build-essential - curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - export PATH="/root/.cargo/bin:$PATH" - - # Fix npm optional dependencies on ARM64 - if [[ "${{ matrix.platform.target }}" == *"aarch64"* ]]; then - # Ensure clean state - rm -rf node_modules - # Install dependencies with --no-optional to skip problematic deps - npm install --no-optional - # Now install only the required optional deps - npm install @rollup/rollup-linux-arm64-gnu - fi - - # Run tests - npm test - ' - + # Publishes the package to npm when a tag is pushed, or performs a dry-run during PRs. publish: - name: Publish + name: ${{ github.event_name == 'pull_request' && '🔍 Publish (Dry Run)' || '📦 Publish' }} runs-on: ubuntu-latest permissions: contents: read - id-token: write - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' needs: - build - test @@ -295,51 +157,43 @@ jobs: submodules: recursive fetch-depth: 0 - - name: Debug - List submodules - run: | - git submodule status - ls -la - ls -la cel-rust/ - ls -la cel-rust/interpreter/ - shell: bash - - name: Setup node uses: actions/setup-node@v4 with: node-version: 18 - check-latest: true cache: npm - - - name: Cache NPM dependencies - uses: actions/cache@v4 - with: - path: node_modules - key: npm-cache-ubuntu-latest-${{ hashFiles('package-lock.json') }} + registry-url: 'https://registry.npmjs.org' - name: Install dependencies - run: npm ci + run: npm install - name: Download all artifacts uses: actions/download-artifact@v4 with: pattern: bindings-* - merge-multiple: false + merge-multiple: true - name: Move artifacts run: | - mkdir -p dist - find . -type f -name '*.node' -exec mv {} . \; + echo "Debug: Showing all downloaded artifacts:" + find . -type f -name "*.node" -ls - - name: List files - run: | - echo "Root directory:" - ls -la - echo "\nNode files:" - find . -maxdepth 1 -name '*.node' - shell: bash + echo "\nMoving only cel-typescript binaries to root:" + find . -type f -name "cel-typescript.*.node" -exec mv {} . \; + + echo "\nBinaries in root directory:" + ls -la cel-typescript.*.node - - name: Link local package - run: npm link + # Count our binaries + node_count=$(ls -1 cel-typescript.*.node 2>/dev/null | wc -l) + expected_count=5 # We target 5 platforms + + echo "\nFound $node_count cel-typescript binaries (expected $expected_count)" + + if [ "$node_count" -ne "$expected_count" ]; then + echo "Error: Expected $expected_count binaries but found $node_count" + exit 1 + fi - name: Build TypeScript run: npm run build:ts @@ -349,12 +203,12 @@ jobs: echo "Package contents:" npm pack --dry-run - - name: Configure npm - run: | - echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - - name: Publish - if: env.NPM_TOKEN != '' + if: startsWith(github.ref, 'refs/tags/v') + run: npm publish --provenance --access public env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - run: npm publish --access public --provenance + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish (Dry Run) + if: github.event_name == 'pull_request' + run: npm publish --dry-run diff --git a/README.md b/README.md index 28aaa2a..62015ed 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # cel-typescript A TypeScript binding for the Common Expression Language (CEL) using -[cel-rust](https://github.com/clarkmcc/cel-rust). This project provides a +[cel-rust][cel-rust]. This project provides a Node.js native module that allows you to use CEL in your TypeScript/JavaScript projects. +[cel-spec]: https://github.com/google/cel-spec +[cel-rust]: https://github.com/clarkmcc/cel-rust + ## What is CEL? -[Common Expression Language (CEL)](https://github.com/google/cel-spec) is an +[Common Expression Language (CEL)][cel-spec] is an expression language created by Google that implements common semantics for expression evaluation. It's a simple language for expressing boolean conditions, calculations, and variable substitutions. CEL is used in various Google products @@ -20,18 +23,7 @@ business rule evaluation. npm install @kevinmichaelchen/cel-typescript ``` -**Requirements:** - -- Node.js 18 or later - -This package includes pre-compiled native binaries for multiple platforms: - -- macOS (x64, arm64) -- Linux (x64, arm64) -- Windows (x64) - -The appropriate binary for your platform will be automatically loaded at -runtime. +Node.js 18 or later is required. ## Usage @@ -169,16 +161,20 @@ pre-compiled native binaries for all supported platforms: However, when you install this package, npm will only extract the `.node` file for your platform. For example: -- On an M1/M2 Mac, only `cel-typescript.darwin-arm64.node` (~7.4 MB) is used +- On Apple Silicon, only `cel-typescript.darwin-arm64.node` (~7.4 MB) is used - On Windows, only `cel-typescript.win32-x64.node` is used - On Linux, only `cel-typescript.linux-x64.node` or `cel-typescript.linux-arm64.node` is used -This is a common pattern for packages with native bindings. For comparison: +This is sometimes a pattern for packages with native bindings. For comparison: + +- [`better-sqlite3`][better-sqlite3]: 10.2 MB unpacked +- [`canvas`][canvas]: 408 kB unpacked +- [`sharp`][sharp]: 522 kB unpacked -- `sharp` (image processing): 39.7 MB unpacked -- `better-sqlite3`: 12.8 MB unpacked -- `canvas`: 8.9 MB unpacked +[better-sqlite3]: https://www.npmjs.com/package/better-sqlite3 +[canvas]: https://www.npmjs.com/package/canvas +[sharp]: https://www.npmjs.com/package/sharp #### A Note on Tree-Shaking diff --git a/__tests__/cel.test.ts b/__tests__/cel.test.ts index 0360bc2..b994987 100644 --- a/__tests__/cel.test.ts +++ b/__tests__/cel.test.ts @@ -152,11 +152,13 @@ describe("Performance measurements", () => { // Allow more variation due to system noise, optimization differences, and convenience overhead const tolerance = 1.0; // Allow 100% variation const expectedEvaluateTime = compileTime + executeTime; - + // Log the actual ratios to help with debugging const ratio = evaluateTime / expectedEvaluateTime; - console.log(`One-step evaluation was ${ratio.toFixed(2)}x the separate steps`); - + console.log( + `One-step evaluation was ${ratio.toFixed(2)}x the separate steps`, + ); + // Only fail if the difference is extreme expect(evaluateTime).toBeGreaterThan( expectedEvaluateTime * (1 - tolerance), @@ -164,3 +166,260 @@ describe("Performance measurements", () => { expect(evaluateTime).toBeLessThan(expectedEvaluateTime * (2 + tolerance)); }); }); + +describe("Timestamp Functions", () => { + it("should correctly parse and compare timestamps", async () => { + const expr = `[ + // Test 1: Compare UTC timestamp with itself + timestamp('2025-04-28T12:00:00Z') == timestamp('2025-04-28T12:00:00Z'), + // Test 2: Compare UTC timestamp with equivalent EDT timestamp + timestamp('2025-04-28T12:00:00Z') == timestamp('2025-04-28T08:00:00-04:00'), + // Test 3: Compare timestamps one day apart + timestamp('2025-04-28T12:00:00Z') < timestamp('2025-04-29T12:00:00Z'), + // Test 4: Get components from UTC timestamp + timestamp('2025-04-28T12:00:00Z').getMonth(), + timestamp('2025-04-28T12:00:00Z').getDayOfMonth(), + timestamp('2025-04-28T12:00:00Z').getHours(), + timestamp('2025-04-28T12:00:00Z').getDayOfWeek() // 1==Monday + ]`; + const result = await evaluate(expr, {}); + expect(result).toEqual([true, true, true, 3, 27, 12, 1]); + }); +}); + +describe("Travel Reservation Rules", () => { + describe("Premium Discount Eligibility", () => { + // Tests a complex pricing rule that considers: + // - Total package cost across flight, hotel, and car + // - Customer loyalty tier + // - Seasonal booking (summer months) + const expr = ` + // Calculate total package cost + double(reservation.flight.price) + + double(reservation.hotel.nightlyRate) * int(reservation.hotel.nights) + + double(reservation.car.dailyRate) * int(reservation.car.days) >= 2000.0 && + // Check loyalty tier + reservation.customer.loyaltyTier in ['GOLD', 'PLATINUM'] && + // Check if booking is in summer months (0-based: 5=June, 6=July, 7=August) + timestamp(reservation.bookingDate).getMonth() in [5, 6, 7] + `; + let program: CelProgram; + + beforeEach(async () => { + program = await CelProgram.compile(expr); + }); + + it("should qualify for premium discount with valid summer booking", async () => { + const result = await program.execute({ + reservation: { + flight: { price: 1000.0 }, + hotel: { nightlyRate: 200.0, nights: 4 }, + car: { dailyRate: 100.0, days: 4 }, + customer: { loyaltyTier: "PLATINUM" }, + bookingDate: "2025-07-15T00:00:00Z", + }, + }); + expect(result).toBe(true); + }); + + it("should not qualify outside summer months", async () => { + const result = await program.execute({ + reservation: { + flight: { price: 1000.0 }, + hotel: { nightlyRate: 200.0, nights: 4 }, + car: { dailyRate: 100.0, days: 4 }, + customer: { loyaltyTier: "PLATINUM" }, + bookingDate: "2025-12-15T00:00:00Z", + }, + }); + expect(result).toBe(false); + program = await CelProgram.compile(expr); + }); + }); + + describe("Multi-condition Booking Validation", () => { + // Tests complex booking validation rules that ensure: + // - All required components are present + // - Logical time sequence of events + // - Location consistency + // - Capacity constraints + const expr = ` + has(reservation.flight) && + timestamp(reservation.flight.departureTime) < timestamp(reservation.hotel.checkIn) && + timestamp(reservation.hotel.checkIn) < timestamp(reservation.hotel.checkOut) && + (timestamp(reservation.hotel.checkOut) - timestamp(reservation.hotel.checkIn)) > duration("1h") && + timestamp(reservation.hotel.checkOut) < timestamp(reservation.flight.returnTime) && + (reservation.car.pickupLocation == reservation.flight.arrivalAirport || + reservation.car.pickupLocation == reservation.hotel.address.city) && + size(reservation.travelers) <= reservation.hotel.maxOccupancy && + size(reservation.travelers) <= reservation.car.capacity + `; + let program: CelProgram; + + beforeEach(async () => { + program = await CelProgram.compile(expr); + }); + + it("should validate a well-formed booking", async () => { + const result = await program.execute({ + reservation: { + flight: { + departureTime: "2025-05-01T10:00:00Z", + returnTime: "2025-05-05T15:00:00Z", + arrivalAirport: "LAX", + }, + hotel: { + checkIn: "2025-05-01T15:00:00Z", + checkOut: "2025-05-05T11:00:00Z", + maxOccupancy: 4, + address: { city: "Los Angeles" }, + }, + car: { + pickupLocation: "LAX", + capacity: 5, + }, + travelers: ["person1", "person2", "person3"], + }, + }); + expect(result).toBe(true); + }); + + it("should reject invalid time sequence", async () => { + const result = await program.execute({ + reservation: { + flight: { + departureTime: "2025-05-01T16:00:00Z", // Later than check-in + returnTime: "2025-05-05T15:00:00Z", + arrivalAirport: "LAX", + }, + hotel: { + checkIn: "2025-05-01T15:00:00Z", + checkOut: "2025-05-05T11:00:00Z", + maxOccupancy: 4, + address: { city: "Los Angeles" }, + }, + car: { + pickupLocation: "LAX", + capacity: 5, + }, + travelers: ["person1", "person2", "person3"], + }, + }); + expect(result).toBe(false); + }); + }); + + describe.skip("Dynamic Pricing with Seasonal Adjustments", () => { + // Tests complex pricing calculations including: + // - Base rates for all components + // - Seasonal multipliers + // - Loyalty discounts + const expr = ` + let basePrice = double(reservation.flight.price) + + double(reservation.hotel.nightlyRate) * int(reservation.hotel.nights) + + double(reservation.car.dailyRate) * int(reservation.car.days); + let seasonalPrice = basePrice * (timestamp(reservation.hotel.checkIn).getMonth() in [11, 0, 1] ? 1.25 : 1.0); + seasonalPrice * (1.0 - {'BRONZE': 0.05, 'SILVER': 0.10, 'GOLD': 0.15, 'PLATINUM': 0.20}[reservation.customer.loyaltyTier]) + `; + let program: CelProgram; + + beforeEach(async () => { + program = await CelProgram.compile(expr); + }); + + it("should calculate winter pricing with loyalty discount", async () => { + const result = await program.execute({ + reservation: { + flight: { price: 1000.0 }, + hotel: { + nightlyRate: 200.0, + nights: 4, + checkIn: "2025-01-15T15:00:00Z", // January + }, + car: { dailyRate: 100.0, days: 4 }, + customer: { loyaltyTier: "GOLD" }, + }, + }); + // Base: 1000 + (200 * 4) + (100 * 4) = 2200 + // Winter multiplier: 2200 * 1.25 = 2750 + // Gold discount (15%): 2750 * 0.85 = 2337.5 + expect(result).toBe(2337.5); + }); + + it("should calculate summer pricing with loyalty discount", async () => { + const result = await program.execute({ + reservation: { + flight: { price: 1000.0 }, + hotel: { + nightlyRate: 200.0, + nights: 4, + checkIn: "2025-07-15T15:00:00Z", // July + }, + car: { dailyRate: 100.0, days: 4 }, + customer: { loyaltyTier: "PLATINUM" }, + }, + }); + // Base: 1000 + (200 * 4) + (100 * 4) = 2200 + // No seasonal multiplier + // Platinum discount (20%): 2200 * 0.80 = 1760 + expect(result).toBe(1760.0); + }); + }); + + describe("Room Upgrade Eligibility", () => { + // Tests complex upgrade eligibility rules considering: + // - Customer loyalty tier + // - Stay duration + // - Hotel occupancy + // - Existing offers + // - Total spend + // - Current booking class + const expr = ` + reservation.customer.loyaltyTier in ['GOLD', 'PLATINUM'] && + reservation.hotel.nights >= 3 && + reservation.hotel.occupancyRate < 0.80 && + !(reservation.specialOffers.exists(o, o.type == 'ROOM_UPGRADE')) && + reservation.totalSpend > 5000.0 && + [reservation.flight.class, reservation.hotel.roomType].all(t, t != 'ECONOMY') + `; + let program: CelProgram; + + beforeEach(async () => { + program = await CelProgram.compile(expr); + }); + + it("should qualify eligible platinum member for upgrade", async () => { + const result = await program.execute({ + reservation: { + customer: { loyaltyTier: "PLATINUM" }, + hotel: { + nights: 4, + occupancyRate: 0.7, + roomType: "DELUXE", + }, + flight: { class: "BUSINESS" }, + specialOffers: [], + totalSpend: 6000.0, + }, + }); + expect(result).toBe(true); + }); + + it("should reject upgrade when conditions not met", async () => { + const result = await program.execute({ + reservation: { + customer: { loyaltyTier: "PLATINUM" }, + hotel: { + nights: 4, + occupancyRate: 0.85, // Too high occupancy + roomType: "DELUXE", + }, + flight: { class: "BUSINESS" }, + specialOffers: [], + totalSpend: 6000.0, + }, + }); + expect(result).toBe(false); + }); + }); +}); diff --git a/biome.json b/biome.json index f5413f3..d7d0524 100644 --- a/biome.json +++ b/biome.json @@ -11,7 +11,8 @@ "enabled": true, "indentStyle": "space", "indentWidth": 2, - "lineWidth": 80 + "lineWidth": 80, + "ignore": ["index.js"] }, "linter": { "enabled": true, diff --git a/package.json b/package.json index eb7de37..d2d3544 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevinmichaelchen/cel-typescript", - "version": "0.0.8", + "version": "0.0.9", "type": "module", "description": "TypeScript bindings for the Common Expression Language (CEL) using cel-rust", "repository": { @@ -13,12 +13,7 @@ "url": "https://github.com/kevinmichaelchen/cel-typescript/issues" }, "homepage": "https://github.com/kevinmichaelchen/cel-typescript#readme", - "files": [ - "dist/**/*", - "*.node", - "index.js", - "index.d.ts" - ], + "files": ["dist/**/*", "*.node", "index.js", "index.d.ts"], "keywords": [ "cel", "common-expression-language", @@ -44,13 +39,12 @@ "napi": { "name": "cel-typescript", "triples": { - "defaults": true, + "defaults": false, "additional": [ "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "aarch64-pc-windows-msvc", - "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", "x86_64-pc-windows-msvc" ] } diff --git a/project.json b/project.json index 5211a1b..04d8380 100644 --- a/project.json +++ b/project.json @@ -5,12 +5,13 @@ "build:native": { "executor": "nx:run-commands", "options": { - "command": "npx @napi-rs/cli build --platform" + "command": "bash scripts/build.sh {args.target}" }, "outputs": [ "{workspaceRoot}/cel-typescript.darwin-arm64.node", "{workspaceRoot}/cel-typescript.darwin-x64.node", "{workspaceRoot}/cel-typescript.linux-x64-gnu.node", + "{workspaceRoot}/cel-typescript.linux-arm64-gnu.node", "{workspaceRoot}/cel-typescript.win32-x64-msvc.node" ] }, diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..a779dea --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [ "$USE_ZIG" = "true" ]; then + npx @napi-rs/cli build --platform --target "$1" --release --zig +else + npx @napi-rs/cli build --platform --target "$1" --release +fi diff --git a/src/index.ts b/src/index.ts index 49456f7..baf68e4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,7 +33,9 @@ class CelProgram { static async compile(source: string): Promise { if (!CelProgram.nativeModule) { // Use the NAPI-RS generated loader which handles platform detection - const nativeBinding = await import("@kevinmichaelchen/cel-typescript/native"); + const nativeBinding = await import( + "@kevinmichaelchen/cel-typescript/native" + ); CelProgram.nativeModule = nativeBinding.CelProgram; console.log("Imported native CelProgram:", CelProgram.nativeModule); }