Skip to content

Commit ef2bb99

Browse files
committed
feat: configure per-service runtimes and restore editing shortcuts
1 parent 6e9eb08 commit ef2bb99

15 files changed

Lines changed: 1201 additions & 32 deletions

‎README.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ StackHub is a native macOS menu bar app built with SwiftUI. Run commands and she
2323

2424
## Features
2525

26-
- **Local services** — Start, stop, and restart project services with custom commands, working directories, and ports.
26+
- **Local services** — Start, stop, and restart project services with custom commands, working directories, and ports. Commands load your `.zshrc` so tools such as jenv and nvm can configure the environment.
27+
- **Per-service runtimes** — Select an installed JDK or Node.js version, or specify a custom path. No version manager required.
2728
- **Live logs** — View service output with ANSI colors in a dedicated log view.
2829
- **CI monitoring** — Follow GitHub Actions and multiple GitLab instances. Each refreshes independently and keeps cached results when offline.
2930
- **Secure credentials** — Store GitHub and GitLab tokens in macOS Keychain.
@@ -40,6 +41,12 @@ Requires **macOS 14 or later**. Release downloads are for **Apple Silicon**.
4041

4142
Configured ports are freed before a service starts; use only ports belonging to that service. Quitting or installing an update stops managed services; start them again when needed.
4243

44+
### JDK and Node.js
45+
46+
Expand **Runtime environment** when editing a service. Automatic mode reads the nearest `.java-version`, `.nvmrc`, or `.node-version` from the service directory upward, then uses your shell default if no version file exists. A manual selection takes priority. The panel shows the resolved version, path, and source.
47+
48+
JDKs, Homebrew Node.js, and installed jenv/nvm versions are detected locally; custom paths work too. Numeric versions can be matched without a manager; manager aliases such as `lts/*` require an installed version resolvable by that manager. Missing requested versions prevent startup, without downloading or silently choosing another version. Changes apply after restarting the service and do not change other services or your terminal environment.
49+
4350
## Build from source
4451

4552
Requires Xcode and Swift 5.9 or later.

‎README.zh-CN.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ StackHub 是一款使用 SwiftUI 构建的原生 macOS 菜单栏应用,将命
2323

2424
## 功能
2525

26-
- **本地服务** — 启动、停止和重启项目服务,自定义命令、工作目录和端口。
26+
- **本地服务** — 启动、停止和重启项目服务,自定义命令、工作目录和端口。启动时加载 `.zshrc`,沿用 jenv、nvm 等工具的环境配置。
27+
- **独立运行环境** — 为每个服务选择已安装的 JDK、Node.js 版本或指定路径,无需安装版本管理器。
2728
- **实时日志** — 在独立日志页面查看服务输出,支持 ANSI 彩色日志。
2829
- **CI 监控** — 关注 GitHub Actions 与多个 GitLab 实例,各自独立刷新,离线时保留缓存。
2930
- **凭据保护** — GitHub 和 GitLab Token 保存在 macOS 钥匙串中。
@@ -40,6 +41,12 @@ StackHub 是一款使用 SwiftUI 构建的原生 macOS 菜单栏应用,将命
4041

4142
配置的端口会在服务启动前释放,请只填写属于该服务的端口。退出或安装更新会停止托管服务,需要时请重新启动服务。
4243

44+
### JDK 与 Node.js
45+
46+
编辑服务时展开“运行环境”。自动模式从服务目录向上查找最近的 `.java-version`、`.nvmrc` 或 `.node-version`,没有版本文件时沿用 Shell 默认;手动选择的优先级最高。面板会显示解析后的版本、路径和来源。
47+
48+
自动检测本机 JDK、Homebrew Node.js 及 jenv/nvm 已安装版本,也支持自定义路径。数字版本不依赖管理器;`lts/*` 等管理器别名需要对应管理器能解析到已安装版本。指定版本缺失时阻止启动,不自动下载或静默换用其他版本。修改后重启该服务生效,不改变其他服务或终端的环境。
49+
4350
## 从源码运行
4451

4552
需要 Xcode 和 Swift 5.9 或更高版本。

