| 53 |
53 |
|
Optional:\n\
|
| 54 |
54 |
|
\x20 --notes Release notes (default: empty)\n\
|
| 55 |
55 |
|
\x20 --signature Minisign signature for Tauri verification (REQUIRED for a working update)\n\
|
|
56 |
+ |
\x20 --release-id Attach to an existing release UUID instead of creating one\n\
|
|
57 |
+ |
\x20 (resume a publish whose artifact upload failed)\n\
|
| 56 |
58 |
|
\x20 --email MNW account email (env MNW_OTA_EMAIL; password auth only)\n\
|
| 57 |
59 |
|
\x20 --password MNW account password (env MNW_OTA_PASSWORD; enables password auth)\n\
|
| 58 |
60 |
|
\x20 --server Server URL (env MNW_OTA_SERVER, default {DEFAULT_SERVER})",
|
| 69 |
71 |
|
artifact: PathBuf,
|
| 70 |
72 |
|
notes: String,
|
| 71 |
73 |
|
signature: String,
|
|
74 |
+ |
// When set, attach the artifact to this existing release instead of creating
|
|
75 |
+ |
// one (resume a failed upload; create would 409 on a duplicate version).
|
|
76 |
+ |
release_id: Option<String>,
|
| 72 |
77 |
|
// email + password drive the legacy password auth path. When absent, the
|
| 73 |
78 |
|
// publisher uses the interactive MNW OAuth flow (the default; required for
|
| 74 |
79 |
|
// accounts with 2FA, which the password endpoint rejects).
|
| 91 |
96 |
|
.field("artifact", &self.artifact)
|
| 92 |
97 |
|
.field("notes", &self.notes)
|
| 93 |
98 |
|
.field("signature", &self.signature)
|
|
99 |
+ |
.field("release_id", &self.release_id)
|
| 94 |
100 |
|
.field("email", &self.email)
|
| 95 |
101 |
|
.field("password", &self.password.as_ref().map(|_| "<redacted>"))
|
| 96 |
102 |
|
.field("api_key", &"<redacted>")
|
| 109 |
115 |
|
let mut artifact = None;
|
| 110 |
116 |
|
let mut notes = String::new();
|
| 111 |
117 |
|
let mut signature = String::new();
|
|
118 |
+ |
let mut release_id = None;
|
| 112 |
119 |
|
let mut email = std::env::var("MNW_OTA_EMAIL").ok();
|
| 113 |
120 |
|
let mut password = std::env::var("MNW_OTA_PASSWORD").ok();
|
| 114 |
121 |
|
let mut api_key = std::env::var("MNW_OTA_API_KEY").ok();
|
| 130 |
137 |
|
"--artifact" => artifact = Some(PathBuf::from(take("--artifact")?)),
|
| 131 |
138 |
|
"--notes" => notes = take("--notes")?,
|
| 132 |
139 |
|
"--signature" => signature = take("--signature")?,
|
|
140 |
+ |
"--release-id" => release_id = Some(take("--release-id")?),
|
| 133 |
141 |
|
"--email" => email = Some(take("--email")?),
|
| 134 |
142 |
|
"--password" => password = Some(take("--password")?),
|
| 135 |
143 |
|
"--api-key" => api_key = Some(take("--api-key")?),
|
| 162 |
170 |
|
artifact: artifact.ok_or_else(|| missing("--artifact"))?,
|
| 163 |
171 |
|
notes,
|
| 164 |
172 |
|
signature,
|
|
173 |
+ |
release_id,
|
| 165 |
174 |
|
email, // optional: only used by the password auth path
|
| 166 |
175 |
|
password,
|
| 167 |
176 |
|
api_key: api_key.ok_or_else(|| missing("--api-key / MNW_OTA_API_KEY"))?,
|
| 227 |
236 |
|
.unwrap_or_default();
|
| 228 |
237 |
|
println!(" authenticated (app {app_id})");
|
| 229 |
238 |
|
|
| 230 |
|
- |
print!(" creating release v{}... ", args.version);
|
| 231 |
|
- |
let release = client
|
| 232 |
|
- |
.ota_create_release(&args.version, &args.notes, &args.signature)
|
| 233 |
|
- |
.await
|
| 234 |
|
- |
.context("create release failed")?;
|
| 235 |
|
- |
println!("ok (release {})", release.id);
|
|
239 |
+ |
// With --release-id, attach to an existing release (resume a failed upload)
|
|
240 |
+ |
// instead of creating one — create would 409 on a duplicate version.
|
|
241 |
+ |
let release_id = match &args.release_id {
|
|
242 |
+ |
Some(rid) => {
|
|
243 |
+ |
let id = rid
|
|
244 |
+ |
.parse::<uuid::Uuid>()
|
|
245 |
+ |
.context("--release-id must be a UUID")?;
|
|
246 |
+ |
println!(" using existing release {id} (skipping create)");
|
|
247 |
+ |
id
|
|
248 |
+ |
}
|
|
249 |
+ |
None => {
|
|
250 |
+ |
print!(" creating release v{}... ", args.version);
|
|
251 |
+ |
let release = client
|
|
252 |
+ |
.ota_create_release(&args.version, &args.notes, &args.signature)
|
|
253 |
+ |
.await
|
|
254 |
+ |
.context("create release failed")?;
|
|
255 |
+ |
println!("ok (release {})", release.id);
|
|
256 |
+ |
release.id
|
|
257 |
+ |
}
|
|
258 |
+ |
};
|
| 236 |
259 |
|
|
| 237 |
260 |
|
print!(" registering artifact... ");
|
| 238 |
261 |
|
let upload = client
|
| 239 |
|
- |
.ota_register_artifact(release.id, &args.target, &args.arch, file_size)
|
|
262 |
+ |
.ota_register_artifact(release_id, &args.target, &args.arch, file_size)
|
| 240 |
263 |
|
.await
|
| 241 |
264 |
|
.context("register artifact failed")?;
|
| 242 |
265 |
|
println!("ok ({})", upload.s3_key);
|