quick-sh 1.0.0 → 1.0.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.
@@ -3,31 +3,37 @@ const path = require('path');
3
3
  const { readConfig, readAliasConfig } = require('./config');
4
4
  const { executeFile, executeFromDirectory, executeSystemCommand } = require('./executor');
5
5
  const { checkSystemCommand } = require('./utils');
6
+ const { findRemoteScript, getRemoteScriptsDir, getSources } = require('./remote-manager');
7
+ const { t } = require('./i18n');
6
8
 
7
9
  // 执行脚本(核心逻辑,包含alias处理和优先级)
8
10
  async function executeScript(scriptName, args = []) {
9
11
  const config = await readConfig();
10
12
 
11
13
  if (!config.scriptPath) {
12
- console.error('No script path configured. Use "q -path <directory>" to set one.');
14
+ console.error(t('commands.noPathConfigured'));
13
15
  process.exit(1);
14
16
  }
15
17
 
16
18
  // 优先级1: 检查alias配置
17
19
  const aliasConfig = await readAliasConfig(config.scriptPath);
18
- if (aliasConfig[scriptName]) {
19
- const aliasValue = aliasConfig[scriptName];
20
+
21
+ // 支持两种格式:直接在根级别定义aliases,或在aliases字段下定义
22
+ const aliases = aliasConfig.aliases || aliasConfig;
23
+
24
+ if (aliases[scriptName]) {
25
+ const aliasValue = aliases[scriptName];
20
26
 
21
27
  if (typeof aliasValue === 'string') {
22
28
  // 字符串值:可能是绝对路径或系统命令
23
29
  if (path.isAbsolute(aliasValue)) {
24
30
  // 绝对路径
25
31
  if (await fs.pathExists(aliasValue)) {
26
- console.log(`Executing alias (absolute path): ${aliasValue}`);
32
+ console.log(t('commands.executingAlias', { type: 'absolute', path: aliasValue }));
27
33
  await executeFile(aliasValue, args);
28
34
  return;
29
35
  } else {
30
- console.error(`Alias target not found: ${aliasValue}`);
36
+ console.error(t('commands.aliasNotFound', { path: aliasValue }));
31
37
  process.exit(1);
32
38
  }
33
39
  } else {
@@ -36,25 +42,44 @@ async function executeScript(scriptName, args = []) {
36
42
  await executeSystemCommand(aliasValue, args);
37
43
  return;
38
44
  } else {
39
- console.error(`System command not found: ${aliasValue}`);
45
+ console.error(t('commands.systemCommandNotFound', { command: aliasValue }));
40
46
  process.exit(1);
41
47
  }
42
48
  }
43
49
  } else if (aliasValue && typeof aliasValue === 'object' && aliasValue.bin) {
44
- // 对象值:相对路径配置
45
- const relativePath = aliasValue.bin;
46
- const fullPath = path.resolve(config.scriptPath, relativePath);
50
+ // 对象值:可能是绝对路径或相对路径配置
51
+ const binPath = aliasValue.bin;
52
+ let fullPath;
53
+
54
+ if (path.isAbsolute(binPath)) {
55
+ // 绝对路径
56
+ fullPath = binPath;
57
+ } else {
58
+ // 相对路径
59
+ fullPath = path.resolve(config.scriptPath, binPath);
60
+ }
47
61
 
48
62
  if (await fs.pathExists(fullPath)) {
49
- console.log(`Executing alias (relative path): ${relativePath}`);
50
- await executeFile(fullPath, args);
63
+ console.log(t('commands.executingAlias', {
64
+ type: path.isAbsolute(binPath) ? 'absolute' : 'relative',
65
+ path: binPath
66
+ }));
67
+
68
+ // 检查是否是脚本文件(有扩展名)还是系统可执行文件
69
+ const ext = path.extname(fullPath);
70
+ if (ext === '.js' || ext === '.mjs' || ext === '.sh') {
71
+ await executeFile(fullPath, args);
72
+ } else {
73
+ // 对于没有扩展名的可执行文件,使用系统命令执行
74
+ await executeSystemCommand(fullPath, args);
75
+ }
51
76
  return;
52
77
  } else {
53
- console.error(`Alias target not found: ${fullPath}`);
78
+ console.error(t('commands.aliasNotFound', { path: fullPath }));
54
79
  process.exit(1);
55
80
  }
56
81
  } else {
57
- console.error(`Invalid alias configuration for: ${scriptName}`);
82
+ console.error(t('commands.invalidAlias', { name: scriptName }));
58
83
  process.exit(1);
59
84
  }
60
85
  }
@@ -89,65 +114,340 @@ async function executeScript(scriptName, args = []) {
89
114
  return;
90
115
  }
91
116
 
92
- // 优先级3: 检查系统可执行命令
117
+ // 优先级3: 检查远程下载的脚本
118
+ const remoteScript = await findRemoteScript(scriptName);
119
+ if (remoteScript) {
120
+ console.log(t('commands.executingRemote', {
121
+ script: `${remoteScript.sourceName}/${path.basename(remoteScript.scriptPath)}`
122
+ }));
123
+ await executeFile(remoteScript.scriptPath, args);
124
+ return;
125
+ }
126
+
127
+ // 优先级4: 检查系统可执行命令
93
128
  if (await checkSystemCommand(scriptName)) {
94
129
  await executeSystemCommand(scriptName, args);
95
130
  return;
96
131
  }
97
132
 
98
- console.error(`Command not found: ${scriptName}`);
99
- console.error(`Looked in:`);
100
- console.error(` - Alias config: ${path.join(config.scriptPath, 'config.json')}`);
101
- console.error(` - Script directory: ${config.scriptPath}`);
102
- console.error(` - System PATH`);
133
+ console.error(t('commands.commandNotFound', { command: scriptName }));
134
+ console.error(t('commands.lookedIn'));
135
+ console.error(t('commands.aliasConfig', { path: path.join(config.scriptPath, 'config.json') }));
136
+ console.error(t('commands.scriptDirectory', { path: config.scriptPath }));
137
+ console.error(t('commands.remoteScripts', { path: getRemoteScriptsDir() }));
138
+ console.error(t('commands.systemPath'));
103
139
  process.exit(1);
104
140
  }
105
141
 
142
+ // 从package.json读取描述信息
143
+ async function getPackageDescription(dirPath) {
144
+ try {
145
+ const packagePath = path.join(dirPath, 'package.json');
146
+ if (await fs.pathExists(packagePath)) {
147
+ const packageJson = await fs.readJson(packagePath);
148
+ return packageJson.description || '';
149
+ }
150
+ } catch (error) {
151
+ // 忽略读取错误
152
+ }
153
+ return '';
154
+ }
155
+
156
+ // 从脚本文件头部注释读取描述信息
157
+ async function getScriptDescription(filePath) {
158
+ try {
159
+ const content = await fs.readFile(filePath, 'utf-8');
160
+ const lines = content.split('\n').slice(0, 10); // 只读取前10行
161
+
162
+ // 查找描述注释
163
+ for (const line of lines) {
164
+ const trimmed = line.trim();
165
+
166
+ // JavaScript风格注释
167
+ if (trimmed.startsWith('// Description:') || trimmed.startsWith('// @description')) {
168
+ return trimmed.replace(/^\/\/ (?:Description:|@description)\s*/, '');
169
+ }
170
+
171
+ // Shell风格注释
172
+ if (trimmed.startsWith('# Description:') || trimmed.startsWith('# @description')) {
173
+ return trimmed.replace(/^# (?:Description:|@description)\s*/, '');
174
+ }
175
+
176
+ // 通用描述格式
177
+ if (trimmed.includes('Description:')) {
178
+ const match = trimmed.match(/Description:\s*(.+)$/);
179
+ if (match) return match[1];
180
+ }
181
+ }
182
+ } catch (error) {
183
+ // 忽略读取错误
184
+ }
185
+ return '';
186
+ }
187
+
188
+ // 递归扫描目录中的脚本文件
189
+ async function scanScripts(dir, basePath = '', maxDepth = 3, currentDepth = 0) {
190
+ const scripts = [];
191
+
192
+ if (currentDepth >= maxDepth) {
193
+ return scripts;
194
+ }
195
+
196
+ try {
197
+ const files = await fs.readdir(dir);
198
+
199
+ for (const file of files) {
200
+ // 跳过config.json和README文件
201
+ if (file === 'config.json' || file.startsWith('README') || file.startsWith('.')) {
202
+ continue;
203
+ }
204
+
205
+ const filePath = path.join(dir, file);
206
+ const stat = await fs.stat(filePath);
207
+ const relativePath = basePath ? `${basePath}/${file}` : file;
208
+
209
+ if (stat.isDirectory()) {
210
+ // 检查目录是否包含index文件
211
+ const indexFiles = ['index.js', 'index.sh', 'index.mjs'];
212
+ let hasIndex = false;
213
+
214
+ for (const indexFile of indexFiles) {
215
+ if (await fs.pathExists(path.join(filePath, indexFile))) {
216
+ // 尝试从package.json获取描述
217
+ const packageDesc = await getPackageDescription(filePath);
218
+ // 如果没有package.json描述,尝试从index文件获取
219
+ const scriptDesc = packageDesc || await getScriptDescription(path.join(filePath, indexFile));
220
+
221
+ scripts.push({
222
+ name: relativePath,
223
+ type: 'directory',
224
+ entry: indexFile,
225
+ path: relativePath,
226
+ description: scriptDesc
227
+ });
228
+ hasIndex = true;
229
+ break;
230
+ }
231
+ }
232
+
233
+ // 递归扫描子目录
234
+ const subScripts = await scanScripts(filePath, relativePath, maxDepth, currentDepth + 1);
235
+ scripts.push(...subScripts);
236
+
237
+ } else if (file.endsWith('.js') || file.endsWith('.sh') || file.endsWith('.mjs')) {
238
+ // 从脚本文件获取描述
239
+ const scriptDesc = await getScriptDescription(filePath);
240
+
241
+ scripts.push({
242
+ name: file,
243
+ type: 'file',
244
+ path: relativePath,
245
+ description: scriptDesc
246
+ });
247
+ }
248
+ }
249
+ } catch (error) {
250
+ // 忽略无法读取的目录
251
+ }
252
+
253
+ return scripts;
254
+ }
255
+
106
256
  // 显示当前配置和可用脚本
107
257
  async function showStatus() {
108
258
  const config = await readConfig();
109
259
 
110
- if (config.scriptPath) {
111
- console.log(`Current script path: ${config.scriptPath}`);
260
+ if (!config.scriptPath) {
261
+ console.log(t('commands.noPathConfigured'));
262
+ return;
263
+ }
264
+
265
+ console.log(t('status.currentPath', { path: config.scriptPath }));
266
+
267
+ // 读取别名配置
268
+ const aliasConfig = await readAliasConfig(config.scriptPath);
269
+ const aliases = aliasConfig.aliases || aliasConfig;
270
+
271
+ // 显示配置的别名
272
+ if (Object.keys(aliases).length > 0) {
273
+ console.log('\n' + t('status.configuredAliases'));
274
+
275
+ // 按类型分组别名
276
+ const aliasGroups = {
277
+ relative: [], // 相对路径
278
+ absolute: [], // 绝对路径
279
+ system: [] // 系统命令
280
+ };
281
+
282
+ for (const [name, config] of Object.entries(aliases)) {
283
+ let description = '';
284
+ let binPath = '';
285
+ let type = 'system';
286
+
287
+ if (typeof config === 'string') {
288
+ binPath = config;
289
+ if (path.isAbsolute(config)) {
290
+ type = 'absolute';
291
+ } else {
292
+ type = 'system';
293
+ }
294
+ } else if (config && typeof config === 'object') {
295
+ binPath = config.bin || '';
296
+ description = config.description || '';
297
+ if (binPath.startsWith('./')) {
298
+ type = 'relative';
299
+ } else if (path.isAbsolute(binPath)) {
300
+ type = 'absolute';
301
+ } else {
302
+ type = 'system';
303
+ }
304
+ }
305
+
306
+ aliasGroups[type].push({
307
+ name,
308
+ bin: binPath,
309
+ description
310
+ });
311
+ }
312
+
313
+ // 显示相对路径别名(项目脚本)
314
+ if (aliasGroups.relative.length > 0) {
315
+ console.log(' ' + t('status.projectScripts'));
316
+ aliasGroups.relative.forEach(alias => {
317
+ const desc = alias.description ? ` - ${alias.description}` : '';
318
+ console.log(` • ${alias.name.padEnd(15)} ${alias.bin}${desc}`);
319
+ });
320
+ }
321
+
322
+ // 显示绝对路径别名
323
+ if (aliasGroups.absolute.length > 0) {
324
+ console.log(' ' + t('status.absolutePathScripts'));
325
+ aliasGroups.absolute.forEach(alias => {
326
+ const desc = alias.description ? ` - ${alias.description}` : '';
327
+ console.log(` • ${alias.name.padEnd(15)} ${alias.bin}${desc}`);
328
+ });
329
+ }
330
+
331
+ // 显示系统命令别名
332
+ if (aliasGroups.system.length > 0) {
333
+ console.log(' ' + t('status.systemCommandAliases'));
334
+ aliasGroups.system.forEach(alias => {
335
+ const desc = alias.description ? ` - ${alias.description}` : '';
336
+ console.log(` • ${alias.name.padEnd(15)} ${alias.bin}${desc}`);
337
+ });
338
+ }
339
+ }
340
+
341
+ // 扫描并显示所有脚本文件
342
+ try {
343
+ const scripts = await scanScripts(config.scriptPath);
112
344
 
113
- // 列出可用的脚本
114
- try {
115
- const files = await fs.readdir(config.scriptPath);
116
- const scripts = [];
345
+ if (scripts.length > 0) {
346
+ console.log('\n' + t('status.availableScripts'));
117
347
 
118
- for (const file of files) {
119
- const filePath = path.join(config.scriptPath, file);
120
- const stat = await fs.stat(filePath);
348
+ // 按目录分组
349
+ const scriptsByDir = {};
350
+ scripts.forEach(script => {
351
+ const dir = path.dirname(script.path);
352
+ const dirName = dir === '.' ? 'root' : dir;
121
353
 
122
- if (stat.isDirectory()) {
123
- // 检查目录是否包含index文件
124
- const indexFiles = ['index.js', 'index.sh', 'index.mjs'];
125
- for (const indexFile of indexFiles) {
126
- if (await fs.pathExists(path.join(filePath, indexFile))) {
127
- scripts.push(`${file}/ (${indexFile})`);
128
- break;
129
- }
354
+ if (!scriptsByDir[dirName]) {
355
+ scriptsByDir[dirName] = [];
356
+ }
357
+ scriptsByDir[dirName].push(script);
358
+ });
359
+
360
+ // 显示每个目录的脚本
361
+ for (const [dirName, dirScripts] of Object.entries(scriptsByDir)) {
362
+ if (dirName === 'root') {
363
+ console.log(' ' + t('status.rootDirectory'));
364
+ } else {
365
+ console.log(` 📁 ${dirName}/:`);
366
+ }
367
+
368
+ dirScripts.forEach(script => {
369
+ const description = script.description ? ` - ${script.description}` : '';
370
+
371
+ if (script.type === 'directory') {
372
+ console.log(` 📁 ${script.name}/ (${script.entry})${description}`);
373
+ } else {
374
+ const ext = path.extname(script.name);
375
+ const icon = ext === '.js' || ext === '.mjs' ? '📄' : ext === '.sh' ? '📜' : '📄';
376
+ console.log(` ${icon} ${script.name.padEnd(20)}${description}`);
377
+ }
378
+ });
379
+ }
380
+ } else {
381
+ console.log('\n' + t('status.noScriptsFound'));
382
+ }
383
+
384
+ } catch (error) {
385
+ console.error(t('status.errorScanning', { error: error.message }));
386
+ }
387
+
388
+ // 显示远程脚本
389
+ try {
390
+ const sources = await getSources();
391
+ if (Object.keys(sources).length > 0) {
392
+ console.log('\n' + t('status.remoteScriptsTitle'));
393
+
394
+ let hasRemoteScripts = false;
395
+ for (const [sourceName, source] of Object.entries(sources)) {
396
+ const sourceDir = path.join(getRemoteScriptsDir(), sourceName);
397
+ if (await fs.pathExists(sourceDir)) {
398
+ const files = await fs.readdir(sourceDir);
399
+ const scripts = files.filter(f => !f.startsWith('.'));
400
+
401
+ if (scripts.length > 0) {
402
+ console.log(` 📡 ${sourceName} (${source.type}):`);
403
+ scripts.sort().forEach(script => {
404
+ const ext = path.extname(script);
405
+ const icon = ext === '.js' || ext === '.mjs' ? '📄' : ext === '.sh' ? '📜' : '📄';
406
+ console.log(` ${icon} ${script}`);
407
+ });
408
+ hasRemoteScripts = true;
130
409
  }
131
- } else if (file.endsWith('.js') || file.endsWith('.sh') || file.endsWith('.mjs')) {
132
- scripts.push(file);
133
410
  }
134
411
  }
135
412
 
136
- if (scripts.length > 0) {
137
- console.log('\nAvailable scripts:');
138
- scripts.forEach(script => console.log(` - ${script}`));
139
- } else {
140
- console.log('\nNo executable scripts found.');
413
+ if (!hasRemoteScripts) {
414
+ console.log(' ' + t('status.noRemoteScripts'));
415
+ console.log(' ' + t('status.downloadTip'));
141
416
  }
142
- } catch (error) {
143
- console.error('Error reading script directory:', error.message);
144
417
  }
145
- } else {
146
- console.log('No script path configured. Use "q -path <directory>" to set one.');
418
+ } catch (error) {
419
+ // 忽略远程脚本显示错误
147
420
  }
421
+
422
+ // 显示使用提示
423
+ console.log('\n' + t('status.usageTips'));
424
+ console.log(' ' + t('status.useAliases'));
425
+ console.log(' ' + t('status.useFileNames'));
426
+ console.log(' ' + t('status.useDirectories'));
427
+ console.log(' ' + t('status.useRemote'));
428
+ console.log(' ' + t('status.getHelp'));
429
+ console.log(' ' + t('status.manageSources'));
430
+ }
431
+
432
+ // 显示简洁的软件介绍
433
+ function showBrief() {
434
+ console.log(`\n🚀 ${t('app.name')}`);
435
+ console.log(`${t('app.description')}\n`);
436
+
437
+ console.log(t('help.usage'));
438
+ console.log(` q <script> ${t('help.executeScript')}`);
439
+ console.log(` q -l ${t('help.listScripts')}`);
440
+ console.log(` q -h, --help ${t('help.showHelp')}`);
441
+ console.log(` q -path <dir> ${t('help.setDirectory')}\n`);
442
+
443
+ console.log(t('help.examples'));
444
+ console.log(` q hello ${t('help.runScript', { script: 'hello' })}`);
445
+ console.log(` q -l ${t('help.listScripts')}`);
446
+ console.log(` q --help ${t('help.showHelp')}\n`);
148
447
  }
149
448
 
150
449
  module.exports = {
151
450
  executeScript,
152
- showStatus
451
+ showStatus,
452
+ showBrief
153
453
  };