zen-gitsync 2.12.2 → 2.12.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.
Files changed (47) hide show
  1. package/LICENSE +190 -21
  2. package/index.js +25 -11
  3. package/package.json +18 -5
  4. package/scripts/convert-colors-to-vars.cjs +286 -272
  5. package/scripts/convert-fontsize-to-vars.cjs +221 -207
  6. package/scripts/convert-spacing-to-vars.cjs +256 -242
  7. package/scripts/convert-to-standard-vars.cjs +282 -268
  8. package/scripts/release.js +599 -585
  9. package/src/config.js +350 -336
  10. package/src/gitCommit.js +455 -440
  11. package/src/ui/public/assets/{EditorView-bnJmBq-i.js → EditorView-BZaOzahT.js} +2 -2
  12. package/src/ui/public/assets/EditorView-CbqSI9nw.css +1 -0
  13. package/src/ui/public/assets/{SourceMapView-Rz5SD0A0.js → SourceMapView-D_8mnVt2.js} +3 -3
  14. package/src/ui/public/assets/SourceMapView-DyMK80hS.css +1 -0
  15. package/src/ui/public/assets/{index-Bo3tntQh.js → index-BgFwmXzV.js} +11 -11
  16. package/src/ui/public/assets/{index-bOs5P8fz.css → index-Dsi6tg7k.css} +1 -1
  17. package/src/ui/public/index.html +2 -2
  18. package/src/ui/server/index.js +410 -396
  19. package/src/ui/server/middleware/requestLogger.js +51 -37
  20. package/src/ui/server/routes/branchStatus.js +101 -87
  21. package/src/ui/server/routes/code.js +110 -96
  22. package/src/ui/server/routes/codeAnalysis.js +995 -981
  23. package/src/ui/server/routes/config.js +1190 -1158
  24. package/src/ui/server/routes/exec.js +272 -258
  25. package/src/ui/server/routes/fileOpen.js +279 -265
  26. package/src/ui/server/routes/fs.js +701 -687
  27. package/src/ui/server/routes/git/diff.js +352 -338
  28. package/src/ui/server/routes/git/diffUtils.js +128 -114
  29. package/src/ui/server/routes/git/stash.js +552 -538
  30. package/src/ui/server/routes/git/tags.js +172 -158
  31. package/src/ui/server/routes/git.js +190 -176
  32. package/src/ui/server/routes/gitOps.js +1179 -1165
  33. package/src/ui/server/routes/instances.js +14 -0
  34. package/src/ui/server/routes/npm.js +1023 -1009
  35. package/src/ui/server/routes/process.js +82 -68
  36. package/src/ui/server/routes/status.js +67 -53
  37. package/src/ui/server/routes/terminal.js +319 -305
  38. package/src/ui/server/socket/registerUiSocketHandlers.js +226 -212
  39. package/src/ui/server/utils/createSavePortToFile.js +46 -32
  40. package/src/ui/server/utils/instanceRegistry.js +14 -0
  41. package/src/ui/server/utils/pathGuard.js +14 -0
  42. package/src/ui/server/utils/pathGuard.test.js +14 -0
  43. package/src/ui/server/utils/randomStartPort.js +14 -0
  44. package/src/ui/server/utils/startServerOnAvailablePort.js +101 -87
  45. package/src/utils/index.js +14 -0
  46. package/src/ui/public/assets/EditorView-CHBjgiZc.css +0 -1
  47. package/src/ui/public/assets/SourceMapView-DhQX0K7t.css +0 -1
