Compare commits
4
Commits
v1.0.0
...
584ab34df6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
584ab34df6 | ||
|
|
0514c1d982 | ||
|
|
2f834a0bcc | ||
|
|
49cd5cb9a0 |
Generated
+480
-648
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -4,7 +4,7 @@ members = ["crates/*", "desktop", "web"]
|
||||
default-members = ["desktop"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.0.0-beta5"
|
||||
version = "1.0.1"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["crates/*", "desktop", "web"]
|
||||
default-members = ["desktop"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.0.0-beta5"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[workspace.dependencies]
|
||||
# GPUI
|
||||
gpui = { git = "https://github.com/zed-industries/zed" }
|
||||
gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland"] }
|
||||
gpui_linux = { git = "https://github.com/zed-industries/zed" }
|
||||
gpui_windows = { git = "https://github.com/zed-industries/zed" }
|
||||
gpui_macos = { git = "https://github.com/zed-industries/zed" }
|
||||
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
|
||||
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
||||
|
||||
# Nostr
|
||||
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
|
||||
nostr-memory = { git = "https://github.com/rust-nostr/nostr" }
|
||||
nostr-blossom = { git = "https://github.com/rust-nostr/nostr" }
|
||||
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
|
||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
||||
nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44" ] }
|
||||
|
||||
# Others
|
||||
anyhow = "1.0.44"
|
||||
chrono = { version = "0.4.38", features = ["wasmbind"] }
|
||||
futures = "0.3"
|
||||
itertools = "0.13.0"
|
||||
log = "0.4"
|
||||
oneshot = "0.1.10"
|
||||
flume = { version = "0.11.1", default-features = false, features = ["async", "select"] }
|
||||
rust-embed = { version = "8.5", features = ["include-exclude"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
schemars = "1"
|
||||
smallvec = "1.14.0"
|
||||
smol = "2"
|
||||
webbrowser = "1.0.4"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
||||
errno = { version = "0.3.14", default-features = false }
|
||||
instant = "0.1"
|
||||
|
||||
[patch.crates-io]
|
||||
# Use stacker's psm version which may have better WASM support
|
||||
psm = { git = "https://github.com/rust-lang/stacker", branch = "master" }
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.profiling]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
@@ -5,6 +5,7 @@ use gpui_updater::{EngineConfig, GitHubSource, UpdateStatus, Updater, Version};
|
||||
use instant::{Duration, Instant};
|
||||
|
||||
const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION";
|
||||
const COOP_BUNDLE_TYPE: &str = "COOP_BUNDLE_TYPE";
|
||||
|
||||
fn get_github_repo_owner() -> String {
|
||||
std::env::var("COOP_GITHUB_REPO_OWNER").unwrap_or_else(|_| "reyakov".to_string())
|
||||
@@ -14,18 +15,27 @@ fn get_github_repo_name() -> String {
|
||||
std::env::var("COOP_GITHUB_REPO_NAME").unwrap_or_else(|_| "coop".to_string())
|
||||
}
|
||||
|
||||
fn is_flatpak_installation() -> bool {
|
||||
std::env::var("FLATPAK_ID").is_ok() || std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
||||
/// Whether updates are managed by an external distribution channel
|
||||
/// (Flatpak/Snap), in which case the in-app updater must not run.
|
||||
fn uses_managed_updates() -> bool {
|
||||
// The Flatpak runtime exports `FLATPAK_ID` inside the sandbox.
|
||||
std::env::var("FLATPAK_ID").is_ok()
|
||||
// Allow opting out of in-app updates via an explicit environment variable.
|
||||
|| std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
||||
// The Snap package sets `COOP_BUNDLE_TYPE=snap` (see snapcraft.yaml.in).
|
||||
|| std::env::var(COOP_BUNDLE_TYPE).is_ok_and(|value| value == "snap")
|
||||
}
|
||||
|
||||
/// Initialize the auto-update system.
|
||||
///
|
||||
/// Skips initialization when running as a Flatpak (updates are handled by the
|
||||
/// Flatpak distribution channel). Otherwise creates the global [`AutoUpdater`]
|
||||
/// Skips initialization when updates are handled by an external distribution
|
||||
/// channel (Flatpak/Snap). Otherwise creates the global [`AutoUpdater`]
|
||||
/// entity and schedules a check for updates after a 2-minute delay.
|
||||
pub fn init(window: &mut Window, cx: &mut App) {
|
||||
if is_flatpak_installation() {
|
||||
log::info!("Skipping auto-update initialization: App is installed via Flatpak");
|
||||
if uses_managed_updates() {
|
||||
log::info!(
|
||||
"Skipping auto-update initialization: App is installed via a managed distribution channel (Flatpak/Snap)"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,7 +67,27 @@ pub struct AutoUpdater {
|
||||
}
|
||||
|
||||
impl AutoUpdater {
|
||||
/// Whether auto-update is available for this installation.
|
||||
///
|
||||
/// Returns `false` on managed distribution channels (Flatpak/Snap), where
|
||||
/// updates are handled by the channel and no global updater is created.
|
||||
pub fn is_available(cx: &App) -> bool {
|
||||
cx.try_global::<GlobalAutoUpdater>().is_some()
|
||||
}
|
||||
|
||||
/// Retrieve the global auto updater instance, if one was initialized.
|
||||
pub fn try_global(cx: &App) -> Option<Entity<Self>> {
|
||||
cx.try_global::<GlobalAutoUpdater>()
|
||||
.map(|global| global.0.clone())
|
||||
}
|
||||
|
||||
/// Retrieve the global auto updater instance.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when auto-update is not available for this installation. Prefer
|
||||
/// [`AutoUpdater::try_global`] when the installation type is not known at
|
||||
/// compile time (e.g. Flatpak/Snap).
|
||||
pub fn global(cx: &App) -> Entity<Self> {
|
||||
cx.global::<GlobalAutoUpdater>().0.clone()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ publish = false
|
||||
atomic-destructor = "0.2"
|
||||
event-listener = "5"
|
||||
nostr.workspace = true
|
||||
opaquerr = "0.1"
|
||||
opaquerr = { version = "0.1", features = ["alloc"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
+19
-6
@@ -365,7 +365,8 @@ impl ChatRegistry {
|
||||
.query(filter)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.first_owned()
|
||||
.into_iter()
|
||||
.next()
|
||||
.is_some();
|
||||
|
||||
if !found {
|
||||
@@ -397,7 +398,8 @@ impl ChatRegistry {
|
||||
.database()
|
||||
.query(filter)
|
||||
.await?
|
||||
.first_owned()
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(anyhow::anyhow!("No inbox relays found"))?;
|
||||
|
||||
let relays: Vec<RelayUrl> = nip17::extract_relay_list(&event).collect();
|
||||
@@ -656,15 +658,26 @@ impl ChatRegistry {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let public_key = signer.get_public_key_async().await?;
|
||||
let contacts = client
|
||||
|
||||
// Query the latest contact list (previously `NostrDatabaseExt::contacts_public_keys`)
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::ContactList)
|
||||
.limit(1);
|
||||
|
||||
let contacts: HashSet<PublicKey> = client
|
||||
.database()
|
||||
.contacts_public_keys(public_key)
|
||||
.query(filter)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|event| event.tags.public_keys().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.custom_tag(SingleLetterTag::lowercase(Alphabet::K), "14");
|
||||
.custom_tag(SingleLetterTag::LOWERCASE_K, "14");
|
||||
|
||||
let events = client.database().query(filter).await?;
|
||||
let mut grouped: HashMap<u64, Vec<UnsignedEvent>> = HashMap::new();
|
||||
@@ -827,7 +840,7 @@ async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Resul
|
||||
async fn get_rumor(client: &Client, gift_wrap: EventId) -> Result<UnsignedEvent, Error> {
|
||||
let filter = Filter::new().identifier(gift_wrap).limit(1);
|
||||
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
if let Some(event) = client.database().query(filter).await?.into_iter().next() {
|
||||
UnsignedEvent::from_json(event.content).map_err(|e| anyhow!(e))
|
||||
} else {
|
||||
Err(anyhow!("Event is not cached yet."))
|
||||
|
||||
@@ -403,7 +403,7 @@ impl Room {
|
||||
cx.background_spawn(async move {
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.custom_tag(SingleLetterTag::lowercase(Alphabet::R), room_id);
|
||||
.custom_tag(SingleLetterTag::LOWERCASE_R, room_id);
|
||||
|
||||
let messages = client
|
||||
.database()
|
||||
@@ -461,13 +461,10 @@ impl Room {
|
||||
// Add all receiver tags (no intermediate allocation)
|
||||
for public_key in self.members.iter().filter(|pk| *pk != &sender) {
|
||||
let member = persons.read(cx).get(public_key, cx);
|
||||
tags.push(
|
||||
Nip01Tag::PublicKey {
|
||||
public_key: member.public_key(),
|
||||
relay_hint: member.messaging_relay_hint(),
|
||||
}
|
||||
.to_tag(),
|
||||
);
|
||||
tags.push(Tag::from(Nip01Tag::PublicKey {
|
||||
public_key: member.public_key(),
|
||||
relay_hint: member.messaging_relay_hint(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Construct a direct message rumor event
|
||||
|
||||
@@ -4,13 +4,13 @@ use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use instant::Duration;
|
||||
|
||||
use anyhow::{Context as AnyhowContext, Error, anyhow};
|
||||
use gpui::{
|
||||
App, AppContext, Context, Entity, EventEmitter, Global, IntoElement, ParentElement,
|
||||
SharedString, Styled, Subscription, Task, Window, div, relative,
|
||||
};
|
||||
use instant::Duration;
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
use settings::AppSettings;
|
||||
@@ -414,7 +414,7 @@ impl DeviceRegistry {
|
||||
.pubkey(app_pubkey)
|
||||
.limit(1);
|
||||
|
||||
match client.database().query(filter).await?.first_owned() {
|
||||
match client.database().query(filter).await?.into_iter().next() {
|
||||
// Found an approval event
|
||||
Some(event) => Ok(Some(event)),
|
||||
// No approval event found, construct a request event
|
||||
|
||||
@@ -84,8 +84,20 @@ impl Screening {
|
||||
|
||||
let task: Task<Result<bool, Error>> = cx.background_spawn(async move {
|
||||
// Check if user is in contact list
|
||||
let contacts = client.database().contacts_public_keys(current_user).await;
|
||||
let followed = contacts.unwrap_or_default().contains(&public_key);
|
||||
let filter = Filter::new()
|
||||
.author(current_user)
|
||||
.kind(Kind::ContactList)
|
||||
.limit(1);
|
||||
|
||||
let followed = client
|
||||
.database()
|
||||
.query(filter)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|event| event.tags.public_keys().any(|k| k == public_key))
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(followed)
|
||||
});
|
||||
@@ -228,11 +240,10 @@ impl Screening {
|
||||
let public_key = self.public_key;
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let tag = Nip56Tag::PublicKey {
|
||||
let tag = Tag::from(Nip56Tag::PublicKey {
|
||||
public_key,
|
||||
report: Report::Impersonation,
|
||||
}
|
||||
.to_tag();
|
||||
});
|
||||
|
||||
let event = EventBuilder::new(Kind::Reporting, "")
|
||||
.tag(tag)
|
||||
|
||||
+27
-16
@@ -380,12 +380,15 @@ impl Workspace {
|
||||
self.import_encryption(window, cx);
|
||||
}
|
||||
Command::Update => {
|
||||
let auto_updater = AutoUpdater::global(cx);
|
||||
auto_updater.update(cx, |this, cx| {
|
||||
this.updater.update(cx, |updater, cx| {
|
||||
updater.check(cx);
|
||||
// No-op on managed distribution channels (Flatpak/Snap) where
|
||||
// the in-app updater is never initialized.
|
||||
if let Some(auto_updater) = AutoUpdater::try_global(cx) {
|
||||
auto_updater.update(cx, |this, cx| {
|
||||
this.updater.update(cx, |updater, cx| {
|
||||
updater.check(cx);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,7 +562,7 @@ impl Workspace {
|
||||
.caret()
|
||||
.compact()
|
||||
.transparent()
|
||||
.dropdown_menu(move |this, _window, _cx| {
|
||||
.dropdown_menu(move |this, _window, cx| {
|
||||
let avatar = avatar.clone();
|
||||
let name = name.clone();
|
||||
|
||||
@@ -593,12 +596,15 @@ impl Workspace {
|
||||
IconName::Sun,
|
||||
Box::new(Command::ToggleTheme),
|
||||
)
|
||||
.separator()
|
||||
.menu_with_icon(
|
||||
"Check for Updates",
|
||||
IconName::Device,
|
||||
Box::new(Command::Update),
|
||||
)
|
||||
// Only offer in-app updates when auto-update is
|
||||
// enabled (managed channels update themselves).
|
||||
.when(AutoUpdater::is_available(cx), |this| {
|
||||
this.separator().menu_with_icon(
|
||||
"Check for Updates",
|
||||
IconName::Device,
|
||||
Box::new(Command::Update),
|
||||
)
|
||||
})
|
||||
.menu_with_icon(
|
||||
"Settings",
|
||||
IconName::Settings,
|
||||
@@ -610,7 +616,6 @@ impl Workspace {
|
||||
}
|
||||
|
||||
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let updater = AutoUpdater::global(cx);
|
||||
let chat = ChatRegistry::global(cx);
|
||||
let nip4e_enabled = AppSettings::get_nip4e(cx);
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
@@ -622,13 +627,19 @@ impl Workspace {
|
||||
let persons = PersonRegistry::global(cx);
|
||||
let profile = persons.read(cx).get(&public_key, cx);
|
||||
let announcement = profile.announcement();
|
||||
let updater_idle = updater.read(cx).idle(cx);
|
||||
|
||||
// Update status is only shown when auto-update is available. On
|
||||
// managed distribution channels (Flatpak/Snap) no updater exists, so
|
||||
// nothing is rendered.
|
||||
let updater_status = AutoUpdater::try_global(cx).and_then(|updater| {
|
||||
let updater = updater.read(cx);
|
||||
(!updater.idle(cx)).then(|| updater.status(cx))
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
|
||||
.gap_2()
|
||||
.when(!updater_idle, |this| {
|
||||
let status = updater.read(cx).status(cx);
|
||||
.when_some(updater_status, |this, status| {
|
||||
this.child(div().text_xs().italic().child(status))
|
||||
})
|
||||
.when(nip4e_enabled, |this| {
|
||||
|
||||
@@ -88,7 +88,20 @@ impl ContactListPanel {
|
||||
};
|
||||
|
||||
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
||||
let contact_list = client.database().contacts_public_keys(public_key).await?;
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::ContactList)
|
||||
.limit(1);
|
||||
|
||||
let contact_list: HashSet<PublicKey> = client
|
||||
.database()
|
||||
.query(filter)
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|event| event.tags.public_keys().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(contact_list)
|
||||
});
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ impl MessagingRelayPanel {
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
if let Some(event) = client.database().query(filter).await?.into_iter().next() {
|
||||
Ok(nip17::extract_relay_list(&event).collect())
|
||||
} else {
|
||||
Err(anyhow!("Not found."))
|
||||
@@ -177,7 +177,7 @@ impl MessagingRelayPanel {
|
||||
let tags: Vec<Tag> = self
|
||||
.relays
|
||||
.iter()
|
||||
.map(|relay| Nip17Tag::Relay(relay.to_owned()).to_tag())
|
||||
.map(|relay| Tag::from(Nip17Tag::Relay(relay.to_owned())))
|
||||
.collect();
|
||||
|
||||
// Set updating state
|
||||
|
||||
@@ -111,7 +111,7 @@ impl RelayListPanel {
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
if let Some(event) = client.database().query(filter).await?.into_iter().next() {
|
||||
Ok(nip65::extract_relay_list(&event).collect())
|
||||
} else {
|
||||
Err(anyhow!("Not found."))
|
||||
|
||||
@@ -158,7 +158,20 @@ impl Sidebar {
|
||||
};
|
||||
|
||||
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
||||
let contacts = client.database().contacts_public_keys(public_key).await?;
|
||||
let filter = Filter::new()
|
||||
.author(public_key)
|
||||
.kind(Kind::ContactList)
|
||||
.limit(1);
|
||||
|
||||
let contacts: HashSet<PublicKey> = client
|
||||
.database()
|
||||
.query(filter)
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|event| event.tags.public_keys().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(contacts)
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ product-name = "Coop"
|
||||
description = "Chat Freely, Stay Private on Nostr"
|
||||
identifier = "su.reya.coop"
|
||||
category = "SocialNetworking"
|
||||
version = "1.0.0-beta5"
|
||||
version = "1.0.1"
|
||||
out-dir = "../dist"
|
||||
before-packaging-command = "cargo build --release"
|
||||
resources = ["Cargo.toml", "src"]
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
[package]
|
||||
name = "coop"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "coop"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.packager]
|
||||
name = "Coop"
|
||||
product-name = "Coop"
|
||||
description = "Chat Freely, Stay Private on Nostr"
|
||||
identifier = "su.reya.coop"
|
||||
category = "SocialNetworking"
|
||||
version = "1.0.0-beta5"
|
||||
out-dir = "../dist"
|
||||
before-packaging-command = "cargo build --release"
|
||||
resources = ["Cargo.toml", "src"]
|
||||
icons = [
|
||||
"resources/32x32.png",
|
||||
"resources/128x128.png",
|
||||
"resources/128x128@2x.png",
|
||||
"resources/icon.icns",
|
||||
"resources/icon.ico",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
assets = { path = "../crates/assets" }
|
||||
workspace = { path = "../crates/workspace" }
|
||||
ui = { path = "../crates/ui" }
|
||||
theme = { path = "../crates/theme" }
|
||||
common = { path = "../crates/common" }
|
||||
state = { path = "../crates/state" }
|
||||
device = { path = "../crates/device" }
|
||||
chat = { path = "../crates/chat" }
|
||||
settings = { path = "../crates/settings" }
|
||||
auto_update = { path = "../crates/auto_update" }
|
||||
person = { path = "../crates/person" }
|
||||
|
||||
gpui.workspace = true
|
||||
gpui_platform.workspace = true
|
||||
gpui_linux.workspace = true
|
||||
gpui_windows.workspace = true
|
||||
gpui_macos.workspace = true
|
||||
reqwest_client.workspace = true
|
||||
|
||||
log.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
nostr-sdk.workspace = true
|
||||
@@ -1,3 +1,7 @@
|
||||
# Snaps built by snapcraft without Snap Store credentials are unsigned and
|
||||
# cannot be installed without bypassing signature checks. Use:
|
||||
# sudo snap install --dangerous ./coop_<version>_<arch>.snap
|
||||
# For signed installs (`snap install coop`), publish via the Snap Store.
|
||||
name: coop
|
||||
title: Coop
|
||||
base: core24
|
||||
|
||||
+4
-4
@@ -55,8 +55,8 @@ flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo
|
||||
|
||||
Ensure you have:
|
||||
- [ ] Committed all changes
|
||||
- [ ] Tagged the release: `git tag -a v1.0.0-beta2 -m "Release v1.0.0-beta2"`
|
||||
- [ ] Pushed the tag: `git push origin v1.0.0-beta2`
|
||||
- [ ] Tagged the release: `git tag -a v1.0.0 -m "Release v1.0.0"`
|
||||
- [ ] Pushed the tag: `git push origin v1.0.0`
|
||||
- [ ] Run `./script/prepare-flathub.sh` to regenerate files
|
||||
|
||||
### 2. Fork and Submit
|
||||
@@ -101,8 +101,8 @@ git push origin su.reya.coop
|
||||
To release a new version:
|
||||
|
||||
1. Update version in workspace `Cargo.toml`
|
||||
2. Tag the new release: `git tag -a v1.0.0-beta3 -m "Release v1.0.0-beta3"`
|
||||
3. Push the tag: `git push origin v1.0.0-beta3`
|
||||
2. Tag the new release: `git tag -a v1.0.0 -m "Release v1.0.0"`
|
||||
3. Push the tag: `git push origin v1.0.0`
|
||||
4. Run `./script/prepare-flathub.sh` to regenerate
|
||||
5. Clone the flathub repo: `git clone https://github.com/flathub/su.reya.coop.git`
|
||||
6. Update the manifest with new commit/tag and hashes
|
||||
|
||||
@@ -35,3 +35,7 @@ SNAP_NAME="coop_${1}_${ARCH_SUFFIX}.snap"
|
||||
snapcraft --destructive-mode --output "$SNAP_NAME"
|
||||
|
||||
echo "Created snap package: $SNAP_NAME"
|
||||
echo ""
|
||||
echo "This snap is unsigned (built without Snap Store credentials)."
|
||||
echo "To install it locally, use:"
|
||||
echo " sudo snap install --dangerous ./$SNAP_NAME"
|
||||
|
||||
+16
-10
@@ -29,23 +29,29 @@ fi
|
||||
# Function to update version in a Cargo.toml file
|
||||
update_version() {
|
||||
local file="$1"
|
||||
local backup="${file}.bak"
|
||||
local tmp="${file}.tmp"
|
||||
|
||||
# Backup the original file
|
||||
cp "$file" "$backup"
|
||||
|
||||
# More flexible regex that handles various version formats and whitespace
|
||||
if sed -i -E "s/^[[:space:]]*version[[:space:]]*=[[:space:]]*\"[^\"]+\"/version = \"$NEW_VERSION\"/" "$file"; then
|
||||
# Portable in-place edit. `sed -i` behaves differently on GNU sed (Linux)
|
||||
# and BSD sed (macOS): on macOS `sed -i -E` treats `-E` as the backup
|
||||
# suffix instead of the extended-regex flag, leaving a stray
|
||||
# `Cargo.toml-E` behind and never updating the file.
|
||||
# Writing to a temp file and moving it over works on both implementations.
|
||||
if sed -E "s/^[[:space:]]*version[[:space:]]*=[[:space:]]*\"[^\"]+\"/version = \"$NEW_VERSION\"/" "$file" > "$tmp" \
|
||||
&& mv "$tmp" "$file"; then
|
||||
echo "✓ Updated version to $NEW_VERSION in $file"
|
||||
else
|
||||
echo "Error: Failed to update version in $file"
|
||||
# Restore original backup
|
||||
mv "$backup" "$file"
|
||||
# Remove any partial temp file; the original file is untouched
|
||||
rm -f "$tmp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Remove the backup file
|
||||
rm -f "$backup"
|
||||
# The substitution can silently match nothing (e.g. a `version.workspace` key),
|
||||
# so verify the new version actually landed before moving on.
|
||||
if ! grep -q "^[[:space:]]*version[[:space:]]*=[[:space:]]*\"$NEW_VERSION\"" "$file"; then
|
||||
echo "Error: Version line not found/updated in $file"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Update both Cargo.toml files
|
||||
|
||||
+16
-1
@@ -15,11 +15,26 @@ if [ "$#" -ne 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get system architecture (same mapping as script/bundle-snap)
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH_SUFFIX="x86_64" ;;
|
||||
aarch64) ARCH_SUFFIX="aarch64" ;;
|
||||
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
|
||||
esac
|
||||
|
||||
snap_file="coop_${1}_${ARCH_SUFFIX}.snap"
|
||||
if [ ! -f "$snap_file" ]; then
|
||||
echo "Snap file not found: $snap_file"
|
||||
echo "Build it first with: script/bundle-snap $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Rerun as root
|
||||
[ "$UID" -eq 0 ] || exec sudo bash -e "$0" "$@"
|
||||
|
||||
snap remove coop || true
|
||||
mkdir -p snap
|
||||
rm -rf snap/unpacked
|
||||
unsquashfs -dest snap/unpacked "coop_$1_amd64.snap"
|
||||
unsquashfs -dest snap/unpacked "$snap_file"
|
||||
snap try --classic snap/unpacked
|
||||
|
||||
Reference in New Issue
Block a user