ccli-core 0.0.1
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/commands/adc.d.ts +2 -0
- package/dist/commands/adc.js +59 -0
- package/dist/commands/adc.js.map +1 -0
- package/dist/commands/cpu-list.d.ts +2 -0
- package/dist/commands/cpu-list.js +44 -0
- package/dist/commands/cpu-list.js.map +1 -0
- package/dist/commands/cpu-log.d.ts +2 -0
- package/dist/commands/cpu-log.js +85 -0
- package/dist/commands/cpu-log.js.map +1 -0
- package/dist/commands/cpu-update.d.ts +2 -0
- package/dist/commands/cpu-update.js +47 -0
- package/dist/commands/cpu-update.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/template.d.ts +5 -0
- package/dist/template.js +23 -0
- package/dist/template.js.map +1 -0
- package/dist/utils/cmdLogger.d.ts +16 -0
- package/dist/utils/cmdLogger.js +80 -0
- package/dist/utils/cmdLogger.js.map +1 -0
- package/dist/utils/escapeChars.d.ts +1 -0
- package/dist/utils/escapeChars.js +20 -0
- package/dist/utils/escapeChars.js.map +1 -0
- package/dist/utils/exec.d.ts +38 -0
- package/dist/utils/exec.js +157 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/getJsMeta.d.ts +16 -0
- package/dist/utils/getJsMeta.js +27 -0
- package/dist/utils/getJsMeta.js.map +1 -0
- package/dist/utils/promise/promiseForEach.d.ts +8 -0
- package/dist/utils/promise/promiseForEach.js +39 -0
- package/dist/utils/promise/promiseForEach.js.map +1 -0
- package/dist/utils/sleep.d.ts +1 -0
- package/dist/utils/sleep.js +4 -0
- package/dist/utils/sleep.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @cmdName adc
|
|
4
|
+
* 在 workspace 中快速创建新命令文件并注册到全局。
|
|
5
|
+
* 必须在 workspace 根目录下运行。
|
|
6
|
+
*/
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import { program } from "commander";
|
|
11
|
+
import { COMMAND_TEMPLATE } from "../template.js";
|
|
12
|
+
function normalizeCmdSlug(raw) {
|
|
13
|
+
const n = raw.trim().replace(/\\/g, "/");
|
|
14
|
+
const rest = n.startsWith("tool/") ? n.slice(5) : n;
|
|
15
|
+
if (!rest || rest.startsWith("/")) {
|
|
16
|
+
throw new Error(`无效的命令名: ${raw}`);
|
|
17
|
+
}
|
|
18
|
+
return rest;
|
|
19
|
+
}
|
|
20
|
+
program
|
|
21
|
+
.arguments("<cmdname> [desc] [targetPath]")
|
|
22
|
+
.description("在 workspace 中创建新命令文件并注册到全局")
|
|
23
|
+
.action((cmdname, desc = "No description provided.", targetPathArg) => {
|
|
24
|
+
const workspaceRoot = process.cwd();
|
|
25
|
+
const cmdSlug = normalizeCmdSlug(cmdname);
|
|
26
|
+
console.log(`创建命令文件:${cmdSlug}\n描述:${desc}`);
|
|
27
|
+
const content = COMMAND_TEMPLATE.replace(/\$\{cmdname\}/g, cmdSlug).replace(/\$\{desc\}/g, desc);
|
|
28
|
+
let targetPath;
|
|
29
|
+
if (targetPathArg) {
|
|
30
|
+
let resolvedPath = path.resolve(workspaceRoot, targetPathArg);
|
|
31
|
+
if (!resolvedPath.endsWith(".ts")) {
|
|
32
|
+
if (fs.existsSync(resolvedPath) &&
|
|
33
|
+
fs.statSync(resolvedPath).isDirectory()) {
|
|
34
|
+
resolvedPath = path.join(resolvedPath, `${cmdSlug}.ts`);
|
|
35
|
+
}
|
|
36
|
+
else if (!path.extname(resolvedPath)) {
|
|
37
|
+
resolvedPath += ".ts";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
targetPath = resolvedPath;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
targetPath = path.join(workspaceRoot, "src/commands/tool", `${cmdSlug}.ts`);
|
|
44
|
+
}
|
|
45
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
46
|
+
fs.writeFileSync(targetPath, content);
|
|
47
|
+
try {
|
|
48
|
+
execSync(`code "${targetPath}"`, { stdio: "inherit" });
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// code 命令不可用时静默忽略
|
|
52
|
+
}
|
|
53
|
+
execSync("cpu-update", { stdio: "inherit", cwd: workspaceRoot });
|
|
54
|
+
console.log(`\n✅ 命令已就绪`);
|
|
55
|
+
console.log(` 命令名:${cmdSlug}`);
|
|
56
|
+
console.log(` 源文件:${targetPath}`);
|
|
57
|
+
})
|
|
58
|
+
.parse(process.argv);
|
|
59
|
+
//# sourceMappingURL=adc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adc.js","sourceRoot":"","sources":["../../src/commands/adc.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * @cmdName adc\n * 在 workspace 中快速创建新命令文件并注册到全局。\n * 必须在 workspace 根目录下运行。\n */\nimport path from \"path\";\nimport fs from \"fs\";\nimport { execSync } from \"child_process\";\nimport { program } from \"commander\";\nimport { COMMAND_TEMPLATE } from \"../template.js\";\n\nfunction normalizeCmdSlug(raw: string): string {\n const n = raw.trim().replace(/\\\\/g, \"/\");\n const rest = n.startsWith(\"tool/\") ? n.slice(5) : n;\n if (!rest || rest.startsWith(\"/\")) {\n throw new Error(`无效的命令名: ${raw}`);\n }\n return rest;\n}\n\nprogram\n .arguments(\"<cmdname> [desc] [targetPath]\")\n .description(\"在 workspace 中创建新命令文件并注册到全局\")\n .action(\n (\n cmdname: string,\n desc: string = \"No description provided.\",\n targetPathArg?: string\n ) => {\n const workspaceRoot = process.cwd();\n const cmdSlug = normalizeCmdSlug(cmdname);\n console.log(`创建命令文件:${cmdSlug}\\n描述:${desc}`);\n\n const content = COMMAND_TEMPLATE.replace(/\\$\\{cmdname\\}/g, cmdSlug).replace(\n /\\$\\{desc\\}/g,\n desc\n );\n\n let targetPath: string;\n if (targetPathArg) {\n let resolvedPath = path.resolve(workspaceRoot, targetPathArg);\n if (!resolvedPath.endsWith(\".ts\")) {\n if (\n fs.existsSync(resolvedPath) &&\n fs.statSync(resolvedPath).isDirectory()\n ) {\n resolvedPath = path.join(resolvedPath, `${cmdSlug}.ts`);\n } else if (!path.extname(resolvedPath)) {\n resolvedPath += \".ts\";\n }\n }\n targetPath = resolvedPath;\n } else {\n targetPath = path.join(\n workspaceRoot,\n \"src/commands/tool\",\n `${cmdSlug}.ts`\n );\n }\n\n fs.mkdirSync(path.dirname(targetPath), { recursive: true });\n fs.writeFileSync(targetPath, content);\n\n try {\n execSync(`code \"${targetPath}\"`, { stdio: \"inherit\" });\n } catch {\n // code 命令不可用时静默忽略\n }\n\n execSync(\"cpu-update\", { stdio: \"inherit\", cwd: workspaceRoot });\n\n console.log(`\\n✅ 命令已就绪`);\n console.log(` 命令名:${cmdSlug}`);\n console.log(` 源文件:${targetPath}`);\n }\n )\n .parse(process.argv);\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @cmdName cpu-list
|
|
4
|
+
* 列出 workspace 当前已注册的所有命令。
|
|
5
|
+
* 必须在 workspace 根目录下运行。
|
|
6
|
+
*/
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import Table from "cli-table3";
|
|
11
|
+
const workspaceRoot = process.cwd();
|
|
12
|
+
const packageJsonPath = path.join(workspaceRoot, "package.json");
|
|
13
|
+
try {
|
|
14
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
15
|
+
const bin = packageJson.bin || {};
|
|
16
|
+
const commandNames = Object.keys(bin);
|
|
17
|
+
if (commandNames.length === 0) {
|
|
18
|
+
console.log(chalk.yellow("目前没有注册任何命令。"));
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
console.log(chalk.cyan(`\n🚀 当前已注册命令 (${commandNames.length}):\n`));
|
|
22
|
+
const table = new Table({
|
|
23
|
+
head: [chalk.green("领域"), chalk.green("命令名称"), chalk.green("源文件路径")],
|
|
24
|
+
colWidths: [15, 25, 60],
|
|
25
|
+
});
|
|
26
|
+
for (const [name, filePath] of Object.entries(bin)) {
|
|
27
|
+
const parts = filePath.split("/");
|
|
28
|
+
let domain = "unknown";
|
|
29
|
+
const commandsIdx = parts.indexOf("commands");
|
|
30
|
+
if (commandsIdx !== -1 && parts.length > commandsIdx + 1) {
|
|
31
|
+
domain = parts[commandsIdx + 1];
|
|
32
|
+
if (domain.endsWith(".js"))
|
|
33
|
+
domain = "root";
|
|
34
|
+
}
|
|
35
|
+
table.push([chalk.blue(domain), chalk.yellow(name), filePath]);
|
|
36
|
+
}
|
|
37
|
+
console.log(table.toString());
|
|
38
|
+
console.log("");
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(chalk.red("读取 package.json 失败:"), error.message);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=cpu-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpu-list.js","sourceRoot":"","sources":["../../src/commands/cpu-list.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AACpC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AAEjE,IAAI,CAAC;IACH,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;IACzE,MAAM,GAAG,GAA2B,WAAW,CAAC,GAAG,IAAI,EAAE,CAAC;IAC1D,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEtC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,YAAY,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACtB,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;KACxB,CAAC,CAAC;IAEH,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,MAAM,GAAG,SAAS,CAAC;QACvB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,WAAW,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;YACzD,MAAM,GAAG,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;YAChC,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,MAAM,GAAG,MAAM,CAAC;QAC9C,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAAC,OAAO,KAAU,EAAE,CAAC;IACpB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * @cmdName cpu-list\n * 列出 workspace 当前已注册的所有命令。\n * 必须在 workspace 根目录下运行。\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport Table from \"cli-table3\";\n\nconst workspaceRoot = process.cwd();\nconst packageJsonPath = path.join(workspaceRoot, \"package.json\");\n\ntry {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf8\"));\n const bin: Record<string, string> = packageJson.bin || {};\n const commandNames = Object.keys(bin);\n\n if (commandNames.length === 0) {\n console.log(chalk.yellow(\"目前没有注册任何命令。\"));\n process.exit(0);\n }\n\n console.log(chalk.cyan(`\\n🚀 当前已注册命令 (${commandNames.length}):\\n`));\n\n const table = new Table({\n head: [chalk.green(\"领域\"), chalk.green(\"命令名称\"), chalk.green(\"源文件路径\")],\n colWidths: [15, 25, 60],\n });\n\n for (const [name, filePath] of Object.entries(bin)) {\n const parts = filePath.split(\"/\");\n let domain = \"unknown\";\n const commandsIdx = parts.indexOf(\"commands\");\n if (commandsIdx !== -1 && parts.length > commandsIdx + 1) {\n domain = parts[commandsIdx + 1];\n if (domain.endsWith(\".js\")) domain = \"root\";\n }\n table.push([chalk.blue(domain), chalk.yellow(name), filePath]);\n }\n\n console.log(table.toString());\n console.log(\"\");\n} catch (error: any) {\n console.error(chalk.red(\"读取 package.json 失败:\"), error.message);\n process.exit(1);\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @cmdName cpu-log
|
|
4
|
+
* 查看所有命令的历史运行记录。
|
|
5
|
+
*/
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { program } from "commander";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import Table from "cli-table3";
|
|
12
|
+
function formatDuration(ms) {
|
|
13
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
14
|
+
}
|
|
15
|
+
function shortenPath(p) {
|
|
16
|
+
return p.replace(homedir(), "~");
|
|
17
|
+
}
|
|
18
|
+
function truncateCmd(cmd, args, maxLen = 38) {
|
|
19
|
+
const full = args.length ? `${cmd} ${args.join(" ")}` : cmd;
|
|
20
|
+
return full.length > maxLen ? full.slice(0, maxLen - 3) + "..." : full;
|
|
21
|
+
}
|
|
22
|
+
function readIndex() {
|
|
23
|
+
const indexFile = join(homedir(), "ccli", "logs", "index.jsonl");
|
|
24
|
+
if (!existsSync(indexFile))
|
|
25
|
+
return [];
|
|
26
|
+
const lines = readFileSync(indexFile, "utf8").trim().split("\n");
|
|
27
|
+
const entries = [];
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
if (!line.trim())
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
entries.push(JSON.parse(line));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// 跳过损坏行
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return entries;
|
|
39
|
+
}
|
|
40
|
+
program
|
|
41
|
+
.description("查看命令历史运行记录(存储于 ~/ccli/logs/)")
|
|
42
|
+
.option("-n, --tail <n>", "显示最近 n 条", "30")
|
|
43
|
+
.option("-c, --cmd <name>", "按命令名过滤")
|
|
44
|
+
.option("--failed", "只显示失败记录")
|
|
45
|
+
.action((opts) => {
|
|
46
|
+
let entries = readIndex();
|
|
47
|
+
if (opts.cmd) {
|
|
48
|
+
entries = entries.filter((e) => e.cmd === opts.cmd);
|
|
49
|
+
}
|
|
50
|
+
if (opts.failed) {
|
|
51
|
+
entries = entries.filter((e) => e.exitCode !== 0);
|
|
52
|
+
}
|
|
53
|
+
// 按时间倒序(最新在上)
|
|
54
|
+
entries.sort((a, b) => b.ts.localeCompare(a.ts));
|
|
55
|
+
const tail = Math.max(1, parseInt(opts.tail, 10) || 30);
|
|
56
|
+
entries = entries.slice(0, tail);
|
|
57
|
+
if (entries.length === 0) {
|
|
58
|
+
console.log(chalk.yellow("暂无运行记录。运行任意带 initCmdLogger 的命令后再试。"));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const table = new Table({
|
|
62
|
+
head: [
|
|
63
|
+
chalk.cyan("命令"),
|
|
64
|
+
chalk.cyan("结果"),
|
|
65
|
+
chalk.cyan("耗时"),
|
|
66
|
+
chalk.cyan("日志位置"),
|
|
67
|
+
],
|
|
68
|
+
colWidths: [40, 14, 8, 48],
|
|
69
|
+
style: { compact: false },
|
|
70
|
+
});
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
const cmdDisplay = truncateCmd(entry.cmd, entry.args);
|
|
73
|
+
const resultDisplay = entry.exitCode === 0
|
|
74
|
+
? chalk.green("✅ 成功")
|
|
75
|
+
: chalk.red(`❌ 失败(${entry.exitCode})`);
|
|
76
|
+
const durationDisplay = formatDuration(entry.duration);
|
|
77
|
+
const pathDisplay = shortenPath(entry.logFile);
|
|
78
|
+
table.push([cmdDisplay, resultDisplay, durationDisplay, pathDisplay]);
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk.bold(`\n最近 ${entries.length} 条运行记录:\n`));
|
|
81
|
+
console.log(table.toString());
|
|
82
|
+
console.log();
|
|
83
|
+
})
|
|
84
|
+
.parse(process.argv);
|
|
85
|
+
//# sourceMappingURL=cpu-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpu-log.js","sourceRoot":"","sources":["../../src/commands/cpu-log.ts"],"names":[],"mappings":";AACA;;;GAGG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B,SAAS,cAAc,CAAC,EAAU;IAChC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAC9D,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,IAAc,EAAE,MAAM,GAAG,EAAE;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5D,OAAO,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACjE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,OAAO,GAAe,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ;QACV,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,OAAO;KACJ,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,gBAAgB,EAAE,UAAU,EAAE,IAAI,CAAC;KAC1C,MAAM,CAAC,kBAAkB,EAAE,QAAQ,CAAC;KACpC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC;KAC7B,MAAM,CAAC,CAAC,IAAsD,EAAE,EAAE;IACjE,IAAI,OAAO,GAAG,SAAS,EAAE,CAAC;IAE1B,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,cAAc;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEjD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACxD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAEjC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,oCAAoC,CAAC,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;QACtB,IAAI,EAAE;YACJ,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;SACnB;QACD,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;KAC1B,CAAC,CAAC;IAEH,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,aAAa,GACjB,KAAK,CAAC,QAAQ,KAAK,CAAC;YAClB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;YACrB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;QAC3C,MAAM,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAE/C,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,OAAO,CAAC,MAAM,WAAW,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC,CAAC;KACD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * @cmdName cpu-log\n * 查看所有命令的历史运行记录。\n */\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { program } from \"commander\";\nimport chalk from \"chalk\";\nimport Table from \"cli-table3\";\nimport type { LogEntry } from \"../utils/cmdLogger.js\";\n\nfunction formatDuration(ms: number): string {\n return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction shortenPath(p: string): string {\n return p.replace(homedir(), \"~\");\n}\n\nfunction truncateCmd(cmd: string, args: string[], maxLen = 38): string {\n const full = args.length ? `${cmd} ${args.join(\" \")}` : cmd;\n return full.length > maxLen ? full.slice(0, maxLen - 3) + \"...\" : full;\n}\n\nfunction readIndex(): LogEntry[] {\n const indexFile = join(homedir(), \"ccli\", \"logs\", \"index.jsonl\");\n if (!existsSync(indexFile)) return [];\n\n const lines = readFileSync(indexFile, \"utf8\").trim().split(\"\\n\");\n const entries: LogEntry[] = [];\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n entries.push(JSON.parse(line) as LogEntry);\n } catch {\n // 跳过损坏行\n }\n }\n return entries;\n}\n\nprogram\n .description(\"查看命令历史运行记录(存储于 ~/ccli/logs/)\")\n .option(\"-n, --tail <n>\", \"显示最近 n 条\", \"30\")\n .option(\"-c, --cmd <name>\", \"按命令名过滤\")\n .option(\"--failed\", \"只显示失败记录\")\n .action((opts: { tail: string; cmd?: string; failed?: boolean }) => {\n let entries = readIndex();\n\n if (opts.cmd) {\n entries = entries.filter((e) => e.cmd === opts.cmd);\n }\n if (opts.failed) {\n entries = entries.filter((e) => e.exitCode !== 0);\n }\n\n // 按时间倒序(最新在上)\n entries.sort((a, b) => b.ts.localeCompare(a.ts));\n\n const tail = Math.max(1, parseInt(opts.tail, 10) || 30);\n entries = entries.slice(0, tail);\n\n if (entries.length === 0) {\n console.log(chalk.yellow(\"暂无运行记录。运行任意带 initCmdLogger 的命令后再试。\"));\n return;\n }\n\n const table = new Table({\n head: [\n chalk.cyan(\"命令\"),\n chalk.cyan(\"结果\"),\n chalk.cyan(\"耗时\"),\n chalk.cyan(\"日志位置\"),\n ],\n colWidths: [40, 14, 8, 48],\n style: { compact: false },\n });\n\n for (const entry of entries) {\n const cmdDisplay = truncateCmd(entry.cmd, entry.args);\n const resultDisplay =\n entry.exitCode === 0\n ? chalk.green(\"✅ 成功\")\n : chalk.red(`❌ 失败(${entry.exitCode})`);\n const durationDisplay = formatDuration(entry.duration);\n const pathDisplay = shortenPath(entry.logFile);\n\n table.push([cmdDisplay, resultDisplay, durationDisplay, pathDisplay]);\n }\n\n console.log(chalk.bold(`\\n最近 ${entries.length} 条运行记录:\\n`));\n console.log(table.toString());\n console.log();\n })\n .parse(process.argv);\n"]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @cmdName cpu-update
|
|
4
|
+
* 先在 workspace 执行一次 tsc(根项目 + cli-core),再扫描 dist/commands、同步 package.json bin 并 npm link。
|
|
5
|
+
* 必须在 workspace 根目录下运行。
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { syncCommands, npmLink } from "../index.js";
|
|
9
|
+
const workspaceRoot = process.cwd();
|
|
10
|
+
console.log("\n[1/5] 正在编译 workspace (tsc)...");
|
|
11
|
+
execSync("npx tsc", { stdio: "inherit", cwd: workspaceRoot });
|
|
12
|
+
execSync("npx tsc -p packages/cli-core/tsconfig.json", {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
cwd: workspaceRoot,
|
|
15
|
+
});
|
|
16
|
+
console.log(" - 编译完成。");
|
|
17
|
+
console.log("\n[2/5] 正在清理旧命令并扫描命令文件...");
|
|
18
|
+
const { previous, added } = syncCommands({ projectRoot: workspaceRoot });
|
|
19
|
+
if (previous.length > 0) {
|
|
20
|
+
console.log(` - 已清理旧命令 (${previous.length} 个): ${previous.join(", ")}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(" - 无需清理旧命令。");
|
|
24
|
+
}
|
|
25
|
+
console.log("\n[3/5] 扫描命令文件完成。");
|
|
26
|
+
if (added.length > 0) {
|
|
27
|
+
console.log(` - 发现 ${added.length} 个命令: ${added.join(", ")}`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(" - 未发现任何命令。");
|
|
31
|
+
}
|
|
32
|
+
console.log("\n[4/5] 正在注册命令到全局 (npm link)...");
|
|
33
|
+
try {
|
|
34
|
+
npmLink(workspaceRoot);
|
|
35
|
+
console.log(" - 注册成功。");
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(" - 注册失败:", error.message);
|
|
39
|
+
}
|
|
40
|
+
console.log("\n[5/5] 更新完成!");
|
|
41
|
+
console.log("---------------------------------------");
|
|
42
|
+
if (added.length > 0) {
|
|
43
|
+
console.log("你可以直接运行以下命令:");
|
|
44
|
+
added.forEach((cmd) => console.log(` $ ${cmd}`));
|
|
45
|
+
}
|
|
46
|
+
console.log("---------------------------------------\n");
|
|
47
|
+
//# sourceMappingURL=cpu-update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cpu-update.js","sourceRoot":"","sources":["../../src/commands/cpu-update.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AAEpC,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAC/C,QAAQ,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;AAC9D,QAAQ,CAAC,4CAA4C,EAAE;IACrD,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,aAAa;CACnB,CAAC,CAAC;AACH,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAEzB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;AAEzC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,YAAY,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC;AAEzE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACxB,OAAO,CAAC,GAAG,CAAC,eAAe,QAAQ,CAAC,MAAM,QAAQ,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3E,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC9B,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACjC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,CAAC,MAAM,SAAS,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACjE,CAAC;KAAM,CAAC;IACN,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC9B,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAC/C,IAAI,CAAC;IACH,OAAO,CAAC,aAAa,CAAC,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAC3B,CAAC;AAAC,OAAO,KAAU,EAAE,CAAC;IACpB,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;AAC7B,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;AACvD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC5B,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AACD,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * @cmdName cpu-update\n * 先在 workspace 执行一次 tsc(根项目 + cli-core),再扫描 dist/commands、同步 package.json bin 并 npm link。\n * 必须在 workspace 根目录下运行。\n */\nimport { execSync } from \"child_process\";\nimport { syncCommands, npmLink } from \"../index.js\";\n\nconst workspaceRoot = process.cwd();\n\nconsole.log(\"\\n[1/5] 正在编译 workspace (tsc)...\");\nexecSync(\"npx tsc\", { stdio: \"inherit\", cwd: workspaceRoot });\nexecSync(\"npx tsc -p packages/cli-core/tsconfig.json\", {\n stdio: \"inherit\",\n cwd: workspaceRoot,\n});\nconsole.log(\" - 编译完成。\");\n\nconsole.log(\"\\n[2/5] 正在清理旧命令并扫描命令文件...\");\n\nconst { previous, added } = syncCommands({ projectRoot: workspaceRoot });\n\nif (previous.length > 0) {\n console.log(` - 已清理旧命令 (${previous.length} 个): ${previous.join(\", \")}`);\n} else {\n console.log(\" - 无需清理旧命令。\");\n}\n\nconsole.log(\"\\n[3/5] 扫描命令文件完成。\");\nif (added.length > 0) {\n console.log(` - 发现 ${added.length} 个命令: ${added.join(\", \")}`);\n} else {\n console.log(\" - 未发现任何命令。\");\n}\n\nconsole.log(\"\\n[4/5] 正在注册命令到全局 (npm link)...\");\ntry {\n npmLink(workspaceRoot);\n console.log(\" - 注册成功。\");\n} catch (error: any) {\n console.error(\" - 注册失败:\", error.message);\n}\n\nconsole.log(\"\\n[5/5] 更新完成!\");\nconsole.log(\"---------------------------------------\");\nif (added.length > 0) {\n console.log(\"你可以直接运行以下命令:\");\n added.forEach((cmd) => console.log(` $ ${cmd}`));\n}\nconsole.log(\"---------------------------------------\\n\");\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface SyncOptions {
|
|
2
|
+
/** 项目根目录(含 package.json) */
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
/** 编译产物命令目录,默认 {projectRoot}/dist/commands */
|
|
5
|
+
commandsDir?: string;
|
|
6
|
+
/** package.json 路径,默认 {projectRoot}/package.json */
|
|
7
|
+
packageJsonPath?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncResult {
|
|
10
|
+
/** 本次同步前已注册的命令名列表 */
|
|
11
|
+
previous: string[];
|
|
12
|
+
/** 本次扫描到的命令名列表 */
|
|
13
|
+
added: string[];
|
|
14
|
+
/** 最终写入 bin 字段的映射 */
|
|
15
|
+
binMap: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/** 递归收集目录下所有 .js 文件的绝对路径 */
|
|
18
|
+
export declare function getAllJsFiles(dir: string): string[];
|
|
19
|
+
/**
|
|
20
|
+
* 从 JS 文件内容中提取 @cmdName 注解。
|
|
21
|
+
* 无注解时按文件名推断(index.js 取父目录名)。
|
|
22
|
+
*/
|
|
23
|
+
export declare function getCmdName(filePath: string): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* 扫描 commandsDir,返回 `{ cmdName: relativePathFromProjectRoot }` 映射。
|
|
26
|
+
*/
|
|
27
|
+
export declare function collectCommands(commandsDir: string, projectRoot: string): Record<string, string>;
|
|
28
|
+
/** 批量取消旧命令的全局链接 */
|
|
29
|
+
export declare function unlinkOldCommands(oldBin: Record<string, string>, projectRoot: string): void;
|
|
30
|
+
/** 执行 npm link 将当前包注册到全局 */
|
|
31
|
+
export declare function npmLink(projectRoot: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* 完整同步流程:
|
|
34
|
+
* 1. 读取 package.json 中现有 bin
|
|
35
|
+
* 2. 取消旧命令全局链接
|
|
36
|
+
* 3. 扫描 commandsDir,收集新命令
|
|
37
|
+
* 4. 写回 package.json bin 字段
|
|
38
|
+
*
|
|
39
|
+
* 注意:本函数不执行 npm link,由调用方在适当时机调用 `npmLink()`。
|
|
40
|
+
*/
|
|
41
|
+
export declare function syncCommands(options: SyncOptions): SyncResult;
|
|
42
|
+
export { getJsMeta } from "./utils/getJsMeta.js";
|
|
43
|
+
export { exec, execDetailed, ExecError } from "./utils/exec.js";
|
|
44
|
+
export type { ExecOptions, ExecResult, ExecErrorDetails } from "./utils/exec.js";
|
|
45
|
+
export { sleep } from "./utils/sleep.js";
|
|
46
|
+
export { escapeChars } from "./utils/escapeChars.js";
|
|
47
|
+
export { promiseForEach } from "./utils/promise/promiseForEach.js";
|
|
48
|
+
export { initCmdLogger } from "./utils/cmdLogger.js";
|
|
49
|
+
export type { LogEntry } from "./utils/cmdLogger.js";
|
|
50
|
+
export { COMMAND_TEMPLATE } from "./template.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
// ──────────────────────────────────────────────
|
|
5
|
+
// 框架 API:命令扫描 & 注册
|
|
6
|
+
// ──────────────────────────────────────────────
|
|
7
|
+
/** 递归收集目录下所有 .js 文件的绝对路径 */
|
|
8
|
+
export function getAllJsFiles(dir) {
|
|
9
|
+
if (!fs.existsSync(dir))
|
|
10
|
+
return [];
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const file of fs.readdirSync(dir)) {
|
|
13
|
+
const fullPath = path.join(dir, file);
|
|
14
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
15
|
+
results.push(...getAllJsFiles(fullPath));
|
|
16
|
+
}
|
|
17
|
+
else if (path.extname(file) === ".js") {
|
|
18
|
+
results.push(fullPath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 从 JS 文件内容中提取 @cmdName 注解。
|
|
25
|
+
* 无注解时按文件名推断(index.js 取父目录名)。
|
|
26
|
+
*/
|
|
27
|
+
export function getCmdName(filePath) {
|
|
28
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
29
|
+
const match = content.match(/@cmdName\s+(\S+)/);
|
|
30
|
+
if (match)
|
|
31
|
+
return match[1];
|
|
32
|
+
const fileName = path.basename(filePath, ".js");
|
|
33
|
+
if (fileName === "index") {
|
|
34
|
+
return path.basename(path.dirname(filePath));
|
|
35
|
+
}
|
|
36
|
+
return fileName;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 扫描 commandsDir,返回 `{ cmdName: relativePathFromProjectRoot }` 映射。
|
|
40
|
+
*/
|
|
41
|
+
export function collectCommands(commandsDir, projectRoot) {
|
|
42
|
+
const bin = {};
|
|
43
|
+
for (const fullPath of getAllJsFiles(commandsDir)) {
|
|
44
|
+
const cmdName = getCmdName(fullPath);
|
|
45
|
+
if (cmdName) {
|
|
46
|
+
bin[cmdName] = `./${path.relative(projectRoot, fullPath)}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return bin;
|
|
50
|
+
}
|
|
51
|
+
/** 批量取消旧命令的全局链接 */
|
|
52
|
+
export function unlinkOldCommands(oldBin, projectRoot) {
|
|
53
|
+
for (const cmdName of Object.keys(oldBin)) {
|
|
54
|
+
try {
|
|
55
|
+
execSync(`npm unlink -g ${cmdName}`, {
|
|
56
|
+
stdio: "ignore",
|
|
57
|
+
cwd: projectRoot,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// 忽略找不到命令的错误
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** 执行 npm link 将当前包注册到全局 */
|
|
66
|
+
export function npmLink(projectRoot) {
|
|
67
|
+
execSync("npm link --force", { stdio: "ignore", cwd: projectRoot });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 完整同步流程:
|
|
71
|
+
* 1. 读取 package.json 中现有 bin
|
|
72
|
+
* 2. 取消旧命令全局链接
|
|
73
|
+
* 3. 扫描 commandsDir,收集新命令
|
|
74
|
+
* 4. 写回 package.json bin 字段
|
|
75
|
+
*
|
|
76
|
+
* 注意:本函数不执行 npm link,由调用方在适当时机调用 `npmLink()`。
|
|
77
|
+
*/
|
|
78
|
+
export function syncCommands(options) {
|
|
79
|
+
const { projectRoot, commandsDir = path.join(projectRoot, "dist", "commands"), packageJsonPath = path.join(projectRoot, "package.json"), } = options;
|
|
80
|
+
const raw = fs.readFileSync(packageJsonPath, "utf8");
|
|
81
|
+
const packageJson = JSON.parse(raw);
|
|
82
|
+
const oldBin = packageJson.bin ?? {};
|
|
83
|
+
const previous = Object.keys(oldBin);
|
|
84
|
+
unlinkOldCommands(oldBin, projectRoot);
|
|
85
|
+
const binMap = collectCommands(commandsDir, projectRoot);
|
|
86
|
+
const added = Object.keys(binMap);
|
|
87
|
+
packageJson.bin = binMap;
|
|
88
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf8");
|
|
89
|
+
return { previous, added, binMap };
|
|
90
|
+
}
|
|
91
|
+
// ──────────────────────────────────────────────
|
|
92
|
+
// 通用工具函数
|
|
93
|
+
// ──────────────────────────────────────────────
|
|
94
|
+
export { getJsMeta } from "./utils/getJsMeta.js";
|
|
95
|
+
export { exec, execDetailed, ExecError } from "./utils/exec.js";
|
|
96
|
+
export { sleep } from "./utils/sleep.js";
|
|
97
|
+
export { escapeChars } from "./utils/escapeChars.js";
|
|
98
|
+
export { promiseForEach } from "./utils/promise/promiseForEach.js";
|
|
99
|
+
export { initCmdLogger } from "./utils/cmdLogger.js";
|
|
100
|
+
// ──────────────────────────────────────────────
|
|
101
|
+
// 命令模板
|
|
102
|
+
// ──────────────────────────────────────────────
|
|
103
|
+
export { COMMAND_TEMPLATE } from "./template.js";
|
|
104
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\n\n// ──────────────────────────────────────────────\n// 类型\n// ──────────────────────────────────────────────\n\nexport interface SyncOptions {\n /** 项目根目录(含 package.json) */\n projectRoot: string;\n /** 编译产物命令目录,默认 {projectRoot}/dist/commands */\n commandsDir?: string;\n /** package.json 路径,默认 {projectRoot}/package.json */\n packageJsonPath?: string;\n}\n\nexport interface SyncResult {\n /** 本次同步前已注册的命令名列表 */\n previous: string[];\n /** 本次扫描到的命令名列表 */\n added: string[];\n /** 最终写入 bin 字段的映射 */\n binMap: Record<string, string>;\n}\n\n// ──────────────────────────────────────────────\n// 框架 API:命令扫描 & 注册\n// ──────────────────────────────────────────────\n\n/** 递归收集目录下所有 .js 文件的绝对路径 */\nexport function getAllJsFiles(dir: string): string[] {\n if (!fs.existsSync(dir)) return [];\n const results: string[] = [];\n for (const file of fs.readdirSync(dir)) {\n const fullPath = path.join(dir, file);\n if (fs.statSync(fullPath).isDirectory()) {\n results.push(...getAllJsFiles(fullPath));\n } else if (path.extname(file) === \".js\") {\n results.push(fullPath);\n }\n }\n return results;\n}\n\n/**\n * 从 JS 文件内容中提取 @cmdName 注解。\n * 无注解时按文件名推断(index.js 取父目录名)。\n */\nexport function getCmdName(filePath: string): string | null {\n const content = fs.readFileSync(filePath, \"utf8\");\n const match = content.match(/@cmdName\\s+(\\S+)/);\n if (match) return match[1];\n\n const fileName = path.basename(filePath, \".js\");\n if (fileName === \"index\") {\n return path.basename(path.dirname(filePath));\n }\n return fileName;\n}\n\n/**\n * 扫描 commandsDir,返回 `{ cmdName: relativePathFromProjectRoot }` 映射。\n */\nexport function collectCommands(\n commandsDir: string,\n projectRoot: string\n): Record<string, string> {\n const bin: Record<string, string> = {};\n for (const fullPath of getAllJsFiles(commandsDir)) {\n const cmdName = getCmdName(fullPath);\n if (cmdName) {\n bin[cmdName] = `./${path.relative(projectRoot, fullPath)}`;\n }\n }\n return bin;\n}\n\n/** 批量取消旧命令的全局链接 */\nexport function unlinkOldCommands(\n oldBin: Record<string, string>,\n projectRoot: string\n): void {\n for (const cmdName of Object.keys(oldBin)) {\n try {\n execSync(`npm unlink -g ${cmdName}`, {\n stdio: \"ignore\",\n cwd: projectRoot,\n });\n } catch {\n // 忽略找不到命令的错误\n }\n }\n}\n\n/** 执行 npm link 将当前包注册到全局 */\nexport function npmLink(projectRoot: string): void {\n execSync(\"npm link --force\", { stdio: \"ignore\", cwd: projectRoot });\n}\n\n/**\n * 完整同步流程:\n * 1. 读取 package.json 中现有 bin\n * 2. 取消旧命令全局链接\n * 3. 扫描 commandsDir,收集新命令\n * 4. 写回 package.json bin 字段\n *\n * 注意:本函数不执行 npm link,由调用方在适当时机调用 `npmLink()`。\n */\nexport function syncCommands(options: SyncOptions): SyncResult {\n const {\n projectRoot,\n commandsDir = path.join(projectRoot, \"dist\", \"commands\"),\n packageJsonPath = path.join(projectRoot, \"package.json\"),\n } = options;\n\n const raw = fs.readFileSync(packageJsonPath, \"utf8\");\n const packageJson = JSON.parse(raw) as {\n bin?: Record<string, string>;\n [key: string]: unknown;\n };\n\n const oldBin: Record<string, string> = packageJson.bin ?? {};\n const previous = Object.keys(oldBin);\n\n unlinkOldCommands(oldBin, projectRoot);\n\n const binMap = collectCommands(commandsDir, projectRoot);\n const added = Object.keys(binMap);\n\n packageJson.bin = binMap;\n fs.writeFileSync(\n packageJsonPath,\n JSON.stringify(packageJson, null, 2) + \"\\n\",\n \"utf8\"\n );\n\n return { previous, added, binMap };\n}\n\n// ──────────────────────────────────────────────\n// 通用工具函数\n// ──────────────────────────────────────────────\n\nexport { getJsMeta } from \"./utils/getJsMeta.js\";\nexport { exec, execDetailed, ExecError } from \"./utils/exec.js\";\nexport type { ExecOptions, ExecResult, ExecErrorDetails } from \"./utils/exec.js\";\nexport { sleep } from \"./utils/sleep.js\";\nexport { escapeChars } from \"./utils/escapeChars.js\";\nexport { promiseForEach } from \"./utils/promise/promiseForEach.js\";\nexport { initCmdLogger } from \"./utils/cmdLogger.js\";\nexport type { LogEntry } from \"./utils/cmdLogger.js\";\n\n// ──────────────────────────────────────────────\n// 命令模板\n// ──────────────────────────────────────────────\n\nexport { COMMAND_TEMPLATE } from \"./template.js\";\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 新命令的默认模板,由 adc 命令使用。
|
|
3
|
+
* 占位符:${cmdname}、${desc}
|
|
4
|
+
*/
|
|
5
|
+
export declare const COMMAND_TEMPLATE = "#!/usr/bin/env node\n/**\n * @cmdName ${cmdname}\n * ${desc}\n */\nimport { program } from \"commander\";\nimport { getJsMeta, initCmdLogger } from \"ccli-core\";\n\nconst { projectRoot, cmdName } = getJsMeta(import.meta);\ninitCmdLogger(import.meta);\n\nprogram\n .description(\"${desc}\")\n .action(() => {\n console.log(`Running ${cmdName}...`);\n })\n .parse(process.argv);\n";
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 新命令的默认模板,由 adc 命令使用。
|
|
3
|
+
* 占位符:${cmdname}、${desc}
|
|
4
|
+
*/
|
|
5
|
+
export const COMMAND_TEMPLATE = `#!/usr/bin/env node
|
|
6
|
+
/**
|
|
7
|
+
* @cmdName \${cmdname}
|
|
8
|
+
* \${desc}
|
|
9
|
+
*/
|
|
10
|
+
import { program } from "commander";
|
|
11
|
+
import { getJsMeta, initCmdLogger } from "ccli-core";
|
|
12
|
+
|
|
13
|
+
const { projectRoot, cmdName } = getJsMeta(import.meta);
|
|
14
|
+
initCmdLogger(import.meta);
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.description("\${desc}")
|
|
18
|
+
.action(() => {
|
|
19
|
+
console.log(\`Running \${cmdName}...\`);
|
|
20
|
+
})
|
|
21
|
+
.parse(process.argv);
|
|
22
|
+
`;
|
|
23
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;CAiB/B,CAAC","sourcesContent":["/**\n * 新命令的默认模板,由 adc 命令使用。\n * 占位符:${cmdname}、${desc}\n */\nexport const COMMAND_TEMPLATE = `#!/usr/bin/env node\n/**\n * @cmdName \\${cmdname}\n * \\${desc}\n */\nimport { program } from \"commander\";\nimport { getJsMeta, initCmdLogger } from \"ccli-core\";\n\nconst { projectRoot, cmdName } = getJsMeta(import.meta);\ninitCmdLogger(import.meta);\n\nprogram\n .description(\"\\${desc}\")\n .action(() => {\n console.log(\\`Running \\${cmdName}...\\`);\n })\n .parse(process.argv);\n`;\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LogEntry {
|
|
2
|
+
ts: string;
|
|
3
|
+
cmd: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
exitCode: number;
|
|
6
|
+
duration: number;
|
|
7
|
+
logFile: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 在命令顶部调用,自动将 stdout/stderr Tee 到日志文件,并在进程退出时
|
|
11
|
+
* 将本次运行元数据追加到 ~/ccli/logs/index.jsonl。
|
|
12
|
+
*
|
|
13
|
+
* 用法:在命令文件顶部加一行
|
|
14
|
+
* initCmdLogger(import.meta);
|
|
15
|
+
*/
|
|
16
|
+
export declare function initCmdLogger(meta: ImportMeta): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { mkdirSync, createWriteStream, appendFileSync } from "fs";
|
|
4
|
+
import { getJsMeta } from "./getJsMeta.js";
|
|
5
|
+
/**
|
|
6
|
+
* 在命令顶部调用,自动将 stdout/stderr Tee 到日志文件,并在进程退出时
|
|
7
|
+
* 将本次运行元数据追加到 ~/ccli/logs/index.jsonl。
|
|
8
|
+
*
|
|
9
|
+
* 用法:在命令文件顶部加一行
|
|
10
|
+
* initCmdLogger(import.meta);
|
|
11
|
+
*/
|
|
12
|
+
export function initCmdLogger(meta) {
|
|
13
|
+
const { cmdName } = getJsMeta(meta);
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
const now = new Date(startTime);
|
|
17
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
18
|
+
const dateStr = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`;
|
|
19
|
+
const timeStr = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
20
|
+
const logsRoot = join(homedir(), "ccli", "logs");
|
|
21
|
+
const logDir = join(logsRoot, dateStr);
|
|
22
|
+
const logFile = join(logDir, `${timeStr}-${cmdName}.log`);
|
|
23
|
+
const indexFile = join(logsRoot, "index.jsonl");
|
|
24
|
+
let stream;
|
|
25
|
+
try {
|
|
26
|
+
mkdirSync(logDir, { recursive: true });
|
|
27
|
+
stream = createWriteStream(logFile, { flags: "a", encoding: "utf8" });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
stream.write(`[START] ${now.toISOString()} ${cmdName}${args.length ? " " + args.join(" ") : ""}\n`);
|
|
33
|
+
stream.write("─".repeat(50) + "\n");
|
|
34
|
+
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
35
|
+
const origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
36
|
+
process.stdout.write = ((chunk, encodingOrCb, cb) => {
|
|
37
|
+
try {
|
|
38
|
+
stream.write(chunk);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* silent */
|
|
42
|
+
}
|
|
43
|
+
return origStdoutWrite(chunk, encodingOrCb, cb);
|
|
44
|
+
});
|
|
45
|
+
process.stderr.write = ((chunk, encodingOrCb, cb) => {
|
|
46
|
+
try {
|
|
47
|
+
stream.write(chunk);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/* silent */
|
|
51
|
+
}
|
|
52
|
+
return origStderrWrite(chunk, encodingOrCb, cb);
|
|
53
|
+
});
|
|
54
|
+
process.on("exit", (code) => {
|
|
55
|
+
const duration = Date.now() - startTime;
|
|
56
|
+
const exitCode = code ?? 0;
|
|
57
|
+
try {
|
|
58
|
+
stream.write("\n" + "─".repeat(50) + "\n");
|
|
59
|
+
stream.write(`[END] exitCode=${exitCode} duration=${duration}ms\n`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* silent */
|
|
63
|
+
}
|
|
64
|
+
const entry = {
|
|
65
|
+
ts: now.toISOString(),
|
|
66
|
+
cmd: cmdName,
|
|
67
|
+
args,
|
|
68
|
+
exitCode,
|
|
69
|
+
duration,
|
|
70
|
+
logFile,
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
appendFileSync(indexFile, JSON.stringify(entry) + "\n", "utf8");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* silent */
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=cmdLogger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cmdLogger.js","sourceRoot":"","sources":["../../src/utils/cmdLogger.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir } from \"os\";\nimport { join } from \"path\";\nimport { mkdirSync, createWriteStream, appendFileSync } from \"fs\";\nimport type { WriteStream } from \"fs\";\nimport { getJsMeta } from \"./getJsMeta.js\";\n\nexport interface LogEntry {\n ts: string;\n cmd: string;\n args: string[];\n exitCode: number;\n duration: number;\n logFile: string;\n}\n\n/**\n * 在命令顶部调用,自动将 stdout/stderr Tee 到日志文件,并在进程退出时\n * 将本次运行元数据追加到 ~/ccli/logs/index.jsonl。\n *\n * 用法:在命令文件顶部加一行\n * initCmdLogger(import.meta);\n */\nexport function initCmdLogger(meta: ImportMeta): void {\n const { cmdName } = getJsMeta(meta);\n const args = process.argv.slice(2);\n const startTime = Date.now();\n\n const now = new Date(startTime);\n const pad = (n: number) => String(n).padStart(2, \"0\");\n const dateStr = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`;\n const timeStr = `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;\n\n const logsRoot = join(homedir(), \"ccli\", \"logs\");\n const logDir = join(logsRoot, dateStr);\n const logFile = join(logDir, `${timeStr}-${cmdName}.log`);\n const indexFile = join(logsRoot, \"index.jsonl\");\n\n let stream: WriteStream;\n try {\n mkdirSync(logDir, { recursive: true });\n stream = createWriteStream(logFile, { flags: \"a\", encoding: \"utf8\" });\n } catch {\n return;\n }\n\n stream.write(`[START] ${now.toISOString()} ${cmdName}${args.length ? \" \" + args.join(\" \") : \"\"}\\n`);\n stream.write(\"─\".repeat(50) + \"\\n\");\n\n const origStdoutWrite = process.stdout.write.bind(process.stdout);\n const origStderrWrite = process.stderr.write.bind(process.stderr);\n\n (process.stdout as NodeJS.WriteStream).write = ((\n chunk: Uint8Array | string,\n encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),\n cb?: (err?: Error | null) => void\n ): boolean => {\n try {\n stream.write(chunk);\n } catch {\n /* silent */\n }\n return origStdoutWrite(chunk as any, encodingOrCb as any, cb as any);\n }) as typeof process.stdout.write;\n\n (process.stderr as NodeJS.WriteStream).write = ((\n chunk: Uint8Array | string,\n encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),\n cb?: (err?: Error | null) => void\n ): boolean => {\n try {\n stream.write(chunk);\n } catch {\n /* silent */\n }\n return origStderrWrite(chunk as any, encodingOrCb as any, cb as any);\n }) as typeof process.stderr.write;\n\n process.on(\"exit\", (code) => {\n const duration = Date.now() - startTime;\n const exitCode = code ?? 0;\n\n try {\n stream.write(\"\\n\" + \"─\".repeat(50) + \"\\n\");\n stream.write(`[END] exitCode=${exitCode} duration=${duration}ms\\n`);\n } catch {\n /* silent */\n }\n\n const entry: LogEntry = {\n ts: now.toISOString(),\n cmd: cmdName,\n args,\n exitCode,\n duration,\n logFile,\n };\n\n try {\n appendFileSync(indexFile, JSON.stringify(entry) + \"\\n\", \"utf8\");\n } catch {\n /* silent */\n }\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const escapeChars: (str: string, chars: string[]) => string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const escapeChars = (str, chars) => {
|
|
2
|
+
if (!str || chars.length === 0) {
|
|
3
|
+
return str;
|
|
4
|
+
}
|
|
5
|
+
let result = str;
|
|
6
|
+
// 去重并确保优先处理反斜杠,避免其他字符转义后被二次转义
|
|
7
|
+
const uniqueChars = [...new Set(chars)];
|
|
8
|
+
const hasBackslash = uniqueChars.includes("\\");
|
|
9
|
+
const otherChars = uniqueChars.filter((char) => char !== "\\");
|
|
10
|
+
if (hasBackslash) {
|
|
11
|
+
result = result.replace(/\\/g, "\\\\");
|
|
12
|
+
}
|
|
13
|
+
for (const char of otherChars) {
|
|
14
|
+
const escapedChar = char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15
|
+
const regex = new RegExp(escapedChar, "g");
|
|
16
|
+
result = result.replace(regex, "\\" + char);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=escapeChars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escapeChars.js","sourceRoot":"","sources":["../../src/utils/escapeChars.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,KAAe,EAAU,EAAE;IAClE,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,MAAM,GAAG,GAAG,CAAC;IAEjB,8BAA8B;IAC9B,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACxC,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAE/D,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC","sourcesContent":["export const escapeChars = (str: string, chars: string[]): string => {\n if (!str || chars.length === 0) {\n return str;\n }\n\n let result = str;\n\n // 去重并确保优先处理反斜杠,避免其他字符转义后被二次转义\n const uniqueChars = [...new Set(chars)];\n const hasBackslash = uniqueChars.includes(\"\\\\\");\n const otherChars = uniqueChars.filter((char) => char !== \"\\\\\");\n\n if (hasBackslash) {\n result = result.replace(/\\\\/g, \"\\\\\\\\\");\n }\n\n for (const char of otherChars) {\n const escapedChar = char.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(escapedChar, \"g\");\n result = result.replace(regex, \"\\\\\" + char);\n }\n\n return result;\n};\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface ExecOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
env?: Record<string, string>;
|
|
4
|
+
stdin?: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
maxBuffer?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ExecResult {
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
exitCode: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ExecErrorDetails {
|
|
14
|
+
message: string;
|
|
15
|
+
stdout?: string;
|
|
16
|
+
stderr?: string;
|
|
17
|
+
exitCode?: number;
|
|
18
|
+
command?: string;
|
|
19
|
+
signal?: string;
|
|
20
|
+
killed?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class ExecError extends Error {
|
|
23
|
+
stdout: string;
|
|
24
|
+
stderr: string;
|
|
25
|
+
exitCode: number;
|
|
26
|
+
command?: string;
|
|
27
|
+
logFilePath: string;
|
|
28
|
+
constructor(details: ExecErrorDetails);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 包装 exec,方便调用命令行工具并获取输出结果。
|
|
32
|
+
* 失败时抛出 ExecError,并将详情写入 logs/error/。
|
|
33
|
+
*/
|
|
34
|
+
export declare function exec(command: string, options?: ExecOptions): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* 执行命令并返回详细结果(stdout, stderr, exitCode),不抛出异常。
|
|
37
|
+
*/
|
|
38
|
+
export declare function execDetailed(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { exec as childProcessExec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
const execAsync = promisify(childProcessExec);
|
|
6
|
+
export class ExecError extends Error {
|
|
7
|
+
stdout;
|
|
8
|
+
stderr;
|
|
9
|
+
exitCode;
|
|
10
|
+
command;
|
|
11
|
+
logFilePath;
|
|
12
|
+
constructor(details) {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
const timestamp = now
|
|
15
|
+
.toISOString()
|
|
16
|
+
.replace(/[-:]/g, "")
|
|
17
|
+
.replace(/\..+/, "")
|
|
18
|
+
.replace("T", "");
|
|
19
|
+
const logFileName = `${timestamp}.error.log`;
|
|
20
|
+
// 使用 process.cwd() 作为日志目录基准(命令总是从 workspace 根运行)
|
|
21
|
+
const logDir = join(process.cwd(), "logs/error");
|
|
22
|
+
const logFilePath = join(logDir, logFileName);
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(logDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// 目录已存在,忽略
|
|
28
|
+
}
|
|
29
|
+
const fullErrorInfo = {
|
|
30
|
+
timestamp: now.toISOString(),
|
|
31
|
+
message: details.message,
|
|
32
|
+
exitCode: details.exitCode,
|
|
33
|
+
stdout: details.stdout,
|
|
34
|
+
stderr: details.stderr,
|
|
35
|
+
command: details.command,
|
|
36
|
+
signal: details.signal,
|
|
37
|
+
killed: details.killed,
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
writeFileSync(logFilePath, JSON.stringify(fullErrorInfo, null, 2), "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch (writeError) {
|
|
43
|
+
console.error("Failed to write error log:", writeError);
|
|
44
|
+
}
|
|
45
|
+
super(`${details.message} | Error log: ${logFilePath}`);
|
|
46
|
+
this.name = "ExecError";
|
|
47
|
+
this.stdout = details.stdout || "";
|
|
48
|
+
this.stderr = details.stderr || "";
|
|
49
|
+
this.exitCode = details.exitCode || 1;
|
|
50
|
+
this.command = details.command;
|
|
51
|
+
this.logFilePath = logFilePath;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 包装 exec,方便调用命令行工具并获取输出结果。
|
|
56
|
+
* 失败时抛出 ExecError,并将详情写入 logs/error/。
|
|
57
|
+
*/
|
|
58
|
+
export async function exec(command, options = {}) {
|
|
59
|
+
try {
|
|
60
|
+
const { cwd, env, stdin, timeout = 30000, maxBuffer = 1024 * 1024, } = options;
|
|
61
|
+
const execOptions = {
|
|
62
|
+
cwd,
|
|
63
|
+
env: { ...process.env, ...env },
|
|
64
|
+
timeout,
|
|
65
|
+
maxBuffer,
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
};
|
|
68
|
+
if (stdin) {
|
|
69
|
+
const { spawn } = await import("child_process");
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const child = spawn(command, {
|
|
72
|
+
shell: true,
|
|
73
|
+
cwd,
|
|
74
|
+
env: { ...process.env, ...env },
|
|
75
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
76
|
+
});
|
|
77
|
+
let stdout = "";
|
|
78
|
+
let stderr = "";
|
|
79
|
+
child.stdout.on("data", (data) => { stdout += data.toString(); });
|
|
80
|
+
child.stderr.on("data", (data) => { stderr += data.toString(); });
|
|
81
|
+
child.on("close", (code) => {
|
|
82
|
+
if (code === 0) {
|
|
83
|
+
resolve(stdout.trim());
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
reject(new ExecError({
|
|
87
|
+
message: `Command failed with exit code ${code}`,
|
|
88
|
+
stdout, stderr,
|
|
89
|
+
exitCode: code || 1,
|
|
90
|
+
command,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
child.on("error", (error) => {
|
|
95
|
+
reject(new ExecError({
|
|
96
|
+
message: `Command execution failed: ${error.message}`,
|
|
97
|
+
stdout, stderr,
|
|
98
|
+
exitCode: 1,
|
|
99
|
+
command,
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
child.stdin.write(stdin);
|
|
103
|
+
child.stdin.end();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const { stdout, stderr } = await execAsync(command, execOptions);
|
|
107
|
+
const stdoutStr = stdout.toString();
|
|
108
|
+
const stderrStr = stderr.toString();
|
|
109
|
+
if (stderrStr && stderrStr.trim()) {
|
|
110
|
+
console.warn(`Command stderr: ${stderrStr.trim()}`);
|
|
111
|
+
}
|
|
112
|
+
return stdoutStr.trim();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error.stdout !== undefined && error.stderr !== undefined) {
|
|
116
|
+
throw new ExecError({
|
|
117
|
+
message: `Command failed with exit code ${error.code}`,
|
|
118
|
+
stdout: error.stdout.toString(),
|
|
119
|
+
stderr: error.stderr.toString(),
|
|
120
|
+
exitCode: error.code || 1,
|
|
121
|
+
command,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (error.killed && error.signal === "SIGTERM") {
|
|
125
|
+
throw new ExecError({
|
|
126
|
+
message: `Command timed out after ${options.timeout || 30000}ms`,
|
|
127
|
+
stdout: "", stderr: "",
|
|
128
|
+
exitCode: 1,
|
|
129
|
+
command,
|
|
130
|
+
signal: error.signal,
|
|
131
|
+
killed: error.killed,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
throw new ExecError({
|
|
135
|
+
message: `Command execution failed: ${error.message}`,
|
|
136
|
+
stdout: "", stderr: "",
|
|
137
|
+
exitCode: 1,
|
|
138
|
+
command,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 执行命令并返回详细结果(stdout, stderr, exitCode),不抛出异常。
|
|
144
|
+
*/
|
|
145
|
+
export async function execDetailed(command, options = {}) {
|
|
146
|
+
try {
|
|
147
|
+
const stdout = await exec(command, options);
|
|
148
|
+
return { stdout, stderr: "", exitCode: 0 };
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error instanceof ExecError) {
|
|
152
|
+
return { stdout: error.stdout, stderr: error.stderr, exitCode: error.exitCode };
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"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","sourcesContent":["import { exec as childProcessExec } from \"child_process\";\nimport { promisify } from \"util\";\nimport { writeFileSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\n\nconst execAsync = promisify(childProcessExec);\n\nexport interface ExecOptions {\n cwd?: string;\n env?: Record<string, string>;\n stdin?: string;\n timeout?: number;\n maxBuffer?: number;\n}\n\nexport interface ExecResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport interface ExecErrorDetails {\n message: string;\n stdout?: string;\n stderr?: string;\n exitCode?: number;\n command?: string;\n signal?: string;\n killed?: boolean;\n}\n\nexport class ExecError extends Error {\n public stdout: string;\n public stderr: string;\n public exitCode: number;\n public command?: string;\n public logFilePath: string;\n\n constructor(details: ExecErrorDetails) {\n const now = new Date();\n const timestamp = now\n .toISOString()\n .replace(/[-:]/g, \"\")\n .replace(/\\..+/, \"\")\n .replace(\"T\", \"\");\n const logFileName = `${timestamp}.error.log`;\n\n // 使用 process.cwd() 作为日志目录基准(命令总是从 workspace 根运行)\n const logDir = join(process.cwd(), \"logs/error\");\n const logFilePath = join(logDir, logFileName);\n\n try {\n mkdirSync(logDir, { recursive: true });\n } catch {\n // 目录已存在,忽略\n }\n\n const fullErrorInfo = {\n timestamp: now.toISOString(),\n message: details.message,\n exitCode: details.exitCode,\n stdout: details.stdout,\n stderr: details.stderr,\n command: details.command,\n signal: details.signal,\n killed: details.killed,\n };\n\n try {\n writeFileSync(logFilePath, JSON.stringify(fullErrorInfo, null, 2), \"utf8\");\n } catch (writeError) {\n console.error(\"Failed to write error log:\", writeError);\n }\n\n super(`${details.message} | Error log: ${logFilePath}`);\n\n this.name = \"ExecError\";\n this.stdout = details.stdout || \"\";\n this.stderr = details.stderr || \"\";\n this.exitCode = details.exitCode || 1;\n this.command = details.command;\n this.logFilePath = logFilePath;\n }\n}\n\n/**\n * 包装 exec,方便调用命令行工具并获取输出结果。\n * 失败时抛出 ExecError,并将详情写入 logs/error/。\n */\nexport async function exec(\n command: string,\n options: ExecOptions = {}\n): Promise<string> {\n try {\n const {\n cwd,\n env,\n stdin,\n timeout = 30000,\n maxBuffer = 1024 * 1024,\n } = options;\n\n const execOptions: Parameters<typeof execAsync>[1] = {\n cwd,\n env: { ...process.env, ...env },\n timeout,\n maxBuffer,\n encoding: \"utf8\",\n };\n\n if (stdin) {\n const { spawn } = await import(\"child_process\");\n return new Promise((resolve, reject) => {\n const child = spawn(command, {\n shell: true,\n cwd,\n env: { ...process.env, ...env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout.on(\"data\", (data) => { stdout += data.toString(); });\n child.stderr.on(\"data\", (data) => { stderr += data.toString(); });\n\n child.on(\"close\", (code) => {\n if (code === 0) {\n resolve(stdout.trim());\n } else {\n reject(new ExecError({\n message: `Command failed with exit code ${code}`,\n stdout, stderr,\n exitCode: code || 1,\n command,\n }));\n }\n });\n\n child.on(\"error\", (error) => {\n reject(new ExecError({\n message: `Command execution failed: ${error.message}`,\n stdout, stderr,\n exitCode: 1,\n command,\n }));\n });\n\n child.stdin.write(stdin);\n child.stdin.end();\n });\n }\n\n const { stdout, stderr } = await execAsync(command, execOptions);\n const stdoutStr = stdout.toString();\n const stderrStr = stderr.toString();\n\n if (stderrStr && stderrStr.trim()) {\n console.warn(`Command stderr: ${stderrStr.trim()}`);\n }\n\n return stdoutStr.trim();\n } catch (error: any) {\n if (error.stdout !== undefined && error.stderr !== undefined) {\n throw new ExecError({\n message: `Command failed with exit code ${error.code}`,\n stdout: error.stdout.toString(),\n stderr: error.stderr.toString(),\n exitCode: error.code || 1,\n command,\n });\n }\n\n if (error.killed && error.signal === \"SIGTERM\") {\n throw new ExecError({\n message: `Command timed out after ${options.timeout || 30000}ms`,\n stdout: \"\", stderr: \"\",\n exitCode: 1,\n command,\n signal: error.signal,\n killed: error.killed,\n });\n }\n\n throw new ExecError({\n message: `Command execution failed: ${error.message}`,\n stdout: \"\", stderr: \"\",\n exitCode: 1,\n command,\n });\n }\n}\n\n/**\n * 执行命令并返回详细结果(stdout, stderr, exitCode),不抛出异常。\n */\nexport async function execDetailed(\n command: string,\n options: ExecOptions = {}\n): Promise<ExecResult> {\n try {\n const stdout = await exec(command, options);\n return { stdout, stderr: \"\", exitCode: 0 };\n } catch (error) {\n if (error instanceof ExecError) {\n return { stdout: error.stdout, stderr: error.stderr, exitCode: error.exitCode };\n }\n throw error;\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取命令行上下文信息。
|
|
3
|
+
*
|
|
4
|
+
* 通过在路径中定位 `dist` 目录边界来推断 projectRoot,
|
|
5
|
+
* 不依赖硬编码的层级数,兼容任意深度的命令文件(如 dist/commands/ai/services/xxx/index.js)。
|
|
6
|
+
*
|
|
7
|
+
* @param meta — 通常传入调用方命令文件自身的 import.meta(不是 cli-core 内部的)
|
|
8
|
+
*/
|
|
9
|
+
export declare function getJsMeta(meta?: ImportMeta): {
|
|
10
|
+
jsDir: string;
|
|
11
|
+
srcDir: string;
|
|
12
|
+
cwd: string;
|
|
13
|
+
projectRoot: string;
|
|
14
|
+
cmdName: string;
|
|
15
|
+
assetsDir: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
import { dirname, join, resolve, relative, basename, extname, sep } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* 获取命令行上下文信息。
|
|
5
|
+
*
|
|
6
|
+
* 通过在路径中定位 `dist` 目录边界来推断 projectRoot,
|
|
7
|
+
* 不依赖硬编码的层级数,兼容任意深度的命令文件(如 dist/commands/ai/services/xxx/index.js)。
|
|
8
|
+
*
|
|
9
|
+
* @param meta — 通常传入调用方命令文件自身的 import.meta(不是 cli-core 内部的)
|
|
10
|
+
*/
|
|
11
|
+
export function getJsMeta(meta = import.meta) {
|
|
12
|
+
const __filename = fileURLToPath(meta.url);
|
|
13
|
+
const jsDir = dirname(__filename);
|
|
14
|
+
// 在路径中定位最后一个 dist 目录,其父目录即为 projectRoot
|
|
15
|
+
const parts = jsDir.split(sep);
|
|
16
|
+
const distIdx = parts.lastIndexOf("dist");
|
|
17
|
+
const projectRoot = distIdx !== -1 ? parts.slice(0, distIdx).join(sep) : resolve(jsDir, "..");
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const distDir = join(projectRoot, "dist");
|
|
20
|
+
const relPath = relative(distDir, jsDir);
|
|
21
|
+
const srcDir = join(projectRoot, "src", relPath);
|
|
22
|
+
const assetsDir = join(projectRoot, "assets");
|
|
23
|
+
const fileName = basename(__filename, extname(__filename));
|
|
24
|
+
const cmdName = fileName !== "index" ? fileName : basename(jsDir);
|
|
25
|
+
return { jsDir, srcDir, cwd, projectRoot, cmdName, assetsDir };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=getJsMeta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getJsMeta.js","sourceRoot":"","sources":["../../src/utils/getJsMeta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,OAAmB,MAAM,CAAC,IAAI;IACtD,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAElC,wCAAwC;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,WAAW,GACf,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAE5E,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC","sourcesContent":["import { fileURLToPath } from \"url\";\nimport { dirname, join, resolve, relative, basename, extname, sep } from \"path\";\n\n/**\n * 获取命令行上下文信息。\n *\n * 通过在路径中定位 `dist` 目录边界来推断 projectRoot,\n * 不依赖硬编码的层级数,兼容任意深度的命令文件(如 dist/commands/ai/services/xxx/index.js)。\n *\n * @param meta — 通常传入调用方命令文件自身的 import.meta(不是 cli-core 内部的)\n */\nexport function getJsMeta(meta: ImportMeta = import.meta) {\n const __filename = fileURLToPath(meta.url);\n const jsDir = dirname(__filename);\n\n // 在路径中定位最后一个 dist 目录,其父目录即为 projectRoot\n const parts = jsDir.split(sep);\n const distIdx = parts.lastIndexOf(\"dist\");\n const projectRoot =\n distIdx !== -1 ? parts.slice(0, distIdx).join(sep) : resolve(jsDir, \"..\");\n\n const cwd = process.cwd();\n const distDir = join(projectRoot, \"dist\");\n const relPath = relative(distDir, jsDir);\n const srcDir = join(projectRoot, \"src\", relPath);\n const assetsDir = join(projectRoot, \"assets\");\n const fileName = basename(__filename, extname(__filename));\n const cmdName = fileName !== \"index\" ? fileName : basename(jsDir);\n\n return { jsDir, srcDir, cwd, projectRoot, cmdName, assetsDir };\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按顺序或并发执行数组中的每个 Promise,可以设置执行间隔和并发量
|
|
3
|
+
* @param array 要处理的数组
|
|
4
|
+
* @param callback 处理每个元素的异步函数
|
|
5
|
+
* @param intervalMs 每项之间的等待时间(毫秒),默认为 0
|
|
6
|
+
* @param concurrency 最大并发量,默认为 1 (顺序执行)
|
|
7
|
+
*/
|
|
8
|
+
export declare const promiseForEach: <T>(array: T[], callback: (item: T, index: number, array: T[]) => Promise<void>, intervalMs?: number, concurrency?: number) => Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按顺序或并发执行数组中的每个 Promise,可以设置执行间隔和并发量
|
|
3
|
+
* @param array 要处理的数组
|
|
4
|
+
* @param callback 处理每个元素的异步函数
|
|
5
|
+
* @param intervalMs 每项之间的等待时间(毫秒),默认为 0
|
|
6
|
+
* @param concurrency 最大并发量,默认为 1 (顺序执行)
|
|
7
|
+
*/
|
|
8
|
+
export const promiseForEach = async (array, callback, intervalMs = 0, concurrency = 1) => {
|
|
9
|
+
if (concurrency <= 1) {
|
|
10
|
+
for (let index = 0; index < array.length; index++) {
|
|
11
|
+
await callback(array[index], index, array);
|
|
12
|
+
if (intervalMs > 0 && index < array.length - 1) {
|
|
13
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let currentIndex = 0;
|
|
19
|
+
const runningTasks = [];
|
|
20
|
+
const processNext = async () => {
|
|
21
|
+
while (currentIndex < array.length) {
|
|
22
|
+
const index = currentIndex++;
|
|
23
|
+
if (intervalMs > 0 && index > 0) {
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
await callback(array[index], index, array);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error(`处理第 ${index} 项时出错:`, error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
for (let i = 0; i < Math.min(concurrency, array.length); i++) {
|
|
35
|
+
runningTasks.push(processNext());
|
|
36
|
+
}
|
|
37
|
+
await Promise.all(runningTasks);
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=promiseForEach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promiseForEach.js","sourceRoot":"","sources":["../../../src/utils/promise/promiseForEach.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,KAAU,EACV,QAA+D,EAC/D,UAAU,GAAG,CAAC,EACd,WAAW,GAAG,CAAC,EACA,EAAE;IACjB,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YAClD,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAC3C,IAAI,UAAU,GAAG,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/C,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QACD,OAAO;IACT,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,YAAY,GAAoB,EAAE,CAAC;IAEzC,MAAM,WAAW,GAAG,KAAK,IAAmB,EAAE;QAC5C,OAAO,YAAY,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;YAE7B,IAAI,UAAU,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7D,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAClC,CAAC,CAAC","sourcesContent":["/**\n * 按顺序或并发执行数组中的每个 Promise,可以设置执行间隔和并发量\n * @param array 要处理的数组\n * @param callback 处理每个元素的异步函数\n * @param intervalMs 每项之间的等待时间(毫秒),默认为 0\n * @param concurrency 最大并发量,默认为 1 (顺序执行)\n */\nexport const promiseForEach = async <T>(\n array: T[],\n callback: (item: T, index: number, array: T[]) => Promise<void>,\n intervalMs = 0,\n concurrency = 1\n): Promise<void> => {\n if (concurrency <= 1) {\n for (let index = 0; index < array.length; index++) {\n await callback(array[index], index, array);\n if (intervalMs > 0 && index < array.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n }\n return;\n }\n\n let currentIndex = 0;\n const runningTasks: Promise<void>[] = [];\n\n const processNext = async (): Promise<void> => {\n while (currentIndex < array.length) {\n const index = currentIndex++;\n\n if (intervalMs > 0 && index > 0) {\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n try {\n await callback(array[index], index, array);\n } catch (error) {\n console.error(`处理第 ${index} 项时出错:`, error);\n }\n }\n };\n\n for (let i = 0; i < Math.min(concurrency, array.length); i++) {\n runningTasks.push(processNext());\n }\n\n await Promise.all(runningTasks);\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sleep(ms: number): Promise<unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/utils/sleep.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,EAAU;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["export async function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ccli-core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"adc": "./dist/commands/adc.js",
|
|
18
|
+
"cpu-update": "./dist/commands/cpu-update.js",
|
|
19
|
+
"cpu-list": "./dist/commands/cpu-list.js",
|
|
20
|
+
"cpu-log": "./dist/commands/cpu-log.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/**/*"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"dev": "tsc -w -p tsconfig.json",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"commander": "^12.1.0",
|
|
32
|
+
"chalk": "^5.4.1",
|
|
33
|
+
"cli-table3": "^0.6.5"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^24.5.2",
|
|
37
|
+
"typescript": "^5.2.2"
|
|
38
|
+
}
|
|
39
|
+
}
|