@@ -1,212 +1,226 @@
1
- export function registerUiSocketHandlers({
2
- io,
3
- getProjectRoomId,
4
- getCurrentProjectPath,
5
- getAndBroadcastStatus,
6
- getCommandHistory,
7
- clearCommandHistory,
8
- addCommandToHistory,
9
- runningProcesses,
10
- nextProcessId,
11
- spawn,
12
- exec,
13
- path,
14
- iconv
15
- }) {
16
- io.on('connection', (socket) => {
17
- console.log('客户端已连接:', socket.id);
18
-
19
- const projectRoomId = getProjectRoomId();
20
- socket.join(projectRoomId);
21
- console.log(`客户端 ${socket.id} 已加入房间: ${projectRoomId}`);
22
-
23
- getAndBroadcastStatus();
24
-
25
- const history = getCommandHistory();
26
- socket.emit('initial_command_history', { history });
27
-
28
- socket.on('request_full_history', () => {
29
- const fullHistory = getCommandHistory();
30
- socket.emit('full_command_history', { history: fullHistory });
31
- });
32
-
33
- socket.on('clear_command_history', () => {
34
- const result = clearCommandHistory();
35
- socket.emit('command_history_cleared', { success: result });
36
- });
37
-
38
- socket.on('exec_interactive', async (data) => {
39
- const { command, directory, sessionId } = data;
40
-
41
- if (!command || typeof command !== 'string' || !command.trim()) {
42
- socket.emit('interactive_error', {
43
- sessionId,
44
- error: 'command 不能为空'
45
- });
46
- return;
47
- }
48
-
49
- const currentProjectPath = getCurrentProjectPath();
50
- const execDirectory = directory && directory.trim()
51
- ? (path.isAbsolute(directory) ? directory : path.join(currentProjectPath, directory))
52
- : currentProjectPath;
53
-
54
- console.log(`[交互式命令] ${sessionId}: ${command} (目录: ${execDirectory})`);
55
-
56
- const processId = nextProcessId();
57
- const startTime = Date.now();
58
-
59
- let collectedStdout = '';
60
- let collectedStderr = '';
61
-
62
- const childProcess = spawn(command.trim(), [], {
63
- cwd: execDirectory,
64
- shell: true,
65
- env: {
66
- ...process.env,
67
- GIT_CONFIG_PARAMETERS: "'color.ui=always' 'color.status=always' 'core.quotepath=false'",
68
- FORCE_COLOR: '3',
69
- NPM_CONFIG_COLOR: 'always',
70
- TERM: 'xterm-256color',
71
- COLORTERM: 'truecolor',
72
- CLICOLOR_FORCE: '1',
73
- PYTHONUNBUFFERED: '1'
74
- }
75
- });
76
-
77
- runningProcesses.set(processId, {
78
- childProcess,
79
- command: command.trim(),
80
- startTime,
81
- directory: execDirectory,
82
- sessionId
83
- });
84
-
85
- console.log(`[交互式命令] 创建进程 #${processId}: ${command.substring(0, 50)}`);
86
-
87
- socket.emit('interactive_process_id', { sessionId, processId });
88
-
89
- const isWindows = process.platform === 'win32';
90
- const cmdBuiltins = ['dir', 'type', 'echo', 'set', 'path', 'cd', 'md', 'rd', 'del', 'copy', 'move', 'ren'];
91
- const needsGbkConversion = isWindows && cmdBuiltins.some(builtin =>
92
- command.trim().toLowerCase().startsWith(builtin + ' ') ||
93
- command.trim().toLowerCase() === builtin
94
- );
95
-
96
- childProcess.stdout?.on('data', (stdoutData) => {
97
- const output = needsGbkConversion ? iconv.decode(stdoutData, 'gbk') : stdoutData.toString('utf8');
98
- collectedStdout += output;
99
- socket.emit('interactive_stdout', { sessionId, data: output });
100
- });
101
-
102
- childProcess.stderr?.on('data', (stderrData) => {
103
- let output;
104
-
105
- if (isWindows) {
106
- const utf8Output = stderrData.toString('utf8');
107
-
108
- if (!utf8Output.includes('�') || utf8Output.match(/[\u4e00-\u9fa5]/)) {
109
- output = utf8Output;
110
- } else {
111
- try {
112
- output = iconv.decode(stderrData, 'gbk');
113
- } catch (e) {
114
- output = utf8Output;
115
- }
116
- }
117
- } else {
118
- output = stderrData.toString('utf8');
119
- }
120
-
121
- collectedStderr += output;
122
- socket.emit('interactive_stderr', { sessionId, data: output });
123
- });
124
-
125
- childProcess.on('close', (code, signal) => {
126
- runningProcesses.delete(processId);
127
- console.log(`[交互式命令] 进程 #${processId} 已结束`);
128
-
129
- const executionTime = Date.now() - startTime;
130
- const error = code !== 0 ? `Command exited with code ${code}` : null;
131
- addCommandToHistory(
132
- command.trim(),
133
- collectedStdout,
134
- collectedStderr,
135
- error,
136
- executionTime
137
- );
138
-
139
- socket.emit('interactive_exit', {
140
- sessionId,
141
- code,
142
- success: code === 0
143
- });
144
- });
145
-
146
- childProcess.on('error', (error) => {
147
- runningProcesses.delete(processId);
148
- console.error(`[交互式命令] 进程 #${processId} 出错:`, error);
149
-
150
- const executionTime = Date.now() - startTime;
151
- addCommandToHistory(
152
- command.trim(),
153
- collectedStdout,
154
- collectedStderr,
155
- error.message,
156
- executionTime
157
- );
158
-
159
- socket.emit('interactive_error', {
160
- sessionId,
161
- error: error.message
162
- });
163
- });
164
-
165
- socket.on(`interactive_stdin_${sessionId}`, (inputData) => {
166
- const { input } = inputData;
167
- console.log(`[交互式命令] 收到 stdin 输入 (${sessionId}):`, input);
168
-
169
- if (childProcess.stdin && !childProcess.stdin.destroyed) {
170
- try {
171
- childProcess.stdin.write(input + '\n');
172
- } catch (err) {
173
- console.error(`[交互式命令] 写入 stdin 失败:`, err);
174
- socket.emit('interactive_error', {
175
- sessionId,
176
- error: `写入输入失败: ${err.message}`
177
- });
178
- }
179
- }
180
- });
181
-
182
- socket.on(`interactive_stop_${sessionId}`, () => {
183
- console.log(`[交互式命令] 收到停止请求 (${sessionId})`);
184
-
185
- if (childProcess && !childProcess.killed) {
186
- try {
187
- if (process.platform === 'win32') {
188
- exec(`taskkill /pid ${childProcess.pid} /T /F`, (error) => {
189
- if (error) {
190
- console.error(`[交互式命令] taskkill 失败:`, error);
191
- }
192
- });
193
- } else {
194
- childProcess.kill('SIGTERM');
195
- setTimeout(() => {
196
- if (!childProcess.killed) {
197
- childProcess.kill('SIGKILL');
198
- }
199
- }, 2000);
200
- }
201
- } catch (err) {
202
- console.error(`[交互式命令] 停止进程失败:`, err);
203
- }
204
- }
205
- });
206
- });
207
-
208
- socket.on('disconnect', () => {
209
- console.log(`客户端已断开连接: ${socket.id} (房间: ${getProjectRoomId()})`);
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
- export function createSavePortToFile({ savePort, fs, path, cwdFn = process.cwd }) {
2
- let savedPort = null;
3
-
4
- return async function savePortToFile(port) {
5
- try {
6
- if (savePort && savedPort !== port) {
7
- savedPort = port;
8
-
9
- const portFilePath = path.join(cwdFn(), '.port');
10
- await fs.writeFile(portFilePath, port.toString(), 'utf8');
11
- console.log(`端口号 ${port} 已保存到 ${portFilePath}`);
12
-
13
- try {
14
- const clientPath = path.join(cwdFn(), 'src', 'ui', 'client');
15
- const envPath = path.join(clientPath, '.env.local');
16
-
17
- await fs.access(clientPath).catch(() => {
18
- console.log(`客户端目录 ${clientPath} 不存在,跳过环境变量设置`);
19
- return Promise.reject(new Error('Client directory not found'));
20
- });
21
-
22
- await fs.writeFile(envPath, `VITE_BACKEND_PORT=${port}\n`, 'utf8');
23
- console.log(`端口号环境变量已保存到 ${envPath}`);
24
- } catch (envError) {
25
- console.error('保存端口号到环境变量失败,但不影响主要功能:', envError);
26
- }
27
- }
28
- } catch (error) {
29
- console.error('保存端口号到文件失败:', error);
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