coding-tool-x 3.7.6 → 3.7.8
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/dist/web/assets/{Analytics-wCRLv26m.js → Analytics-BE5H9JGG.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-aa1blEHh.js → ConfigTemplates-z118k1BE.js} +1 -1
- package/dist/web/assets/{Home-eIxO50-k.js → Home-CBhtmOIN.js} +1 -1
- package/dist/web/assets/{PluginManager-BAwGFNjN.js → PluginManager-CGhfZ3t8.js} +1 -1
- package/dist/web/assets/ProjectList-BMPLJtgQ.js +1 -0
- package/dist/web/assets/ProjectList-VeMNXfgO.css +1 -0
- package/dist/web/assets/SessionList-C4chCPxK.css +1 -0
- package/dist/web/assets/SessionList-D9e7wGF1.js +1 -0
- package/dist/web/assets/{SkillManager-Bhzs1O5S.js → SkillManager-DJpdUmnk.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CLdX-pTT.js → WorkspaceManager-C0Ud_V7v.js} +1 -1
- package/dist/web/assets/index-CeTaG4IX.js +2 -0
- package/dist/web/assets/index-S8wrU2Nf.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/commands/daemon.js +3 -1
- package/src/commands/port-config.js +0 -2
- package/src/commands/switch.js +1 -5
- package/src/commands/ui.js +4 -3
- package/src/commands/workspace.js +35 -3
- package/src/config/loader.js +76 -5
- package/src/index.js +11 -0
- package/src/server/api/config.js +0 -3
- package/src/server/api/omp-channels.js +29 -9
- package/src/server/api/omp-config.js +6 -8
- package/src/server/api/settings.js +0 -2
- package/src/server/api/workspaces.js +39 -1
- package/src/server/services/dashboard-snapshot-worker.js +1 -0
- package/src/server/services/https-cert.js +3 -2
- package/src/server/services/model-detector.js +106 -26
- package/src/server/services/omp-auth-providers.js +1 -0
- package/src/server/services/omp-config.js +22 -0
- package/src/server/services/omp-settings-manager.js +31 -16
- package/src/server/services/session-snapshots.js +1 -0
- package/src/server/services/sessions.js +35 -19
- package/src/server/services/web-build.js +2 -1
- package/src/server/services/workspace-service.js +204 -98
- package/src/utils/cli-flags.js +7 -0
- package/src/utils/session.js +10 -4
- package/dist/web/assets/ProjectList-CRiAEixW.js +0 -1
- package/dist/web/assets/ProjectList-Dw2omgOH.css +0 -1
- package/dist/web/assets/SessionList-BQ0FW6Gd.js +0 -1
- package/dist/web/assets/SessionList-kYJ6-Ies.css +0 -1
- package/dist/web/assets/index-B_nFqixU.css +0 -1
- package/dist/web/assets/index-ClMNA9Hf.js +0 -2
package/src/config/loader.js
CHANGED
|
@@ -3,7 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const DEFAULT_CONFIG = require('./default');
|
|
6
|
-
const { PATHS, ensureStorageDirMigrated } = require('./paths');
|
|
6
|
+
const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('./paths');
|
|
7
7
|
const { resolvePreferredHomeDir } = require('../utils/home-dir');
|
|
8
8
|
const eventBus = require('../plugins/event-bus');
|
|
9
9
|
|
|
@@ -23,12 +23,79 @@ function getConfigFilePath() {
|
|
|
23
23
|
* 展开 ~ 为用户主目录
|
|
24
24
|
*/
|
|
25
25
|
function expandHome(filepath) {
|
|
26
|
+
if (typeof filepath !== 'string') {
|
|
27
|
+
return filepath;
|
|
28
|
+
}
|
|
26
29
|
if (filepath.startsWith('~')) {
|
|
27
30
|
return path.join(HOME_DIR, filepath.slice(1));
|
|
28
31
|
}
|
|
29
32
|
return filepath;
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
function collapseHome(filepath) {
|
|
36
|
+
if (typeof filepath !== 'string') {
|
|
37
|
+
return filepath;
|
|
38
|
+
}
|
|
39
|
+
if (filepath === HOME_DIR) {
|
|
40
|
+
return '~';
|
|
41
|
+
}
|
|
42
|
+
const prefix = `${HOME_DIR}${path.sep}`;
|
|
43
|
+
if (filepath.startsWith(prefix)) {
|
|
44
|
+
return path.join('~', filepath.slice(prefix.length));
|
|
45
|
+
}
|
|
46
|
+
return filepath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getNativeClaudeProjectsDir() {
|
|
50
|
+
return NATIVE_PATHS?.claude?.projects || path.join(HOME_DIR, '.claude', 'projects');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasExplicitProjectsDir(config = {}) {
|
|
54
|
+
return Object.prototype.hasOwnProperty.call(config, 'projectsDir') &&
|
|
55
|
+
typeof config.projectsDir === 'string' &&
|
|
56
|
+
config.projectsDir.trim() !== '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveClaudeProjectsDir(config = {}) {
|
|
60
|
+
if (!hasExplicitProjectsDir(config) || isDefaultProjectsDir(config.projectsDir)) {
|
|
61
|
+
return getNativeClaudeProjectsDir();
|
|
62
|
+
}
|
|
63
|
+
return expandHome(config.projectsDir);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeComparablePath(filepath) {
|
|
67
|
+
if (typeof filepath !== 'string' || !filepath.trim()) {
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
70
|
+
return path.normalize(expandHome(filepath.trim()));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isDefaultProjectsDir(projectsDir) {
|
|
74
|
+
const normalized = normalizeComparablePath(projectsDir);
|
|
75
|
+
if (!normalized) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const nativeDefault = normalizeComparablePath(getNativeClaudeProjectsDir());
|
|
79
|
+
const legacyHomeDefault = normalizeComparablePath(path.join(HOME_DIR, '.claude', 'projects'));
|
|
80
|
+
if (normalized === nativeDefault || normalized === legacyHomeDefault) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
const normalizedParts = normalized.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
84
|
+
const last = normalizedParts[normalizedParts.length - 1];
|
|
85
|
+
const previous = normalizedParts[normalizedParts.length - 2];
|
|
86
|
+
return previous === '.claude' && last === 'projects' && !fs.existsSync(normalized);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeConfigForSave(config = {}) {
|
|
90
|
+
const normalized = { ...config };
|
|
91
|
+
if (isDefaultProjectsDir(normalized.projectsDir)) {
|
|
92
|
+
delete normalized.projectsDir;
|
|
93
|
+
} else if (typeof normalized.projectsDir === 'string') {
|
|
94
|
+
normalized.projectsDir = collapseHome(normalized.projectsDir);
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
|
|
32
99
|
function mergePricing(defaultPricing, overrides = {}) {
|
|
33
100
|
const merged = {};
|
|
34
101
|
Object.keys(defaultPricing).forEach((key) => {
|
|
@@ -96,7 +163,7 @@ function loadConfig() {
|
|
|
96
163
|
if (fs.existsSync(configFilePath)) {
|
|
97
164
|
const userConfig = readJsonFile(configFilePath);
|
|
98
165
|
const config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
99
|
-
config.projectsDir =
|
|
166
|
+
config.projectsDir = resolveClaudeProjectsDir(userConfig);
|
|
100
167
|
|
|
101
168
|
// 合并 ports 配置
|
|
102
169
|
config.ports = { ...DEFAULT_CONFIG.ports, ...userConfig.ports };
|
|
@@ -120,7 +187,7 @@ function loadConfig() {
|
|
|
120
187
|
}
|
|
121
188
|
const defaultConfig = {
|
|
122
189
|
...DEFAULT_CONFIG,
|
|
123
|
-
projectsDir:
|
|
190
|
+
projectsDir: resolveClaudeProjectsDir(),
|
|
124
191
|
currentProject: DEFAULT_CONFIG.defaultProject
|
|
125
192
|
};
|
|
126
193
|
eventBus.emitSync('config:loaded', { config: defaultConfig });
|
|
@@ -137,8 +204,9 @@ function saveConfig(config) {
|
|
|
137
204
|
if (!fs.existsSync(dir)) {
|
|
138
205
|
fs.mkdirSync(dir, { recursive: true });
|
|
139
206
|
}
|
|
140
|
-
|
|
141
|
-
|
|
207
|
+
const normalizedConfig = normalizeConfigForSave(config);
|
|
208
|
+
fs.writeFileSync(configFilePath, JSON.stringify(normalizedConfig, null, 2), 'utf8');
|
|
209
|
+
eventBus.emitSync('config:saved', { config: normalizedConfig });
|
|
142
210
|
} catch (error) {
|
|
143
211
|
console.error(`保存配置失败 (${configFilePath}):`, error.message);
|
|
144
212
|
}
|
|
@@ -148,5 +216,8 @@ module.exports = {
|
|
|
148
216
|
loadConfig,
|
|
149
217
|
saveConfig,
|
|
150
218
|
expandHome,
|
|
219
|
+
resolveClaudeProjectsDir,
|
|
220
|
+
normalizeConfigForSave,
|
|
221
|
+
isDefaultProjectsDir,
|
|
151
222
|
getConfigFilePath
|
|
152
223
|
};
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const { ensureStorageDirMigrated } = require('./config/paths');
|
|
|
17
17
|
const { normalizePlatformKey } = require('./shared/platforms');
|
|
18
18
|
const PluginManager = require('./plugins/plugin-manager');
|
|
19
19
|
const eventBus = require('./plugins/event-bus');
|
|
20
|
+
const { hasHostFlag } = require('./utils/cli-flags');
|
|
20
21
|
const chalk = require('chalk');
|
|
21
22
|
const path = require('path');
|
|
22
23
|
const fs = require('fs');
|
|
@@ -54,9 +55,12 @@ function showHelp() {
|
|
|
54
55
|
console.log(' ctx ui 前台启动 Web UI(仅本地访问)');
|
|
55
56
|
console.log(' ctx ui --https 前台启动 Web UI(本地 HTTPS)');
|
|
56
57
|
console.log(' ctx ui --host 前台启动 Web UI(允许 LAN 访问,默认允许远程写操作)');
|
|
58
|
+
console.log(' ctx ui --hosts 前台启动 Web UI(同 --host)');
|
|
59
|
+
console.log(' ctx --hosts 前台启动 Web UI(同 ctx ui --host)');
|
|
57
60
|
console.log(' ctx ui start 后台启动 Web UI');
|
|
58
61
|
console.log(' ctx ui start --https 后台启动 Web UI(本地 HTTPS)');
|
|
59
62
|
console.log(' ctx ui start --host 后台启动 Web UI(允许 LAN 访问,默认允许远程写操作)');
|
|
63
|
+
console.log(' ctx ui start --hosts 后台启动 Web UI(同 --host)');
|
|
60
64
|
console.log(' ctx ui stop 停止 Web UI');
|
|
61
65
|
console.log(' ctx ui restart 重启 Web UI\n');
|
|
62
66
|
console.log(chalk.gray(' 提示: 如需禁止 LAN 远程写操作,可设置 CC_TOOL_ALLOW_REMOTE_WRITE=false\n'));
|
|
@@ -149,6 +153,13 @@ async function main() {
|
|
|
149
153
|
// 处理命令行参数
|
|
150
154
|
const args = process.argv.slice(2);
|
|
151
155
|
|
|
156
|
+
// LAN 快捷入口,等同于 ctx ui --host/--hosts
|
|
157
|
+
if (hasHostFlag(process.argv) && !args.some((arg) => arg && !arg.startsWith('-'))) {
|
|
158
|
+
const { handleUI } = require('./commands/ui');
|
|
159
|
+
await handleUI();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
// --version 或 -v - 显示版本号
|
|
153
164
|
if (args[0] === '--version' || args[0] === '-v') {
|
|
154
165
|
console.log(getVersion());
|
package/src/server/api/config.js
CHANGED
|
@@ -330,7 +330,6 @@ router.post('/default-models', (req, res) => {
|
|
|
330
330
|
// Save config
|
|
331
331
|
const newConfig = {
|
|
332
332
|
...config,
|
|
333
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
334
333
|
defaultModels: newDefaultModels
|
|
335
334
|
};
|
|
336
335
|
|
|
@@ -378,7 +377,6 @@ router.post('/default-models/reset', (req, res) => {
|
|
|
378
377
|
// Save config
|
|
379
378
|
const newConfig = {
|
|
380
379
|
...config,
|
|
381
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
382
380
|
defaultModels: newDefaultModels
|
|
383
381
|
};
|
|
384
382
|
|
|
@@ -490,7 +488,6 @@ router.post('/advanced', (req, res) => {
|
|
|
490
488
|
// 更新配置
|
|
491
489
|
const newConfig = {
|
|
492
490
|
...config,
|
|
493
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
494
491
|
ports: normalizedPorts,
|
|
495
492
|
maxLogs: maxLogs !== undefined ? parseInt(maxLogs) : config.maxLogs,
|
|
496
493
|
statsInterval: statsInterval !== undefined ? parseInt(statsInterval) : config.statsInterval,
|
|
@@ -24,8 +24,7 @@ const {
|
|
|
24
24
|
const {
|
|
25
25
|
findAuthProviderForKey,
|
|
26
26
|
getCachedOmpAuthProviderSnapshot,
|
|
27
|
-
getOmpAuthProviderCacheMeta
|
|
28
|
-
warmOmpAuthProviderSnapshot
|
|
27
|
+
getOmpAuthProviderCacheMeta
|
|
29
28
|
} = require('../services/omp-auth-providers');
|
|
30
29
|
|
|
31
30
|
const CHANNEL_LIST_AUTH_OPTIONS = { accountCheck: false, includeStatus: false };
|
|
@@ -77,6 +76,8 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
|
|
|
77
76
|
models: preferredModels,
|
|
78
77
|
supported: preferredModels.length > 0,
|
|
79
78
|
cached: false,
|
|
79
|
+
stale: false,
|
|
80
|
+
retryAfter: null,
|
|
80
81
|
fallbackUsed: true,
|
|
81
82
|
fetchedAt: new Date().toISOString(),
|
|
82
83
|
error: preferredModels.length > 0 ? null : 'OAuth 渠道未配置 API Key,无法直接调用远端模型列表接口',
|
|
@@ -91,11 +92,27 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
|
|
|
91
92
|
});
|
|
92
93
|
const listedModels = Array.isArray(listResult.models) ? uniqueModels(listResult.models) : [];
|
|
93
94
|
|
|
95
|
+
if (listResult.backoff || listResult.stale) {
|
|
96
|
+
return {
|
|
97
|
+
models: listedModels,
|
|
98
|
+
supported: listedModels.length > 0,
|
|
99
|
+
cached: !!listResult.cached,
|
|
100
|
+
stale: true,
|
|
101
|
+
retryAfter: listResult.retryAfter || null,
|
|
102
|
+
fallbackUsed: false,
|
|
103
|
+
fetchedAt: listResult.lastChecked || new Date().toISOString(),
|
|
104
|
+
error: listResult.error || (listedModels.length > 0 ? null : '模型目录缓存已过期'),
|
|
105
|
+
errorHint: listResult.errorHint || '可点击“刷新可选模型”更新,或手动填写模型名称。'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
if (listedModels.length > 0) {
|
|
95
110
|
return {
|
|
96
111
|
models: listedModels,
|
|
97
112
|
supported: true,
|
|
98
113
|
cached: !!listResult.cached,
|
|
114
|
+
stale: false,
|
|
115
|
+
retryAfter: null,
|
|
99
116
|
fallbackUsed: false,
|
|
100
117
|
fetchedAt: listResult.lastChecked || new Date().toISOString(),
|
|
101
118
|
error: null,
|
|
@@ -114,6 +131,8 @@ async function discoverOmpModels(channel, gatewaySourceType, { forceRefresh = fa
|
|
|
114
131
|
models: probedModels,
|
|
115
132
|
supported: probedModels.length > 0,
|
|
116
133
|
cached: !!probe.cached || !!listResult.cached,
|
|
134
|
+
stale: false,
|
|
135
|
+
retryAfter: null,
|
|
117
136
|
fallbackUsed: probedModels.length > 0,
|
|
118
137
|
fetchedAt: probe.lastChecked || listResult.lastChecked || new Date().toISOString(),
|
|
119
138
|
error: probedModels.length > 0 ? null : (listResult.error || '无法获取可用模型'),
|
|
@@ -142,17 +161,12 @@ function attachAuthProvider(channel, snapshot) {
|
|
|
142
161
|
|
|
143
162
|
function getAuthSnapshotForChannelList() {
|
|
144
163
|
const snapshot = getCachedOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
|
|
145
|
-
const shouldWarm = !snapshot || snapshot.stale;
|
|
146
|
-
if (shouldWarm) {
|
|
147
|
-
warmOmpAuthProviderSnapshot(CHANNEL_LIST_AUTH_OPTIONS);
|
|
148
|
-
}
|
|
149
164
|
const meta = getOmpAuthProviderCacheMeta(CHANNEL_LIST_AUTH_OPTIONS);
|
|
150
165
|
return {
|
|
151
166
|
snapshot,
|
|
152
167
|
meta: {
|
|
153
168
|
...meta,
|
|
154
169
|
stale: !snapshot || Boolean(snapshot.stale),
|
|
155
|
-
refreshing: shouldWarm || meta.refreshing,
|
|
156
170
|
fallback: !snapshot
|
|
157
171
|
}
|
|
158
172
|
};
|
|
@@ -241,7 +255,8 @@ module.exports = () => {
|
|
|
241
255
|
model,
|
|
242
256
|
speedTestModel,
|
|
243
257
|
allowedModels,
|
|
244
|
-
modelRedirects
|
|
258
|
+
modelRedirects,
|
|
259
|
+
forceRefresh
|
|
245
260
|
} = req.body || {};
|
|
246
261
|
if (!baseUrl) {
|
|
247
262
|
return res.status(400).json({ error: 'baseUrl is required' });
|
|
@@ -259,11 +274,14 @@ module.exports = () => {
|
|
|
259
274
|
modelRedirects: Array.isArray(modelRedirects) ? modelRedirects : []
|
|
260
275
|
};
|
|
261
276
|
const resolvedGatewaySourceType = resolveGatewaySourceType(tempChannel);
|
|
262
|
-
const result = await discoverOmpModels(tempChannel, resolvedGatewaySourceType, { forceRefresh: true });
|
|
277
|
+
const result = await discoverOmpModels(tempChannel, resolvedGatewaySourceType, { forceRefresh: forceRefresh === true });
|
|
263
278
|
res.json({
|
|
264
279
|
models: result.models,
|
|
265
280
|
supported: result.supported,
|
|
266
281
|
fallbackUsed: result.fallbackUsed,
|
|
282
|
+
cached: result.cached,
|
|
283
|
+
stale: result.stale,
|
|
284
|
+
retryAfter: result.retryAfter,
|
|
267
285
|
error: result.error,
|
|
268
286
|
errorHint: result.errorHint
|
|
269
287
|
});
|
|
@@ -291,6 +309,8 @@ module.exports = () => {
|
|
|
291
309
|
models: result.models,
|
|
292
310
|
supported: result.supported,
|
|
293
311
|
cached: result.cached,
|
|
312
|
+
stale: result.stale,
|
|
313
|
+
retryAfter: result.retryAfter,
|
|
294
314
|
fallbackUsed: result.fallbackUsed,
|
|
295
315
|
fetchedAt: result.fetchedAt,
|
|
296
316
|
error: result.error,
|
|
@@ -6,8 +6,7 @@ const {
|
|
|
6
6
|
getOmpPaths,
|
|
7
7
|
getOmpStatus,
|
|
8
8
|
readJsonFile,
|
|
9
|
-
readYamlFile
|
|
10
|
-
readOmpSettings
|
|
9
|
+
readYamlFile
|
|
11
10
|
} = require('../services/omp-config');
|
|
12
11
|
const {
|
|
13
12
|
getOmpAuthProviderSnapshot
|
|
@@ -89,13 +88,12 @@ function buildCapabilities() {
|
|
|
89
88
|
|
|
90
89
|
router.get('/', (req, res) => {
|
|
91
90
|
try {
|
|
92
|
-
const paths = getOmpPaths();
|
|
93
|
-
const settings =
|
|
91
|
+
const paths = getOmpPaths(process.env, { resolveRuntime: false });
|
|
92
|
+
const settings = readYamlFile(paths.settings, readJsonFile(paths.settingsJsonLegacy, {}));
|
|
94
93
|
const resources = {
|
|
95
94
|
config: readYamlFile(paths.config, {}),
|
|
96
95
|
settings: readYamlFile(paths.settings, {}),
|
|
97
96
|
auth: fs.existsSync(paths.auth) ? { exists: true, path: paths.auth } : { exists: false, path: paths.auth },
|
|
98
|
-
authProviders: getOmpAuthProviderSnapshot({ accountCheck: false }),
|
|
99
97
|
models: readYamlFile(paths.modelsYml, {}),
|
|
100
98
|
legacyModels: readJsonFile(paths.modelsJsonLegacy, {}),
|
|
101
99
|
packages: normalizeArray(settings.packages),
|
|
@@ -110,7 +108,7 @@ router.get('/', (req, res) => {
|
|
|
110
108
|
|
|
111
109
|
res.json({
|
|
112
110
|
success: true,
|
|
113
|
-
status: getOmpStatus(),
|
|
111
|
+
status: getOmpStatus(process.env, { resolveRuntime: false }),
|
|
114
112
|
capabilities: buildCapabilities(),
|
|
115
113
|
resources
|
|
116
114
|
});
|
|
@@ -143,8 +141,8 @@ router.get('/auth-providers', (req, res) => {
|
|
|
143
141
|
|
|
144
142
|
router.get('/resources', (req, res) => {
|
|
145
143
|
try {
|
|
146
|
-
const paths = getOmpPaths();
|
|
147
|
-
const settings =
|
|
144
|
+
const paths = getOmpPaths(process.env, { resolveRuntime: false });
|
|
145
|
+
const settings = readYamlFile(paths.settings, readJsonFile(paths.settingsJsonLegacy, {}));
|
|
148
146
|
res.json({
|
|
149
147
|
success: true,
|
|
150
148
|
paths,
|
|
@@ -90,7 +90,6 @@ function handleSaveModelSettings(req, res) {
|
|
|
90
90
|
const config = loadConfig();
|
|
91
91
|
const newConfig = {
|
|
92
92
|
...config,
|
|
93
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
94
93
|
modelMetadataOverrides: overrides && typeof overrides === 'object'
|
|
95
94
|
? overrides
|
|
96
95
|
: (config.modelMetadataOverrides || {})
|
|
@@ -124,7 +123,6 @@ function handleDeleteModelOverride(req, res) {
|
|
|
124
123
|
|
|
125
124
|
const newConfig = {
|
|
126
125
|
...config,
|
|
127
|
-
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
128
126
|
modelMetadataOverrides: overrides
|
|
129
127
|
};
|
|
130
128
|
saveConfig(newConfig);
|
|
@@ -33,6 +33,24 @@ function validateBranchName(branchName) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function normalizeBranchMode(branchMode) {
|
|
37
|
+
if (typeof branchMode !== 'string' || !branchMode.trim()) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
return branchMode.trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateBranchMode(branchMode) {
|
|
44
|
+
const normalized = normalizeBranchMode(branchMode);
|
|
45
|
+
if (!normalized) {
|
|
46
|
+
return { valid: true, normalized: '' };
|
|
47
|
+
}
|
|
48
|
+
if (normalized !== 'existing' && normalized !== 'new') {
|
|
49
|
+
return { valid: false, normalized, message: `分支模式不合法: ${normalized}` };
|
|
50
|
+
}
|
|
51
|
+
return { valid: true, normalized };
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
/**
|
|
37
55
|
* GET /api/workspaces
|
|
38
56
|
* 获取所有工作区列表
|
|
@@ -226,6 +244,17 @@ router.post('/', (req, res) => {
|
|
|
226
244
|
});
|
|
227
245
|
}
|
|
228
246
|
|
|
247
|
+
const branchModeValidation = validateBranchMode(proj.branchMode);
|
|
248
|
+
if (!branchModeValidation.valid) {
|
|
249
|
+
return res.status(400).json({
|
|
250
|
+
success: false,
|
|
251
|
+
message: branchModeValidation.message
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (branchModeValidation.normalized) {
|
|
255
|
+
proj.branchMode = branchModeValidation.normalized;
|
|
256
|
+
}
|
|
257
|
+
|
|
229
258
|
const normalizedBranch = normalizeBranchName(proj.branch);
|
|
230
259
|
if (normalizedBranch) {
|
|
231
260
|
const branchValidation = validateBranchName(normalizedBranch);
|
|
@@ -329,9 +358,10 @@ router.put('/:id/last-used', (req, res) => {
|
|
|
329
358
|
router.post('/:id/projects', (req, res) => {
|
|
330
359
|
try {
|
|
331
360
|
const { id } = req.params;
|
|
332
|
-
const { sourcePath, name, createWorktree, branch, baseBranch } = req.body;
|
|
361
|
+
const { sourcePath, name, createWorktree, branch, baseBranch, branchMode } = req.body;
|
|
333
362
|
const normalizedBranch = normalizeBranchName(branch);
|
|
334
363
|
const normalizedBaseBranch = normalizeBranchName(baseBranch);
|
|
364
|
+
const branchModeValidation = validateBranchMode(branchMode);
|
|
335
365
|
|
|
336
366
|
if (!sourcePath || !sourcePath.trim()) {
|
|
337
367
|
return res.status(400).json({
|
|
@@ -340,6 +370,13 @@ router.post('/:id/projects', (req, res) => {
|
|
|
340
370
|
});
|
|
341
371
|
}
|
|
342
372
|
|
|
373
|
+
if (!branchModeValidation.valid) {
|
|
374
|
+
return res.status(400).json({
|
|
375
|
+
success: false,
|
|
376
|
+
message: branchModeValidation.message
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
343
380
|
if (normalizedBranch) {
|
|
344
381
|
const branchValidation = validateBranchName(normalizedBranch);
|
|
345
382
|
if (!branchValidation.valid) {
|
|
@@ -364,6 +401,7 @@ router.post('/:id/projects', (req, res) => {
|
|
|
364
401
|
sourcePath,
|
|
365
402
|
name,
|
|
366
403
|
createWorktree,
|
|
404
|
+
branchMode: branchModeValidation.normalized || branchMode,
|
|
367
405
|
branch: normalizedBranch || branch,
|
|
368
406
|
baseBranch: normalizedBaseBranch || baseBranch
|
|
369
407
|
});
|
|
@@ -130,6 +130,7 @@ function runDashboardSnapshotWorker(kind, source, config = {}, options = {}) {
|
|
|
130
130
|
return new Promise((resolve, reject) => {
|
|
131
131
|
const child = fork(__filename, [], {
|
|
132
132
|
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
133
|
+
windowsHide: true,
|
|
133
134
|
env: {
|
|
134
135
|
...process.env,
|
|
135
136
|
CC_TOOL_DASHBOARD_SNAPSHOT_WORKER: '1'
|
|
@@ -42,7 +42,7 @@ function resolveOpenSslCommand() {
|
|
|
42
42
|
|
|
43
43
|
for (const command of commands) {
|
|
44
44
|
try {
|
|
45
|
-
execFileSync(command, ['version'], { stdio: 'ignore' });
|
|
45
|
+
execFileSync(command, ['version'], { stdio: 'ignore', windowsHide: true });
|
|
46
46
|
return command;
|
|
47
47
|
} catch {
|
|
48
48
|
// Try next candidate.
|
|
@@ -96,7 +96,8 @@ function generatePemCertificateWithOpenSsl(command) {
|
|
|
96
96
|
'-config',
|
|
97
97
|
configPath
|
|
98
98
|
], {
|
|
99
|
-
stdio: 'ignore'
|
|
99
|
+
stdio: 'ignore',
|
|
100
|
+
windowsHide: true
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
writeMeta({
|