Fix Flatpak updates and bump to 0.24.1 (#6649)

* fix(flatpak): allow release checks and self-update helpers

* test: keep Flatpak permission parsing within strict Clippy rules

* chore: bump version to 0.24.1 for the Flatpak update fix

* test: locate CLI binaries after Nextest archive relocation

* docs: explain the Flatpak update permission bootstrap
This commit is contained in:
Dylan M. Taylor
2026-11-11 21:30:40 -05:00
committed by GitHub
parent 588f1678e3
commit a971a14898
14 changed files with 135 additions and 86 deletions
Generated
+30 -30
View File
@@ -6313,7 +6313,7 @@ dependencies = [
[[package]]
name = "vectorcraft"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"arboard",
"block2 0.6.2",
@@ -6360,7 +6360,7 @@ dependencies = [
[[package]]
name = "vectorcraft-affinity"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"crc32fast",
"flate2",
@@ -6371,14 +6371,14 @@ dependencies = [
[[package]]
name = "vectorcraft-apischema"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"serde_json",
]
[[package]]
name = "vectorcraft-brush"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"kurbo 0.13.1",
"serde",
@@ -6390,7 +6390,7 @@ dependencies = [
[[package]]
name = "vectorcraft-cad"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -6406,7 +6406,7 @@ dependencies = [
[[package]]
name = "vectorcraft-cli"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"log",
"objc2-core-foundation",
@@ -6429,7 +6429,7 @@ dependencies = [
[[package]]
name = "vectorcraft-color"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"kurbo 0.13.1",
"moxcms",
@@ -6441,7 +6441,7 @@ dependencies = [
[[package]]
name = "vectorcraft-doc"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"chrono",
"hayro-ccitt",
@@ -6460,7 +6460,7 @@ dependencies = [
[[package]]
name = "vectorcraft-effects"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -6478,7 +6478,7 @@ dependencies = [
[[package]]
name = "vectorcraft-engine"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"cfb",
"chrono",
@@ -6531,7 +6531,7 @@ dependencies = [
[[package]]
name = "vectorcraft-eps"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"craft-pdf",
"encoding_rs",
@@ -6559,7 +6559,7 @@ dependencies = [
[[package]]
name = "vectorcraft-format"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"craft-vectorcraft-format",
"flate2",
@@ -6575,7 +6575,7 @@ dependencies = [
[[package]]
name = "vectorcraft-geom"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"kurbo 0.13.1",
"proptest",
@@ -6587,7 +6587,7 @@ dependencies = [
[[package]]
name = "vectorcraft-js"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"chrono",
"getrandom 0.4.3",
@@ -6597,7 +6597,7 @@ dependencies = [
[[package]]
name = "vectorcraft-mcp"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"image",
"log",
@@ -6616,7 +6616,7 @@ dependencies = [
[[package]]
name = "vectorcraft-metafile"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -6630,7 +6630,7 @@ dependencies = [
[[package]]
name = "vectorcraft-pathops"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"kurbo 0.13.1",
"linesweeper",
@@ -6642,7 +6642,7 @@ dependencies = [
[[package]]
name = "vectorcraft-pdf"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"aes",
"chrono",
@@ -6676,7 +6676,7 @@ dependencies = [
[[package]]
name = "vectorcraft-plugins"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"craft-vectorcraft-format",
"image",
@@ -6695,7 +6695,7 @@ dependencies = [
[[package]]
name = "vectorcraft-render"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"flate2",
"gif 0.14.2",
@@ -6725,7 +6725,7 @@ dependencies = [
[[package]]
name = "vectorcraft-scriptcheck"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"proptest",
"unicode-properties",
@@ -6733,7 +6733,7 @@ dependencies = [
[[package]]
name = "vectorcraft-svg"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"craft-docpath",
"flate2",
@@ -6760,7 +6760,7 @@ dependencies = [
[[package]]
name = "vectorcraft-testkit"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"fax",
"image",
@@ -6778,7 +6778,7 @@ dependencies = [
[[package]]
name = "vectorcraft-text"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"harfrust 0.13.3",
"icu_segmenter",
@@ -6799,7 +6799,7 @@ dependencies = [
[[package]]
name = "vectorcraft-three-d"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"vectorcraft-geom",
"vectorcraft-pathops",
@@ -6807,7 +6807,7 @@ dependencies = [
[[package]]
name = "vectorcraft-tools"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -6824,7 +6824,7 @@ dependencies = [
[[package]]
name = "vectorcraft-trace"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"image",
"kurbo 0.13.1",
@@ -6836,7 +6836,7 @@ dependencies = [
[[package]]
name = "vectorcraft-ui-egui"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"bytemuck",
"craft-keymap",
@@ -6874,7 +6874,7 @@ dependencies = [
[[package]]
name = "vectorcraft-web"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"craft-web-api",
"eframe",
@@ -8006,7 +8006,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
[[package]]
name = "xtask"
version = "0.24.0"
version = "0.24.1"
dependencies = [
"serde_json",
]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/vectorcraft", "apps/vectorcraft-cli", "xtask"]
[workspace.package]
version = "0.24.0"
version = "0.24.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
+39 -34
View File
@@ -3,8 +3,6 @@ use std::process::{Command, Stdio};
use serde_json::{Value, json};
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli");
/// Returns the path of `name` in `vectorcraft-cli-test-<pid>` in the temp folder. The first call removes
/// that folder and creates it again empty.
fn tmp(name: &str) -> std::path::PathBuf {
@@ -27,7 +25,12 @@ fn approval_param_needs_the_start_up_flag(key: &str, value: Value) {
std::fs::write(&steps, json!([{"command": "plugin.install", "params": {"dataBase64": module, key: value}}]).to_string()).unwrap();
let config = empty_dir(std::env::temp_dir().join(format!("vectorcraft-cli-test-{key}-{}", std::process::id())));
let run = |extra: &[&str]| {
Command::new(BIN).env("VECTORCRAFT_CONFIG_DIR", &config).args(["run", "--commands", steps.to_str().unwrap()]).args(extra).output().unwrap()
Command::new(crate::bin())
.env("VECTORCRAFT_CONFIG_DIR", &config)
.args(["run", "--commands", steps.to_str().unwrap()])
.args(extra)
.output()
.unwrap()
};
let out = run(&[]);
assert!(!out.status.success());
@@ -38,7 +41,7 @@ fn approval_param_needs_the_start_up_flag(key: &str, value: Value) {
let mcp_config = empty_dir(std::env::temp_dir().join(format!("vectorcraft-cli-test-mcp-{key}-{}", std::process::id())));
let mcp_install = |extra: &[&str]| -> Value {
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.env("VECTORCRAFT_CONFIG_DIR", &mcp_config)
.args(["mcp", "--headless"])
.args(extra)
@@ -77,22 +80,23 @@ fn plugin_grants_need_the_start_up_flag() {
approval_param_needs_the_start_up_flag("grant", json!(["doc.read"]));
approval_param_needs_the_start_up_flag("allowHeavy", json!(true));
let out = Command::new(BIN).args(["mcp", "--allow-plugin-grants", "--connect", "127.0.0.1:1"]).output().unwrap();
let out = Command::new(crate::bin()).args(["mcp", "--allow-plugin-grants", "--connect", "127.0.0.1:1"]).output().unwrap();
assert!(!out.status.success());
assert!(String::from_utf8_lossy(&out.stderr).contains("--allow-plugin-grants applies to the headless server"));
let root = std::env::temp_dir();
for flag in ["--automation-read-root", "--automation-write-root"] {
let out = Command::new(BIN).args(["mcp", "--allow-plugin-grants", flag, root.to_str().unwrap()]).stdin(Stdio::null()).output().unwrap();
let out =
Command::new(crate::bin()).args(["mcp", "--allow-plugin-grants", flag, root.to_str().unwrap()]).stdin(Stdio::null()).output().unwrap();
assert!(!out.status.success(), "{flag}");
assert!(String::from_utf8_lossy(&out.stderr).contains("--allow-plugin-grants can't be used with"), "{flag}");
}
let usage = Command::new(BIN).arg("help").output().unwrap();
let usage = Command::new(crate::bin()).arg("help").output().unwrap();
assert!(String::from_utf8_lossy(&usage.stdout).contains("--allow-plugin-grants lets plugin.install's grant and allowHeavy take effect"));
}
#[test]
fn commands_prints_catalogue() {
let out = Command::new(BIN).arg("commands").output().unwrap();
let out = Command::new(crate::bin()).arg("commands").output().unwrap();
assert!(out.status.success());
let v: Value = serde_json::from_slice(&out.stdout).unwrap();
let ids: Vec<&str> = v.as_array().unwrap().iter().filter_map(|c| c["id"].as_str()).collect();
@@ -104,7 +108,7 @@ fn run_batch_exports() {
let svg = tmp("batch.svg");
let png = tmp("batch.png");
let dc = tmp("batch.vectorcraft");
let out = Command::new(BIN)
let out = Command::new(crate::bin())
.args(["run", "--cmd", "file.new", "--params", r#"{"width":200,"height":100}"#])
.args(["--cmd", "shape.ellipse", "--params", r#"{"x":10,"y":10,"width":80,"height":60}"#])
.args(["--cmd", "paint.setFill", "--params", r##"{"color":"#3366ff"}"##])
@@ -121,7 +125,7 @@ fn run_batch_exports() {
assert_eq!(u32::from_be_bytes(png_bytes[16..20].try_into().unwrap()), 400);
// Re-open the native file and inspect.
let out = Command::new(BIN).args(["run", "--in", dc.to_str().unwrap(), "--cmd", "document.inspect"]).output().unwrap();
let out = Command::new(crate::bin()).args(["run", "--in", dc.to_str().unwrap(), "--cmd", "document.inspect"]).output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
let last: Value = serde_json::from_str(String::from_utf8(out.stdout).unwrap().lines().last().unwrap()).unwrap();
assert_eq!(last["result"]["artboards"][0]["width"], 200.0);
@@ -137,7 +141,7 @@ fn scale_sizes_raster_exports_only() {
let svg = svg.to_str().unwrap();
let (plain, scaled, png) = (tmp("plain.dxf"), tmp("scaled.dxf"), tmp("scaled.png"));
let ok = |args: &[&str]| {
let out = Command::new(BIN).args(args).output().unwrap();
let out = Command::new(crate::bin()).args(args).output().unwrap();
assert!(out.status.success(), "{args:?}: {}", String::from_utf8_lossy(&out.stderr));
};
ok(&["convert", svg, plain.to_str().unwrap()]);
@@ -151,11 +155,11 @@ fn scale_sizes_raster_exports_only() {
#[test]
fn run_reports_errors() {
let out = Command::new(BIN).args(["run", "--cmd", "nope.nothing"]).output().unwrap();
let out = Command::new(crate::bin()).args(["run", "--cmd", "nope.nothing"]).output().unwrap();
assert!(!out.status.success());
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert!(stderr.contains("step 0 (nope.nothing)"), "{stderr}");
let out = Command::new(BIN).args(["run", "--params", "{}"]).output().unwrap();
let out = Command::new(crate::bin()).args(["run", "--params", "{}"]).output().unwrap();
assert!(!out.status.success());
}
@@ -184,7 +188,7 @@ fn run_reads_steps_from_a_commands_file() {
{"id": "object.setProps", "params": {"id": "$2.ids.0", "name": "barcode"}},
]);
std::fs::write(&steps, serde_json::to_string_pretty(&file).unwrap()).unwrap();
let out = Command::new(BIN)
let out = Command::new(crate::bin())
.args(["run", "--cmd", "file.new", "--params", r#"{"width":200,"height":100}"#, "--commands", steps.to_str().unwrap()])
.args(["--cmd", "document.node", "--params", r#"{"id":"$2.ids.0"}"#, "--export", svg.to_str().unwrap()])
.output()
@@ -204,7 +208,7 @@ fn run_reads_steps_from_a_commands_file() {
#[test]
fn run_reads_json_lines_from_standard_input() {
let svg = tmp("stdin.svg");
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.args(["run", "--commands", "-", "--export", svg.to_str().unwrap()])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
@@ -227,7 +231,7 @@ fn run_reads_json_lines_from_standard_input() {
#[test]
fn run_refuses_malformed_commands_naming_the_step() {
let fails = |args: &[&str]| {
let out = Command::new(BIN).args(args).stdin(Stdio::null()).output().unwrap();
let out = Command::new(crate::bin()).args(args).stdin(Stdio::null()).output().unwrap();
assert!(!out.status.success(), "{args:?}");
(String::from_utf8_lossy(&out.stderr).to_string(), json_lines(&out.stdout).len())
};
@@ -275,11 +279,11 @@ fn run_refuses_malformed_commands_naming_the_step() {
/// `--help` and `-h` right after a subcommand print the usage that `vectorcraft-cli help` prints.
#[test]
fn help_after_a_subcommand_prints_the_usage() {
let usage = Command::new(BIN).arg("help").output().unwrap();
let usage = Command::new(crate::bin()).arg("help").output().unwrap();
assert!(usage.status.success() && String::from_utf8_lossy(&usage.stdout).contains("USAGE:"));
for sub in ["mcp", "run", "commands", "convert", "info", "bench", "perf"] {
for flag in ["--help", "-h"] {
let out = Command::new(BIN).args([sub, flag]).stdin(Stdio::null()).output().unwrap();
let out = Command::new(crate::bin()).args([sub, flag]).stdin(Stdio::null()).output().unwrap();
assert!(out.status.success(), "{sub} {flag}: {}", String::from_utf8_lossy(&out.stderr));
assert!(out.stdout == usage.stdout, "{sub} {flag}: {}", String::from_utf8_lossy(&out.stdout));
}
@@ -290,7 +294,7 @@ fn help_after_a_subcommand_prints_the_usage() {
/// with them.
#[test]
fn help_later_in_the_line_prints_the_usage() {
let usage = Command::new(BIN).arg("help").output().unwrap();
let usage = Command::new(crate::bin()).arg("help").output().unwrap();
let mut wrong = vec![];
for args in [
&["mcp", "--headless", "--help"][..],
@@ -311,7 +315,7 @@ fn help_later_in_the_line_prints_the_usage() {
&["perf", "--paths", "many", "-h"],
&["convert", "--decimals", "many", "--help"],
] {
let out = Command::new(BIN).args(args).stdin(Stdio::null()).output().unwrap();
let out = Command::new(crate::bin()).args(args).stdin(Stdio::null()).output().unwrap();
if !out.status.success() || out.stdout != usage.stdout {
wrong.push(format!("{args:?}: {}{}", String::from_utf8_lossy(&out.stderr), String::from_utf8_lossy(&out.stdout)));
}
@@ -322,17 +326,18 @@ fn help_later_in_the_line_prints_the_usage() {
/// Further along the line, `-h` is the value of the option before it.
#[test]
fn help_as_an_option_value_goes_to_the_option() {
let out = Command::new(BIN).args(["run", "--cmd", "-h"]).output().unwrap();
let out = Command::new(crate::bin()).args(["run", "--cmd", "-h"]).output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success() && err.contains("vectorcraft-cli: step 0 (-h):") && out.stdout.is_empty(), "{err}");
let out = Command::new(BIN).args(["convert", "in.svg", "--range", "-h"]).output().unwrap();
let out = Command::new(crate::bin()).args(["convert", "in.svg", "--range", "-h"]).output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success() && err.contains("convert needs IN and OUT") && out.stdout.is_empty(), "{err}");
}
#[test]
fn mcp_headless_over_stdio() {
let mut child = Command::new(BIN).args(["mcp", "--headless"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap();
let mut child =
Command::new(crate::bin()).args(["mcp", "--headless"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap();
{
let mut stdin = child.stdin.take().unwrap();
for m in [
@@ -359,13 +364,13 @@ fn mcp_headless_over_stdio() {
/// plug-ins.
#[test]
fn safe_mode_mcp_does_not_connect_to_a_running_app() {
let out = Command::new(BIN).args(["--safe-mode", "mcp", "--connect", "127.0.0.1:9"]).env("VECTORCRAFT_NO_PREFS", "1").output().unwrap();
let out = Command::new(crate::bin()).args(["--safe-mode", "mcp", "--connect", "127.0.0.1:9"]).env("VECTORCRAFT_NO_PREFS", "1").output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success() && err.contains("--safe-mode runs a headless session"), "{err}");
// Something listening on the default address, as a running app does; a running app or another
// program already holding it serves as well.
let _app = std::net::TcpListener::bind(vectorcraft_mcp::DEFAULT_ADDR);
let out = Command::new(BIN).args(["--safe-mode", "mcp"]).env("VECTORCRAFT_NO_PREFS", "1").stdin(Stdio::null()).output().unwrap();
let out = Command::new(crate::bin()).args(["--safe-mode", "mcp"]).env("VECTORCRAFT_NO_PREFS", "1").stdin(Stdio::null()).output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success() && err.contains("MCP server on stdio (headless)"), "{err}");
}
@@ -402,7 +407,7 @@ fn mcp_automation_roots_take_several_folders() {
}
let dir = |d: &str| base.join(d).to_string_lossy().into_owned();
let file = |d: &str, f: &str| base.join(d).join(f).to_string_lossy().into_owned();
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.args(["mcp", "--automation-read-root", &dir("in"), &format!("--automation-read-root={}", dir("in2"))])
.args(["--automation-write-root", &dir("out"), "--automation-write-root", &dir("out2")])
.stdin(Stdio::piped())
@@ -457,7 +462,7 @@ fn automation_roots(base: &std::path::Path) {
}
let dir = |d: &str| base.join(d).to_string_lossy().into_owned();
let fails = |args: &[&str], why: &str| {
let out = Command::new(BIN).args(args).stdin(Stdio::null()).output().unwrap();
let out = Command::new(crate::bin()).args(args).stdin(Stdio::null()).output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(!out.status.success() && err.contains(why), "{args:?}: {err}");
};
@@ -469,7 +474,7 @@ fn automation_roots(base: &std::path::Path) {
"a running app enforces its own roots: start it with `vectorcraft --control <port>",
);
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.args(["mcp", "--automation-read-root", &dir("in"), &format!("--automation-write-root={}", dir("out"))])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
@@ -530,7 +535,7 @@ fn automation_roots(base: &std::path::Path) {
fn mcp_protocol_over_stdio() {
use std::io::{BufRead, BufReader};
let mut child =
Command::new(BIN).args(["mcp", "--headless"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
Command::new(crate::bin()).args(["mcp", "--headless"]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
let mut stdin = child.stdin.take().unwrap();
let mut lines = BufReader::new(child.stdout.take().unwrap()).lines();
let mut notes = vec![];
@@ -711,7 +716,7 @@ fn run_command(id: u64, command: &str, params: Value) -> Value {
#[test]
fn closed_stdout_ends_quietly() {
use std::io::Read;
let mut child = Command::new(BIN).arg("commands").stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
let mut child = Command::new(crate::bin()).arg("commands").stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
let mut first = [0u8; 16];
child.stdout.as_mut().unwrap().read_exact(&mut first).unwrap();
drop(child.stdout.take());
@@ -728,13 +733,13 @@ fn run_with_a_closed_stdout_ends_quietly_and_finishes_the_batch() {
let _ = std::fs::remove_file(&svg);
let rect = r#"{"x":1,"y":1,"width":5,"height":5}"#;
let args = ["run", "--cmd", "document.json", "--cmd", "shape.rectangle", "--params", rect, "--export", svg.to_str().unwrap()];
let mut child = Command::new(BIN).args(args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
let mut child = Command::new(crate::bin()).args(args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
drop(child.stdout.take());
let out = child.wait_with_output().unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success() && err.is_empty(), "{:?}: {err}", out.status);
assert!(svg.is_file(), "the export after the closed reader still ran");
let out = Command::new(BIN).args(["run", "--cmd", "not-a-command"]).stdout(Stdio::null()).output().unwrap();
let out = Command::new(crate::bin()).args(["run", "--cmd", "not-a-command"]).stdout(Stdio::null()).output().unwrap();
assert!(!out.status.success());
}
@@ -749,7 +754,7 @@ fn config_dir(name: &str) -> std::path::PathBuf {
/// One `vectorcraft-cli run` with `steps` ((command, params)) against the settings folder `config`;
/// the result of each step.
fn run_in(config: &std::path::Path, steps: &[(&str, Value)]) -> Vec<Value> {
let mut cmd = Command::new(BIN);
let mut cmd = Command::new(crate::bin());
cmd.arg("run").env("VECTORCRAFT_CONFIG_DIR", config).env_remove("VECTORCRAFT_NO_PREFS");
for (id, params) in steps {
cmd.args(["--cmd", id, "--params", &params.to_string()]);
@@ -820,7 +825,7 @@ fn saved_pdf_presets_and_loaded_profiles_survive_a_restart() {
#[test]
fn runs_without_preferences_remember_nothing() {
let config = config_dir("noprefs");
let out = Command::new(BIN)
let out = Command::new(crate::bin())
.args(["run", "--cmd", "pdf.preset.save", "--params", r#"{"name":"Press"}"#])
.env("VECTORCRAFT_CONFIG_DIR", &config)
.env("VECTORCRAFT_NO_PREFS", "1")
+4 -6
View File
@@ -4,8 +4,6 @@ use std::process::Command;
use serde_json::Value;
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli");
/// Returns the path of `name` in `vectorcraft-cli-fileio-<pid>` in the temp folder. The first call
/// removes that folder and creates it again empty.
fn tmp(name: &str) -> String {
@@ -20,7 +18,7 @@ fn tmp(name: &str) -> String {
}
fn ok(args: &[&str]) -> String {
let out = Command::new(BIN).args(args).output().unwrap();
let out = Command::new(crate::bin()).args(args).output().unwrap();
assert!(out.status.success(), "{args:?}: {}", String::from_utf8_lossy(&out.stderr));
String::from_utf8(out.stdout).unwrap()
}
@@ -130,7 +128,7 @@ fn raster_exports_use_document_resolution_unless_overridden() {
fn raster_export_rejects_invalid_ppi() {
for ppi in ["0", "-150", "NaN", "inf", "high"] {
for args in [vec!["run", "--export", "unused.png", "--ppi", ppi], vec!["convert", "unused.svg", "unused.png", "--ppi", ppi]] {
let out = Command::new(BIN).args(&args).output().unwrap();
let out = Command::new(crate::bin()).args(&args).output().unwrap();
assert!(!out.status.success(), "{args:?}");
assert!(String::from_utf8_lossy(&out.stderr).contains("positive finite number"), "{args:?}: {out:?}");
}
@@ -228,7 +226,7 @@ fn convert_writes_legacy_ai_with_its_options() {
assert!(r["warnings"].to_string().contains("hidden objects are left out"), "{r}");
assert!(std::fs::read(&one).unwrap().starts_with(b"%!PS-Adobe-3.0"));
// A value the engine doesn't take is an error.
let bad = Command::new(BIN).args(["convert", &src, &tmp("bad.ai"), "--ai-version", "7", "--hidden-objects", "behind"]).output().unwrap();
let bad = Command::new(crate::bin()).args(["convert", &src, &tmp("bad.ai"), "--ai-version", "7", "--hidden-objects", "behind"]).output().unwrap();
assert!(!bad.status.success());
assert!(String::from_utf8_lossy(&bad.stderr).contains("hiddenObjects"), "{}", String::from_utf8_lossy(&bad.stderr));
}
@@ -253,7 +251,7 @@ fn convert_and_run_write_ait_templates() {
assert_eq!(info(ait)["artboards"].as_array().unwrap().len(), 2, "{ait}");
}
// A template holds every artboard: one can't be picked.
let one = Command::new(BIN).args(["convert", &pdf, &tmp("one.ait"), "--artboard", "0"]).output().unwrap();
let one = Command::new(crate::bin()).args(["convert", &pdf, &tmp("one.ait"), "--artboard", "0"]).output().unwrap();
assert!(!one.status.success());
assert!(String::from_utf8_lossy(&one.stderr).contains("every artboard"), "{}", String::from_utf8_lossy(&one.stderr));
}
+8
View File
@@ -6,3 +6,11 @@ mod cli;
mod fileio;
mod mcp_e2e;
mod script;
/// Nextest relocates archived binaries; Cargo's compile-time path still names the build runner.
fn bin() -> std::path::PathBuf {
std::env::var_os("NEXTEST_BIN_EXE_vectorcraft_cli")
.or_else(|| std::env::var_os("CARGO_BIN_EXE_vectorcraft-cli"))
.map(std::path::PathBuf::from)
.unwrap_or_else(|| env!("CARGO_BIN_EXE_vectorcraft-cli").into())
}
+1 -3
View File
@@ -6,8 +6,6 @@ use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
use serde_json::{Value, json};
use vectorcraft_testkit::raster::Image;
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli");
struct Mcp {
child: Child,
stdin: Option<ChildStdin>,
@@ -17,7 +15,7 @@ struct Mcp {
impl Mcp {
fn spawn() -> Self {
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.args(["mcp", "--headless"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
+1 -3
View File
@@ -2,11 +2,9 @@ use std::process::Command;
use serde_json::{Value, json};
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft-cli");
/// `run` with `script.run` after `file.new`, with `params`.
fn run_script(params: &Value) -> std::process::Output {
Command::new(BIN).args(["run", "--cmd", "file.new", "--cmd", "script.run", "--params", &params.to_string()]).output().unwrap()
Command::new(crate::bin()).args(["run", "--cmd", "file.new", "--cmd", "script.run", "--params", &params.to_string()]).output().unwrap()
}
/// `run` runs a script headless: the reply holds the report, with what the script logged and the
+5 -6
View File
@@ -1,8 +1,6 @@
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft");
/// Invalid roots must exit before desktop initialization on every platform, including a CI
/// machine with no display. A regression must fail rather than hang with a window open.
fn rejects(args: &[&str], message: &str) {
@@ -10,7 +8,7 @@ fn rejects(args: &[&str], message: &str) {
}
fn rejects_with_env(args: &[&str], env: &[(&str, &str)], message: &str) {
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.args(args)
.env_remove("VECTORCRAFT_CONTROL_PORT")
.env_remove("VECTORCRAFT_AUTOMATION_READ_ROOT")
@@ -62,7 +60,7 @@ fn missing_empty_or_option_operands_fail_before_startup() {
#[test]
fn repeated_roots_are_accepted_for_both_spellings() {
for flag in ["--automation-read-root", "--automation-write-root"] {
let out = Command::new(BIN).args([flag, ".", format!("{flag}=.").as_str(), flag, "./", "--version"]).output().unwrap();
let out = Command::new(crate::bin()).args([flag, ".", format!("{flag}=.").as_str(), flag, "./", "--version"]).output().unwrap();
assert!(out.status.success(), "{flag}: {}", String::from_utf8_lossy(&out.stderr));
assert_eq!(String::from_utf8(out.stdout).unwrap(), format!("vectorcraft {}\n", env!("CARGO_PKG_VERSION")));
assert!(out.stderr.is_empty());
@@ -73,7 +71,8 @@ fn repeated_roots_are_accepted_for_both_spellings() {
#[test]
fn explicit_dash_folder_does_not_consume_version() {
let out = Command::new(BIN).args(["--automation-read-root=-folder", "--automation-write-root", "./-folder", "--version"]).output().unwrap();
let out =
Command::new(crate::bin()).args(["--automation-read-root=-folder", "--automation-write-root", "./-folder", "--version"]).output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
assert_eq!(String::from_utf8(out.stdout).unwrap(), format!("vectorcraft {}\n", env!("CARGO_PKG_VERSION")));
assert!(out.stderr.is_empty());
@@ -95,7 +94,7 @@ fn control_without_a_valid_port_fails_before_startup() {
#[test]
fn version_wins_over_a_bad_control_port_variable() {
let out = Command::new(BIN).arg("--version").env("VECTORCRAFT_CONTROL_PORT", "abc").output().unwrap();
let out = Command::new(crate::bin()).arg("--version").env("VECTORCRAFT_CONTROL_PORT", "abc").output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
assert_eq!(String::from_utf8(out.stdout).unwrap(), format!("vectorcraft {}\n", env!("CARGO_PKG_VERSION")));
}
+1 -3
View File
@@ -6,12 +6,10 @@ use std::process::{Command, Output, Stdio};
use std::sync::mpsc;
use std::time::{Duration, Instant};
const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft");
/// Runs the desktop binary without a display and with a control port it cannot parse, so a
/// regression exits with that error instead of opening a window.
fn run(args: &[&str], env: &[(&str, &str)]) -> Output {
let mut child = Command::new(BIN)
let mut child = Command::new(crate::bin())
.args(args)
.env_remove("DISPLAY")
.env_remove("WAYLAND_DISPLAY")
+8
View File
@@ -4,3 +4,11 @@
mod automation_args;
mod help;
/// Nextest relocates archived binaries; Cargo's compile-time path still names the build runner.
fn bin() -> std::path::PathBuf {
std::env::var_os("NEXTEST_BIN_EXE_vectorcraft")
.or_else(|| std::env::var_os("CARGO_BIN_EXE_vectorcraft"))
.map(std::path::PathBuf::from)
.unwrap_or_else(|| env!("CARGO_BIN_EXE_vectorcraft").into())
}
+27
View File
@@ -0,0 +1,27 @@
//! Release and source Flatpaks must allow release checks and host update helpers.
fn finish_args(manifest: &str) -> Vec<&str> {
manifest
.split("finish-args:")
.nth(1)
.unwrap_or_default()
.lines()
.take_while(|line| line.is_empty() || line.starts_with(' ') || line.starts_with('#'))
.filter_map(|line| line.trim().strip_prefix("- "))
.map(|arg| arg.trim_matches(['\'', '"']))
.collect()
}
#[test]
fn flatpak_update_permissions() {
let manifests = [
include_str!("../../../packaging/linux/flatpak/ai.storyteller.vectorcraft.bundle.yml"),
include_str!("../../../packaging/linux/flatpak/ai.storyteller.vectorcraft.yml"),
];
let args = manifests.map(finish_args);
assert_eq!(args[0], args[1], "source and release permissions must agree");
for permissions in args {
for required in ["--share=network", "--talk-name=org.freedesktop.Flatpak"] {
assert!(permissions.contains(&required), "missing update permission: {required}");
}
}
}
+6
View File
@@ -42,6 +42,12 @@ pick that branch for a full run. Run on any other branch, it is a dry run: the L
FreeBSD and web jobs build and upload their artifacts, the environment refuses the signing jobs,
and no draft release is created.
Flatpak permission repairs need one update through the existing host-side installation method.
After that update, Help > Check for Updates can inspect and update standalone bundles.
Repository-managed installations continue to use their package manager; launcher-pinned
versions keep their selected version. Both Flatpak manifests must grant network access,
the host Flatpak interface, and read-only access to the shared version choices.
## What gets built
| Platform | Artifacts | Built on |
@@ -17,6 +17,8 @@ runtime-version: '25.08'
sdk: org.freedesktop.Sdk
command: vectorcraft
finish-args:
# The shared updater inspects and updates this installation via flatpak-spawn --host.
- --talk-name=org.freedesktop.Flatpak
- --filesystem=xdg-data/ArtCraft/versions:ro
# X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails.
- --share=ipc
@@ -17,6 +17,8 @@ sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: vectorcraft
finish-args:
# The shared updater inspects and updates this installation via flatpak-spawn --host.
- --talk-name=org.freedesktop.Flatpak
- --filesystem=xdg-data/ArtCraft/versions:ro
# Keep identical to ai.storyteller.vectorcraft.bundle.yml (the release bundle); comments there.
- --share=ipc