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
8 changes: 8 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion crates/tools/fission-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ serde_json = "1.0"
sha2 = "0.10"
tar = "0.4"
toml = "0.8"
toml_edit = "0.23"
fission = { path = "../../authoring/fission", version = "0.1.1", default-features = false, features = ["terminal-shell"] }
fission-shell-site = { path = "../../shell/fission-shell-site", version = "0.1.1" }
base64 = "0.22"
chacha20poly1305 = "0.10"
getrandom = { version = "0.2", features = ["std"] }
keyring = { version = "3", features = ["apple-native", "windows-native", "linux-native-sync-persistent", "crypto-rust"] }
keyring = { version = "3", default-features = false, features = ["apple-native", "windows-native", "linux-native", "crypto-rust"] }
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"] }
aws-config = "1"
aws-sdk-s3 = "1"
Expand Down
67 changes: 62 additions & 5 deletions crates/tools/fission-cli/src/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ use std::io::{self, IsTerminal, Read};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use toml_edit::{
Array as TomlEditArray, DocumentMut, Item as TomlEditItem, Table as TomlEditTable,
Value as TomlEditValue,
};

mod content;
mod microsoft_store_ops;
Expand Down Expand Up @@ -780,11 +784,9 @@ fn set_release_field(project_dir: &Path, field: &str, value: &str, yes: bool) ->
let path = project_dir.join("fission.toml");
let data =
fs::read_to_string(&path).with_context(|| format!("failed to read {}", path.display()))?;
let mut doc: toml::Value =
toml::from_str(&data).with_context(|| format!("failed to parse {}", path.display()))?;
set_toml_path(&mut doc, field, toml::Value::String(value.to_string()))?;
fs::write(&path, toml::to_string_pretty(&doc)? + "\n")
.with_context(|| format!("failed to write {}", path.display()))?;
let mut doc = parse_toml_edit_document(&data, &path)?;
set_toml_edit_path(&mut doc, field, toml_edit::value(value.to_string()))?;
write_toml_edit_document(&path, &doc)?;
Ok(())
}

Expand Down Expand Up @@ -812,6 +814,41 @@ fn add_release(
Ok(())
}

fn parse_toml_edit_document(text: &str, path: &Path) -> Result<DocumentMut> {
text.parse::<DocumentMut>()
.with_context(|| format!("failed to parse {}", path.display()))
}

fn write_toml_edit_document(path: &Path, doc: &DocumentMut) -> Result<()> {
fs::write(path, format!("{doc}\n"))
.with_context(|| format!("failed to write {}", path.display()))
}

fn set_toml_edit_path(root: &mut DocumentMut, path: &str, value: TomlEditItem) -> Result<()> {
let parts = path.split('.').collect::<Vec<_>>();
if parts.is_empty() || parts.iter().any(|part| part.trim().is_empty()) {
bail!("field path must be dot-separated and non-empty");
}
let mut current = root.as_table_mut();
for part in &parts[..parts.len() - 1] {
current = current
.entry(part)
.or_insert(TomlEditItem::Table(TomlEditTable::new()))
.as_table_mut()
.context("field path traversed through a non-table value")?;
}
current[parts[parts.len() - 1]] = value;
Ok(())
}

fn toml_edit_string_array(values: impl IntoIterator<Item = String>) -> TomlEditItem {
let mut array = TomlEditArray::default();
for value in values {
array.push(value);
}
TomlEditItem::Value(TomlEditValue::Array(array))
}

fn edit_release_file(
project_dir: &Path,
release: &str,
Expand Down Expand Up @@ -1282,6 +1319,7 @@ fn print_report(mut report: LifecycleReport, json: bool) -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use std::fs;

#[test]
fn auth_setup_documents_provider_credentials_without_secrets() {
Expand All @@ -1302,4 +1340,23 @@ mod tests {
.is_some_and(|details| details.contains("Pages"))
}));
}

