Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/ci-path-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ shared:
- 'crates/adaptive/src/**'
- 'crates/core/Cargo.toml'
- 'crates/core/src/**'
- 'crates/types/Cargo.toml'
- 'crates/types/src/**'
- 'justfile'
- 'rust-toolchain.toml'

Expand All @@ -24,6 +26,10 @@ rust_package:
- 'crates/cli/src/**'
- 'crates/core/Cargo.toml'
- 'crates/core/src/**'
- 'crates/plugin/Cargo.toml'
- 'crates/plugin/src/**'
- 'crates/types/Cargo.toml'
- 'crates/types/src/**'
- 'crates/ffi/Cargo.toml'
- 'crates/ffi/build.rs'
- 'crates/ffi/cbindgen.toml'
Expand All @@ -39,6 +45,8 @@ node_package:
- 'crates/adaptive/src/**'
- 'crates/core/Cargo.toml'
- 'crates/core/src/**'
- 'crates/types/Cargo.toml'
- 'crates/types/src/**'
- 'crates/node/Cargo.toml'
- 'crates/node/build.rs'
- 'crates/node/package.json'
Expand All @@ -59,6 +67,8 @@ python_package:
- 'crates/adaptive/src/**'
- 'crates/core/Cargo.toml'
- 'crates/core/src/**'
- 'crates/types/Cargo.toml'
- 'crates/types/src/**'
- 'crates/python/Cargo.toml'
- 'crates/python/src/**'
- 'justfile'
Expand Down Expand Up @@ -88,6 +98,8 @@ wasm_package:
- 'crates/adaptive/src/**'
- 'crates/core/Cargo.toml'
- 'crates/core/src/**'
- 'crates/types/Cargo.toml'
- 'crates/types/src/**'
- 'crates/wasm/Cargo.toml'
- 'crates/wasm/scripts/**'
- 'crates/wasm/src/**'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,10 @@ jobs:
set -euo pipefail
version="${{ github.ref_name }}"
packages=(
nemo-relay-types
nemo-relay
nemo-relay-adaptive
nemo-relay-plugin
nemo-relay-pii-redaction
nemo-relay-ffi
nemo-relay-cli
Expand Down
2 changes: 1 addition & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ publish:artifactory:cargo:
artifactory = { index = "sparse+${NEMO_RELAY_CI_ARTIFACTORY_CARGO_URL}" }
EOF
export CARGO_REGISTRIES_ARTIFACTORY_TOKEN="Bearer ${NEMO_RELAY_CI_ARTIFACTORY_KEY}"
export NEMO_RELAY_ARTIFACTORY_CRATE_DIRS="core adaptive pii-redaction ffi cli"
export NEMO_RELAY_ARTIFACTORY_CRATE_DIRS="types core adaptive plugin pii-redaction ffi cli"

crates="$(
uv run --no-project python - <<'PY'
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
members = [
"crates/core",
"crates/types",
"crates/plugin",
"crates/adaptive",
"crates/pii-redaction",
"crates/cli",
Expand All @@ -26,6 +27,7 @@ repository = "https://github.com/NVIDIA/NeMo-Relay"
[workspace.dependencies]
nemo-relay = { version = "0.5.0", path = "crates/core", default-features = false }
nemo-relay-types = { version = "0.5.0", path = "crates/types" }
nemo-relay-plugin = { version = "0.5.0", path = "crates/plugin" }
nemo-relay-adaptive = { version = "0.5.0", path = "crates/adaptive" }
nemo-relay-pii-redaction = { version = "0.5.0", path = "crates/pii-redaction" }
nemo-relay-ffi = { version = "0.5.0", path = "crates/ffi" }
Expand Down
30 changes: 18 additions & 12 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The release pipeline publishes these package surfaces from a tag push:

| Ecosystem | Published Surface |
|---|---|
| crates.io | `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, `nemo-relay-cli` |
| crates.io | `nemo-relay-types`, `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-plugin`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, `nemo-relay-cli` |
| PyPI | `nemo-relay` |
| npm | `nemo-relay-node`, `nemo-relay-openclaw`, `nemo-relay-wasm` |
| GitHub Releases | CLI binaries and `SHA256SUMS` |
Expand All @@ -50,7 +50,8 @@ NeMo Relay versions are anchored on the workspace SemVer in the repository root

- The root `Cargo.toml` `workspace.package.version` is the canonical release
version for the Rust workspace.
- The root `Cargo.toml` `workspace.dependencies` entries for `nemo-relay`,
- The root `Cargo.toml` `workspace.dependencies` entries for
`nemo-relay-types`, `nemo-relay`, `nemo-relay-plugin`,
`nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and
`nemo-relay-cli` must stay aligned with that same version.
- `crates/node/package.json` carries the base npm version for the Node.js
Expand Down Expand Up @@ -132,9 +133,9 @@ Before you create a release tag, confirm the following:
3. The working tree you use for local validation is clean or disposable.
4. Registry credentials and repository settings are in place:
- GitHub Actions `id-token: write` access for the top-level crates.io publish job
- crates.io trusted publishers for `nemo-relay`, `nemo-relay-adaptive`,
`nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli` are
configured for the top-level
- crates.io trusted publishers for `nemo-relay-types`, `nemo-relay`,
`nemo-relay-adaptive`, `nemo-relay-plugin`, `nemo-relay-pii-redaction`,
`nemo-relay-ffi`, and `nemo-relay-cli` are configured for the top-level
[`.github/workflows/ci.yaml`](.github/workflows/ci.yaml) workflow
- GitHub Actions `id-token: write` access is available for the top-level npm publish job
- GitHub Actions `id-token: write` access for the top-level PyPI publish job
Expand All @@ -154,8 +155,9 @@ The helper updates:

1. The root [`Cargo.toml`](Cargo.toml) workspace version.
2. The root [`Cargo.toml`](Cargo.toml) `workspace.dependencies` versions for
`nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-pii-redaction`,
`nemo-relay-ffi`, and `nemo-relay-cli`.
`nemo-relay-types`, `nemo-relay`, `nemo-relay-plugin`,
`nemo-relay-adaptive`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and
`nemo-relay-cli`.
3. [`crates/node/package.json`](crates/node/package.json) and the `crates/node`
entry in the root [`package-lock.json`](package-lock.json) to the same
release version.
Expand Down Expand Up @@ -193,6 +195,7 @@ If you want to validate the packaging recipes before pushing a tag, run:
```bash
just --set output_dir "$PWD/target/release-artifacts" --set ref_name 0.1.0 package-node
just --set output_dir "$PWD/target/release-artifacts" --set ref_name 0.1.0 package-openclaw
just --set output_dir "$PWD/target/release-artifacts" --set ref_name 0.1.0 package-rust
just --set output_dir "$PWD/target/release-artifacts" --set ref_name 0.1.0 package-python
just --set output_dir "$PWD/target/release-artifacts" --set ref_name 0.1.0 package-wasm
```
Expand Down Expand Up @@ -234,6 +237,7 @@ The release pipeline then:
2. Runs the required repository checks, language test jobs, and Fern documentation
validation.
3. Builds publishable package artifacts with the exact tag version:
- `package-rust` packs the published Rust crates for local validation.
- `package-node` packs the npm Node.js package.
- `package-openclaw` packs the npm OpenClaw plugin package.
- `package-python` builds platform wheels.
Expand All @@ -243,9 +247,10 @@ The release pipeline then:
4. Publishes packages from the top-level workflow after the reusable packaging
jobs complete:
- `publish-rust` stamps Cargo workspace versions from the release tag, then
runs `cargo publish --package` for `nemo-relay`, `nemo-relay-adaptive`,
`nemo-relay-pii-redaction`, `nemo-relay-ffi`, and `nemo-relay-cli`
through trusted publishing from the top-level workflow
runs `cargo publish --package` for `nemo-relay-types`, `nemo-relay`,
`nemo-relay-adaptive`, `nemo-relay-plugin`, `nemo-relay-pii-redaction`,
`nemo-relay-ffi`, and `nemo-relay-cli` through trusted publishing from
the top-level workflow
- `publish-python` uploads the wheel artifacts to PyPI with trusted
publishing from the top-level workflow
- `publish-npm` publishes the Node.js, OpenClaw plugin, and WebAssembly npm
Expand Down Expand Up @@ -310,8 +315,9 @@ for that tag.

After the release is live, verify:

1. The `nemo-relay`, `nemo-relay-adaptive`, `nemo-relay-pii-redaction`,
`nemo-relay-ffi`, and `nemo-relay-cli` crates are visible on crates.io.
1. The `nemo-relay-types`, `nemo-relay`, `nemo-relay-adaptive`,
`nemo-relay-plugin`, `nemo-relay-pii-redaction`, `nemo-relay-ffi`, and
`nemo-relay-cli` crates are visible on crates.io.
2. The `nemo-relay` wheel is visible on PyPI.
3. The `nemo-relay-node`, `nemo-relay-openclaw`, and `nemo-relay-wasm` packages
are visible on npm.
Expand Down
52 changes: 52 additions & 0 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,61 @@ async fn run_default(server_args: &ServerArgs) -> Result<ExitCode, error::CliErr

#[cfg(test)]
mod test_support {
#[must_use]
pub(crate) struct CwdTestScope {
_guard: std::sync::MutexGuard<'static, ()>,
prev: Option<std::path::PathBuf>,
}

impl CwdTestScope {
pub(crate) fn locked() -> Self {
Self {
_guard: lock_cwd(),
prev: None,
}
}

pub(crate) fn enter(path: &std::path::Path) -> Self {
let guard = lock_cwd();
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(path).unwrap();
Self {
_guard: guard,
prev: Some(prev),
}
}
}

impl Drop for CwdTestScope {
fn drop(&mut self) {
if let Some(prev) = &self.prev
&& let Err(error) = std::env::set_current_dir(prev)
{
CWD_RESTORE_FAILED.store(true, std::sync::atomic::Ordering::SeqCst);
if std::thread::panicking() {
eprintln!("failed to restore current_dir to {prev:?}: {error}");
} else {
panic!("failed to restore current_dir to {prev:?}: {error}");
}
}
}
}

pub(crate) static CWD_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
static CWD_RESTORE_FAILED: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub(crate) static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
pub(crate) static PLUGIN_CONFIG_TEST_LOCK: tokio::sync::Mutex<()> =
tokio::sync::Mutex::const_new(());

fn lock_cwd() -> std::sync::MutexGuard<'static, ()> {
let guard = CWD_TEST_LOCK.lock().expect("CWD_TEST_LOCK poisoned");
assert!(
!CWD_RESTORE_FAILED.load(std::sync::atomic::Ordering::SeqCst),
"current_dir restore failed in a previous test; aborting to prevent cross-test contamination",
);
guard
}
}

#[cfg(test)]
Expand Down
18 changes: 13 additions & 5 deletions crates/cli/tests/coverage/main_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::config::{
};

struct EnvScope {
_cwd_guard: Option<crate::test_support::CwdTestScope>,
_guard: std::sync::MutexGuard<'static, ()>,
values: Vec<(&'static str, Option<OsString>)>,
}
Expand All @@ -20,13 +21,19 @@ impl EnvScope {
fn hermetic(temp: &tempfile::TempDir) -> Self {
let xdg = temp.path().join("xdg");
std::fs::create_dir_all(&xdg).unwrap();
Self::set(&[
("HOME", Some(temp.path().as_os_str())),
("XDG_CONFIG_HOME", Some(xdg.as_os_str())),
])
Self::set_with_cwd_guard(
&[
("HOME", Some(temp.path().as_os_str())),
("XDG_CONFIG_HOME", Some(xdg.as_os_str())),
],
Some(crate::test_support::CwdTestScope::locked()),
)
}

fn set(values: &[(&'static str, Option<&std::ffi::OsStr>)]) -> Self {
fn set_with_cwd_guard(
values: &[(&'static str, Option<&std::ffi::OsStr>)],
cwd_guard: Option<crate::test_support::CwdTestScope>,
) -> Self {
let guard = crate::test_support::ENV_TEST_LOCK
.lock()
.unwrap_or_else(|error| error.into_inner());
Expand All @@ -43,6 +50,7 @@ impl EnvScope {
}
}
Self {
_cwd_guard: cwd_guard,
_guard: guard,
values: previous,
}
Expand Down
32 changes: 1 addition & 31 deletions crates/cli/tests/coverage/setup_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@
// SPDX-License-Identifier: Apache-2.0

use super::*;
use crate::test_support::CwdTestScope as CwdScope;
use std::ffi::OsString;
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};

// Current-directory changes are process-wide, so tests that enter a temp workspace
// must run serially with respect to each other.
fn cwd_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}

// Tests that exercise the global-config write path clear `$XDG_CONFIG_HOME`
// because CI runners commonly set it to a real `/home/runner/.config` path.
Expand Down Expand Up @@ -47,29 +40,6 @@ impl Drop for XdgScope {
}
}

struct CwdScope {
_guard: std::sync::MutexGuard<'static, ()>,
prev: PathBuf,
}

impl CwdScope {
fn enter(path: &std::path::Path) -> Self {
let guard = cwd_lock().lock().unwrap_or_else(|e| e.into_inner());
let prev = std::env::current_dir().unwrap();
std::env::set_current_dir(path).unwrap();
Self {
_guard: guard,
prev,
}
}
}

impl Drop for CwdScope {
fn drop(&mut self) {
std::env::set_current_dir(&self.prev).unwrap();
}
}

struct EnvScope {
_guard: std::sync::MutexGuard<'static, ()>,
values: Vec<(&'static str, Option<OsString>)>,
Expand Down
18 changes: 18 additions & 0 deletions crates/plugin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

[package]
name = "nemo-relay-plugin"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Rust plugin authoring SDK and stable native plugin ABI for NeMo Relay."

[lints]
workspace = true

[dependencies]
nemo-relay-types.workspace = true
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Loading
Loading