Skip to content
Merged
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
11 changes: 8 additions & 3 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -803,12 +803,17 @@ jobs:
with:
target: ${{ matrix.target }}
cache-workspaces: rust/cube-cli
- name: Set crate version to the release version
# The CLI version comes from lerna.json at build time (see
# rust/cube-cli/build.rs); just guard against tag/lerna drift.
- name: Check release version matches lerna.json
shell: bash
run: |
v="${GITHUB_REF_NAME#v}"
sed -i.bak "s/^version = .*/version = \"$v\"/" rust/cube-cli/Cargo.toml
rm -f rust/cube-cli/Cargo.toml.bak
l=$(jq -r .version lerna.json)
if [ "$v" != "$l" ]; then
echo "tag version $v does not match lerna.json version $l" >&2
exit 1
fi
- name: Build
working-directory: rust/cube-cli
run: cargo build --release --target ${{ matrix.target }}
Expand Down
2 changes: 1 addition & 1 deletion rust/cube-cli/Cargo.lock

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

11 changes: 8 additions & 3 deletions rust/cube-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[package]
name = "cube-cli"
# Kept in lockstep with the Cube monorepo version (lerna.json). The release
# workflow overrides this from the pushed `v*` tag at build time.
version = "1.7.2"
# Placeholder — the real version is the Cube monorepo version, read from the
# repo-root lerna.json by build.rs at build time (CUBE_CLI_VERSION). This
# field is only the fallback for builds outside the monorepo.
version = "0.0.0"
edition = "2021"
build = "build.rs"
description = "Cube Cloud command line interface"
license = "Apache-2.0"
repository = "https://github.com/cube-js/cube"
Expand Down Expand Up @@ -40,6 +42,9 @@ etcetera = "0.8"
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[build-dependencies]
serde_json = "1"

[profile.release]
lto = "thin"
strip = true
Expand Down
8 changes: 5 additions & 3 deletions rust/cube-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ no OpenSSL dependency and musl builds work out of the box.

## Versioning & releases

The CLI version tracks the Cube monorepo version (`lerna.json`); `Cargo.toml`
is kept in sync and the release build overrides it from the pushed tag, so
`cube --version` always matches the Cube release.
The CLI version tracks the Cube monorepo version: `build.rs` reads the
repo-root `lerna.json` at build time (the `Cargo.toml` version is only a
fallback for out-of-tree builds), so `cube --version` always reports the
real Cube version — for release builds and source builds alike. The release
workflow just verifies the pushed tag matches `lerna.json`.

The CLI is built and published by the **same release workflow as the rest of
Cube** (`.github/workflows/publish.yml`, on `v*.*.*` tags). Its `cube-cli`
Expand Down
21 changes: 21 additions & 0 deletions rust/cube-cli/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::path::Path;

/// The CLI version tracks the Cube monorepo version. The single source of
/// truth is the repo-root `lerna.json` (bumped by every release commit), so
/// read it at build time instead of keeping Cargo.toml in sync by hand —
/// source builds and release builds then always report the real version.
/// Falls back to the crate version if lerna.json isn't present (e.g. the
/// crate directory built outside the monorepo).
fn main() {
println!("cargo:rerun-if-changed=../../lerna.json");
let version = lerna_version().unwrap_or_else(|| env!("CARGO_PKG_VERSION").to_string());
println!("cargo:rustc-env=CUBE_CLI_VERSION={version}");
}