‎docs/releases/v1.0.13.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## 更新内容
2+
3+
- 每个本地服务可独立选择 JDK 和 Node.js:自动识别、已安装版本或自定义路径,无需安装 jenv/nvm。
4+
- 自动模式优先读取 `.java-version`、`.nvmrc` 和 `.node-version`,没有项目配置时沿用 `.zshrc`;手动选择优先级最高,并显示实际版本、路径和来源。
5+
- 启动前检查指定运行时,版本缺失时明确报错,不自动下载或静默切换;环境仅作用于当前服务,重启后应用更改。
6+
- 修复日志无法使用快捷键复制、输入框无法粘贴的问题,补齐原生编辑菜单和常用快捷键。
7+
8+
## What's new
9+
10+
- Choose a JDK and Node.js runtime independently for each local service: automatic detection, an installed version, or a custom path. jenv/nvm are optional.
11+
- Automatic mode reads `.java-version`, `.nvmrc`, and `.node-version`, falling back to your `.zshrc` environment when no project version file exists. Manual selections take priority; resolved versions, paths, and sources are shown in settings.
12+
- Validate requested runtimes before startup. Missing versions report a clear error without downloading or silently selecting another version. Runtime changes are isolated per service and apply after restart.
13+
- Restore keyboard copy in logs and paste in text fields with a native Edit menu and standard editing shortcuts.
14+
15+
---
16+
17+
macOS 14+ · Apple Silicon
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import AppKit
2+
3+
/// The custom NSApplication entry point has no storyboard or SwiftUI App scene
4+
/// to provide Edit commands. Native text controls need these key equivalents.
5+
@MainActor
6+
enum AppMainMenu {
7+
static func make() -> NSMenu {
8+
let menu = NSMenu()
9+
let appMenu = NSMenu(title: "StackHub")
10+
appMenu.addItem(withTitle: L("退出 StackHub"), action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
11+
addSubmenu(appMenu, to: menu)
12+
13+
let editMenu = NSMenu(title: L("编辑"))
14+
editMenu.addItem(withTitle: L("撤销"), action: Selector(("undo:")), keyEquivalent: "z")
15+
let redo = editMenu.addItem(withTitle: L("重做"), action: Selector(("redo:")), keyEquivalent: "Z")
16+
redo.keyEquivalentModifierMask = [.command, .shift]
17+
editMenu.addItem(.separator())
18+
editMenu.addItem(withTitle: L("剪切"), action: #selector(NSText.cut(_:)), keyEquivalent: "x")
19+
editMenu.addItem(withTitle: L("复制"), action: #selector(NSText.copy(_:)), keyEquivalent: "c")
20+
editMenu.addItem(withTitle: L("粘贴"), action: #selector(NSText.paste(_:)), keyEquivalent: "v")
21+
editMenu.addItem(withTitle: L("全选"), action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")
22+
// Leave targets nil so AppKit validates and dispatches through the
23+
// focused control, including field editors, secure fields, and sheets.
24+
addSubmenu(editMenu, to: menu)
25+
return menu
26+
}
27+
28+
private static func addSubmenu(_ submenu: NSMenu, to menu: NSMenu) {
29+
let item = NSMenuItem(title: submenu.title, action: nil, keyEquivalent: "")
30+
item.submenu = submenu
31+
menu.addItem(item)
32+
}
33+
}

‎swift-prototype/Sources/Localization/en.lproj/Localizable.strings‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
"StackHub · 菜单栏" = "StackHub · Menu Bar";
2020
"退出" = "Quit";
2121
"退出 StackHub" = "Quit StackHub";
22+
"编辑" = "Edit";
23+
"撤销" = "Undo";
24+
"重做" = "Redo";
25+
"剪切" = "Cut";
26+
"复制" = "Copy";
27+
"粘贴" = "Paste";
28+
"全选" = "Select All";
2229
"管理 CI 实例" = "Manage CI instances";
2330
"实例管理" = "Instance management";
2431
"管理 GitHub 授权和 GitLab 实例" = "Manage GitHub authorization and GitLab instances";
@@ -279,3 +286,32 @@
279286
"检测全部" = "Check all";
280287
"重新检测连接" = "Check connection again";
281288
"检测于 %@" = "Checked at %@";
289+
290+
// Per-service runtimes
291+
"运行环境" = "Runtime environment";
292+
"自动(项目配置 / Shell 默认)" = "Automatic (project / shell default)";
293+
"已安装版本" = "Installed version";
294+
"自定义路径" = "Custom path";
295+
"Shell 默认" = "Shell default";
296+
"优先使用手动选择,其次读取项目版本文件,最后沿用 Shell 默认。修改后重启服务生效。" = "Uses your selection, then project version files, then the shell default. Restart the service to apply changes.";
297+
"正在检测运行环境…" = "Detecting runtimes…";
298+
"仅使用已安装版本,不自动下载" = "Uses installed versions; no automatic downloads";
299+
"重新检测运行环境" = "Refresh runtimes";
300+
"请选择版本" = "Choose a version";
301+
"当前路径:%@" = "Current path: %@";
302+
"JDK 主目录或 .jdk 目录" = "JDK home or .jdk directory";
303+
"Node 可执行文件路径" = "Node executable path";
304+
"%@ 自定义路径" = "%@ custom path";
305+
"选择运行时路径" = "Choose runtime path";
306+
"选择 %@ 路径" = "Choose %@ path";
307+
"来源:%@" = "Source: %@";
308+
"Shell 中未检测到 %@(仅在服务需要时配置)" = "%@ not found in the shell (configure only if needed)";
309+
"版本文件无效:%@" = "Invalid version file: %@";
310+
"请选择 %@ 的已安装版本或路径" = "Select an installed %@ version or path";
311+
"未找到 %@ %@(来自 %@)。请安装该版本或在运行环境中选择已安装版本。" = "%@ %@ from %@ was not found. Install it or select an installed version in Runtime environment.";
312+
"无法读取 Shell 环境,请检查 .zshrc" = "Cannot read the shell environment. Check .zshrc.";
313+
"%@ 路径不可用:%@" = "%@ path is unavailable: %@";
314+
"%@ 路径不是完整 JDK:%@" = "%@ path is not a complete JDK: %@";
315+
"无法识别 %@ 版本:%@" = "Cannot determine the %@ version: %@";
316+
"运行环境检测超时,请检查 Shell 启动配置和运行时路径" = "Runtime detection timed out. Check shell startup files and runtime paths.";
317+
"运行环境检测失败,请检查 Shell 启动配置和运行时路径" = "Runtime detection failed. Check shell startup files and runtime paths.";

‎swift-prototype/Sources/Localization/zh-Hans.lproj/Localizable.strings‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,42 @@
4343
"检测于 %@" = "检测于 %@";
4444
"最多关注 %ld 个项目,请先取消一个关注。" = "最多关注 %ld 个项目,请先取消一个关注。";
4545
"管理关注项目" = "管理关注项目";
46+
"编辑" = "编辑";
47+
"撤销" = "撤销";
48+
"重做" = "重做";
49+
"剪切" = "剪切";
50+
"复制" = "复制";
51+
"粘贴" = "粘贴";
52+
"全选" = "全选";
4653
"取消关注 %@" = "取消关注 %@";
4754
"最近流水线" = "最近流水线";
4855
"显示最近 20 个项目的流水线;关注项目会在上方保留最近 5 条。" = "显示最近 20 个项目的流水线;关注项目会在上方保留最近 5 条。";
56+
57+
// Per-service runtimes
58+
"运行环境" = "运行环境";
59+
"自动(项目配置 / Shell 默认)" = "自动(项目配置 / Shell 默认)";
60+
"已安装版本" = "已安装版本";
61+
"自定义路径" = "自定义路径";
62+
"Shell 默认" = "Shell 默认";
63+
"优先使用手动选择,其次读取项目版本文件,最后沿用 Shell 默认。修改后重启服务生效。" = "优先使用手动选择,其次读取项目版本文件,最后沿用 Shell 默认。修改后重启服务生效。";
64+
"正在检测运行环境…" = "正在检测运行环境…";
65+
"仅使用已安装版本,不自动下载" = "仅使用已安装版本,不自动下载";
66+
"重新检测运行环境" = "重新检测运行环境";
67+
"请选择版本" = "请选择版本";
68+
"当前路径:%@" = "当前路径:%@";
69+
"JDK 主目录或 .jdk 目录" = "JDK 主目录或 .jdk 目录";
70+
"Node 可执行文件路径" = "Node 可执行文件路径";
71+
"%@ 自定义路径" = "%@ 自定义路径";
72+
"选择运行时路径" = "选择运行时路径";
73+
"选择 %@ 路径" = "选择 %@ 路径";
74+
"来源:%@" = "来源:%@";
75+
"Shell 中未检测到 %@(仅在服务需要时配置)" = "Shell 中未检测到 %@(仅在服务需要时配置)";
76+
"版本文件无效:%@" = "版本文件无效:%@";
77+
"请选择 %@ 的已安装版本或路径" = "请选择 %@ 的已安装版本或路径";
78+
"未找到 %@ %@(来自 %@)。请安装该版本或在运行环境中选择已安装版本。" = "未找到 %@ %@(来自 %@)。请安装该版本或在运行环境中选择已安装版本。";
79+
"无法读取 Shell 环境,请检查 .zshrc" = "无法读取 Shell 环境,请检查 .zshrc";
80+
"%@ 路径不可用:%@" = "%@ 路径不可用:%@";
81+
"%@ 路径不是完整 JDK:%@" = "%@ 路径不是完整 JDK:%@";
82+
"无法识别 %@ 版本:%@" = "无法识别 %@ 版本:%@";
83+
"运行环境检测超时,请检查 Shell 启动配置和运行时路径" = "运行环境检测超时,请检查 Shell 启动配置和运行时路径";
84+
"运行环境检测失败,请检查 Shell 启动配置和运行时路径" = "运行环境检测失败,请检查 Shell 启动配置和运行时路径";

0 commit comments

Comments
 (0)