zen-gitsync 2.12.2 → 2.12.3
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/LICENSE +190 -21
- package/index.js +25 -11
- package/package.json +2 -2
- package/scripts/convert-colors-to-vars.cjs +286 -272
- package/scripts/convert-fontsize-to-vars.cjs +221 -207
- package/scripts/convert-spacing-to-vars.cjs +256 -242
- package/scripts/convert-to-standard-vars.cjs +282 -268
- package/scripts/release.js +599 -585
- package/src/config.js +350 -336
- package/src/gitCommit.js +455 -440
- package/src/ui/public/assets/EditorView-CbqSI9nw.css +1 -0
- package/src/ui/public/assets/{EditorView-bnJmBq-i.js → EditorView-GS5cmh99.js} +2 -2
- package/src/ui/public/assets/SourceMapView-DyMK80hS.css +1 -0
- package/src/ui/public/assets/{SourceMapView-Rz5SD0A0.js → SourceMapView-_YRtzmZZ.js} +3 -3
- package/src/ui/public/assets/{index-bOs5P8fz.css → index-ML5Y-5lO.css} +1 -1
- package/src/ui/public/assets/{index-Bo3tntQh.js → index-yky0Sd13.js} +11 -11
- package/src/ui/public/index.html +2 -2
- package/src/ui/server/index.js +410 -396
- package/src/ui/server/middleware/requestLogger.js +51 -37
- package/src/ui/server/routes/branchStatus.js +101 -87
- package/src/ui/server/routes/code.js +110 -96
- package/src/ui/server/routes/codeAnalysis.js +995 -981
- package/src/ui/server/routes/config.js +1172 -1158
- package/src/ui/server/routes/exec.js +272 -258
- package/src/ui/server/routes/fileOpen.js +279 -265
- package/src/ui/server/routes/fs.js +701 -687
- package/src/ui/server/routes/git/diff.js +352 -338
- package/src/ui/server/routes/git/diffUtils.js +128 -114
- package/src/ui/server/routes/git/stash.js +552 -538
- package/src/ui/server/routes/git/tags.js +172 -158
- package/src/ui/server/routes/git.js +190 -176
- package/src/ui/server/routes/gitOps.js +1179 -1165
- package/src/ui/server/routes/instances.js +14 -0
- package/src/ui/server/routes/npm.js +1023 -1009
- package/src/ui/server/routes/process.js +82 -68
- package/src/ui/server/routes/status.js +67 -53
- package/src/ui/server/routes/terminal.js +319 -305
- package/src/ui/server/socket/registerUiSocketHandlers.js +226 -212
- package/src/ui/server/utils/createSavePortToFile.js +46 -32
- package/src/ui/server/utils/instanceRegistry.js +14 -0
- package/src/ui/server/utils/pathGuard.js +14 -0
- package/src/ui/server/utils/pathGuard.test.js +14 -0
- package/src/ui/server/utils/randomStartPort.js +14 -0
- package/src/ui/server/utils/startServerOnAvailablePort.js +101 -87
- package/src/utils/index.js +14 -0
- package/src/ui/public/assets/EditorView-CHBjgiZc.css +0 -1
- package/src/ui/public/assets/SourceMapView-DhQX0K7t.css +0 -1
|
@@ -1,212 +1,226 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
io
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
command.trim()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
export function registerUiSocketHandlers({
|
|
16
|
+
io,
|
|
17
|
+
getProjectRoomId,
|
|
18
|
+
getCurrentProjectPath,
|
|
19
|
+
getAndBroadcastStatus,
|
|
20
|
+
getCommandHistory,
|
|
21
|
+
clearCommandHistory,
|
|
22
|
+
addCommandToHistory,
|
|
23
|
+
runningProcesses,
|
|
24
|
+
nextProcessId,
|
|
25
|
+
spawn,
|
|
26
|
+
exec,
|
|
27
|
+
path,
|
|
28
|
+
iconv
|
|
29
|
+
}) {
|
|
30
|
+
io.on('connection', (socket) => {
|
|
31
|
+
console.log('客户端已连接:', socket.id);
|
|
32
|
+
|
|
33
|
+
const projectRoomId = getProjectRoomId();
|
|
34
|
+
socket.join(projectRoomId);
|
|
35
|
+
console.log(`客户端 ${socket.id} 已加入房间: ${projectRoomId}`);
|
|
36
|
+
|
|
37
|
+
getAndBroadcastStatus();
|
|
38
|
+
|
|
39
|
+
const history = getCommandHistory();
|
|
40
|
+
socket.emit('initial_command_history', { history });
|
|
41
|
+
|
|
42
|
+
socket.on('request_full_history', () => {
|
|
43
|
+
const fullHistory = getCommandHistory();
|
|
44
|
+
socket.emit('full_command_history', { history: fullHistory });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
socket.on('clear_command_history', () => {
|
|
48
|
+
const result = clearCommandHistory();
|
|
49
|
+
socket.emit('command_history_cleared', { success: result });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
socket.on('exec_interactive', async (data) => {
|
|
53
|
+
const { command, directory, sessionId } = data;
|
|
54
|
+
|
|
55
|
+
if (!command || typeof command !== 'string' || !command.trim()) {
|
|
56
|
+
socket.emit('interactive_error', {
|
|
57
|
+
sessionId,
|
|
58
|
+
error: 'command 不能为空'
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const currentProjectPath = getCurrentProjectPath();
|
|
64
|
+
const execDirectory = directory && directory.trim()
|
|
65
|
+
? (path.isAbsolute(directory) ? directory : path.join(currentProjectPath, directory))
|
|
66
|
+
: currentProjectPath;
|
|
67
|
+
|
|
68
|
+
console.log(`[交互式命令] ${sessionId}: ${command} (目录: ${execDirectory})`);
|
|
69
|
+
|
|
70
|
+
const processId = nextProcessId();
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
|
|
73
|
+
let collectedStdout = '';
|
|
74
|
+
let collectedStderr = '';
|
|
75
|
+
|
|
76
|
+
const childProcess = spawn(command.trim(), [], {
|
|
77
|
+
cwd: execDirectory,
|
|
78
|
+
shell: true,
|
|
79
|
+
env: {
|
|
80
|
+
...process.env,
|
|
81
|
+
GIT_CONFIG_PARAMETERS: "'color.ui=always' 'color.status=always' 'core.quotepath=false'",
|
|
82
|
+
FORCE_COLOR: '3',
|
|
83
|
+
NPM_CONFIG_COLOR: 'always',
|
|
84
|
+
TERM: 'xterm-256color',
|
|
85
|
+
COLORTERM: 'truecolor',
|
|
86
|
+
CLICOLOR_FORCE: '1',
|
|
87
|
+
PYTHONUNBUFFERED: '1'
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
runningProcesses.set(processId, {
|
|
92
|
+
childProcess,
|
|
93
|
+
command: command.trim(),
|
|
94
|
+
startTime,
|
|
95
|
+
directory: execDirectory,
|
|
96
|
+
sessionId
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log(`[交互式命令] 创建进程 #${processId}: ${command.substring(0, 50)}`);
|
|
100
|
+
|
|
101
|
+
socket.emit('interactive_process_id', { sessionId, processId });
|
|
102
|
+
|
|
103
|
+
const isWindows = process.platform === 'win32';
|
|
104
|
+
const cmdBuiltins = ['dir', 'type', 'echo', 'set', 'path', 'cd', 'md', 'rd', 'del', 'copy', 'move', 'ren'];
|
|
105
|
+
const needsGbkConversion = isWindows && cmdBuiltins.some(builtin =>
|
|
106
|
+
command.trim().toLowerCase().startsWith(builtin + ' ') ||
|
|
107
|
+
command.trim().toLowerCase() === builtin
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
childProcess.stdout?.on('data', (stdoutData) => {
|
|
111
|
+
const output = needsGbkConversion ? iconv.decode(stdoutData, 'gbk') : stdoutData.toString('utf8');
|
|
112
|
+
collectedStdout += output;
|
|
113
|
+
socket.emit('interactive_stdout', { sessionId, data: output });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
childProcess.stderr?.on('data', (stderrData) => {
|
|
117
|
+
let output;
|
|
118
|
+
|
|
119
|
+
if (isWindows) {
|
|
120
|
+
const utf8Output = stderrData.toString('utf8');
|
|
121
|
+
|
|
122
|
+
if (!utf8Output.includes('�') || utf8Output.match(/[\u4e00-\u9fa5]/)) {
|
|
123
|
+
output = utf8Output;
|
|
124
|
+
} else {
|
|
125
|
+
try {
|
|
126
|
+
output = iconv.decode(stderrData, 'gbk');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
output = utf8Output;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
output = stderrData.toString('utf8');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
collectedStderr += output;
|
|
136
|
+
socket.emit('interactive_stderr', { sessionId, data: output });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
childProcess.on('close', (code, signal) => {
|
|
140
|
+
runningProcesses.delete(processId);
|
|
141
|
+
console.log(`[交互式命令] 进程 #${processId} 已结束`);
|
|
142
|
+
|
|
143
|
+
const executionTime = Date.now() - startTime;
|
|
144
|
+
const error = code !== 0 ? `Command exited with code ${code}` : null;
|
|
145
|
+
addCommandToHistory(
|
|
146
|
+
command.trim(),
|
|
147
|
+
collectedStdout,
|
|
148
|
+
collectedStderr,
|
|
149
|
+
error,
|
|
150
|
+
executionTime
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
socket.emit('interactive_exit', {
|
|
154
|
+
sessionId,
|
|
155
|
+
code,
|
|
156
|
+
success: code === 0
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
childProcess.on('error', (error) => {
|
|
161
|
+
runningProcesses.delete(processId);
|
|
162
|
+
console.error(`[交互式命令] 进程 #${processId} 出错:`, error);
|
|
163
|
+
|
|
164
|
+
const executionTime = Date.now() - startTime;
|
|
165
|
+
addCommandToHistory(
|
|
166
|
+
command.trim(),
|
|
167
|
+
collectedStdout,
|
|
168
|
+
collectedStderr,
|
|
169
|
+
error.message,
|
|
170
|
+
executionTime
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
socket.emit('interactive_error', {
|
|
174
|
+
sessionId,
|
|
175
|
+
error: error.message
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
socket.on(`interactive_stdin_${sessionId}`, (inputData) => {
|
|
180
|
+
const { input } = inputData;
|
|
181
|
+
console.log(`[交互式命令] 收到 stdin 输入 (${sessionId}):`, input);
|
|
182
|
+
|
|
183
|
+
if (childProcess.stdin && !childProcess.stdin.destroyed) {
|
|
184
|
+
try {
|
|
185
|
+
childProcess.stdin.write(input + '\n');
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error(`[交互式命令] 写入 stdin 失败:`, err);
|
|
188
|
+
socket.emit('interactive_error', {
|
|
189
|
+
sessionId,
|
|
190
|
+
error: `写入输入失败: ${err.message}`
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
socket.on(`interactive_stop_${sessionId}`, () => {
|
|
197
|
+
console.log(`[交互式命令] 收到停止请求 (${sessionId})`);
|
|
198
|
+
|
|
199
|
+
if (childProcess && !childProcess.killed) {
|
|
200
|
+
try {
|
|
201
|
+
if (process.platform === 'win32') {
|
|
202
|
+
exec(`taskkill /pid ${childProcess.pid} /T /F`, (error) => {
|
|
203
|
+
if (error) {
|
|
204
|
+
console.error(`[交互式命令] taskkill 失败:`, error);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
childProcess.kill('SIGTERM');
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
if (!childProcess.killed) {
|
|
211
|
+
childProcess.kill('SIGKILL');
|
|
212
|
+
}
|
|
213
|
+
}, 2000);
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error(`[交互式命令] 停止进程失败:`, err);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
socket.on('disconnect', () => {
|
|
223
|
+
console.log(`客户端已断开连接: ${socket.id} (房间: ${getProjectRoomId()})`);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -1,32 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
export function createSavePortToFile({ savePort, fs, path, cwdFn = process.cwd }) {
|
|
16
|
+
let savedPort = null;
|
|
17
|
+
|
|
18
|
+
return async function savePortToFile(port) {
|
|
19
|
+
try {
|
|
20
|
+
if (savePort && savedPort !== port) {
|
|
21
|
+
savedPort = port;
|
|
22
|
+
|
|
23
|
+
const portFilePath = path.join(cwdFn(), '.port');
|
|
24
|
+
await fs.writeFile(portFilePath, port.toString(), 'utf8');
|
|
25
|
+
console.log(`端口号 ${port} 已保存到 ${portFilePath}`);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const clientPath = path.join(cwdFn(), 'src', 'ui', 'client');
|
|
29
|
+
const envPath = path.join(clientPath, '.env.local');
|
|
30
|
+
|
|
31
|
+
await fs.access(clientPath).catch(() => {
|
|
32
|
+
console.log(`客户端目录 ${clientPath} 不存在,跳过环境变量设置`);
|
|
33
|
+
return Promise.reject(new Error('Client directory not found'));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await fs.writeFile(envPath, `VITE_BACKEND_PORT=${port}\n`, 'utf8');
|
|
37
|
+
console.log(`端口号环境变量已保存到 ${envPath}`);
|
|
38
|
+
} catch (envError) {
|
|
39
|
+
console.error('保存端口号到环境变量失败,但不影响主要功能:', envError);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('保存端口号到文件失败:', error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
1
15
|
// 实例注册表工具
|
|
2
16
|
// 维护 ~/.zen-gitsync-instances.json,记录所有正在运行的 GUI 实例
|
|
3
17
|
// 多进程并发写采用 atomic temp+rename + 进程内串行化 Promise 链
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
1
15
|
/**
|
|
2
16
|
* 路径越界检查中间件 / 工具
|
|
3
17
|
*
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
1
15
|
// 路径越界检查单元测试(用 node:test 内置)
|
|
2
16
|
import { test } from 'node:test'
|
|
3
17
|
import assert from 'node:assert/strict'
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
1
15
|
// 随机起点端口选择
|
|
2
16
|
// 默认:避免与系统保留 / 常见服务端口重叠,在较宽的"用户态"范围里随机挑一个起点
|
|
3
17
|
// 然后由 startServerOnAvailablePort 在该起点基础上顺序往上扫描 EADDRINUSE
|