diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1fc6137 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,246 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + build-rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + + - name: Package crate + run: cargo package -p sdk-core --allow-dirty + + - uses: actions/upload-artifact@v4 + with: + name: rust-crate + path: target/package/sdk-core-*.crate + + build-python: + strategy: + fail-fast: false + matrix: + python-version: ['3.11', '3.12', '3.13', '3.14'] + target: + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - aarch64-unknown-linux-musl + # Placeholder for future macOS support: + # - aarch64-apple-darwin + include: + - target: x86_64-unknown-linux-gnu + cross: false + - target: aarch64-unknown-linux-gnu + cross: true + - target: x86_64-unknown-linux-musl + cross: true + - target: aarch64-unknown-linux-musl + cross: true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install maturin + run: pip install "maturin>=1.5,<2.0" + + - name: Install Zig + if: matrix.cross + uses: goto-bus-stop/setup-zig@v2 + + - name: Build wheel + run: | + maturin build --release \ + --strip \ + --target ${{ matrix.target }} \ + ${{ matrix.cross && '--zig' || '' }} \ + -i python${{ matrix.python-version }} \ + --out dist/ + + - name: Build sdist + if: matrix.target == 'x86_64-unknown-linux-gnu' && matrix.python-version == '3.11' + run: maturin sdist --out dist/ + + - uses: actions/upload-artifact@v4 + with: + name: python-${{ matrix.target }}-py${{ matrix.python-version }} + path: dist/* + + build-node: + strategy: + fail-fast: false + matrix: + target: + - x86_64-unknown-linux-gnu + - aarch64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - aarch64-unknown-linux-musl + # Placeholder for future macOS support: + # - aarch64-apple-darwin + include: + - target: x86_64-unknown-linux-gnu + cross: false + - target: aarch64-unknown-linux-gnu + cross: true + - target: x86_64-unknown-linux-musl + cross: true + - target: aarch64-unknown-linux-musl + cross: true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Zig + if: matrix.cross + uses: goto-bus-stop/setup-zig@v2 + + - name: Install npm dependencies + working-directory: npm + run: npm install + + - name: Build native module + working-directory: npm + run: | + npx napi build --release \ + --platform \ + --target ${{ matrix.target }} \ + --cargo-cwd ../crates/node + + - name: Strip debug symbols + run: | + if [ "${{ matrix.cross }}" = "true" ]; then + sudo apt-get install -y llvm + llvm-strip npm/index.*.node + else + strip npm/index.*.node + fi + + - uses: actions/upload-artifact@v4 + with: + name: node-${{ matrix.target }} + path: npm/index.*.node + + build-ruby: + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + cross: false + gem_platform: x86_64-linux + - target: aarch64-unknown-linux-gnu + cross: true + gem_platform: aarch64-linux + # musl targets are excluded: Rust cdylib is incompatible with musl (no dynamic linker). + # Users on musl Linux (e.g. Alpine) will use the source gem which compiles on install. + # Placeholder for future macOS support: + # - target: aarch64-apple-darwin + # cross: false + # gem_platform: arm64-darwin + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + + - name: Install Zig + if: matrix.cross + uses: goto-bus-stop/setup-zig@v2 + + - name: Install cargo-zigbuild + if: matrix.cross + run: cargo install cargo-zigbuild + + - name: Build native extension (cross) + if: matrix.cross + run: | + cargo zigbuild --release -p sdk-ruby --target ${{ matrix.target }} + cp target/${{ matrix.target }}/release/libquicknode_sdk.so ruby/lib/quicknode_sdk.so + + - name: Build native extension (native) + if: '!matrix.cross' + run: | + cargo build --release -p sdk-ruby + cp target/release/libquicknode_sdk.so ruby/lib/quicknode_sdk.so + + - name: Strip debug symbols + run: | + if [ "${{ matrix.cross }}" = "true" ]; then + sudo apt-get install -y llvm + llvm-strip ruby/lib/quicknode_sdk.so + else + strip ruby/lib/quicknode_sdk.so + fi + + - name: Build platform gem + working-directory: ruby + run: | + ruby -e " + spec = Gem::Specification.load('quicknode_sdk.gemspec') + spec.platform = Gem::Platform.new('${{ matrix.gem_platform }}') + spec.extensions = [] + spec.files += ['lib/quicknode_sdk.so'] + File.write('quicknode_sdk_platform.gemspec', spec.to_ruby) + " + gem build quicknode_sdk_platform.gemspec + rm quicknode_sdk_platform.gemspec + + - name: Build source gem + if: matrix.target == 'x86_64-unknown-linux-gnu' + working-directory: ruby + run: gem build quicknode_sdk.gemspec + + - uses: actions/upload-artifact@v4 + with: + name: ruby-${{ matrix.target }} + path: ruby/*.gem + + release: + needs: [build-rust, build-python, build-node, build-ruby] + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + path: artifacts/ + merge-multiple: true + + - uses: softprops/action-gh-release@v2 + with: + files: artifacts/**/* + generate_release_notes: true + draft: false + prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CLAUDE.md b/CLAUDE.md index 30b90c6..361abb3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,6 +136,7 @@ Core clients are tested using mocked API calls with wiremock. All functions maki - When adding a new type with `#[cfg_attr(feature = "node", napi(object))]`, also add it to the named `export type { ... }` block in `npm/sdk.d.ts` — this is the user-facing type file and is not auto-updated by napi-rs - When adding a new `#[napi(string_enum)]` Rust enum, it generates a TypeScript `const enum` in `npm/index.d.ts`. In `npm/sdk.d.ts`, these must be re-exported using a regular `export { ... }` (not `export type { ... }`), otherwise TypeScript consumers cannot use them as values (e.g., `StreamDataset.Block`) - When updating `sdk.js` wrapper methods, verify the argument types match the underlying napi-rs constructor/method signature (object vs primitive) +- When adding a new export to `sdk.js`, also add it to the named exports in `npm/sdk.mjs` — ESM named exports cannot be spread dynamically and must be listed explicitly - `python/sdk/__init__.pyi` is overwritten by `just python-build` — edit `init_manual_override.pyi` instead ### Security diff --git a/Justfile b/Justfile index 583d6c6..2049394 100644 --- a/Justfile +++ b/Justfile @@ -22,3 +22,17 @@ test: lint: cargo clippy --workspace --lib --tests -- -D warnings + +# Bump version across all manifests, commit, and tag for release. +# Usage: just release 0.2.0 +release version: + sed -i.bak 's/^version = ".*"/version = "{{version}}"/' Cargo.toml && rm Cargo.toml.bak + sed -i.bak 's/^version = ".*"/version = "{{version}}"/' pyproject.toml && rm pyproject.toml.bak + uv lock + sed -i.bak 's/"version": ".*"/"version": "{{version}}"/' npm/package.json && rm npm/package.json.bak + cd npm && npm install --package-lock-only && cd .. + sed -i.bak 's/s\.version *= *".*"/s.version = "{{version}}"/' ruby/quicknode_sdk.gemspec && rm ruby/quicknode_sdk.gemspec.bak + git add Cargo.toml pyproject.toml uv.lock npm/package.json npm/package-lock.json ruby/quicknode_sdk.gemspec + git commit -m "chore: release v{{version}}" + git tag v{{version}} + @echo "Tagged v{{version}}. Push with: git push && git push origin v{{version}}" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 0ebb03f..a16ed81 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true license.workspace = true description = "Core library for quicknode sdk" +repository = "https://github.com/quiknode-labs/sdk" +keywords = ["quicknode", "blockchain", "web3", "sdk"] +categories = ["api-bindings", "web-programming"] [lints] workspace = true @@ -19,7 +22,7 @@ extension-module = ["pyo3/extension-module"] thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -reqwest = { version = "0.13.2", features = ["json", "query"] } +reqwest = { version = "0.13.2", default-features = false, features = ["json", "query", "rustls", "http2"] } pyo3 = { workspace = true, optional = true, features = ["experimental-async"] } pyo3-async-runtimes = { workspace = true, optional = true, features = ["tokio-runtime"] } napi = { workspace = true, optional = true, features = ["async", "tokio_rt"] } diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 6429694..a25a2a7 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -3,7 +3,7 @@ name = "sdk-node" version.workspace = true edition.workspace = true license.workspace = true -description = "Node.js bindings for my-sdk" +description = "Node.js bindings for quicknode sdk" [lints] workspace = true diff --git a/npm/browser.js b/npm/browser.js new file mode 100644 index 0000000..47bf184 --- /dev/null +++ b/npm/browser.js @@ -0,0 +1,9 @@ +// Native Node.js bindings (.node files compiled from Rust) cannot run in +// browser environments — they are binary extensions that require the Node.js +// runtime and OS-level dynamic linking. Bundlers (Webpack, Vite, etc.) that +// encounter this package in a browser target will load this shim instead, +// producing a clear error rather than a cryptic binary load failure. +throw new Error( + '@quicknode/sdk-next does not support browser environments. ' + + 'This package requires Node.js.' +); diff --git a/npm/index.d.ts b/npm/index.d.ts index ba1dd4e..f1e5ba1 100644 --- a/npm/index.d.ts +++ b/npm/index.d.ts @@ -953,7 +953,7 @@ export interface Webhook { status: string network: string createdAt: string - updatedAt: string + updatedAt?: string templateId?: string notificationEmail?: string /** Destination-specific configuration as a JSON string. */ diff --git a/npm/package-lock.json b/npm/package-lock.json index 5c8dbf0..acac2f0 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@quicknode/sdk", + "name": "@quicknode/sdk-next", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@quicknode/sdk", + "name": "@quicknode/sdk-next", "version": "0.1.0", "license": "MIT", "devDependencies": { diff --git a/npm/package.json b/npm/package.json index faebd25..51359a4 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,24 +1,33 @@ { - "name": "@quicknode/sdk", + "name": "@quicknode/sdk-next", "version": "0.1.0", "description": "Quicknode SDK", "main": "sdk.js", "types": "sdk.d.ts", + "exports": { + ".": { + "import": "./sdk.mjs", + "require": "./sdk.js", + "types": "./sdk.d.ts" + } + }, + "browser": "./browser.js", "files": [ "index.js", "index.d.ts", "sdk.js", "sdk.d.ts", + "sdk.mjs", + "browser.js", "*.node" ], "napi": { - "binaryName": "my-sdk", + "binaryName": "quicknode-sdk", "targets": [ - "x86_64-apple-darwin", - "aarch64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", - "x86_64-pc-windows-msvc" + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-musl" ] }, "scripts": { diff --git a/npm/sdk.mjs b/npm/sdk.mjs new file mode 100644 index 0000000..a23a4b1 --- /dev/null +++ b/npm/sdk.mjs @@ -0,0 +1,28 @@ +// ESM wrapper: re-exports the CJS entry via dynamic import. +// The native .node binary loader (index.js) uses require() for platform +// detection and cannot be expressed as static ESM — this wrapper bridges +// the gap for ESM consumers while keeping the loader intact. +// +// We re-export everything from the default export so that new types added +// to sdk.js are automatically available here without manual updates. +import cjs from './sdk.js'; + +export default cjs; +export const { + QuickNodeSdk, + DestinationAttributes, + TemplateArgs, + StreamRegion, + StreamDataset, + StreamDestination, + FilterLanguage, + StreamMetadataLocation, + ProductType, + StreamStatus, + WebhookTemplateId, + WebhookStartFrom, + AdminApiClient, + StreamsApiClient, + WebhooksApiClient, + KvStoreApiClient, +} = cjs; diff --git a/ruby/quicknode_sdk.gemspec b/ruby/quicknode_sdk.gemspec new file mode 100644 index 0000000..526d0a5 --- /dev/null +++ b/ruby/quicknode_sdk.gemspec @@ -0,0 +1,9 @@ +Gem::Specification.new do |s| + s.name = "quicknode_sdk" + s.version = "0.1.0" + s.summary = "QuickNode SDK for Ruby" + s.authors = ["QuickNode"] + s.license = "MIT" + s.files = ["lib/quicknode_sdk.rb"] + s.required_ruby_version = ">= 3.0" +end