Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df26abf37 | ||
|
|
e887ffc66b |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
# The Windows build, which the Macs this is developed on cannot compile:
|
||||
# the whole crate through clippy, the external CLI's tests, including the
|
||||
# named pipe between two processes, and the updater's.
|
||||
# named pipe between two processes, the updater's, and the local SFTP pane's
|
||||
# paths (std canonicalizes to `\\?\C:\…` there).
|
||||
#
|
||||
# A push runs it only when it touches code that differs on Windows (the
|
||||
# files with `cfg(windows)` and friends), the build script or the
|
||||
@@ -21,6 +22,8 @@ on:
|
||||
- "src/bin/**"
|
||||
- "src/ssh/connection.rs"
|
||||
- "src/sftp/download.rs"
|
||||
- "src/sftp/model.rs"
|
||||
- "src/sftp/upload.rs"
|
||||
- "src/credential/credential_dialog.rs"
|
||||
- "src/settings/settings_panel.rs"
|
||||
- "tests/cli_bin.rs"
|
||||
@@ -48,4 +51,5 @@ jobs:
|
||||
- run: cargo clippy --all-targets -- -D warnings
|
||||
- run: "cargo test --lib cli::"
|
||||
- run: "cargo test --lib update::"
|
||||
- run: "cargo test --lib sftp::tests::local_listing"
|
||||
- run: cargo test --test cli_bin
|
||||
@@ -17,6 +17,12 @@ ShellRS 的第一个公开版本。
|
||||
- 外部 CLI:`shellrs` 命令让 AI Agent 用已保存的主机执行命令、传输文件。
|
||||
- 在线升级:在后台检查并下载新版本,下好后标题栏右上角提示,重启或退出时安装。
|
||||
|
||||
## [0.1.0-beta.4]
|
||||
|
||||
### 修复
|
||||
|
||||
- Windows:SFTP 本地面板的路径不再显示成 `\\?\C:\Users` 这样,而是平常的 `C:\Users`,传输队列的「来源」也一样。以前添加的本地书签如果带着 `\\?\`,删掉重新添加即可;升级前停在一半的上传不能接着续传,需要重新开始。
|
||||
|
||||
## [0.1.0-beta.3]
|
||||
|
||||
### 改进
|
||||
|
||||
@@ -133,7 +133,7 @@ cargo test -- --ignored # 会读写真实钥匙串的测试
|
||||
- 用同步的 Win32 管道,不用 tokio 的:请求线程要阻塞的 `File`,而 tokio 的管道是 overlapped 句柄,拿去做同步 IO 时 std 会中止进程。
|
||||
- 监听线程先建好下一个实例再把已连接的交给请求线程,否则那一刻来的客户端会看到「没运行」。请求线程结束时直接关句柄,**不要** `DisconnectNamedPipe`(会丢掉客户端还没读的数据)也不要 `FlushFileBuffers`(客户端挂住时会一直阻塞)。停止时置标志、反复以客户端身份连一下唤醒 `ConnectNamedPipe`,直到线程结束。
|
||||
- **Windows 的 PATH 是副本加用户 PATH。** `IntegrationPaths::system()` 在 Windows 上:`exe` 是 `shellrs.exe` 旁边的 `shellrs-cli.exe`,`bin_link` 是 `%LOCALAPPDATA%\ShellRS\bin\shellrs.exe`(副本,不是链接),`user_path` 是 `UserPath::Registry`(`HKCU\Environment\Path`,写回 `REG_EXPAND_SZ` 后广播 `WM_SETTINGCHANGE`;只有之后启动的程序看得到)。副本按长度加修改时间判断是否本版(安装时用 `set_modified` 把时间设成源文件的)。正在运行的副本不能覆盖但能改名,所以安装、移除都先把旧的改名成 `shellrs.exe.<uuid>.old` 再尽量删掉。生产环境启动时 `CliIntegration::update_outdated` 在后台更新旧副本,否则 Agent 会一直碰到 `version_mismatch`。测试一律 `user_path: None` 或 `UserPath::File`,绝不碰注册表。开发时 `cargo run` 只构建 `shellrs`,要 `cargo build` 才有 `shellrs-cli.exe`,否则设置页显示找不到。
|
||||
- **Windows 只在 CI 上编译和测试**(`.github/workflows/windows.yml`:clippy 全 crate、`cargo test --lib cli::`、`cargo test --lib update::`、`cargo test --test cli_bin`),本机没有 Windows 工具链;改 `pipe_windows.rs`、`install_windows.rs` 的 Windows 部分和 `cfg(windows)` 代码后要看 CI。它不是每次推送都跑:只在推送改到 `paths` 里列的文件(含 `cfg(windows)` 等平台代码的文件、`build.rs`、`Cargo.toml` / `Cargo.lock`)时才跑,tag 不触发;在别的文件里新加 Windows 代码时把文件加进这个列表,或者在 Actions 里手动运行。
|
||||
- **Windows 只在 CI 上编译和测试**(`.github/workflows/windows.yml`:clippy 全 crate、`cargo test --lib cli::`、`cargo test --lib update::`、`cargo test --lib sftp::tests::local_listing`(本地面板的路径:std 在 Windows 上把路径规范化成 `\\?\C:\…`,读目录改用 `dunce::canonicalize`)、`cargo test --test cli_bin`),本机没有 Windows 工具链;改 `pipe_windows.rs`、`install_windows.rs` 的 Windows 部分和 `cfg(windows)` 代码后要看 CI。它不是每次推送都跑:只在推送改到 `paths` 里列的文件(含 `cfg(windows)` 等平台代码的文件、`build.rs`、`Cargo.toml` / `Cargo.lock`)时才跑,tag 不触发;在别的文件里新加 Windows 代码时把文件加进这个列表,或者在 Actions 里手动运行。
|
||||
- **每个请求一个线程**,后端方法都是阻塞的;SSH 句柄绑在创建它的 runtime 上,所以 `ssh::run_command` 和 SFTP 工作线程各自建 current-thread runtime。
|
||||
- **exec 不许挂住**:`SshPrompts` 非交互;陌生主机在 events 回调里直接返回 `false`(`ask` 没人回答会永远等下去,因为超时不计提示时间),缺凭据由 `MissingCredential` 失败。`exec(true)` 后立刻 `eof()`,远程没有 stdin;读循环读到 `Close` / `None` 为止,退出码可能在 EOF 之后才到。输出回调写失败(Agent 断开)就放弃命令、断开连接。
|
||||
- **传输无人值守**:`SshCliBackend::transfer` 先等 `Connected` 再发请求;连接前的 `Prompt` 一律回 `Cancel` 并按提示种类给出错误码;`Question` 按固定策略回答(冲突覆盖并勾选全部、续传一律重新开始、出错跳过并记为失败);以 `Idle` 为结束。请求用 `UploadRequest::scp` / `DownloadRequest::scp`:worker 连上后按 scp 规则解析目标(`scp_remote_target` / `scp_local_target`,目标目录必须存在,远程还要 `stat` 确认,因为有的服务器 realpath 不存在的路径也成功),并让新建文件保留源文件的权限位(上传整套 `0o777`,下载只补可执行位);SFTP 标签的请求不受影响。批次准备失败时 worker 也发 `Idle`。
|
||||
|
||||
Generated
+2
-1
@@ -7686,7 +7686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shellrs"
|
||||
version = "0.1.0-beta.3"
|
||||
version = "0.1.0-beta.4"
|
||||
dependencies = [
|
||||
"alacritty_terminal",
|
||||
"anyhow",
|
||||
@@ -7695,6 +7695,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dirs 6.0.0",
|
||||
"dunce",
|
||||
"futures",
|
||||
"gpui-kit",
|
||||
"keyring",
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "shellrs"
|
||||
version = "0.1.0-beta.3"
|
||||
version = "0.1.0-beta.4"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
description = "SSH session manager with local terminal support"
|
||||
@@ -26,6 +26,8 @@ async-channel = "2"
|
||||
base64 = "0.22"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
dirs = "6"
|
||||
# Canonical local paths without the `\\?\` prefix std adds on Windows.
|
||||
dunce = "1"
|
||||
futures = "0.3"
|
||||
portable-pty = "0.9"
|
||||
rand = "0.9"
|
||||
|
||||
+4
-1
@@ -694,7 +694,10 @@ impl LocalDirectoryProvider for SystemLocalDirectoryProvider {
|
||||
.collect()
|
||||
}
|
||||
fn list(&self, path: &Path) -> Result<DirectoryListing> {
|
||||
let path = std::fs::canonicalize(path)?;
|
||||
// std's canonical form on Windows is `\\?\C:\Users`; the pane shows
|
||||
// and bookmarks the `C:\Users` people type, wherever that means the
|
||||
// same.
|
||||
let path = dunce::canonicalize(path)?;
|
||||
let mut entries = Vec::new();
|
||||
for entry in std::fs::read_dir(&path)? {
|
||||
let entry = entry?;
|
||||
|
||||
@@ -895,6 +895,19 @@ fn paths_and_journals_reject_unsafe_identity() {
|
||||
});
|
||||
}
|
||||
|
||||
/// On Windows std canonicalizes to `\\?\C:\…`, which the pane showed as is.
|
||||
#[test]
|
||||
fn local_listing_reports_the_path_people_type() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let listing = SystemLocalDirectoryProvider.list(tmp.path()).unwrap();
|
||||
assert!(!listing.path().starts_with(r"\\?\"), "{}", listing.path());
|
||||
// Still the directory asked for, links resolved.
|
||||
assert_eq!(
|
||||
std::fs::canonicalize(listing.path()).unwrap(),
|
||||
std::fs::canonicalize(tmp.path()).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn local_listing_rejects_unrepresentable_names() {
|
||||
|
||||
+2
-1
@@ -51,7 +51,8 @@ impl UploadBatch {
|
||||
let path = if let (Some(parent), Some(name)) = (absolute.parent(), absolute.file_name())
|
||||
{
|
||||
match tokio::fs::canonicalize(parent).await {
|
||||
Ok(parent) => parent.join(name),
|
||||
// Without Windows' `\\?\`, as the queue shows it.
|
||||
Ok(parent) => dunce::simplified(&parent).join(name),
|
||||
Err(_) => absolute,
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user