@wmj-code/clone_npm 1.0.0

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/main.css ADDED
@@ -0,0 +1,374 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ /* padding: 20px; */
9
+ font-family: Arial;
10
+ background: #f5f5f5;
11
+ position: relative;
12
+ scroll-behavior: smooth;
13
+ /* 预留底部按钮区域 */
14
+ /* padding-bottom: 120px; */
15
+ }
16
+
17
+ .container {
18
+ background: white;
19
+ padding: 30px;
20
+ /* border-radius: 8px; */
21
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
22
+ }
23
+
24
+ .header {
25
+ margin-bottom: 20px;
26
+ }
27
+
28
+ button {
29
+ padding: 10px 20px;
30
+ cursor: pointer;
31
+ border: none;
32
+ border-radius: 4px;
33
+ font-size: 14px;
34
+ }
35
+
36
+ .btn-primary {
37
+ background: #409eff;
38
+ color: white;
39
+ }
40
+
41
+ .btn-primary:hover {
42
+ background: #66b1ff;
43
+ }
44
+
45
+ .btn-danger {
46
+ background: #f56c6c;
47
+ color: white;
48
+ }
49
+
50
+ .btn-danger:hover {
51
+ background: #f78989;
52
+ }
53
+
54
+ .config-item {
55
+ border: 1px solid #e6e6e6;
56
+ padding: 20px;
57
+ margin-bottom: 15px;
58
+ border-radius: 6px;
59
+ position: relative;
60
+ transition: all 0.3s;
61
+ }
62
+
63
+ .config-item.highlight {
64
+ border-color: #409eff;
65
+ box-shadow: 0 0 8px rgba(64, 158, 255, 0.3);
66
+ }
67
+
68
+ .config-item .row {
69
+ margin-bottom: 15px;
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 10px;
73
+ }
74
+
75
+ .config-item label {
76
+ width: 120px;
77
+ text-align: right;
78
+ font-size: 14px;
79
+ color: #666;
80
+ }
81
+
82
+ .config-item input {
83
+ flex: 1;
84
+ padding: 10px;
85
+ border: 1px solid #e6e6e6;
86
+ border-radius: 4px;
87
+ font-size: 14px;
88
+ }
89
+
90
+ .config-item .enable-checkbox {
91
+ width: 20px;
92
+ height: 20px;
93
+ flex: none;
94
+ cursor: pointer;
95
+ }
96
+
97
+ .editor-path {
98
+ margin: 20px 0;
99
+ padding: 20px;
100
+ border: 1px solid #e6e6e6;
101
+ border-radius: 6px;
102
+ }
103
+
104
+ .editor-path label {
105
+ display: inline-block;
106
+ /* width: 120px; */
107
+ text-align: right;
108
+ margin-right: 10px;
109
+ color: #666;
110
+ }
111
+
112
+ .editor-path input {
113
+ width: calc(100% - 130px);
114
+ padding: 10px;
115
+ border: 1px solid #e6e6e6;
116
+ border-radius: 4px;
117
+ }
118
+
119
+ .msg {
120
+ margin: 0px 0;
121
+ padding: 10px;
122
+ border-radius: 4px;
123
+ font-size: 14px;
124
+ display: none;
125
+ position: fixed;
126
+ /* width: min-content; */
127
+ top: 10px;
128
+ left: 50%;
129
+ transform: translateX(-50%);
130
+ z-index: 1000;
131
+ }
132
+
133
+ .msg-success {
134
+ background: #f0f9ff;
135
+ color: #67c23a;
136
+ border: 1px solid #e1f3d8;
137
+ }
138
+
139
+ .msg-error {
140
+ background: #fef0f0;
141
+ color: #f56c6c;
142
+ border: 1px solid #fbc4c4;
143
+ }
144
+
145
+ .floating-btn {
146
+ position: fixed;
147
+ right: 20px;
148
+ top: 50%;
149
+ transform: translateY(-50%);
150
+ z-index: 1000;
151
+ }
152
+
153
+ .back-to-top {
154
+ position: fixed;
155
+ right: 20px;
156
+ bottom: 20px;
157
+ width: 50px;
158
+ height: 50px;
159
+ background: #409eff;
160
+ color: white;
161
+ border: none;
162
+ border-radius: 50%;
163
+ cursor: pointer;
164
+ display: none;
165
+ z-index: 1000;
166
+ font-size: 20px;
167
+ }
168
+
169
+ .back-to-top:hover {
170
+ background: #66b1ff;
171
+ }
172
+
173
+ .enabled-items-panel {
174
+ position: fixed;
175
+ top: 20px;
176
+ right: 20px;
177
+ width: 300px;
178
+ max-height: 50vh;
179
+ background: white;
180
+ border: 1px solid #e6e6e6;
181
+ border-radius: 8px;
182
+ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
183
+ padding: 15px;
184
+ z-index: 1001;
185
+ overflow-y: auto;
186
+ }
187
+
188
+ .enabled-items-panel .title {
189
+ font-size: 16px;
190
+ font-weight: bold;
191
+ color: #333;
192
+ margin-bottom: 10px;
193
+ padding-bottom: 8px;
194
+ border-bottom: 1px solid #eee;
195
+ }
196
+
197
+ .enabled-items-panel .empty-tip {
198
+ color: #999;
199
+ font-size: 14px;
200
+ text-align: center;
201
+ padding: 10px 0;
202
+ }
203
+
204
+ .enabled-items-panel .item {
205
+ font-size: 13px;
206
+ color: #666;
207
+ padding: 6px 0;
208
+ border-bottom: 1px dashed #f0f0f0;
209
+ cursor: pointer;
210
+ transition: all 0.2s;
211
+ }
212
+
213
+ .enabled-items-panel .item:hover {
214
+ color: #409eff;
215
+ background-color: #f5f9ff;
216
+ padding-left: 5px;
217
+ }
218
+
219
+ .enabled-items-panel .item:last-child {
220
+ border-bottom: none;
221
+ }
222
+
223
+ .enabled-items-panel .item .name {
224
+ color: #409eff;
225
+ font-weight: 500;
226
+ }
227
+ /* 新增:功能按钮悬浮栏样式 */
228
+
229
+ .function-buttons {
230
+ position: fixed;
231
+ bottom: 10px;
232
+ left: 50%;
233
+ transform: translateX(-40%);
234
+ display: flex;
235
+ gap: 10px;
236
+ background: white;
237
+ padding: 15px;
238
+ border-radius: 8px;
239
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
240
+ z-index: 999;
241
+ }
242
+
243
+ .function-btn {
244
+ padding: 12px 20px;
245
+ background: #67c23a;
246
+ color: white;
247
+ border-radius: 6px;
248
+ font-size: 14px;
249
+ transition: all 0.2s;
250
+ }
251
+
252
+ .function-btn:hover {
253
+ background: #85ce61;
254
+ transform: translateY(-2px);
255
+ box-shadow: 0 2px 8px rgba(103, 194, 58, 0.3);
256
+ }
257
+
258
+ .function-btn:disabled {
259
+ background: #cccccc;
260
+ cursor: not-allowed;
261
+ transform: none;
262
+ box-shadow: none;
263
+ }
264
+ /* 主容器样式 */
265
+
266
+ .container {
267
+ position: relative;
268
+ }
269
+ /* 项目名称标签样式 */
270
+
271
+ .pName-tabs {
272
+ border: 1px solid #e6e6e6;
273
+ border-radius: 6px;
274
+ padding: 10px;
275
+ background: #f9f9f9;
276
+ max-height: 600px;
277
+ overflow-y: auto;
278
+ transition: all 0.3s;
279
+ position: sticky;
280
+ top: 20px;
281
+ align-self: flex-start;
282
+ }
283
+ /* 悬浮状态样式 */
284
+ /* .pName-tabs.fixed {
285
+ position: fixed;
286
+ top: 20px;
287
+ left: 20px;
288
+ z-index: 998;
289
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
290
+ max-height: calc(100vh - 40px);
291
+ } */
292
+
293
+ .pName-tab {
294
+ padding: 8px 12px;
295
+ margin-bottom: 8px;
296
+ background: white;
297
+ border: 1px solid #e6e6e6;
298
+ border-radius: 4px;
299
+ cursor: pointer;
300
+ transition: all 0.2s;
301
+ font-size: 14px;
302
+ display: flex;
303
+ justify-content: space-between;
304
+ align-items: center;
305
+ }
306
+
307
+ .pName-tab:hover {
308
+ background: #ecf5ff;
309
+ border-color: #c6e2ff;
310
+ color: #409eff;
311
+ }
312
+
313
+ .pName-tab .count {
314
+ font-size: 12px;
315
+ color: #999;
316
+ background: #f0f0f0;
317
+ padding: 2px 6px;
318
+ border-radius: 10px;
319
+ }
320
+
321
+ .pName-tab:hover .count {
322
+ background: #d9ecff;
323
+ color: #409eff;
324
+ }
325
+ /* 新增:终端输出面板样式 */
326
+
327
+ .terminal-panel {
328
+ background: #1e1e1e;
329
+ color: #ffffff;
330
+ height: 100vh;
331
+ /* border-radius: 8px; */
332
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
333
+ padding: 10px;
334
+ font-family: "Consolas", "Monaco", monospace;
335
+ font-size: 13px;
336
+ overflow-y: auto;
337
+ z-index: 998;
338
+ display: none;
339
+ }
340
+
341
+ .terminal-panel .terminal-header {
342
+ padding-bottom: 8px;
343
+ border-bottom: 1px solid #333;
344
+ margin-bottom: 8px;
345
+ display: flex;
346
+ justify-content: space-between;
347
+ align-items: center;
348
+ }
349
+
350
+ .terminal-panel .clear-btn {
351
+ padding: 4px 8px;
352
+ font-size: 12px;
353
+ background: #409eff;
354
+ color: white;
355
+ border-radius: 4px;
356
+ cursor: pointer;
357
+ }
358
+
359
+ .terminal-panel .log {
360
+ line-height: 1.4;
361
+ white-space: pre-wrap;
362
+ }
363
+
364
+ .terminal-panel .log-success {
365
+ color: #67c23a;
366
+ }
367
+
368
+ .terminal-panel .log-error {
369
+ color: #f56c6c;
370
+ }
371
+
372
+ .terminal-panel .log-info {
373
+ color: #409eff;
374
+ }
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@wmj-code/clone_npm",
3
+ "version": "1.0.0",
4
+ "description": "一个自定义命令行工具,打通clone和npm install的流程",
5
+ "main": "clone_npm.js",
6
+ "bin": {
7
+ "cn": "./clone_npm.js"
8
+ },
9
+ "keywords": [
10
+ "cli",
11
+ "cn"
12
+ ],
13
+ "author": "wmj",
14
+ "license": "MIT",
15
+ "engines": {
16
+ "node": ">=14.0.0"
17
+ },
18
+ "preferGlobal": true
19
+ }
package/server.js ADDED
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+
3
+ const http = require('http');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const url = require('url');
7
+ const { spawn } = require('child_process');
8
+
9
+ // 配置文件路径
10
+ const CONFIG_PATH = path.join(__dirname, 'config.json');
11
+ const PORT = 3000;
12
+
13
+ // 创建服务器
14
+ const server = http.createServer((req, res) => {
15
+ const parsedUrl = url.parse(req.url, true);
16
+ const pathname = parsedUrl.pathname;
17
+ console.log("🚀 ~ pathname:", pathname)
18
+
19
+ // 设置跨域和响应头
20
+ res.setHeader('Access-Control-Allow-Origin', '*');
21
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
22
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
23
+
24
+ // 处理 OPTIONS 预检请求
25
+ if (req.method === 'OPTIONS') {
26
+ res.writeHead(200);
27
+ res.end();
28
+ return;
29
+ }
30
+
31
+ // 1. 读取配置(GET /api/config)
32
+ if (req.method === 'GET' && pathname === '/api/config') {
33
+ try {
34
+ if (!fs.existsSync(CONFIG_PATH)) {
35
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify({
36
+ CodingEditPath: '',
37
+ configList: [{ enable: false, pName: '', projectName: '', gitUrl: '', branch: 'master', savePath: '', codePath: '', testEnvUrl: '', prodEnvUrl: '' }]
38
+ }, null, 2), 'utf8');
39
+ }
40
+ const config = fs.readFileSync(CONFIG_PATH, 'utf8');
41
+ res.writeHead(200, { 'Content-Type': 'application/json' });
42
+ res.end(config);
43
+ } catch (err) {
44
+ res.writeHead(500, { 'Content-Type': 'application/json' });
45
+ res.end(JSON.stringify({ error: '读取配置失败:' + err.message }));
46
+ }
47
+ return;
48
+ }
49
+
50
+ // 2. 保存配置(POST /api/save)
51
+ if (req.method === 'POST' && pathname === '/api/save') {
52
+ let body = '';
53
+ req.on('data', chunk => body += chunk);
54
+ req.on('end', () => {
55
+ try {
56
+ const config = JSON.parse(body);
57
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
58
+ res.writeHead(200, { 'Content-Type': 'application/json' });
59
+ res.end(JSON.stringify({ success: true, message: '配置保存成功!' }));
60
+ } catch (err) {
61
+ res.writeHead(500, { 'Content-Type': 'application/json' });
62
+ res.end(JSON.stringify({ success: false, error: '保存配置失败:' + err.message }));
63
+ }
64
+ });
65
+ return;
66
+ }
67
+
68
+ // 3. 选择本地文件/文件夹(POST /api/select-path)
69
+ if (req.method === 'POST' && pathname === '/api/select-path') {
70
+ let body = '';
71
+ req.on('data', chunk => body += chunk);
72
+ req.on('end', () => {
73
+ try {
74
+ const { type } = JSON.parse(body); // type: 'file' | 'folder'
75
+ let psScript;
76
+ if (type === 'folder') {
77
+ psScript = `
78
+ Add-Type -AssemblyName System.Windows.Forms
79
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
80
+ $dialog.Description = '请选择文件夹'
81
+ $dialog.ShowNewFolderButton = $true
82
+ if ($dialog.ShowDialog() -eq 'OK') { $dialog.SelectedPath } else { '' }
83
+ `;
84
+ } else {
85
+ psScript = `
86
+ Add-Type -AssemblyName System.Windows.Forms
87
+ $dialog = New-Object System.Windows.Forms.OpenFileDialog
88
+ $dialog.Title = '请选择文件'
89
+ $dialog.Filter = '可执行文件 (*.exe)|*.exe|所有文件 (*.*)|*.*'
90
+ if ($dialog.ShowDialog() -eq 'OK') { $dialog.FileName } else { '' }
91
+ `;
92
+ }
93
+
94
+ const child = spawn('powershell', ['-NoProfile', '-Command', psScript], { windowsHide: true });
95
+ let result = '';
96
+ child.stdout.on('data', (data) => { result += data.toString(); });
97
+ child.stderr.on('data', (data) => { console.error('select-path stderr:', data.toString()); });
98
+ child.on('close', (code) => {
99
+ const selectedPath = result.trim();
100
+ res.writeHead(200, { 'Content-Type': 'application/json' });
101
+ res.end(JSON.stringify({ success: true, path: selectedPath }));
102
+ });
103
+ } catch (err) {
104
+ res.writeHead(500, { 'Content-Type': 'application/json' });
105
+ res.end(JSON.stringify({ success: false, error: err.message }));
106
+ }
107
+ });
108
+ return;
109
+ }
110
+
111
+ // 4. 执行命令(POST /api/execute)
112
+ if (req.method === 'POST' && pathname === '/api/execute') {
113
+ let body = '';
114
+ req.on('data', chunk => body += chunk);
115
+ req.on('end', async() => {
116
+ try {
117
+ const { flag } = JSON.parse(body);
118
+ if (!flag || isNaN(flag)) {
119
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
120
+ res.end('ERROR: 无效的命令标识');
121
+ return;
122
+ }
123
+
124
+ // 设置流式响应头
125
+ res.writeHead(200, {
126
+ 'Content-Type': 'text/plain; charset=utf-8',
127
+ 'Transfer-Encoding': 'chunked'
128
+ });
129
+
130
+ // 执行clone_npm.js并传递flag参数
131
+ const child = spawn('node', [path.join(__dirname, 'clone_npm.js'), flag], {
132
+ windowsHide: true,
133
+ });
134
+
135
+ // 实时输出stdout
136
+ child.stdout.on('data', (data) => {
137
+ res.write(`INFO: ${data.toString()}`);
138
+ });
139
+
140
+ // 实时输出stderr
141
+ child.stderr.on('data', (data) => {
142
+ res.write(`ERROR: ${data.toString()}`);
143
+ });
144
+
145
+ // 子进程退出
146
+ child.on('close', (code) => {
147
+ if (code === 0) {
148
+ res.end(`SUCCESS: 命令 ${flag} 执行完成`);
149
+ } else {
150
+ res.end(`ERROR: 命令 ${flag} 执行失败,退出码:${code}`);
151
+ }
152
+ });
153
+
154
+ } catch (err) {
155
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
156
+ res.end(`ERROR: ${err.message}`);
157
+ }
158
+ });
159
+ return;
160
+ }
161
+
162
+ // 4. 提供静态文件
163
+ if (req.method === 'GET') {
164
+ // 根路径返回 config-editor.html
165
+ if (pathname === '/') {
166
+ const htmlPath = path.join(__dirname, 'config-editor.html');
167
+ fs.readFile(htmlPath, 'utf8', (err, data) => {
168
+ if (err) {
169
+ res.writeHead(404);
170
+ res.end('config-editor.html 不存在');
171
+ return;
172
+ }
173
+ res.writeHead(200, { 'Content-Type': 'text/html' });
174
+ res.end(data);
175
+ });
176
+ return;
177
+ }
178
+
179
+ // 处理其他静态文件请求
180
+ const staticFilePath = path.join(__dirname, pathname);
181
+
182
+ // 检查文件是否存在
183
+ if (fs.existsSync(staticFilePath)) {
184
+ // 根据文件扩展名设置 Content-Type
185
+ const ext = path.extname(staticFilePath).toLowerCase();
186
+ let contentType = 'application/octet-stream';
187
+
188
+ if (ext === '.html') contentType = 'text/html';
189
+ else if (ext === '.css') contentType = 'text/css';
190
+ else if (ext === '.js') contentType = 'text/javascript';
191
+ else if (ext === '.json') contentType = 'application/json';
192
+ else if (ext === '.png') contentType = 'image/png';
193
+ else if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg';
194
+ else if (ext === '.gif') contentType = 'image/gif';
195
+ else if (ext === '.svg') contentType = 'image/svg+xml';
196
+
197
+ // 读取并返回静态文件
198
+ fs.readFile(staticFilePath, (err, data) => {
199
+ if (err) {
200
+ res.writeHead(500);
201
+ res.end('读取文件失败');
202
+ return;
203
+ }
204
+ res.writeHead(200, { 'Content-Type': contentType });
205
+ res.end(data);
206
+ });
207
+ return;
208
+ }
209
+ }
210
+
211
+ // 5. 404 处理
212
+ res.writeHead(404);
213
+ res.end('接口不存在');
214
+ });
215
+
216
+ // 启动服务
217
+ server.on('error', (err) => {
218
+ if (err.code === 'EADDRINUSE') {
219
+ console.log(`\x1B[33m端口 ${PORT} 已被占用(配置服务已启动)\x1B[0m`);
220
+ return;
221
+ }
222
+ console.error('\x1B[31m服务启动失败:\x1B[0m', err.message);
223
+ });
224
+ process.on('SIGTERM', () => {
225
+ console.log('收到终止信号,开始清理资源...');
226
+ // 清理完成后主动退出
227
+ setTimeout(() => {
228
+ process.exit(0);
229
+ }, 1000);
230
+ });
231
+
232
+ server.listen(PORT, 'localhost', () => {
233
+ console.log(`\x1B[36m配置编辑器服务已启动:http://localhost:${PORT}\x1B[0m`);
234
+ });
235
+
236
+ module.exports = { server, PORT };
package/server.pid ADDED
@@ -0,0 +1 @@
1
+ 11500
@@ -0,0 +1,3 @@
1
+ 前提 node环境、git环境
2
+
3
+ 打开文件夹,右键打开终端 执行npm link ,然后执行cn命令,有功能说明