fn lerna_version() -> Option<String> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok()?;
let lerna = Path::new(&manifest_dir).join("../../lerna.json");
let raw = std::fs::read_to_string(lerna).ok()?;
let json: serde_json::Value = serde_json::from_str(&raw).ok()?;
json.get("version")?.as_str().map(str::to_string)
}
14 changes: 7 additions & 7 deletions rust/cube-cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Client {
}
Ok(Self {
http: reqwest::Client::builder()
.user_agent(concat!("cube-cli/", env!("CARGO_PKG_VERSION")))
.user_agent(concat!("cube-cli/", env!("CUBE_CLI_VERSION")))
.build()?,
base_url,
token: Mutex::new(token.to_string()),
Expand Down Expand Up @@ -74,16 +74,16 @@ impl Client {
self.token.lock().unwrap().clone()
}

/// Authorization header value for a credential. JWTs (three dot-separated
/// segments — OAuth access tokens, legacy deploy JWTs) use the `Bearer`
/// scheme; opaque credentials are Cube Cloud API keys and use `Api-Key`.
/// `CUBE_AUTH_SCHEME=bearer|api-key` overrides the heuristic.
/// Authorization header value for a credential. Cube Cloud API keys are
/// prefixed `sk-` and use the `Api-Key` scheme; everything else (OAuth
/// access tokens — not necessarily JWTs — and legacy deploy JWTs) uses
/// `Bearer`. `CUBE_AUTH_SCHEME=bearer|api-key` overrides the detection.
fn authorization(token: &str) -> String {
let scheme = match std::env::var("CUBE_AUTH_SCHEME").as_deref() {
Ok("bearer") | Ok("Bearer") => "Bearer",
Ok("api-key") | Ok("Api-Key") => "Api-Key",
_ if token.split('.').count() == 3 => "Bearer",
_ => "Api-Key",
_ if token.starts_with("sk-") => "Api-Key",
_ => "Bearer",
};
format!("{scheme} {token}")
}
Expand Down
4 changes: 2 additions & 2 deletions rust/cube-cli/src/commands/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub async fn command(args: Args, ctx: &mut Ctx) -> Result<()> {
.with_placeholder("https://<tenant>.cubecloud.dev")
.prompt()?,
};
let url = url.trim_end_matches('/').to_string();
let url = url.trim().trim_end_matches('/').to_string();

let (token, refresh_token) = match args.api_key {
Some(key) => (key, None),
Expand Down Expand Up @@ -64,7 +64,7 @@ pub async fn command(args: Args, ctx: &mut Ctx) -> Result<()> {
async fn device_login(url: &str) -> Result<(String, Option<String>)> {
let cfg = oauth::OAuthConfig::from_env();
let http = reqwest::Client::builder()
.user_agent(concat!("cube-cli/", env!("CARGO_PKG_VERSION")))
.user_agent(concat!("cube-cli/", env!("CUBE_CLI_VERSION")))
.build()?;

let device = oauth::request_device_code(&http, url, &cfg).await?;
Expand Down
6 changes: 3 additions & 3 deletions rust/cube-cli/src/commands/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ pub struct Args {

pub async fn command(args: Args, _ctx: &Ctx) -> Result<()> {
let http = reqwest::Client::builder()
.user_agent(concat!("cube-cli/", env!("CARGO_PKG_VERSION")))
.user_agent(concat!("cube-cli/", env!("CUBE_CLI_VERSION")))
.build()?;

let release = update::latest_release(&http).await?;
let latest = release.version();

if !is_newer(latest, CURRENT_VERSION) {
output::success(&format!(
"cube {CURRENT_VERSION} is up to date (latest release: {latest})"
"Cube CLI {CURRENT_VERSION} is up to date (latest release: {latest})"
));
return Ok(());
}
Expand Down Expand Up @@ -57,7 +57,7 @@ pub async fn command(args: Args, _ctx: &Ctx) -> Result<()> {
let new_binary = extract_binary(&bytes)?;
replace_current_exe(&new_binary)?;

output::success(&format!("Updated cube {CURRENT_VERSION} → {latest}"));
output::success(&format!("Updated Cube CLI {CURRENT_VERSION} → {latest}"));
Ok(())
}

Expand Down
6 changes: 3 additions & 3 deletions rust/cube-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(
name = "cube",
version,
about = "Cube Cloud command line interface",
propagate_version = true
display_name = "Cube CLI",
version = env!("CUBE_CLI_VERSION"),
about = "Cube Cloud command line interface"
)]
struct Cli {
#[command(flatten)]
Expand Down
4 changes: 2 additions & 2 deletions rust/cube-cli/src/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn event(name: &str, props: Map<String, Value>) {
}
let mut payload = json!({
"event": name,
"cliVersion": env!("CARGO_PKG_VERSION"),
"cliVersion": env!("CUBE_CLI_VERSION"),
"clientTimestamp": timestamp(),
"id": random_id(),
"platform": platform(),
Expand All @@ -58,7 +58,7 @@ pub async fn flush() {
return;
}
let Ok(http) = reqwest::Client::builder()
.user_agent(concat!("cube-cli/", env!("CARGO_PKG_VERSION")))
.user_agent(concat!("cube-cli/", env!("CUBE_CLI_VERSION")))
.timeout(Duration::from_secs(2))
.build()
else {
Expand Down
6 changes: 3 additions & 3 deletions rust/cube-cli/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn release_api_base() -> String {
std::env::var("CUBE_UPDATE_API").unwrap_or_else(|_| "https://api.github.com".to_string())
}

pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const CURRENT_VERSION: &str = env!("CUBE_CLI_VERSION");

/// The release target triple this binary maps to. Linux always maps to the
/// musl asset — that's the only Linux artifact we ship, and it runs anywhere.
Expand Down Expand Up @@ -102,15 +102,15 @@ pub fn spawn_check() -> tokio::task::JoinHandle<Option<String>> {
return None;
}
let http = reqwest::Client::builder()
.user_agent(concat!("cube-cli/", env!("CARGO_PKG_VERSION")))
.user_agent(concat!("cube-cli/", env!("CUBE_CLI_VERSION")))
.build()
.ok()?;
let release = latest_release(&http).await.ok()?;
let latest = release.version().to_string();
if newer_than(&latest, CURRENT_VERSION) {
Some(format!(
"\n{} {} → {}\nRun {} to install it.",
"A new release of cube is available:".yellow(),
"A new release of Cube CLI is available:".yellow(),
CURRENT_VERSION.dimmed(),
latest.bold().green(),
"cube update".bold().cyan(),
Expand Down
Loading