From d6abb72e14d3c1e0ccdd5d65bd19ddb66ac91eeb Mon Sep 17 00:00:00 2001 From: Aniket Maurya Date: Wed, 24 Jun 2026 20:37:18 +0100 Subject: [PATCH] Add npm trusted publishing workflow --- .github/workflows/publish-node.yml | 148 +++++++++++++++++++++++++++++ README.md | 25 ++++- bindings/node/README.md | 95 ++++++++++++++++++ bindings/node/package.json | 8 +- 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/publish-node.yml create mode 100644 bindings/node/README.md diff --git a/.github/workflows/publish-node.yml b/.github/workflows/publish-node.yml new file mode 100644 index 0000000..4728c98 --- /dev/null +++ b/.github/workflows/publish-node.yml @@ -0,0 +1,148 @@ +name: Publish Node Package + +on: + push: + tags: + - "v*" + workflow_dispatch: + +permissions: + contents: read + +env: + CARGO_INCREMENTAL: "0" + NODE_VERSION: "24" + +jobs: + build-native: + name: Build Node native binding on ${{ matrix.node_target }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + rust_target: x86_64-unknown-linux-gnu + node_target: linux-x64-gnu + smoke: true + - os: ubuntu-24.04 + rust_target: aarch64-unknown-linux-gnu + node_target: linux-arm64-gnu + smoke: false + - os: macos-latest + rust_target: aarch64-apple-darwin + node_target: darwin-arm64 + smoke: true + - os: macos-15-intel + rust_target: x86_64-apple-darwin + node_target: darwin-x64 + smoke: true + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@c93f4f9c67595668add93d3d6895795ce52d8c2d # 1.85.0 + with: + targets: ${{ matrix.rust_target }} + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + package-manager-cache: false + + - name: Install Linux arm64 build tools + if: matrix.rust_target == 'aarch64-unknown-linux-gnu' + timeout-minutes: 15 + run: | + set -euo pipefail + sudo apt-get update \ + -o Acquire::Retries=5 \ + -o Acquire::http::Timeout=30 \ + -o Acquire::https::Timeout=30 + sudo apt-get install -y --no-install-recommends \ + -o Acquire::Retries=5 \ + -o Acquire::http::Timeout=30 \ + -o Acquire::https::Timeout=30 \ + gcc-aarch64-linux-gnu libc6-dev-arm64-cross + + - name: Install TypeScript SDK dependencies + working-directory: bindings/node + run: npm ci + + - name: Build native binding + working-directory: bindings/node + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + run: npm run build -- --target ${{ matrix.rust_target }} + + - name: Smoke-test native binding + if: matrix.smoke + working-directory: bindings/node + run: node -e "const { doctor } = require('./index.js'); const report = doctor(); if (!report.storageBackend) process.exit(1)" + + - name: Upload native binding artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: smolfs-${{ matrix.node_target }} + path: bindings/node/smolfs.${{ matrix.node_target }}.node + if-no-files-found: error + + publish: + name: Publish to npm + runs-on: ubuntu-24.04 + needs: build-native + if: startsWith(github.ref, 'refs/tags/v') + timeout-minutes: 10 + environment: npm + permissions: + contents: read + id-token: write + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: "https://registry.npmjs.org" + package-manager-cache: false + + - name: Check release tag matches package versions + run: | + set -euo pipefail + tag_version="${GITHUB_REF_NAME#v}" + cargo_version=$(awk -F '"' '/^version = / { print $2; exit }' Cargo.toml) + node_version=$(node -p "require('./bindings/node/package.json').version") + if [ "$tag_version" != "$cargo_version" ] || [ "$tag_version" != "$node_version" ]; then + echo "Release tag v$tag_version does not match Cargo version $cargo_version and Node package version $node_version" + exit 1 + fi + + - name: Download native binding artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: bindings/node + merge-multiple: true + + - name: Install TypeScript SDK dependencies + working-directory: bindings/node + run: npm ci + + - name: Verify npm package contents + working-directory: bindings/node + run: | + set -euo pipefail + for target in darwin-arm64 darwin-x64 linux-arm64-gnu linux-x64-gnu; do + test -f "smolfs.$target.node" + done + npm pack --dry-run --json + + - name: Publish package + working-directory: bindings/node + run: npm publish --access public diff --git a/README.md b/README.md index b88fc6c..a0efbb7 100644 --- a/README.md +++ b/README.md @@ -327,7 +327,14 @@ SmolFS. ## TypeScript SDK The TypeScript package is a native Node.js binding over the same Rust core. The -npm package is not published yet; for now, use the local checkout flow below. +npm package ships prebuilt native bindings for Linux and macOS on x86_64 and +arm64. + +Install it with npm: + +```bash +npm install @celestoai/smolfs +``` For local development from this checkout, use Node.js 18 or newer: @@ -472,6 +479,11 @@ Python packaging uses `uv` and `maturin`. The GitHub workflow at `.github/workflows/publish-python.yml` builds wheels for Linux and macOS, builds an sdist, and publishes to PyPI when a `v*` tag is pushed. +Node.js packaging uses `npm` and `napi-rs`. The GitHub workflow at +`.github/workflows/publish-node.yml` builds native TypeScript SDK bindings for +Linux and macOS, checks the npm package contents, and publishes to npm when a +`v*` tag is pushed. + Before the first Python release, configure PyPI Trusted Publishing: 1. Create or claim the `smolfs` project on PyPI. @@ -479,6 +491,16 @@ Before the first Python release, configure PyPI Trusted Publishing: 3. Set the workflow name to `publish-python.yml`. 4. Set the environment name to `pypi`. +Before the first npm release, configure npm trusted publishing for +`@celestoai/smolfs`: + +1. Add a trusted publisher for GitHub Actions. +2. Set the organization or user to `CelestoAI`. +3. Set the repository to `smolfs`. +4. Set the workflow filename to `publish-node.yml`. +5. Set the environment name to `npm`. +6. Allow `npm publish`. + Release: ```bash @@ -488,7 +510,6 @@ git push origin v0.1.0 ## Roadmap -- Add npm publishing with prebuilt TypeScript SDK native artifacts. - Add type stubs for the Python package. - Add a Linux CI job that mounts a local dev volume when mount support is available. diff --git a/bindings/node/README.md b/bindings/node/README.md new file mode 100644 index 0000000..9fc6491 --- /dev/null +++ b/bindings/node/README.md @@ -0,0 +1,95 @@ +# SmolFS TypeScript SDK + +SmolFS gives TypeScript agents a workspace folder that can survive after the +agent process stops. You can create a volume, mount it like a normal directory, +write files into it, flush important changes, unmount it, and mount the same +files again later. + +The npm package is a native Node.js SDK over the same Rust core as the `smolfs` +command. It is useful when an agent runner or automation service wants to manage +workspace volumes from TypeScript without shelling out for every operation. + +## Install + +Install the SDK from npm: + +```bash +npm install @celestoai/smolfs +``` + +Mounting volumes also needs SmolFS' managed storage backend on the machine. The +installer sets up both the CLI and backend: + +```bash +curl -fsSL https://raw.githubusercontent.com/CelestoAI/smolfs/main/scripts/install.sh | sh +``` + +## Quickstart + +Start with a local development volume: + +```ts +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { SmolFS, doctor } from "@celestoai/smolfs"; + +const report = doctor(); +if (!report.storageBackend.found || !report.mountSupport.found) { + throw new Error(`SmolFS is not ready: ${JSON.stringify(report)}`); +} + +const fs = SmolFS.fromEnv(); +const volume = fs.ensureVolume({ name: "demo", dev: true }); +const mount = fs.mount({ name: volume.name, path: "./workspace" }); + +mkdirSync(mount.mountpoint, { recursive: true }); +writeFileSync(join(mount.mountpoint, "hello.txt"), "hello from SmolFS\n"); + +try { + fs.flush(volume.name); +} finally { + fs.unmount(volume.name); +} +``` + +`dev: true` creates a local-only volume. It is the easiest way to test SmolFS on +a single machine before connecting shared metadata and object storage. + +## Cloud Volumes + +Cloud volumes use the same API with explicit metadata and object storage +settings: + +```ts +import { SmolFS } from "@celestoai/smolfs"; + +const fs = SmolFS.fromEnv(); +fs.ensureVolume({ + name: "agent-workspace", + metadata: "redis://localhost:6379/1", + storage: "s3", + bucket: "https://my-bucket.s3.us-east-2.amazonaws.com", +}); +``` + +Keep storage credentials in the environment used by SmolFS. Do not print them in +logs or store them in source files. + +## API Overview + +- `doctor()` checks whether the machine can create and mount volumes. +- `SmolFS.fromEnv()` creates a client using `SMOLFS_HOME` and the current + environment. +- `ensureVolume(...)` creates a volume if it does not exist and returns the + existing volume if it does. +- `init(...)` creates a new volume. +- `mount({ name, path })` mounts a volume at a local directory. +- `flush(name)` asks SmolFS to sync important writes. +- `unmount(name, { force })` unmounts a mounted volume. +- `status(name?)` lists known volumes and mountpoints. + +## Links + +- Repository: https://github.com/CelestoAI/smolfs +- Issues: https://github.com/CelestoAI/smolfs/issues +- CLI installer: https://raw.githubusercontent.com/CelestoAI/smolfs/main/scripts/install.sh diff --git a/bindings/node/package.json b/bindings/node/package.json index 32231d8..342d3b5 100644 --- a/bindings/node/package.json +++ b/bindings/node/package.json @@ -1,8 +1,7 @@ { "name": "@celestoai/smolfs", "version": "0.1.0", - "description": "TypeScript bindings for SmolFS", - "private": true, + "description": "Durable workspace folders for TypeScript agents", "license": "Apache-2.0", "repository": { "type": "git", @@ -17,8 +16,13 @@ "agents", "filesystem", "object-storage", + "persistent-workspaces", "smolfs" ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, "main": "index.js", "types": "index.d.ts", "files": [