clawdex-mobile 5.0.8 → 5.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/docs/setup-and-operations.md +44 -5
- package/docs/troubleshooting.md +12 -0
- package/package.json +1 -1
- package/scripts/bridge-self-update.js +246 -65
- package/scripts/start-bridge-secure.js +3 -0
- package/services/rust-bridge/Cargo.lock +55 -3
- package/services/rust-bridge/Cargo.toml +3 -2
- package/services/rust-bridge/src/main.rs +8127 -3889
- package/services/rust-bridge/src/services/git.rs +211 -5
- package/services/rust-bridge/src/services/update.rs +130 -16
- package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
|
@@ -4,9 +4,9 @@ use std::{
|
|
|
4
4
|
};
|
|
5
5
|
|
|
6
6
|
use crate::{
|
|
7
|
-
normalize_path, BridgeError, GitCommitResponse, GitDiffResponse,
|
|
8
|
-
|
|
9
|
-
GitUnstageAllResponse, GitUnstageResponse,
|
|
7
|
+
normalize_path, BridgeError, GitCloneResponse, GitCommitResponse, GitDiffResponse,
|
|
8
|
+
GitHistoryCommit, GitHistoryResponse, GitPushResponse, GitStageAllResponse, GitStageResponse,
|
|
9
|
+
GitStatusEntry, GitStatusResponse, GitUnstageAllResponse, GitUnstageResponse,
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
use super::TerminalService;
|
|
@@ -196,6 +196,96 @@ impl GitService {
|
|
|
196
196
|
})
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
pub(crate) async fn get_history(
|
|
200
|
+
&self,
|
|
201
|
+
raw_cwd: Option<&str>,
|
|
202
|
+
limit: Option<usize>,
|
|
203
|
+
) -> Result<GitHistoryResponse, BridgeError> {
|
|
204
|
+
let repo_path = self.resolve_repo_path(raw_cwd)?;
|
|
205
|
+
let history_limit = limit.unwrap_or(12).clamp(1, 30);
|
|
206
|
+
let args = vec![
|
|
207
|
+
"-C".to_string(),
|
|
208
|
+
repo_path.to_string_lossy().to_string(),
|
|
209
|
+
"log".to_string(),
|
|
210
|
+
"--first-parent".to_string(),
|
|
211
|
+
"--decorate=short".to_string(),
|
|
212
|
+
"--date=iso-strict".to_string(),
|
|
213
|
+
format!("--max-count={history_limit}"),
|
|
214
|
+
"--pretty=format:%H\x1f%h\x1f%an\x1f%aI\x1f%D\x1f%s\x1e".to_string(),
|
|
215
|
+
"HEAD".to_string(),
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
let result = self
|
|
219
|
+
.terminal
|
|
220
|
+
.execute_binary("git", &args, repo_path.clone(), None)
|
|
221
|
+
.await?;
|
|
222
|
+
|
|
223
|
+
if result.code != Some(0) {
|
|
224
|
+
return Err(BridgeError::server(
|
|
225
|
+
&(if !result.stderr.is_empty() {
|
|
226
|
+
result.stderr
|
|
227
|
+
} else if !result.stdout.is_empty() {
|
|
228
|
+
result.stdout
|
|
229
|
+
} else {
|
|
230
|
+
"git log failed".to_string()
|
|
231
|
+
}),
|
|
232
|
+
));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
Ok(GitHistoryResponse {
|
|
236
|
+
commits: parse_git_history(&result.stdout),
|
|
237
|
+
cwd: repo_path.to_string_lossy().to_string(),
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
pub(crate) async fn clone_repo(
|
|
242
|
+
&self,
|
|
243
|
+
repository_url: &str,
|
|
244
|
+
raw_parent_path: Option<&str>,
|
|
245
|
+
directory_name: &str,
|
|
246
|
+
) -> Result<GitCloneResponse, BridgeError> {
|
|
247
|
+
let parent_path = self.resolve_repo_path(raw_parent_path)?;
|
|
248
|
+
if !parent_path.exists() {
|
|
249
|
+
return Err(BridgeError::invalid_params(
|
|
250
|
+
"destination parent path must exist",
|
|
251
|
+
));
|
|
252
|
+
}
|
|
253
|
+
if !parent_path.is_dir() {
|
|
254
|
+
return Err(BridgeError::invalid_params(
|
|
255
|
+
"destination parent path must be a directory",
|
|
256
|
+
));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
let normalized_directory_name = resolve_clone_directory_name(directory_name)?;
|
|
260
|
+
let destination_path = normalize_path(&parent_path.join(&normalized_directory_name));
|
|
261
|
+
if destination_path.exists() {
|
|
262
|
+
return Err(BridgeError::invalid_params(
|
|
263
|
+
"destination path already exists",
|
|
264
|
+
));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let args = vec![
|
|
268
|
+
"clone".to_string(),
|
|
269
|
+
"--".to_string(),
|
|
270
|
+
repository_url.trim().to_string(),
|
|
271
|
+
normalized_directory_name,
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
let result = self
|
|
275
|
+
.terminal
|
|
276
|
+
.execute_binary("git", &args, parent_path.clone(), None)
|
|
277
|
+
.await?;
|
|
278
|
+
|
|
279
|
+
Ok(GitCloneResponse {
|
|
280
|
+
code: result.code,
|
|
281
|
+
stdout: result.stdout,
|
|
282
|
+
stderr: result.stderr,
|
|
283
|
+
cloned: result.code == Some(0),
|
|
284
|
+
cwd: destination_path.to_string_lossy().to_string(),
|
|
285
|
+
url: repository_url.trim().to_string(),
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
199
289
|
pub(crate) async fn stage_file(
|
|
200
290
|
&self,
|
|
201
291
|
path: &str,
|
|
@@ -549,6 +639,49 @@ fn parse_status_has_upstream(raw: &str) -> bool {
|
|
|
549
639
|
.unwrap_or(false)
|
|
550
640
|
}
|
|
551
641
|
|
|
642
|
+
fn parse_git_history(raw: &str) -> Vec<GitHistoryCommit> {
|
|
643
|
+
raw.split('\x1e')
|
|
644
|
+
.filter_map(|record| {
|
|
645
|
+
let trimmed = record.trim();
|
|
646
|
+
if trimmed.is_empty() {
|
|
647
|
+
return None;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
let mut parts = trimmed.split('\x1f');
|
|
651
|
+
let hash = parts.next()?.trim().to_string();
|
|
652
|
+
let short_hash = parts.next().unwrap_or_default().trim().to_string();
|
|
653
|
+
let author_name = parts.next().unwrap_or_default().trim().to_string();
|
|
654
|
+
let authored_at = parts.next().unwrap_or_default().trim().to_string();
|
|
655
|
+
let refs_raw = parts.next().unwrap_or_default().trim().to_string();
|
|
656
|
+
let subject = parts.next().unwrap_or_default().trim().to_string();
|
|
657
|
+
|
|
658
|
+
if hash.is_empty() || short_hash.is_empty() || subject.is_empty() {
|
|
659
|
+
return None;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let ref_names = refs_raw
|
|
663
|
+
.split(',')
|
|
664
|
+
.map(str::trim)
|
|
665
|
+
.filter(|entry| !entry.is_empty())
|
|
666
|
+
.map(str::to_string)
|
|
667
|
+
.collect::<Vec<_>>();
|
|
668
|
+
let is_head = ref_names
|
|
669
|
+
.iter()
|
|
670
|
+
.any(|entry| entry == "HEAD" || entry.starts_with("HEAD ->"));
|
|
671
|
+
|
|
672
|
+
Some(GitHistoryCommit {
|
|
673
|
+
hash,
|
|
674
|
+
short_hash,
|
|
675
|
+
subject,
|
|
676
|
+
author_name,
|
|
677
|
+
authored_at,
|
|
678
|
+
ref_names,
|
|
679
|
+
is_head,
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
.collect()
|
|
683
|
+
}
|
|
684
|
+
|
|
552
685
|
fn select_default_remote_name(raw: &str) -> Option<String> {
|
|
553
686
|
let remotes = raw
|
|
554
687
|
.lines()
|
|
@@ -626,11 +759,47 @@ fn resolve_repo_relative_path(raw_path: &str, repo_path: &Path) -> Result<String
|
|
|
626
759
|
Ok(relative.to_string_lossy().to_string())
|
|
627
760
|
}
|
|
628
761
|
|
|
762
|
+
fn resolve_clone_directory_name(raw_name: &str) -> Result<String, BridgeError> {
|
|
763
|
+
let trimmed = raw_name.trim();
|
|
764
|
+
if trimmed.is_empty() {
|
|
765
|
+
return Err(BridgeError::invalid_params(
|
|
766
|
+
"directoryName must not be empty",
|
|
767
|
+
));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
let requested = PathBuf::from(trimmed);
|
|
771
|
+
if requested.is_absolute() {
|
|
772
|
+
return Err(BridgeError::invalid_params(
|
|
773
|
+
"directoryName must be a folder name, not a path",
|
|
774
|
+
));
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
let mut components = requested.components();
|
|
778
|
+
let Some(component) = components.next() else {
|
|
779
|
+
return Err(BridgeError::invalid_params(
|
|
780
|
+
"directoryName must not be empty",
|
|
781
|
+
));
|
|
782
|
+
};
|
|
783
|
+
if components.next().is_some() {
|
|
784
|
+
return Err(BridgeError::invalid_params(
|
|
785
|
+
"directoryName must be a single folder name",
|
|
786
|
+
));
|
|
787
|
+
}
|
|
788
|
+
if !matches!(component, std::path::Component::Normal(_)) {
|
|
789
|
+
return Err(BridgeError::invalid_params(
|
|
790
|
+
"directoryName must be a valid folder name",
|
|
791
|
+
));
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
Ok(trimmed.to_string())
|
|
795
|
+
}
|
|
796
|
+
|
|
629
797
|
#[cfg(test)]
|
|
630
798
|
mod tests {
|
|
631
799
|
use super::{
|
|
632
|
-
parse_porcelain_status_entries, parse_status_has_upstream,
|
|
633
|
-
|
|
800
|
+
parse_git_history, parse_porcelain_status_entries, parse_status_has_upstream,
|
|
801
|
+
resolve_clone_directory_name, resolve_git_cwd, resolve_repo_relative_path,
|
|
802
|
+
select_default_remote_name,
|
|
634
803
|
};
|
|
635
804
|
use std::path::{Path, PathBuf};
|
|
636
805
|
|
|
@@ -685,6 +854,20 @@ mod tests {
|
|
|
685
854
|
assert_eq!(error.code, -32602);
|
|
686
855
|
}
|
|
687
856
|
|
|
857
|
+
#[test]
|
|
858
|
+
fn resolves_clone_directory_name_from_single_segment() {
|
|
859
|
+
let resolved =
|
|
860
|
+
resolve_clone_directory_name("my-repo").expect("resolve single directory name");
|
|
861
|
+
assert_eq!(resolved, "my-repo");
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
#[test]
|
|
865
|
+
fn rejects_nested_clone_directory_name() {
|
|
866
|
+
let error = resolve_clone_directory_name("nested/repo")
|
|
867
|
+
.expect_err("reject nested clone directory name");
|
|
868
|
+
assert_eq!(error.code, -32602);
|
|
869
|
+
}
|
|
870
|
+
|
|
688
871
|
#[test]
|
|
689
872
|
fn parses_porcelain_entries_for_rename_and_untracked() {
|
|
690
873
|
let raw = "## main...origin/main\0R new/path.ts\0old/path.ts\0?? fresh/file.ts\0";
|
|
@@ -729,4 +912,27 @@ mod tests {
|
|
|
729
912
|
);
|
|
730
913
|
assert_eq!(select_default_remote_name(""), None);
|
|
731
914
|
}
|
|
915
|
+
|
|
916
|
+
#[test]
|
|
917
|
+
fn parses_git_history_records() {
|
|
918
|
+
let raw = concat!(
|
|
919
|
+
"abc123\x1fabc123\x1fMohit\x1f2026-04-05T10:00:00+05:30\x1fHEAD -> feat/test, origin/feat/test\x1fAdd history card\x1e",
|
|
920
|
+
"def456\x1fdef456\x1fMohit\x1f2026-04-04T09:00:00+05:30\x1forigin/main\x1fPrevious commit\x1e"
|
|
921
|
+
);
|
|
922
|
+
|
|
923
|
+
let commits = parse_git_history(raw);
|
|
924
|
+
assert_eq!(commits.len(), 2);
|
|
925
|
+
assert_eq!(commits[0].hash, "abc123");
|
|
926
|
+
assert_eq!(commits[0].subject, "Add history card");
|
|
927
|
+
assert!(commits[0].is_head);
|
|
928
|
+
assert_eq!(
|
|
929
|
+
commits[0].ref_names,
|
|
930
|
+
vec![
|
|
931
|
+
"HEAD -> feat/test".to_string(),
|
|
932
|
+
"origin/feat/test".to_string()
|
|
933
|
+
]
|
|
934
|
+
);
|
|
935
|
+
assert_eq!(commits[1].subject, "Previous commit");
|
|
936
|
+
assert!(!commits[1].is_head);
|
|
937
|
+
}
|
|
732
938
|
}
|
|
@@ -36,6 +36,7 @@ pub(crate) struct BridgeRuntimeInfo {
|
|
|
36
36
|
pub(crate) version: String,
|
|
37
37
|
pub(crate) install_kind: BridgeInstallKind,
|
|
38
38
|
pub(crate) self_update_supported: bool,
|
|
39
|
+
pub(crate) safe_restart_supported: bool,
|
|
39
40
|
pub(crate) latest_version: Option<String>,
|
|
40
41
|
pub(crate) updater_status: Option<BridgeUpdaterStatus>,
|
|
41
42
|
}
|
|
@@ -50,6 +51,44 @@ pub(crate) struct BridgeUpdateStartResponse {
|
|
|
50
51
|
pub(crate) log_path: Option<String>,
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
#[derive(Debug, Clone, Serialize)]
|
|
55
|
+
#[serde(rename_all = "camelCase")]
|
|
56
|
+
pub(crate) struct BridgeRestartStartResponse {
|
|
57
|
+
pub(crate) ok: bool,
|
|
58
|
+
pub(crate) job_id: String,
|
|
59
|
+
pub(crate) message: String,
|
|
60
|
+
pub(crate) log_path: Option<String>,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
64
|
+
enum BridgeMaintenanceAction {
|
|
65
|
+
Update,
|
|
66
|
+
Restart,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl BridgeMaintenanceAction {
|
|
70
|
+
fn as_arg(self) -> &'static str {
|
|
71
|
+
match self {
|
|
72
|
+
Self::Update => "update",
|
|
73
|
+
Self::Restart => "restart",
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn job_prefix(self) -> &'static str {
|
|
78
|
+
match self {
|
|
79
|
+
Self::Update => "bridge-update",
|
|
80
|
+
Self::Restart => "bridge-restart",
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[derive(Debug, Clone)]
|
|
86
|
+
struct BridgeMaintenanceJobStart {
|
|
87
|
+
job_id: String,
|
|
88
|
+
target_version: String,
|
|
89
|
+
log_path: Option<String>,
|
|
90
|
+
}
|
|
91
|
+
|
|
53
92
|
#[derive(Clone)]
|
|
54
93
|
pub(crate) struct UpdateService {
|
|
55
94
|
package_root: Option<PathBuf>,
|
|
@@ -57,6 +96,8 @@ pub(crate) struct UpdateService {
|
|
|
57
96
|
status_path: Option<PathBuf>,
|
|
58
97
|
log_path: Option<PathBuf>,
|
|
59
98
|
script_path: Option<PathBuf>,
|
|
99
|
+
launcher_path: Option<PathBuf>,
|
|
100
|
+
secure_env_path: Option<PathBuf>,
|
|
60
101
|
}
|
|
61
102
|
|
|
62
103
|
impl UpdateService {
|
|
@@ -75,6 +116,10 @@ impl UpdateService {
|
|
|
75
116
|
let script_path = package_root
|
|
76
117
|
.as_ref()
|
|
77
118
|
.map(|root| root.join("scripts").join("bridge-self-update.js"));
|
|
119
|
+
let launcher_path = package_root
|
|
120
|
+
.as_ref()
|
|
121
|
+
.map(|root| root.join("scripts").join("start-bridge-secure.js"));
|
|
122
|
+
let secure_env_path = package_root.as_ref().map(|root| root.join(".env.secure"));
|
|
78
123
|
|
|
79
124
|
Self {
|
|
80
125
|
package_root,
|
|
@@ -82,13 +127,26 @@ impl UpdateService {
|
|
|
82
127
|
status_path,
|
|
83
128
|
log_path,
|
|
84
129
|
script_path,
|
|
130
|
+
launcher_path,
|
|
131
|
+
secure_env_path,
|
|
85
132
|
}
|
|
86
133
|
}
|
|
87
134
|
|
|
135
|
+
pub(crate) fn is_safe_restart_supported(&self) -> bool {
|
|
136
|
+
self.package_root.is_some()
|
|
137
|
+
&& self.script_path.as_ref().is_some_and(|path| path.is_file())
|
|
138
|
+
&& self
|
|
139
|
+
.launcher_path
|
|
140
|
+
.as_ref()
|
|
141
|
+
.is_some_and(|path| path.is_file())
|
|
142
|
+
&& self
|
|
143
|
+
.secure_env_path
|
|
144
|
+
.as_ref()
|
|
145
|
+
.is_some_and(|path| path.is_file())
|
|
146
|
+
}
|
|
147
|
+
|
|
88
148
|
pub(crate) fn is_self_update_supported(&self) -> bool {
|
|
89
|
-
self.install_kind == BridgeInstallKind::PublishedCli
|
|
90
|
-
&& self.package_root.is_some()
|
|
91
|
-
&& self.script_path.as_ref().is_some_and(|path| path.exists())
|
|
149
|
+
self.install_kind == BridgeInstallKind::PublishedCli && self.is_safe_restart_supported()
|
|
92
150
|
}
|
|
93
151
|
|
|
94
152
|
pub(crate) async fn runtime_info(&self) -> BridgeRuntimeInfo {
|
|
@@ -96,6 +154,7 @@ impl UpdateService {
|
|
|
96
154
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
|
97
155
|
install_kind: self.install_kind,
|
|
98
156
|
self_update_supported: self.is_self_update_supported(),
|
|
157
|
+
safe_restart_supported: self.is_safe_restart_supported(),
|
|
99
158
|
latest_version: fetch_latest_npm_version().await,
|
|
100
159
|
updater_status: self.read_status(),
|
|
101
160
|
}
|
|
@@ -107,11 +166,68 @@ impl UpdateService {
|
|
|
107
166
|
bridge_pid: u32,
|
|
108
167
|
now_iso: &str,
|
|
109
168
|
) -> Result<BridgeUpdateStartResponse, String> {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
169
|
+
let job = self.start_job(
|
|
170
|
+
BridgeMaintenanceAction::Update,
|
|
171
|
+
version,
|
|
172
|
+
bridge_pid,
|
|
173
|
+
now_iso,
|
|
174
|
+
)?;
|
|
175
|
+
|
|
176
|
+
Ok(BridgeUpdateStartResponse {
|
|
177
|
+
ok: true,
|
|
178
|
+
job_id: job.job_id,
|
|
179
|
+
target_version: job.target_version.clone(),
|
|
180
|
+
message: format!(
|
|
181
|
+
"Bridge update scheduled for {}. The bridge will disconnect briefly and should restart automatically.",
|
|
182
|
+
job.target_version
|
|
183
|
+
),
|
|
184
|
+
log_path: job.log_path,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pub(crate) fn start_restart(
|
|
189
|
+
&self,
|
|
190
|
+
bridge_pid: u32,
|
|
191
|
+
now_iso: &str,
|
|
192
|
+
) -> Result<BridgeRestartStartResponse, String> {
|
|
193
|
+
let job = self.start_job(
|
|
194
|
+
BridgeMaintenanceAction::Restart,
|
|
195
|
+
env!("CARGO_PKG_VERSION"),
|
|
196
|
+
bridge_pid,
|
|
197
|
+
now_iso,
|
|
198
|
+
)?;
|
|
199
|
+
|
|
200
|
+
Ok(BridgeRestartStartResponse {
|
|
201
|
+
ok: true,
|
|
202
|
+
job_id: job.job_id,
|
|
203
|
+
message:
|
|
204
|
+
"Bridge restart scheduled. The bridge will disconnect briefly and should restart automatically."
|
|
113
205
|
.to_string(),
|
|
114
|
-
|
|
206
|
+
log_path: job.log_path,
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn start_job(
|
|
211
|
+
&self,
|
|
212
|
+
action: BridgeMaintenanceAction,
|
|
213
|
+
version: &str,
|
|
214
|
+
bridge_pid: u32,
|
|
215
|
+
now_iso: &str,
|
|
216
|
+
) -> Result<BridgeMaintenanceJobStart, String> {
|
|
217
|
+
match action {
|
|
218
|
+
BridgeMaintenanceAction::Update if !self.is_self_update_supported() => {
|
|
219
|
+
return Err(
|
|
220
|
+
"Bridge self-update is only supported for published clawdex-mobile CLI installs."
|
|
221
|
+
.to_string(),
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
BridgeMaintenanceAction::Restart if !self.is_safe_restart_supported() => {
|
|
225
|
+
return Err(
|
|
226
|
+
"Bridge safe restart requires a detected clawdex-mobile install with .env.secure and launcher scripts available."
|
|
227
|
+
.to_string(),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
_ => {}
|
|
115
231
|
}
|
|
116
232
|
|
|
117
233
|
let package_root = self
|
|
@@ -132,7 +248,7 @@ impl UpdateService {
|
|
|
132
248
|
.ok_or_else(|| "bridge updater log path is missing".to_string())?;
|
|
133
249
|
|
|
134
250
|
let target_version = normalize_target_version(version)?;
|
|
135
|
-
let job_id = create_job_id();
|
|
251
|
+
let job_id = create_job_id(action.job_prefix());
|
|
136
252
|
|
|
137
253
|
let log_file = OpenOptions::new()
|
|
138
254
|
.create(true)
|
|
@@ -146,6 +262,8 @@ impl UpdateService {
|
|
|
146
262
|
let mut command = std::process::Command::new(node_command());
|
|
147
263
|
command
|
|
148
264
|
.arg(script_path)
|
|
265
|
+
.arg("--action")
|
|
266
|
+
.arg(action.as_arg())
|
|
149
267
|
.arg("--job-id")
|
|
150
268
|
.arg(&job_id)
|
|
151
269
|
.arg("--bridge-pid")
|
|
@@ -170,13 +288,9 @@ impl UpdateService {
|
|
|
170
288
|
.map_err(|error| format!("failed to spawn updater: {error}"))?;
|
|
171
289
|
let _ = child.id();
|
|
172
290
|
|
|
173
|
-
Ok(
|
|
174
|
-
ok: true,
|
|
291
|
+
Ok(BridgeMaintenanceJobStart {
|
|
175
292
|
job_id,
|
|
176
|
-
target_version
|
|
177
|
-
message: format!(
|
|
178
|
-
"Bridge update scheduled for {target_version}. The bridge will disconnect briefly and should restart automatically."
|
|
179
|
-
),
|
|
293
|
+
target_version,
|
|
180
294
|
log_path: Some(log_path.to_string_lossy().to_string()),
|
|
181
295
|
})
|
|
182
296
|
}
|
|
@@ -273,9 +387,9 @@ fn normalize_target_version(value: &str) -> Result<String, String> {
|
|
|
273
387
|
Err("version must be 'latest' or a simple npm package version".to_string())
|
|
274
388
|
}
|
|
275
389
|
|
|
276
|
-
fn create_job_id() -> String {
|
|
390
|
+
fn create_job_id(prefix: &str) -> String {
|
|
277
391
|
format!(
|
|
278
|
-
"
|
|
392
|
+
"{prefix}-{}-{}",
|
|
279
393
|
chrono::Utc::now().timestamp_millis(),
|
|
280
394
|
std::process::id()
|
|
281
395
|
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|