Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 + tools/ci) with a "Connect to a Different Server" menu item and popout that lets the desktop app switch to any self-hosted Fluxer instance, plus fixes for well-known discovery on single-domain self-hosted deployments and a false-positive ERR_ABORTED on same-origin client redirects during the switch. Defaults to chat.fluxr.chat and uses an isolated userData directory from the official build.
270 lines
8.2 KiB
Rust
270 lines
8.2 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use crate::common::{CommandSpec, run_command};
|
|
use crate::desktop::write_build_channel_file;
|
|
use crate::gateway::{GatewayStep, run_gateway_step};
|
|
use anyhow::{Context, Result};
|
|
use clap::{Args, ValueEnum};
|
|
use std::env;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Debug, Args, Clone)]
|
|
pub struct CiArgs {
|
|
#[arg(long, value_enum)]
|
|
step: CiStep,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, ValueEnum)]
|
|
#[clap(rename_all = "snake_case")]
|
|
enum CiStep {
|
|
InstallDependencies,
|
|
Typecheck,
|
|
Test,
|
|
Knip,
|
|
GatewayFmt,
|
|
GatewayCompile,
|
|
GatewayDialyzer,
|
|
GatewayEunit,
|
|
}
|
|
|
|
#[derive(Debug, Args, Clone)]
|
|
pub struct CiScriptsArgs {
|
|
#[arg(long, value_enum)]
|
|
step: CiScriptsStep,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, ValueEnum)]
|
|
#[clap(rename_all = "snake_case")]
|
|
enum CiScriptsStep {
|
|
Sync,
|
|
Test,
|
|
}
|
|
|
|
pub async fn run_ci(args: CiArgs) -> Result<()> {
|
|
let root = repo_root()?;
|
|
match args.step {
|
|
CiStep::InstallDependencies => run_command(
|
|
CommandSpec::new("pnpm")
|
|
.args(["install", "--frozen-lockfile"])
|
|
.current_dir(root),
|
|
),
|
|
CiStep::Typecheck => {
|
|
ensure_desktop_build_channel_file(&root)?;
|
|
run_generators(&root, true)?;
|
|
run_app_test_artifact_generators(&root)?;
|
|
run_command(
|
|
CommandSpec::new("pnpm")
|
|
.args(["-r", "--if-present", "typecheck"])
|
|
.current_dir(root),
|
|
)
|
|
}
|
|
CiStep::Test => {
|
|
run_generators(&root, false)?;
|
|
run_app_test_artifact_generators(&root)?;
|
|
run_workspace_tests(&root)?;
|
|
run_command(with_test_env(
|
|
CommandSpec::new("pnpm")
|
|
.args(["--filter", "fluxer_api", "test"])
|
|
.current_dir(root),
|
|
))
|
|
}
|
|
CiStep::Knip => {
|
|
run_app_test_artifact_generators(&root)?;
|
|
ensure_desktop_build_channel_file(&root)?;
|
|
run_fluxer_app_script(&root, "i18n:compile")?;
|
|
run_command(
|
|
CommandSpec::new("pnpm")
|
|
.args(["exec", "knip"])
|
|
.current_dir(root),
|
|
)
|
|
}
|
|
CiStep::GatewayFmt => {
|
|
run_gateway_step(&root.join("fluxer_gateway"), GatewayStep::FmtCheck, "test")
|
|
}
|
|
CiStep::GatewayCompile => {
|
|
run_gateway_step(&root.join("fluxer_gateway"), GatewayStep::Compile, "test")
|
|
}
|
|
CiStep::GatewayDialyzer => {
|
|
run_gateway_step(&root.join("fluxer_gateway"), GatewayStep::Dialyzer, "test")
|
|
}
|
|
CiStep::GatewayEunit => {
|
|
run_gateway_step(&root.join("fluxer_gateway"), GatewayStep::Eunit, "test")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ensure_desktop_build_channel_file(root: &Path) -> Result<()> {
|
|
let channel = env::var("BUILD_CHANNEL").unwrap_or_else(|_| "stable".to_string());
|
|
write_build_channel_file(&root.join("fluxer_desktop"), &channel)
|
|
}
|
|
|
|
fn run_app_test_artifact_generators(root: &Path) -> Result<()> {
|
|
run_fluxer_app_script(root, "wasm:codegen")?;
|
|
run_fluxer_app_script(root, "generate:masks")
|
|
}
|
|
|
|
fn run_fluxer_app_script(root: &Path, script: &str) -> Result<()> {
|
|
run_command(
|
|
CommandSpec::new("pnpm")
|
|
.args(["--filter", "fluxer_app", script])
|
|
.current_dir(root),
|
|
)
|
|
}
|
|
|
|
pub async fn run_ci_scripts(args: CiScriptsArgs) -> Result<()> {
|
|
let root = repo_root()?;
|
|
match args.step {
|
|
CiScriptsStep::Sync => run_command(
|
|
CommandSpec::new("cargo")
|
|
.args([
|
|
"fetch",
|
|
"--locked",
|
|
"--manifest-path",
|
|
"tools/ci/Cargo.toml",
|
|
])
|
|
.current_dir(root),
|
|
),
|
|
CiScriptsStep::Test => run_command(
|
|
CommandSpec::new("cargo")
|
|
.args(["test", "--locked", "--manifest-path", "tools/ci/Cargo.toml"])
|
|
.current_dir(root),
|
|
),
|
|
}
|
|
}
|
|
|
|
fn run_generators(root: &Path, for_typecheck: bool) -> Result<()> {
|
|
for command in generator_commands(for_typecheck) {
|
|
run_command(command.current_dir(root))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generator_commands(for_typecheck: bool) -> Vec<CommandSpec> {
|
|
let mut commands = vec![
|
|
CommandSpec::new("pnpm").args(["--filter", "@fluxer/config", "generate"]),
|
|
CommandSpec::new("pnpm").args(["--filter", "@fluxer/schema", "generate"]),
|
|
];
|
|
if for_typecheck {
|
|
commands.push(CommandSpec::new("pnpm").args([
|
|
"--filter",
|
|
"@fluxer/i18n",
|
|
"generate:types",
|
|
]));
|
|
}
|
|
commands.push(CommandSpec::new("pnpm").args(["--filter", "fluxer_app", "i18n:compile"]));
|
|
commands
|
|
}
|
|
|
|
fn run_workspace_tests(root: &Path) -> Result<()> {
|
|
let workspace_concurrency =
|
|
env::var("PNPM_TEST_WORKSPACE_CONCURRENCY").unwrap_or_else(|_| "2".to_string());
|
|
run_command(with_test_env(
|
|
CommandSpec::new("pnpm")
|
|
.args([
|
|
"-r",
|
|
&format!("--workspace-concurrency={workspace_concurrency}"),
|
|
"--filter",
|
|
"!fluxer_api",
|
|
"--filter",
|
|
"!fluxer",
|
|
"--if-present",
|
|
"test",
|
|
])
|
|
.current_dir(root),
|
|
))
|
|
}
|
|
|
|
fn with_test_env(spec: CommandSpec) -> CommandSpec {
|
|
let nats_url = env::var("FLUXER_NATS_URL").unwrap_or_else(|_| default_test_nats_url());
|
|
let api_workers = env::var("API_TEST_MAX_WORKERS").unwrap_or_else(|_| "2".to_string());
|
|
spec.env("FLUXER_NATS_URL", &nats_url)
|
|
.env(
|
|
"FLUXER_NATS_CORE_URL",
|
|
env::var("FLUXER_NATS_CORE_URL").unwrap_or_else(|_| nats_url.clone()),
|
|
)
|
|
.env(
|
|
"FLUXER_NATS_JETSTREAM_URL",
|
|
env::var("FLUXER_NATS_JETSTREAM_URL").unwrap_or_else(|_| nats_url.clone()),
|
|
)
|
|
.env("API_TEST_MAX_WORKERS", &api_workers)
|
|
.env(
|
|
"API_TEST_MAX_CONCURRENCY",
|
|
env::var("API_TEST_MAX_CONCURRENCY").unwrap_or(api_workers),
|
|
)
|
|
}
|
|
|
|
fn default_test_nats_url() -> String {
|
|
if Path::new("/.dockerenv").exists() {
|
|
"nats://nats:4222".to_string()
|
|
} else {
|
|
"nats://127.0.0.1:4222".to_string()
|
|
}
|
|
}
|
|
|
|
fn repo_root() -> Result<PathBuf> {
|
|
env::var("GITHUB_WORKSPACE")
|
|
.map(PathBuf::from)
|
|
.or_else(|_| env::current_dir())
|
|
.context("Failed to resolve repository root")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::ffi::OsString;
|
|
|
|
#[test]
|
|
fn generator_commands_include_i18n_types_only_for_typecheck() {
|
|
let typecheck = generator_commands(true)
|
|
.into_iter()
|
|
.map(|command| command.args)
|
|
.collect::<Vec<_>>();
|
|
let test = generator_commands(false)
|
|
.into_iter()
|
|
.map(|command| command.args)
|
|
.collect::<Vec<_>>();
|
|
|
|
assert!(typecheck.contains(&vec![
|
|
OsString::from("--filter"),
|
|
OsString::from("@fluxer/i18n"),
|
|
OsString::from("generate:types"),
|
|
]));
|
|
assert!(!test.contains(&vec![
|
|
OsString::from("--filter"),
|
|
OsString::from("@fluxer/i18n"),
|
|
OsString::from("generate:types"),
|
|
]));
|
|
}
|
|
|
|
#[test]
|
|
fn with_test_env_sets_all_nats_urls_and_concurrency() {
|
|
let spec = with_test_env(CommandSpec::new("pnpm"));
|
|
let env = spec
|
|
.env
|
|
.into_iter()
|
|
.collect::<std::collections::BTreeMap<_, _>>();
|
|
let default_nats_url = OsString::from(default_test_nats_url());
|
|
|
|
assert_eq!(
|
|
env.get(&OsString::from("FLUXER_NATS_URL")),
|
|
Some(&default_nats_url)
|
|
);
|
|
assert_eq!(
|
|
env.get(&OsString::from("FLUXER_NATS_CORE_URL")),
|
|
Some(&default_nats_url)
|
|
);
|
|
assert_eq!(
|
|
env.get(&OsString::from("FLUXER_NATS_JETSTREAM_URL")),
|
|
Some(&default_nats_url)
|
|
);
|
|
assert_eq!(
|
|
env.get(&OsString::from("API_TEST_MAX_WORKERS")),
|
|
Some(&OsString::from("2"))
|
|
);
|
|
assert_eq!(
|
|
env.get(&OsString::from("API_TEST_MAX_CONCURRENCY")),
|
|
Some(&OsString::from("2"))
|
|
);
|
|
}
|
|
}
|