#[test]
fn release_config_set_preserves_existing_comments_and_formatting() {
let dir =
std::env::temp_dir().join(format!("fission-release-config-set-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
let path = dir.join("fission.toml");
fs::write(&path, "# keep this comment\n[app]\nname = \"Todo\"\n").unwrap();

set_release_field(&dir, "app.version", "1.2.3", true).unwrap();

let text = fs::read_to_string(&path).unwrap();
assert!(text.contains("# keep this comment"));
assert!(text.contains("version = \"1.2.3\""));
assert!(text.contains("name = \"Todo\""));

let _ = fs::remove_dir_all(&dir);
}
}
16 changes: 8 additions & 8 deletions crates/tools/fission-cli/src/release/microsoft_store_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,40 +435,40 @@ fn write_imported_microsoft_listings(
.as_deref()
.context("active release metadata path is required for Microsoft Store metadata import")?;
let toml_path = project_dir.join("fission.toml");
let mut fission_doc: toml::Value = toml::from_str(&fs::read_to_string(&toml_path)?)?;
let mut fission_doc = parse_toml_edit_document(&fs::read_to_string(&toml_path)?, &toml_path)?;
for listing in remote {
if let Some(title) = listing.title.clone() {
set_toml_path(
set_toml_edit_path(
&mut fission_doc,
&format!(
"release.store_listing.microsoft_store.{}.title",
listing.language
),
toml::Value::String(title),
toml_edit::value(title),
)?;
}
if let Some(short_description) = listing.short_description.clone() {
set_toml_path(
set_toml_edit_path(
&mut fission_doc,
&format!(
"release.store_listing.microsoft_store.{}.short_description",
listing.language
),
toml::Value::String(short_description),
toml_edit::value(short_description),
)?;
}
if let Some(privacy_url) = listing.privacy_url.clone() {
set_toml_path(
set_toml_edit_path(
&mut fission_doc,
&format!(
"release.store_listing.microsoft_store.{}.privacy_url",
listing.language
),
toml::Value::String(privacy_url),
toml_edit::value(privacy_url),
)?;
}
}
fs::write(&toml_path, toml::to_string_pretty(&fission_doc)? + "\n")?;
write_toml_edit_document(&toml_path, &fission_doc)?;

let metadata_abs = project_dir.join(metadata_path);
let mut metadata_doc: toml::Value = if metadata_abs.exists() {
Expand Down
17 changes: 8 additions & 9 deletions crates/tools/fission-cli/src/release/signing_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,22 @@ fn import_android(
let relative = project_relative_or_absolute(project_dir, &keystore);
let path = project_dir.join("fission.toml");
let data = fs::read_to_string(&path).unwrap_or_default();
let mut root: toml::Value = if data.trim().is_empty() {
toml::Value::Table(Default::default())
let mut root = if data.trim().is_empty() {
toml_edit::DocumentMut::new()
} else {
toml::from_str(&data).with_context(|| format!("failed to parse {}", path.display()))?
parse_toml_edit_document(&data, &path)?
};
set_toml_path(
set_toml_edit_path(
&mut root,
"package.android.keystore",
toml::Value::String(relative.clone()),
toml_edit::value(relative.clone()),
)?;
set_toml_path(
set_toml_edit_path(
&mut root,
"package.android.keystore_alias",
toml::Value::String(alias.clone()),
toml_edit::value(alias.clone()),
)?;
fs::write(&path, toml::to_string_pretty(&root)? + "\n")
.with_context(|| format!("failed to write {}", path.display()))?;
write_toml_edit_document(&path, &root)?;
report.checks.push(ok_check(
"signing.android.config_written",
format!("package.android.keystore = {relative}, alias = {alias}"),
Expand Down
39 changes: 19 additions & 20 deletions crates/tools/fission-cli/src/release/store_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1557,7 +1557,8 @@ fn write_imported_app_store_localizations(
} else {
toml::Value::Table(Default::default())
};
let mut fission_doc: toml::Value = toml::from_str(&fs::read_to_string(&fission_path)?)?;
let mut fission_doc =
parse_toml_edit_document(&fs::read_to_string(&fission_path)?, &fission_path)?;
for item in remote {
if selected.is_some_and(|selected| !selected.contains(&item.locale)) {
continue;
Expand All @@ -1575,34 +1576,34 @@ fn write_imported_app_store_localizations(
)?;
}
if let Some(value) = &item.support_url {
set_toml_path(
set_toml_edit_path(
&mut fission_doc,
&format!(
"release.store_listing.app_store.{}.support_url",
item.locale
),
toml::Value::String(value.clone()),
toml_edit::value(value.clone()),
)?;
}
if let Some(value) = &item.marketing_url {
set_toml_path(
set_toml_edit_path(
&mut fission_doc,
&format!(
"release.store_listing.app_store.{}.marketing_url",
item.locale
),
toml::Value::String(value.clone()),
toml_edit::value(value.clone()),
)?;
}
if let Some(value) = &item.keywords {
set_toml_path(
set_toml_edit_path(
&mut fission_doc,
&format!("release.store_listing.app_store.{}.keywords", item.locale),
toml::Value::Array(
toml_edit_string_array(
value
.split(',')
.map(|item| toml::Value::String(item.trim().to_string()))
.collect(),
.map(|item| item.trim().to_string())
.collect::<Vec<_>>(),
),
)?;
}
Expand All @@ -1614,7 +1615,7 @@ fn write_imported_app_store_localizations(
&metadata_path,
toml::to_string_pretty(&metadata_doc)? + "\n",
)?;
fs::write(&fission_path, toml::to_string_pretty(&fission_doc)? + "\n")?;
write_toml_edit_document(&fission_path, &fission_doc)?;
Ok(())
}

Expand Down Expand Up @@ -1830,32 +1831,30 @@ fn write_imported_play_listings(
let fission_path = project_dir.join("fission.toml");
let data = fs::read_to_string(&fission_path)
.with_context(|| format!("failed to read {}", fission_path.display()))?;
let mut doc: toml::Value = toml::from_str(&data)
.with_context(|| format!("failed to parse {}", fission_path.display()))?;
let mut doc = parse_toml_edit_document(&data, &fission_path)?;
for listing in listings {
set_toml_path(
set_toml_edit_path(
&mut doc,
&format!("release.store_listing.play_store.{}.title", listing.locale),
toml::Value::String(listing.title.clone()),
toml_edit::value(listing.title.clone()),
)?;
set_toml_path(
set_toml_edit_path(
&mut doc,
&format!(
"release.store_listing.play_store.{}.short_description",
listing.locale
),
toml::Value::String(listing.short_description.clone()),
toml_edit::value(listing.short_description.clone()),
)?;
if let Some(video) = &listing.video {
set_toml_path(
set_toml_edit_path(
&mut doc,
&format!("release.store_listing.play_store.{}.video", listing.locale),
toml::Value::String(video.clone()),
toml_edit::value(video.clone()),
)?;
}
}
fs::write(&fission_path, toml::to_string_pretty(&doc)? + "\n")
.with_context(|| format!("failed to write {}", fission_path.display()))?;
write_toml_edit_document(&fission_path, &doc)?;

let metadata_path = active_release(root)
.and_then(|release| release.metadata.as_deref())
Expand Down
Loading