colyn-cli 3.1.4
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/README-en.md +85 -0
- package/README.md +85 -0
- package/bin/colyn +25 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.helpers.d.ts +48 -0
- package/dist/commands/add.helpers.d.ts.map +1 -0
- package/dist/commands/add.helpers.js +251 -0
- package/dist/commands/add.helpers.js.map +1 -0
- package/dist/commands/add.js +286 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/checkout.d.ts +16 -0
- package/dist/commands/checkout.d.ts.map +1 -0
- package/dist/commands/checkout.js +428 -0
- package/dist/commands/checkout.js.map +1 -0
- package/dist/commands/completion.d.ts +10 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +380 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +338 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/index.d.ts +10 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +41 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +374 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.handlers.d.ts +27 -0
- package/dist/commands/init.handlers.d.ts.map +1 -0
- package/dist/commands/init.handlers.js +314 -0
- package/dist/commands/init.handlers.js.map +1 -0
- package/dist/commands/init.helpers.d.ts +42 -0
- package/dist/commands/init.helpers.d.ts.map +1 -0
- package/dist/commands/init.helpers.js +275 -0
- package/dist/commands/init.helpers.js.map +1 -0
- package/dist/commands/init.js +61 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.d.ts +6 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.helpers.d.ts +32 -0
- package/dist/commands/install.helpers.d.ts.map +1 -0
- package/dist/commands/install.helpers.js +124 -0
- package/dist/commands/install.helpers.js.map +1 -0
- package/dist/commands/install.js +104 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list-project.d.ts +11 -0
- package/dist/commands/list-project.d.ts.map +1 -0
- package/dist/commands/list-project.js +260 -0
- package/dist/commands/list-project.js.map +1 -0
- package/dist/commands/list.d.ts +15 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.helpers.d.ts +50 -0
- package/dist/commands/list.helpers.d.ts.map +1 -0
- package/dist/commands/list.helpers.js +143 -0
- package/dist/commands/list.helpers.js.map +1 -0
- package/dist/commands/list.js +530 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/merge.d.ts +6 -0
- package/dist/commands/merge.d.ts.map +1 -0
- package/dist/commands/merge.helpers.d.ts +74 -0
- package/dist/commands/merge.helpers.d.ts.map +1 -0
- package/dist/commands/merge.helpers.js +307 -0
- package/dist/commands/merge.helpers.js.map +1 -0
- package/dist/commands/merge.js +260 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/release.d.ts +6 -0
- package/dist/commands/release.d.ts.map +1 -0
- package/dist/commands/release.helpers.d.ts +61 -0
- package/dist/commands/release.helpers.d.ts.map +1 -0
- package/dist/commands/release.helpers.js +277 -0
- package/dist/commands/release.helpers.js.map +1 -0
- package/dist/commands/release.js +127 -0
- package/dist/commands/release.js.map +1 -0
- package/dist/commands/remove.d.ts +6 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.helpers.d.ts +59 -0
- package/dist/commands/remove.helpers.d.ts.map +1 -0
- package/dist/commands/remove.helpers.js +190 -0
- package/dist/commands/remove.helpers.js.map +1 -0
- package/dist/commands/remove.js +137 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/repair.d.ts +6 -0
- package/dist/commands/repair.d.ts.map +1 -0
- package/dist/commands/repair.helpers.d.ts +5 -0
- package/dist/commands/repair.helpers.d.ts.map +1 -0
- package/dist/commands/repair.helpers.js +499 -0
- package/dist/commands/repair.helpers.js.map +1 -0
- package/dist/commands/repair.js +28 -0
- package/dist/commands/repair.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +116 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/system-integration.d.ts +6 -0
- package/dist/commands/system-integration.d.ts.map +1 -0
- package/dist/commands/system-integration.helpers.d.ts +70 -0
- package/dist/commands/system-integration.helpers.d.ts.map +1 -0
- package/dist/commands/system-integration.helpers.js +313 -0
- package/dist/commands/system-integration.helpers.js.map +1 -0
- package/dist/commands/system-integration.js +132 -0
- package/dist/commands/system-integration.js.map +1 -0
- package/dist/commands/tmux.d.ts +11 -0
- package/dist/commands/tmux.d.ts.map +1 -0
- package/dist/commands/tmux.js +500 -0
- package/dist/commands/tmux.js.map +1 -0
- package/dist/commands/todo.d.ts +6 -0
- package/dist/commands/todo.d.ts.map +1 -0
- package/dist/commands/todo.helpers.d.ts +50 -0
- package/dist/commands/todo.helpers.d.ts.map +1 -0
- package/dist/commands/todo.helpers.js +297 -0
- package/dist/commands/todo.helpers.js.map +1 -0
- package/dist/commands/todo.js +579 -0
- package/dist/commands/todo.js.map +1 -0
- package/dist/commands/update.d.ts +18 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.helpers.d.ts +89 -0
- package/dist/commands/update.helpers.d.ts.map +1 -0
- package/dist/commands/update.helpers.js +335 -0
- package/dist/commands/update.helpers.js.map +1 -0
- package/dist/commands/update.js +187 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/core/config-loader.d.ts +47 -0
- package/dist/core/config-loader.d.ts.map +1 -0
- package/dist/core/config-loader.js +188 -0
- package/dist/core/config-loader.js.map +1 -0
- package/dist/core/config-merger.d.ts +29 -0
- package/dist/core/config-merger.d.ts.map +1 -0
- package/dist/core/config-merger.js +100 -0
- package/dist/core/config-merger.js.map +1 -0
- package/dist/core/config-migration.d.ts +64 -0
- package/dist/core/config-migration.d.ts.map +1 -0
- package/dist/core/config-migration.js +340 -0
- package/dist/core/config-migration.js.map +1 -0
- package/dist/core/config-new.d.ts +19 -0
- package/dist/core/config-new.d.ts.map +1 -0
- package/dist/core/config-new.js +58 -0
- package/dist/core/config-new.js.map +1 -0
- package/dist/core/config-schema.d.ts +221 -0
- package/dist/core/config-schema.d.ts.map +1 -0
- package/dist/core/config-schema.js +168 -0
- package/dist/core/config-schema.js.map +1 -0
- package/dist/core/config.d.ts +55 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/dev-server.d.ts +29 -0
- package/dist/core/dev-server.d.ts.map +1 -0
- package/dist/core/dev-server.js +54 -0
- package/dist/core/dev-server.js.map +1 -0
- package/dist/core/discovery.d.ts +51 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +247 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/env.d.ts +13 -0
- package/dist/core/env.d.ts.map +1 -0
- package/dist/core/env.js +75 -0
- package/dist/core/env.js.map +1 -0
- package/dist/core/git.d.ts +31 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +56 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/paths.d.ts +86 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +256 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/tmux-config.d.ts +174 -0
- package/dist/core/tmux-config.d.ts.map +1 -0
- package/dist/core/tmux-config.js +545 -0
- package/dist/core/tmux-config.js.map +1 -0
- package/dist/core/tmux-config.legacy.d.ts +243 -0
- package/dist/core/tmux-config.legacy.d.ts.map +1 -0
- package/dist/core/tmux-config.legacy.js +896 -0
- package/dist/core/tmux-config.legacy.js.map +1 -0
- package/dist/core/tmux.d.ts +214 -0
- package/dist/core/tmux.d.ts.map +1 -0
- package/dist/core/tmux.js +712 -0
- package/dist/core/tmux.js.map +1 -0
- package/dist/core/toolchain-resolver.d.ts +51 -0
- package/dist/core/toolchain-resolver.d.ts.map +1 -0
- package/dist/core/toolchain-resolver.js +364 -0
- package/dist/core/toolchain-resolver.js.map +1 -0
- package/dist/core/worktree-status.d.ts +20 -0
- package/dist/core/worktree-status.d.ts.map +1 -0
- package/dist/core/worktree-status.js +67 -0
- package/dist/core/worktree-status.js.map +1 -0
- package/dist/i18n/index.d.ts +36 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +157 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locales/en.d.ts +866 -0
- package/dist/i18n/locales/en.d.ts.map +1 -0
- package/dist/i18n/locales/en.js +985 -0
- package/dist/i18n/locales/en.js.map +1 -0
- package/dist/i18n/locales/zh-CN.d.ts +865 -0
- package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
- package/dist/i18n/locales/zh-CN.js +985 -0
- package/dist/i18n/locales/zh-CN.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/builtin/gradle.d.ts +9 -0
- package/dist/plugins/builtin/gradle.d.ts.map +1 -0
- package/dist/plugins/builtin/gradle.js +164 -0
- package/dist/plugins/builtin/gradle.js.map +1 -0
- package/dist/plugins/builtin/maven.d.ts +9 -0
- package/dist/plugins/builtin/maven.d.ts.map +1 -0
- package/dist/plugins/builtin/maven.js +127 -0
- package/dist/plugins/builtin/maven.js.map +1 -0
- package/dist/plugins/builtin/npm.d.ts +9 -0
- package/dist/plugins/builtin/npm.d.ts.map +1 -0
- package/dist/plugins/builtin/npm.js +238 -0
- package/dist/plugins/builtin/npm.js.map +1 -0
- package/dist/plugins/builtin/pip.d.ts +9 -0
- package/dist/plugins/builtin/pip.d.ts.map +1 -0
- package/dist/plugins/builtin/pip.js +210 -0
- package/dist/plugins/builtin/pip.js.map +1 -0
- package/dist/plugins/builtin/xcode.d.ts +12 -0
- package/dist/plugins/builtin/xcode.d.ts.map +1 -0
- package/dist/plugins/builtin/xcode.js +438 -0
- package/dist/plugins/builtin/xcode.js.map +1 -0
- package/dist/plugins/index.d.ts +13 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +24 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/manager.d.ts +93 -0
- package/dist/plugins/manager.d.ts.map +1 -0
- package/dist/plugins/manager.js +270 -0
- package/dist/plugins/manager.js.map +1 -0
- package/dist/plugins/utils.d.ts +42 -0
- package/dist/plugins/utils.d.ts.map +1 -0
- package/dist/plugins/utils.js +175 -0
- package/dist/plugins/utils.js.map +1 -0
- package/dist/types/index.d.ts +104 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +21 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/plugin.d.ts +200 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +19 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/utils/logger.d.ts +42 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +80 -0
- package/dist/utils/logger.js.map +1 -0
- package/docs/en/manual/04-command-reference/README.md +58 -0
- package/docs/en/manual/README.md +108 -0
- package/docs/zh-CN/manual/04-command-reference/README.md +58 -0
- package/docs/zh-CN/manual/README.md +108 -0
- package/package.json +65 -0
- package/shell/colyn.sh +55 -0
- package/shell/completion.bash +270 -0
- package/shell/completion.zsh +167 -0
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tmux Pane 命令配置模块
|
|
3
|
+
*
|
|
4
|
+
* 提供 tmux pane 命令的配置加载和自动检测功能
|
|
5
|
+
*
|
|
6
|
+
* 配置文件层级(优先级从低到高):
|
|
7
|
+
* 1. 内置默认值
|
|
8
|
+
* 2. 用户级配置:~/.config/colyn/settings.json
|
|
9
|
+
* 3. 项目级配置:{projectRoot}/.colyn/settings.json
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs/promises';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import { getDevServerCommand } from './dev-server.js';
|
|
15
|
+
/**
|
|
16
|
+
* 内置命令常量
|
|
17
|
+
*/
|
|
18
|
+
export const BUILTIN_COMMANDS = {
|
|
19
|
+
/** 启动 dev server */
|
|
20
|
+
AUTO_DEV_SERVER: 'start dev server',
|
|
21
|
+
/** 继续 Claude 会话 */
|
|
22
|
+
AUTO_CLAUDE: 'continue claude session',
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 窗格名称映射表
|
|
26
|
+
* 定义每种布局支持的窗格名称
|
|
27
|
+
*/
|
|
28
|
+
export const LAYOUT_PANES = {
|
|
29
|
+
'single-pane': [],
|
|
30
|
+
'two-pane-horizontal': ['leftPane', 'rightPane'],
|
|
31
|
+
'two-pane-vertical': ['topPane', 'bottomPane'],
|
|
32
|
+
'three-pane': ['leftPane', 'topRightPane', 'bottomRightPane'],
|
|
33
|
+
'four-pane': ['topLeftPane', 'topRightPane', 'bottomLeftPane', 'bottomRightPane'],
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 当前配置文件版本号
|
|
37
|
+
*/
|
|
38
|
+
export const CURRENT_CONFIG_VERSION = 3;
|
|
39
|
+
/**
|
|
40
|
+
* 配置文件路径
|
|
41
|
+
*/
|
|
42
|
+
const SETTINGS_FILENAME = 'settings.json';
|
|
43
|
+
const CONFIG_DIR = '.colyn';
|
|
44
|
+
/**
|
|
45
|
+
* 默认配置(三窗格布局)
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_CONFIG = {
|
|
48
|
+
autoRun: true,
|
|
49
|
+
layout: 'three-pane',
|
|
50
|
+
leftPane: {
|
|
51
|
+
command: BUILTIN_COMMANDS.AUTO_CLAUDE,
|
|
52
|
+
size: '60%',
|
|
53
|
+
},
|
|
54
|
+
topRightPane: {
|
|
55
|
+
command: BUILTIN_COMMANDS.AUTO_DEV_SERVER,
|
|
56
|
+
size: '30%',
|
|
57
|
+
},
|
|
58
|
+
bottomRightPane: {
|
|
59
|
+
command: null,
|
|
60
|
+
size: '70%',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* 系统内置的分支特定默认配置
|
|
65
|
+
* 优先级最低,会被任何用户或项目配置覆盖
|
|
66
|
+
*/
|
|
67
|
+
const BUILTIN_BRANCH_DEFAULTS = {
|
|
68
|
+
// Main 分支默认使用单窗格布局
|
|
69
|
+
main: {
|
|
70
|
+
layout: 'single-pane',
|
|
71
|
+
autoRun: false,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* 获取用户级配置目录
|
|
76
|
+
* 遵循 XDG Base Directory 规范
|
|
77
|
+
*/
|
|
78
|
+
export function getUserConfigDir() {
|
|
79
|
+
return path.join(os.homedir(), '.config', 'colyn');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 获取用户级设置文件路径
|
|
83
|
+
*/
|
|
84
|
+
export function getUserConfigPath() {
|
|
85
|
+
return path.join(getUserConfigDir(), SETTINGS_FILENAME);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 获取项目级设置文件路径
|
|
89
|
+
* @param projectRoot 项目根目录
|
|
90
|
+
*/
|
|
91
|
+
export function getProjectConfigPath(projectRoot) {
|
|
92
|
+
return path.join(projectRoot, CONFIG_DIR, SETTINGS_FILENAME);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 从设置文件加载 tmux 配置
|
|
96
|
+
* @param configPath 设置文件路径
|
|
97
|
+
* @returns tmux 配置对象,如果文件不存在或无法解析则返回 null
|
|
98
|
+
*/
|
|
99
|
+
async function loadConfigFromFile(configPath) {
|
|
100
|
+
try {
|
|
101
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
102
|
+
const settings = JSON.parse(content);
|
|
103
|
+
return settings.tmux ?? null;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Migration 函数列表
|
|
111
|
+
* 索引 i 的函数负责从 version i 迁移到 version i+1
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* 废弃的内置命令常量
|
|
115
|
+
*/
|
|
116
|
+
const DEPRECATED_BUILTIN_COMMANDS = {
|
|
117
|
+
AUTO_CLAUDE_DANGEROUSLY: 'auto continues claude session with dangerously skip permissions',
|
|
118
|
+
AUTO_CLAUDE_OLD: 'auto continues claude session',
|
|
119
|
+
AUTO_DEV_SERVER_OLD: 'auto start dev server',
|
|
120
|
+
};
|
|
121
|
+
const MIGRATIONS = [
|
|
122
|
+
// Migration 0 -> 1: 添加版本号
|
|
123
|
+
(settings) => {
|
|
124
|
+
// 版本 0(无版本号)-> 版本 1
|
|
125
|
+
// 这是第一次引入版本号,无需修改其他字段
|
|
126
|
+
return {
|
|
127
|
+
...settings,
|
|
128
|
+
version: 1,
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
// Migration 1 -> 2: 迁移配置结构和废弃的内置命令
|
|
132
|
+
(settings) => {
|
|
133
|
+
const migrateSettings = (s) => {
|
|
134
|
+
const result = { ...s };
|
|
135
|
+
// 1. 迁移 npm 和 claudeCommand 到 systemCommands
|
|
136
|
+
const hasOldNpm = 'npm' in s && s.npm !== undefined;
|
|
137
|
+
const hasOldClaudeCommand = 'claudeCommand' in s && s.claudeCommand !== undefined;
|
|
138
|
+
if (hasOldNpm || hasOldClaudeCommand) {
|
|
139
|
+
result.systemCommands = {
|
|
140
|
+
...result.systemCommands,
|
|
141
|
+
};
|
|
142
|
+
if (hasOldNpm) {
|
|
143
|
+
result.systemCommands.npm = s.npm;
|
|
144
|
+
delete result.npm;
|
|
145
|
+
}
|
|
146
|
+
if (hasOldClaudeCommand) {
|
|
147
|
+
result.systemCommands.claude = s.claudeCommand;
|
|
148
|
+
delete result.claudeCommand;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// 2. 迁移废弃的内置命令:auto continues claude session with dangerously skip permissions
|
|
152
|
+
if (result.tmux) {
|
|
153
|
+
let needsDangerouslySkipPermissions = false;
|
|
154
|
+
// 检查所有 pane 配置
|
|
155
|
+
const checkAndMigratePane = (paneConfig) => {
|
|
156
|
+
if (!paneConfig || !paneConfig.command) {
|
|
157
|
+
return paneConfig;
|
|
158
|
+
}
|
|
159
|
+
if (paneConfig.command === DEPRECATED_BUILTIN_COMMANDS.AUTO_CLAUDE_DANGEROUSLY) {
|
|
160
|
+
needsDangerouslySkipPermissions = true;
|
|
161
|
+
return {
|
|
162
|
+
...paneConfig,
|
|
163
|
+
command: BUILTIN_COMMANDS.AUTO_CLAUDE,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return paneConfig;
|
|
167
|
+
};
|
|
168
|
+
// 迁移所有 pane 配置
|
|
169
|
+
const migratedTmux = { ...result.tmux };
|
|
170
|
+
migratedTmux.leftPane = checkAndMigratePane(migratedTmux.leftPane);
|
|
171
|
+
migratedTmux.rightPane = checkAndMigratePane(migratedTmux.rightPane);
|
|
172
|
+
migratedTmux.topPane = checkAndMigratePane(migratedTmux.topPane);
|
|
173
|
+
migratedTmux.bottomPane = checkAndMigratePane(migratedTmux.bottomPane);
|
|
174
|
+
migratedTmux.topRightPane = checkAndMigratePane(migratedTmux.topRightPane);
|
|
175
|
+
migratedTmux.bottomRightPane = checkAndMigratePane(migratedTmux.bottomRightPane);
|
|
176
|
+
migratedTmux.topLeftPane = checkAndMigratePane(migratedTmux.topLeftPane);
|
|
177
|
+
migratedTmux.bottomLeftPane = checkAndMigratePane(migratedTmux.bottomLeftPane);
|
|
178
|
+
result.tmux = migratedTmux;
|
|
179
|
+
// 如果检测到废弃的命令,添加 --dangerously-skip-permissions 到 systemCommands.claude
|
|
180
|
+
if (needsDangerouslySkipPermissions) {
|
|
181
|
+
result.systemCommands = result.systemCommands || {};
|
|
182
|
+
const currentClaudeCommand = result.systemCommands.claude || 'claude';
|
|
183
|
+
// 如果还没有 --dangerously-skip-permissions 参数,添加它
|
|
184
|
+
if (!currentClaudeCommand.includes('--dangerously-skip-permissions')) {
|
|
185
|
+
result.systemCommands.claude = `${currentClaudeCommand} --dangerously-skip-permissions`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// 3. 递归处理 branchOverrides
|
|
190
|
+
if (result.branchOverrides) {
|
|
191
|
+
const migratedOverrides = {};
|
|
192
|
+
for (const [branch, branchSettings] of Object.entries(result.branchOverrides)) {
|
|
193
|
+
migratedOverrides[branch] = migrateSettings(branchSettings);
|
|
194
|
+
}
|
|
195
|
+
result.branchOverrides = migratedOverrides;
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
...migrateSettings(settings),
|
|
201
|
+
version: 2,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
// Migration 2 -> 3: 重命名内置命令(去掉 "auto" 前缀)
|
|
205
|
+
(settings) => {
|
|
206
|
+
const migrateSettings = (s) => {
|
|
207
|
+
const result = { ...s };
|
|
208
|
+
// 迁移内置命令名称
|
|
209
|
+
if (result.tmux) {
|
|
210
|
+
const migrateCommand = (command) => {
|
|
211
|
+
if (command === DEPRECATED_BUILTIN_COMMANDS.AUTO_CLAUDE_OLD) {
|
|
212
|
+
return BUILTIN_COMMANDS.AUTO_CLAUDE;
|
|
213
|
+
}
|
|
214
|
+
if (command === DEPRECATED_BUILTIN_COMMANDS.AUTO_DEV_SERVER_OLD) {
|
|
215
|
+
return BUILTIN_COMMANDS.AUTO_DEV_SERVER;
|
|
216
|
+
}
|
|
217
|
+
return command;
|
|
218
|
+
};
|
|
219
|
+
const migratePaneConfig = (paneConfig) => {
|
|
220
|
+
if (!paneConfig || !paneConfig.command) {
|
|
221
|
+
return paneConfig;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
...paneConfig,
|
|
225
|
+
command: migrateCommand(paneConfig.command),
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
// 迁移所有 pane 配置
|
|
229
|
+
const migratedTmux = { ...result.tmux };
|
|
230
|
+
migratedTmux.leftPane = migratePaneConfig(migratedTmux.leftPane);
|
|
231
|
+
migratedTmux.rightPane = migratePaneConfig(migratedTmux.rightPane);
|
|
232
|
+
migratedTmux.topPane = migratePaneConfig(migratedTmux.topPane);
|
|
233
|
+
migratedTmux.bottomPane = migratePaneConfig(migratedTmux.bottomPane);
|
|
234
|
+
migratedTmux.topRightPane = migratePaneConfig(migratedTmux.topRightPane);
|
|
235
|
+
migratedTmux.bottomRightPane = migratePaneConfig(migratedTmux.bottomRightPane);
|
|
236
|
+
migratedTmux.topLeftPane = migratePaneConfig(migratedTmux.topLeftPane);
|
|
237
|
+
migratedTmux.bottomLeftPane = migratePaneConfig(migratedTmux.bottomLeftPane);
|
|
238
|
+
result.tmux = migratedTmux;
|
|
239
|
+
}
|
|
240
|
+
// 递归处理 branchOverrides
|
|
241
|
+
if (result.branchOverrides) {
|
|
242
|
+
const migratedOverrides = {};
|
|
243
|
+
for (const [branch, branchSettings] of Object.entries(result.branchOverrides)) {
|
|
244
|
+
migratedOverrides[branch] = migrateSettings(branchSettings);
|
|
245
|
+
}
|
|
246
|
+
result.branchOverrides = migratedOverrides;
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
};
|
|
250
|
+
return {
|
|
251
|
+
...migrateSettings(settings),
|
|
252
|
+
version: 3,
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
/**
|
|
257
|
+
* 执行配置迁移
|
|
258
|
+
* @param settings 原始配置
|
|
259
|
+
* @returns 迁移后的配置
|
|
260
|
+
*/
|
|
261
|
+
function migrateSettings(settings) {
|
|
262
|
+
const currentVersion = settings.version ?? 0;
|
|
263
|
+
// 如果已经是最新版本,直接返回
|
|
264
|
+
if (currentVersion >= CURRENT_CONFIG_VERSION) {
|
|
265
|
+
return settings;
|
|
266
|
+
}
|
|
267
|
+
// 按顺序执行所有必要的 migration
|
|
268
|
+
let migratedSettings = { ...settings };
|
|
269
|
+
for (let version = currentVersion; version < CURRENT_CONFIG_VERSION; version++) {
|
|
270
|
+
const migrationFn = MIGRATIONS[version];
|
|
271
|
+
if (migrationFn) {
|
|
272
|
+
migratedSettings = migrationFn(migratedSettings);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// 如果缺少某个版本的 migration 函数,直接更新版本号
|
|
276
|
+
migratedSettings.version = version + 1;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return migratedSettings;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 保存配置到文件
|
|
283
|
+
* @param configPath 配置文件路径
|
|
284
|
+
* @param settings 配置对象
|
|
285
|
+
*/
|
|
286
|
+
async function saveSettingsToFile(configPath, settings) {
|
|
287
|
+
// 确保配置有版本号
|
|
288
|
+
const settingsWithVersion = {
|
|
289
|
+
...settings,
|
|
290
|
+
version: settings.version ?? CURRENT_CONFIG_VERSION,
|
|
291
|
+
};
|
|
292
|
+
// 确保目录存在
|
|
293
|
+
const dir = path.dirname(configPath);
|
|
294
|
+
await fs.mkdir(dir, { recursive: true });
|
|
295
|
+
// 写入文件
|
|
296
|
+
await fs.writeFile(configPath, JSON.stringify(settingsWithVersion, null, 2), 'utf-8');
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 从设置文件加载完整设置(用于 config 命令显示)
|
|
300
|
+
* 自动执行必要的 migration 并保存更新后的配置
|
|
301
|
+
* @param configPath 设置文件路径
|
|
302
|
+
* @returns 完整设置对象,如果文件不存在或无法解析则返回 null
|
|
303
|
+
*/
|
|
304
|
+
export async function loadSettingsFromFile(configPath) {
|
|
305
|
+
try {
|
|
306
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
307
|
+
const rawSettings = JSON.parse(content);
|
|
308
|
+
// 执行 migration
|
|
309
|
+
const migratedSettings = migrateSettings(rawSettings);
|
|
310
|
+
// 如果版本号有变化,保存更新后的配置
|
|
311
|
+
const oldVersion = rawSettings.version ?? 0;
|
|
312
|
+
const newVersion = migratedSettings.version ?? CURRENT_CONFIG_VERSION;
|
|
313
|
+
if (oldVersion !== newVersion) {
|
|
314
|
+
await saveSettingsToFile(configPath, migratedSettings);
|
|
315
|
+
}
|
|
316
|
+
return migratedSettings;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* 合并单个 Pane 配置
|
|
324
|
+
*/
|
|
325
|
+
function mergePaneConfig(base, override) {
|
|
326
|
+
if (override === undefined) {
|
|
327
|
+
return base;
|
|
328
|
+
}
|
|
329
|
+
if (base === undefined) {
|
|
330
|
+
return override;
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
...base,
|
|
334
|
+
...override,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 深度合并两个配置对象
|
|
339
|
+
* 后者的值会覆盖前者,但只覆盖明确设置的字段
|
|
340
|
+
* @param base 基础配置
|
|
341
|
+
* @param override 覆盖配置
|
|
342
|
+
*/
|
|
343
|
+
function mergeConfigs(base, override) {
|
|
344
|
+
const result = { ...base };
|
|
345
|
+
// 合并 autoRun(如果 override 中明确设置了)
|
|
346
|
+
if (override.autoRun !== undefined) {
|
|
347
|
+
result.autoRun = override.autoRun;
|
|
348
|
+
}
|
|
349
|
+
// 合并各个 pane 配置
|
|
350
|
+
result.leftPane = mergePaneConfig(base.leftPane, override.leftPane);
|
|
351
|
+
result.topRightPane = mergePaneConfig(base.topRightPane, override.topRightPane);
|
|
352
|
+
result.bottomRightPane = mergePaneConfig(base.bottomRightPane, override.bottomRightPane);
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 加载 tmux 配置(两层配置机制)
|
|
357
|
+
*
|
|
358
|
+
* 配置加载顺序(优先级从低到高):
|
|
359
|
+
* 1. 内置默认值
|
|
360
|
+
* 2. 用户级配置:~/.config/colyn/settings.json 中的 tmux 字段
|
|
361
|
+
* 3. 项目级配置:{projectRoot}/.colyn/settings.json 中的 tmux 字段
|
|
362
|
+
*
|
|
363
|
+
* @param projectRoot 项目根目录
|
|
364
|
+
* @returns 合并后的配置对象
|
|
365
|
+
*/
|
|
366
|
+
export async function loadTmuxConfig(projectRoot) {
|
|
367
|
+
// 并行加载用户级和项目级配置
|
|
368
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
369
|
+
loadConfigFromFile(getUserConfigPath()),
|
|
370
|
+
loadConfigFromFile(getProjectConfigPath(projectRoot)),
|
|
371
|
+
]);
|
|
372
|
+
// 从空对象开始,依次合并
|
|
373
|
+
let config = {};
|
|
374
|
+
// 合并用户级配置
|
|
375
|
+
if (userConfig) {
|
|
376
|
+
config = mergeConfigs(config, userConfig);
|
|
377
|
+
}
|
|
378
|
+
// 合并项目级配置(优先级更高)
|
|
379
|
+
if (projectConfig) {
|
|
380
|
+
config = mergeConfigs(config, projectConfig);
|
|
381
|
+
}
|
|
382
|
+
return config;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 检查 worktree 是否存在 Claude session
|
|
386
|
+
* 通过检查 ~/.claude/projects/{encodedPath} 下是否存在会话文件来判断
|
|
387
|
+
* @param worktreePath worktree 路径
|
|
388
|
+
*/
|
|
389
|
+
export async function hasClaudeSession(worktreePath) {
|
|
390
|
+
try {
|
|
391
|
+
const projectDir = getClaudeProjectDir(worktreePath);
|
|
392
|
+
const stat = await fs.stat(projectDir);
|
|
393
|
+
if (!stat.isDirectory()) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
const files = await fs.readdir(projectDir);
|
|
397
|
+
return files.some((file) => file.endsWith('.jsonl'));
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 获取 Claude 项目会话目录
|
|
405
|
+
* Claude CLI 将每个目录的会话写入 ~/.claude/projects/{encodedPath}
|
|
406
|
+
*/
|
|
407
|
+
function getClaudeProjectDir(worktreePath) {
|
|
408
|
+
const encodedPath = encodeClaudeProjectPath(worktreePath);
|
|
409
|
+
return path.join(os.homedir(), '.claude', 'projects', encodedPath);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Claude 项目目录编码规则:将路径分隔符替换为 '-'
|
|
413
|
+
* 例:/Users/name/project -> -Users-name-project
|
|
414
|
+
*/
|
|
415
|
+
function encodeClaudeProjectPath(worktreePath) {
|
|
416
|
+
return worktreePath.split(path.sep).join('-');
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* 解析 Claude 会话命令
|
|
420
|
+
* - 如果存在 .claude 目录,运行 `claude -c` 继续会话
|
|
421
|
+
* - 否则运行 `claude` 启动新会话
|
|
422
|
+
* @param worktreePath worktree 路径
|
|
423
|
+
* @param claudeCommand 自定义 Claude 命令(默认为 'claude')
|
|
424
|
+
*/
|
|
425
|
+
async function resolveClaudeCommand(worktreePath, claudeCommand = 'claude') {
|
|
426
|
+
const hasSession = await hasClaudeSession(worktreePath);
|
|
427
|
+
return hasSession ? `${claudeCommand} -c` : claudeCommand;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 解析 dev server 命令
|
|
431
|
+
* 使用 dev-server 模块检测 dev 命令
|
|
432
|
+
* @param worktreePath worktree 路径
|
|
433
|
+
*/
|
|
434
|
+
async function resolveDevServerCommand(worktreePath) {
|
|
435
|
+
return await getDevServerCommand(worktreePath);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* 解析单个 pane 命令
|
|
439
|
+
* @param command 命令配置值
|
|
440
|
+
* @param worktreePath worktree 路径
|
|
441
|
+
* @param claudeCommand 自定义 Claude 命令
|
|
442
|
+
*/
|
|
443
|
+
async function resolvePaneCommand(command, worktreePath, claudeCommand) {
|
|
444
|
+
// 如果是 null 或 undefined,不执行命令
|
|
445
|
+
if (command === null || command === undefined) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
// 处理内置命令
|
|
449
|
+
switch (command) {
|
|
450
|
+
case BUILTIN_COMMANDS.AUTO_CLAUDE:
|
|
451
|
+
return await resolveClaudeCommand(worktreePath, claudeCommand);
|
|
452
|
+
case BUILTIN_COMMANDS.AUTO_DEV_SERVER:
|
|
453
|
+
return await resolveDevServerCommand(worktreePath);
|
|
454
|
+
default:
|
|
455
|
+
// 自定义命令,直接返回
|
|
456
|
+
return command;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* 解析百分比字符串为数字
|
|
461
|
+
* @param sizeStr 大小字符串(如 "50%")
|
|
462
|
+
* @param defaultValue 默认值
|
|
463
|
+
*/
|
|
464
|
+
function parsePercentage(sizeStr, defaultValue) {
|
|
465
|
+
if (!sizeStr) {
|
|
466
|
+
return defaultValue;
|
|
467
|
+
}
|
|
468
|
+
const match = sizeStr.match(/^(\d+)%?$/);
|
|
469
|
+
if (match) {
|
|
470
|
+
return parseInt(match[1], 10);
|
|
471
|
+
}
|
|
472
|
+
return defaultValue;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* 解析所有 pane 命令
|
|
476
|
+
* @param config tmux 配置
|
|
477
|
+
* @param worktreePath worktree 路径
|
|
478
|
+
* @param projectRoot 项目根目录(可选,用于读取 claudeCommand 配置)
|
|
479
|
+
* @param branchName 分支名称(可选,用于读取分支特定的 claudeCommand 配置)
|
|
480
|
+
*/
|
|
481
|
+
export async function resolvePaneCommands(config, worktreePath, projectRoot, branchName) {
|
|
482
|
+
// 如果禁用自动运行,返回空对象
|
|
483
|
+
const autoRun = config.autoRun !== undefined ? config.autoRun : DEFAULT_CONFIG.autoRun;
|
|
484
|
+
if (!autoRun) {
|
|
485
|
+
return {};
|
|
486
|
+
}
|
|
487
|
+
// 读取 claude 命令配置
|
|
488
|
+
let claudeCommand = 'claude';
|
|
489
|
+
if (projectRoot && branchName) {
|
|
490
|
+
const settings = await loadSettingsForBranch(projectRoot, branchName);
|
|
491
|
+
claudeCommand = settings.systemCommands?.claude ?? 'claude';
|
|
492
|
+
}
|
|
493
|
+
// 检测布局类型
|
|
494
|
+
const layout = detectLayoutType(config);
|
|
495
|
+
// 根据布局类型解析命令
|
|
496
|
+
switch (layout) {
|
|
497
|
+
case 'single-pane':
|
|
498
|
+
// 单窗格:无命令
|
|
499
|
+
return {};
|
|
500
|
+
case 'two-pane-horizontal':
|
|
501
|
+
return await resolveTwoPaneHorizontalCommands(config, worktreePath, claudeCommand);
|
|
502
|
+
case 'two-pane-vertical':
|
|
503
|
+
return await resolveTwoPaneVerticalCommands(config, worktreePath, claudeCommand);
|
|
504
|
+
case 'three-pane':
|
|
505
|
+
return await resolveThreePaneCommands(config, worktreePath, claudeCommand);
|
|
506
|
+
case 'four-pane':
|
|
507
|
+
return await resolveFourPaneCommands(config, worktreePath, claudeCommand);
|
|
508
|
+
default:
|
|
509
|
+
// 默认使用三窗格
|
|
510
|
+
return await resolveThreePaneCommands(config, worktreePath, claudeCommand);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* 解析两窗格水平布局的命令
|
|
515
|
+
*/
|
|
516
|
+
async function resolveTwoPaneHorizontalCommands(config, worktreePath, claudeCommand) {
|
|
517
|
+
const leftCommand = config.leftPane?.command;
|
|
518
|
+
const rightCommand = config.rightPane?.command;
|
|
519
|
+
const [pane0, pane1] = await Promise.all([
|
|
520
|
+
resolvePaneCommand(leftCommand, worktreePath, claudeCommand),
|
|
521
|
+
resolvePaneCommand(rightCommand, worktreePath, claudeCommand),
|
|
522
|
+
]);
|
|
523
|
+
return { pane0, pane1 };
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* 解析两窗格垂直布局的命令
|
|
527
|
+
*/
|
|
528
|
+
async function resolveTwoPaneVerticalCommands(config, worktreePath, claudeCommand) {
|
|
529
|
+
const topCommand = config.topPane?.command;
|
|
530
|
+
const bottomCommand = config.bottomPane?.command;
|
|
531
|
+
const [pane0, pane1] = await Promise.all([
|
|
532
|
+
resolvePaneCommand(topCommand, worktreePath, claudeCommand),
|
|
533
|
+
resolvePaneCommand(bottomCommand, worktreePath, claudeCommand),
|
|
534
|
+
]);
|
|
535
|
+
return { pane0, pane1 };
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* 解析三窗格布局的命令
|
|
539
|
+
*/
|
|
540
|
+
async function resolveThreePaneCommands(config, worktreePath, claudeCommand) {
|
|
541
|
+
// 使用默认值(向后兼容)
|
|
542
|
+
const leftCommand = config.leftPane?.command !== undefined
|
|
543
|
+
? config.leftPane.command
|
|
544
|
+
: DEFAULT_CONFIG.leftPane.command;
|
|
545
|
+
const topRightCommand = config.topRightPane?.command !== undefined
|
|
546
|
+
? config.topRightPane.command
|
|
547
|
+
: DEFAULT_CONFIG.topRightPane.command;
|
|
548
|
+
const bottomRightCommand = config.bottomRightPane?.command !== undefined
|
|
549
|
+
? config.bottomRightPane.command
|
|
550
|
+
: DEFAULT_CONFIG.bottomRightPane.command;
|
|
551
|
+
const [pane0, pane1, pane2] = await Promise.all([
|
|
552
|
+
resolvePaneCommand(leftCommand, worktreePath, claudeCommand),
|
|
553
|
+
resolvePaneCommand(topRightCommand, worktreePath, claudeCommand),
|
|
554
|
+
resolvePaneCommand(bottomRightCommand, worktreePath, claudeCommand),
|
|
555
|
+
]);
|
|
556
|
+
return { pane0, pane1, pane2 };
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* 解析四窗格布局的命令
|
|
560
|
+
*/
|
|
561
|
+
async function resolveFourPaneCommands(config, worktreePath, claudeCommand) {
|
|
562
|
+
const topLeftCommand = config.topLeftPane?.command;
|
|
563
|
+
const topRightCommand = config.topRightPane?.command;
|
|
564
|
+
const bottomLeftCommand = config.bottomLeftPane?.command;
|
|
565
|
+
const bottomRightCommand = config.bottomRightPane?.command;
|
|
566
|
+
const [pane0, pane1, pane2, pane3] = await Promise.all([
|
|
567
|
+
resolvePaneCommand(topLeftCommand, worktreePath, claudeCommand),
|
|
568
|
+
resolvePaneCommand(topRightCommand, worktreePath, claudeCommand),
|
|
569
|
+
resolvePaneCommand(bottomLeftCommand, worktreePath, claudeCommand),
|
|
570
|
+
resolvePaneCommand(bottomRightCommand, worktreePath, claudeCommand),
|
|
571
|
+
]);
|
|
572
|
+
return { pane0, pane1, pane2, pane3 };
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* 解析 Pane 布局配置
|
|
576
|
+
* @param config tmux 配置
|
|
577
|
+
*/
|
|
578
|
+
export function resolvePaneLayout(config) {
|
|
579
|
+
const layout = detectLayoutType(config);
|
|
580
|
+
const result = { layout };
|
|
581
|
+
switch (layout) {
|
|
582
|
+
case 'single-pane':
|
|
583
|
+
// 单窗格,无需配置
|
|
584
|
+
break;
|
|
585
|
+
case 'two-pane-horizontal':
|
|
586
|
+
// 水平两分割:leftPane size
|
|
587
|
+
result.leftSize = parsePercentage(config.leftPane?.size, 50);
|
|
588
|
+
break;
|
|
589
|
+
case 'two-pane-vertical':
|
|
590
|
+
// 垂直两分割:topPane size
|
|
591
|
+
result.topSize = parsePercentage(config.topPane?.size, 50);
|
|
592
|
+
break;
|
|
593
|
+
case 'three-pane':
|
|
594
|
+
// 三窗格:leftPane size 和 topRightPane size
|
|
595
|
+
result.leftSize = parsePercentage(config.leftPane?.size, parsePercentage(DEFAULT_CONFIG.leftPane.size, 60));
|
|
596
|
+
result.rightTopSize = parsePercentage(config.topRightPane?.size, parsePercentage(DEFAULT_CONFIG.topRightPane.size, 30));
|
|
597
|
+
break;
|
|
598
|
+
case 'four-pane':
|
|
599
|
+
// 四窗格:根据配置的 split 类型决定
|
|
600
|
+
if (config.horizontalSplit) {
|
|
601
|
+
result.horizontalSplit = parsePercentage(config.horizontalSplit, 50);
|
|
602
|
+
}
|
|
603
|
+
if (config.verticalSplit) {
|
|
604
|
+
result.verticalSplit = parsePercentage(config.verticalSplit, 50);
|
|
605
|
+
}
|
|
606
|
+
// 如果两个 split 都配置了,忽略 pane size
|
|
607
|
+
if (result.horizontalSplit !== undefined && result.verticalSplit !== undefined) {
|
|
608
|
+
// 同时配置:忽略 pane size
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
// 如果只配置了一个 split,解析 pane size
|
|
612
|
+
result.topLeftSize = parsePercentage(config.topLeftPane?.size, 50);
|
|
613
|
+
result.topRightSize = parsePercentage(config.topRightPane?.size, 50);
|
|
614
|
+
result.bottomLeftSize = parsePercentage(config.bottomLeftPane?.size, 50);
|
|
615
|
+
result.bottomRightSize = parsePercentage(config.bottomRightPane?.size, 50);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* 检测布局类型
|
|
622
|
+
* 根据配置的窗格自动检测布局类型(向后兼容)
|
|
623
|
+
* @param config tmux 配置
|
|
624
|
+
* @returns 检测到的布局类型
|
|
625
|
+
*/
|
|
626
|
+
export function detectLayoutType(config) {
|
|
627
|
+
// 如果明确指定了 layout,直接返回
|
|
628
|
+
if (config.layout) {
|
|
629
|
+
return config.layout;
|
|
630
|
+
}
|
|
631
|
+
// 检查是否配置了任何窗格
|
|
632
|
+
const hasLeftPane = config.leftPane !== undefined;
|
|
633
|
+
const hasTopRightPane = config.topRightPane !== undefined;
|
|
634
|
+
const hasBottomRightPane = config.bottomRightPane !== undefined;
|
|
635
|
+
const hasRightPane = config.rightPane !== undefined;
|
|
636
|
+
const hasTopPane = config.topPane !== undefined;
|
|
637
|
+
const hasBottomPane = config.bottomPane !== undefined;
|
|
638
|
+
const hasTopLeftPane = config.topLeftPane !== undefined;
|
|
639
|
+
const hasBottomLeftPane = config.bottomLeftPane !== undefined;
|
|
640
|
+
// 四窗格:配置了任何一个四窗格特有的窗格
|
|
641
|
+
if (hasTopLeftPane || hasBottomLeftPane) {
|
|
642
|
+
return 'four-pane';
|
|
643
|
+
}
|
|
644
|
+
// 两窗格水平:配置了 rightPane
|
|
645
|
+
if (hasRightPane) {
|
|
646
|
+
return 'two-pane-horizontal';
|
|
647
|
+
}
|
|
648
|
+
// 两窗格垂直:配置了 topPane 或 bottomPane
|
|
649
|
+
if (hasTopPane || hasBottomPane) {
|
|
650
|
+
return 'two-pane-vertical';
|
|
651
|
+
}
|
|
652
|
+
// 三窗格:配置了 leftPane, topRightPane 或 bottomRightPane
|
|
653
|
+
if (hasLeftPane || hasTopRightPane || hasBottomRightPane) {
|
|
654
|
+
return 'three-pane';
|
|
655
|
+
}
|
|
656
|
+
// 默认:三窗格布局
|
|
657
|
+
return 'three-pane';
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* 验证 tmux 配置
|
|
661
|
+
* @param config tmux 配置
|
|
662
|
+
* @returns 验证结果
|
|
663
|
+
*/
|
|
664
|
+
export function validateTmuxConfig(config) {
|
|
665
|
+
const errors = [];
|
|
666
|
+
const warnings = [];
|
|
667
|
+
// 检测布局类型
|
|
668
|
+
const layout = detectLayoutType(config);
|
|
669
|
+
const supportedPanes = LAYOUT_PANES[layout];
|
|
670
|
+
// 检查所有可能的窗格配置
|
|
671
|
+
const allPaneNames = [
|
|
672
|
+
'leftPane',
|
|
673
|
+
'rightPane',
|
|
674
|
+
'topPane',
|
|
675
|
+
'bottomPane',
|
|
676
|
+
'topRightPane',
|
|
677
|
+
'bottomRightPane',
|
|
678
|
+
'topLeftPane',
|
|
679
|
+
'bottomLeftPane',
|
|
680
|
+
];
|
|
681
|
+
for (const paneName of allPaneNames) {
|
|
682
|
+
const paneConfig = config[paneName];
|
|
683
|
+
if (paneConfig === undefined) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
// 检查当前布局是否支持此窗格
|
|
687
|
+
if (!supportedPanes.includes(paneName)) {
|
|
688
|
+
warnings.push(`Layout "${layout}" does not support pane "${paneName}", it will be ignored. ` +
|
|
689
|
+
`Supported panes: ${supportedPanes.join(', ')}`);
|
|
690
|
+
}
|
|
691
|
+
// 验证 size 配置
|
|
692
|
+
if (paneConfig.size) {
|
|
693
|
+
const size = parsePercentage(paneConfig.size, -1);
|
|
694
|
+
if (size < 0 || size > 100) {
|
|
695
|
+
warnings.push(`Pane "${paneName}" size "${paneConfig.size}" is out of reasonable range (0%-100%), ` +
|
|
696
|
+
`using default value`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
hasError: errors.length > 0,
|
|
702
|
+
errors,
|
|
703
|
+
warnings,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* 匹配通配符模式
|
|
708
|
+
* @param pattern 通配符模式(如 "feature/*")
|
|
709
|
+
* @param branchName 分支名称
|
|
710
|
+
* @returns 是否匹配
|
|
711
|
+
*/
|
|
712
|
+
function matchWildcard(pattern, branchName) {
|
|
713
|
+
// 将通配符模式转换为正则表达式
|
|
714
|
+
// 支持 * 匹配任意字符
|
|
715
|
+
const regexPattern = pattern
|
|
716
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
|
|
717
|
+
.replace(/\*/g, '.*'); // * 替换为 .*
|
|
718
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
719
|
+
return regex.test(branchName);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* 从设置中加载特定分支的配置
|
|
723
|
+
* @param settings 完整设置对象
|
|
724
|
+
* @param branchName 分支名称
|
|
725
|
+
* @returns 合并后的设置
|
|
726
|
+
*/
|
|
727
|
+
export function loadBranchSettings(settings, branchName) {
|
|
728
|
+
const branchOverrides = settings.branchOverrides;
|
|
729
|
+
if (!branchOverrides) {
|
|
730
|
+
return settings;
|
|
731
|
+
}
|
|
732
|
+
// 优先级:精确匹配 > 通配符匹配
|
|
733
|
+
const overrideKeys = Object.keys(branchOverrides);
|
|
734
|
+
// 1. 精确匹配
|
|
735
|
+
if (overrideKeys.includes(branchName)) {
|
|
736
|
+
const branchConfig = branchOverrides[branchName];
|
|
737
|
+
return mergeSettings(settings, branchConfig);
|
|
738
|
+
}
|
|
739
|
+
// 2. 通配符匹配(找到第一个匹配的)
|
|
740
|
+
for (const pattern of overrideKeys) {
|
|
741
|
+
if (matchWildcard(pattern, branchName)) {
|
|
742
|
+
const branchConfig = branchOverrides[pattern];
|
|
743
|
+
return mergeSettings(settings, branchConfig);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// 3. 无匹配,返回基础配置
|
|
747
|
+
return settings;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* 深度合并两个 Settings 对象
|
|
751
|
+
* @param base 基础设置
|
|
752
|
+
* @param override 覆盖设置
|
|
753
|
+
* @returns 合并后的设置
|
|
754
|
+
*/
|
|
755
|
+
function mergeSettings(base, override) {
|
|
756
|
+
const result = { ...base };
|
|
757
|
+
// 合并顶层字段
|
|
758
|
+
if (override.lang !== undefined) {
|
|
759
|
+
result.lang = override.lang;
|
|
760
|
+
}
|
|
761
|
+
// 深度合并 systemCommands 配置
|
|
762
|
+
if (override.systemCommands !== undefined) {
|
|
763
|
+
result.systemCommands = {
|
|
764
|
+
...base.systemCommands,
|
|
765
|
+
...override.systemCommands,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
// 深度合并 tmux 配置
|
|
769
|
+
if (override.tmux !== undefined) {
|
|
770
|
+
result.tmux = base.tmux
|
|
771
|
+
? mergeConfigs(base.tmux, override.tmux)
|
|
772
|
+
: override.tmux;
|
|
773
|
+
}
|
|
774
|
+
// 注意:branchOverrides 不需要合并(只在顶层使用)
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* 从 branchOverrides 中查找匹配的分支配置
|
|
779
|
+
* @param branchOverrides 分支覆盖配置
|
|
780
|
+
* @param branchName 分支名称
|
|
781
|
+
* @returns 匹配的分支配置,如果没有匹配则返回 null
|
|
782
|
+
*/
|
|
783
|
+
function findBranchOverride(branchOverrides, branchName) {
|
|
784
|
+
const overrideKeys = Object.keys(branchOverrides);
|
|
785
|
+
// 1. 精确匹配
|
|
786
|
+
if (overrideKeys.includes(branchName)) {
|
|
787
|
+
return branchOverrides[branchName];
|
|
788
|
+
}
|
|
789
|
+
// 2. 通配符匹配(找到第一个匹配的)
|
|
790
|
+
for (const pattern of overrideKeys) {
|
|
791
|
+
if (matchWildcard(pattern, branchName)) {
|
|
792
|
+
return branchOverrides[pattern];
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// 3. 无匹配
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* 加载特定分支的完整设置
|
|
800
|
+
*
|
|
801
|
+
* 配置优先级(从低到高):
|
|
802
|
+
* 1. User default(用户级全局配置)
|
|
803
|
+
* 2. Project default(项目级全局配置)
|
|
804
|
+
* 3. User override(用户级分支覆盖)
|
|
805
|
+
* 4. Project override(项目级分支覆盖)
|
|
806
|
+
*
|
|
807
|
+
* @param projectRoot 项目根目录
|
|
808
|
+
* @param branchName 分支名称
|
|
809
|
+
* @returns 合并后的完整设置
|
|
810
|
+
*/
|
|
811
|
+
export async function loadSettingsForBranch(projectRoot, branchName) {
|
|
812
|
+
// 并行加载用户级和项目级配置
|
|
813
|
+
const [userSettings, projectSettings] = await Promise.all([
|
|
814
|
+
loadSettingsFromFile(getUserConfigPath()),
|
|
815
|
+
loadSettingsFromFile(getProjectConfigPath(projectRoot)),
|
|
816
|
+
]);
|
|
817
|
+
// 从空设置开始
|
|
818
|
+
let settings = {};
|
|
819
|
+
// 1. User default - 用户级全局配置
|
|
820
|
+
if (userSettings) {
|
|
821
|
+
settings = mergeSettings(settings, userSettings);
|
|
822
|
+
}
|
|
823
|
+
// 2. Project default - 项目级全局配置
|
|
824
|
+
if (projectSettings) {
|
|
825
|
+
settings = mergeSettings(settings, projectSettings);
|
|
826
|
+
}
|
|
827
|
+
// 3. User override - 用户级分支覆盖
|
|
828
|
+
if (userSettings?.branchOverrides) {
|
|
829
|
+
const userBranchConfig = findBranchOverride(userSettings.branchOverrides, branchName);
|
|
830
|
+
if (userBranchConfig) {
|
|
831
|
+
settings = mergeSettings(settings, userBranchConfig);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// 4. Project override - 项目级分支覆盖
|
|
835
|
+
if (projectSettings?.branchOverrides) {
|
|
836
|
+
const projectBranchConfig = findBranchOverride(projectSettings.branchOverrides, branchName);
|
|
837
|
+
if (projectBranchConfig) {
|
|
838
|
+
settings = mergeSettings(settings, projectBranchConfig);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return settings;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* 加载特定分支的 tmux 配置
|
|
845
|
+
*
|
|
846
|
+
* 配置优先级(从低到高):
|
|
847
|
+
* 1. System builtin(系统内置默认,如 main 分支默认单窗格)
|
|
848
|
+
* 2. User default(用户级全局配置,~/.config/colyn/settings.json 的 tmux)
|
|
849
|
+
* 3. Project default(项目级全局配置,.colyn/settings.json 的 tmux)
|
|
850
|
+
* 4. User override(用户级分支覆盖,~/.config/colyn/settings.json 的 branchOverrides[branch].tmux)
|
|
851
|
+
* 5. Project override(项目级分支覆盖,.colyn/settings.json 的 branchOverrides[branch].tmux)
|
|
852
|
+
*
|
|
853
|
+
* 全部按照字段级覆盖(field-level override)
|
|
854
|
+
*
|
|
855
|
+
* @param projectRoot 项目根目录
|
|
856
|
+
* @param branchName 分支名称
|
|
857
|
+
* @returns 合并后的 tmux 配置
|
|
858
|
+
*/
|
|
859
|
+
export async function loadTmuxConfigForBranch(projectRoot, branchName) {
|
|
860
|
+
// 并行加载用户级和项目级配置
|
|
861
|
+
const [userSettings, projectSettings] = await Promise.all([
|
|
862
|
+
loadSettingsFromFile(getUserConfigPath()),
|
|
863
|
+
loadSettingsFromFile(getProjectConfigPath(projectRoot)),
|
|
864
|
+
]);
|
|
865
|
+
// 从空配置开始
|
|
866
|
+
let config = {};
|
|
867
|
+
// 1. System builtin - 系统内置分支默认配置
|
|
868
|
+
const builtinBranchDefault = BUILTIN_BRANCH_DEFAULTS[branchName];
|
|
869
|
+
if (builtinBranchDefault) {
|
|
870
|
+
config = mergeConfigs(config, builtinBranchDefault);
|
|
871
|
+
}
|
|
872
|
+
// 2. User default - 用户级全局配置
|
|
873
|
+
if (userSettings?.tmux) {
|
|
874
|
+
config = mergeConfigs(config, userSettings.tmux);
|
|
875
|
+
}
|
|
876
|
+
// 3. Project default - 项目级全局配置
|
|
877
|
+
if (projectSettings?.tmux) {
|
|
878
|
+
config = mergeConfigs(config, projectSettings.tmux);
|
|
879
|
+
}
|
|
880
|
+
// 4. User override - 用户级分支覆盖
|
|
881
|
+
if (userSettings?.branchOverrides) {
|
|
882
|
+
const userBranchConfig = findBranchOverride(userSettings.branchOverrides, branchName);
|
|
883
|
+
if (userBranchConfig?.tmux) {
|
|
884
|
+
config = mergeConfigs(config, userBranchConfig.tmux);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
// 5. Project override - 项目级分支覆盖
|
|
888
|
+
if (projectSettings?.branchOverrides) {
|
|
889
|
+
const projectBranchConfig = findBranchOverride(projectSettings.branchOverrides, branchName);
|
|
890
|
+
if (projectBranchConfig?.tmux) {
|
|
891
|
+
config = mergeConfigs(config, projectBranchConfig.tmux);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return config;
|
|
895
|
+
}
|
|
896
|
+
//# sourceMappingURL=tmux-config.legacy.js.map
|