@edmund32/edx-cli 0.1.2

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.
@@ -0,0 +1,301 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { detectPlatform } from '../platform/platform.mjs';
6
+ import { commandExists as defaultCommandExists, runCommand as defaultRunCommand } from '../utils/process.mjs';
7
+ import { fail, ok } from '../utils/result.mjs';
8
+
9
+ function defaultConfigPath() {
10
+ return path.join(os.homedir(), '.edx', 'aliases.json');
11
+ }
12
+
13
+ function help() {
14
+ return [
15
+ '--- edx-alias 快捷指令 ---',
16
+ '用法:',
17
+ ' edx-alias add <名称> <路径> --type=dir|file [--app <应用>]',
18
+ ' edx-alias update <名称> <路径> --type=dir|file [--app <应用>]',
19
+ ' edx-alias open <名称> [--app <应用>]',
20
+ ' edx-alias ls',
21
+ ' edx-alias list',
22
+ ' edx-alias remove <名称>',
23
+ '提示: --app 的应用名包含空格时请加引号,例如 --app "Visual Studio Code"',
24
+ '',
25
+ `配置文件: ${defaultConfigPath()}`,
26
+ ].join('\n');
27
+ }
28
+
29
+ function loadConfig(configPath) {
30
+ if (!fs.existsSync(configPath)) {
31
+ return { version: 1, aliases: {} };
32
+ }
33
+
34
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
35
+ return {
36
+ version: 1,
37
+ aliases: parsed.aliases && typeof parsed.aliases === 'object' ? parsed.aliases : {},
38
+ };
39
+ }
40
+
41
+ function saveConfig(configPath, config) {
42
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
43
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
44
+ }
45
+
46
+ function parseAliasArgs(args) {
47
+ const positionals = [];
48
+ let type = null;
49
+ let app = null;
50
+
51
+ for (let i = 0; i < args.length; i++) {
52
+ const item = args[i];
53
+ if (item === '--type') {
54
+ type = args[i + 1] ?? null;
55
+ i++;
56
+ continue;
57
+ }
58
+ if (item.startsWith('--type=')) {
59
+ type = item.slice('--type='.length);
60
+ continue;
61
+ }
62
+ if (item === '--app') {
63
+ app = args[i + 1] ?? null;
64
+ i++;
65
+ continue;
66
+ }
67
+ if (item.startsWith('--app=')) {
68
+ app = item.slice('--app='.length);
69
+ continue;
70
+ }
71
+ positionals.push(item);
72
+ }
73
+
74
+ return { positionals, type, app };
75
+ }
76
+
77
+ function validateName(name) {
78
+ return /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name);
79
+ }
80
+
81
+ function typeLabel(type) {
82
+ return type === 'file' ? '文件' : '目录';
83
+ }
84
+
85
+ function resolveTarget(targetPath, type, options) {
86
+ const absolutePath = path.resolve(options.cwd ?? process.cwd(), targetPath);
87
+ if (!fs.existsSync(absolutePath)) {
88
+ return { error: `错误: '${absolutePath}' 不存在。` };
89
+ }
90
+
91
+ const stat = fs.statSync(absolutePath);
92
+ if (type === 'dir' && !stat.isDirectory()) {
93
+ return { error: `错误: '${absolutePath}' 不是一个目录。` };
94
+ }
95
+ if (type === 'file' && !stat.isFile()) {
96
+ return { error: `错误: '${absolutePath}' 不是一个文件。` };
97
+ }
98
+
99
+ return { absolutePath };
100
+ }
101
+
102
+ function colorType(type) {
103
+ const colors = {
104
+ dir: '\x1b[36m',
105
+ file: '\x1b[32m',
106
+ app: '\x1b[35m',
107
+ script: '\x1b[33m',
108
+ };
109
+ const color = colors[type];
110
+ return color ? `${color}${type}\x1b[0m` : type;
111
+ }
112
+
113
+ function addAlias(args, options) {
114
+ const parsed = parseAliasArgs(args);
115
+ const [name, targetPath] = parsed.positionals;
116
+
117
+ if (!name || !targetPath || parsed.positionals.length !== 2) {
118
+ return fail(help());
119
+ }
120
+ if (parsed.type !== 'dir' && parsed.type !== 'file') {
121
+ return fail('错误: 添加快捷项必须使用 --type=dir 或 --type=file。');
122
+ }
123
+ if (!validateName(name)) {
124
+ return fail('错误: 名称只能包含字母、数字、下划线和中划线,并且必须以字母或数字开头。');
125
+ }
126
+
127
+ const resolved = resolveTarget(targetPath, parsed.type, options);
128
+ if (resolved.error) {
129
+ return fail(resolved.error);
130
+ }
131
+
132
+ const configPath = options.configPath ?? defaultConfigPath();
133
+ const config = loadConfig(configPath);
134
+ config.aliases[name] = {
135
+ type: parsed.type,
136
+ path: resolved.absolutePath,
137
+ ...(parsed.type === 'file' && parsed.app ? { app: parsed.app } : {}),
138
+ };
139
+ saveConfig(configPath, config);
140
+
141
+ return ok(`已添加${typeLabel(parsed.type)}快捷项: ${name}\n路径: ${resolved.absolutePath}`);
142
+ }
143
+
144
+ function listAliases(options) {
145
+ const config = loadConfig(options.configPath ?? defaultConfigPath());
146
+ const entries = Object.entries(config.aliases)
147
+ .sort(([a], [b]) => a.localeCompare(b));
148
+
149
+ if (entries.length === 0) {
150
+ return ok('暂无快捷项。使用 edx-alias add <名称> <路径> --type=dir|file 添加。');
151
+ }
152
+
153
+ return ok([
154
+ 'target\tpath\ttype',
155
+ ...entries.map(([name, value]) => `${name}\t${value.path}\t${colorType(value.type)}`),
156
+ ].join('\n'));
157
+ }
158
+
159
+ function launcherFor(platform, entry, options = {}) {
160
+ const targetPath = entry.path;
161
+ const app = options.app ?? entry.app;
162
+ if (platform === 'macos') {
163
+ if (entry.type === 'file' && app) {
164
+ return { command: 'open', args: ['-a', app, targetPath] };
165
+ }
166
+ return { command: 'open', args: [targetPath] };
167
+ }
168
+ if (platform === 'windows') {
169
+ if (entry.type === 'file' && app) {
170
+ return {
171
+ command: 'powershell.exe',
172
+ args: [
173
+ '-NoProfile',
174
+ '-Command',
175
+ `Start-Process -FilePath ${JSON.stringify(app)} -ArgumentList ${JSON.stringify(targetPath)}`,
176
+ ],
177
+ };
178
+ }
179
+ return entry.type === 'dir'
180
+ ? { command: 'explorer', args: [targetPath] }
181
+ : {
182
+ command: 'powershell.exe',
183
+ args: ['-NoProfile', '-Command', `Start-Process ${JSON.stringify(targetPath)}`],
184
+ };
185
+ }
186
+ if (platform === 'linux') {
187
+ if (entry.type === 'file' && app) {
188
+ return { command: app, args: [targetPath] };
189
+ }
190
+ return { command: 'xdg-open', args: [targetPath] };
191
+ }
192
+ return null;
193
+ }
194
+
195
+ function openAlias(name, options) {
196
+ if (!name) {
197
+ return fail(help());
198
+ }
199
+
200
+ const config = loadConfig(options.configPath ?? defaultConfigPath());
201
+ const entry = config.aliases[name];
202
+ if (!entry) {
203
+ return fail(`错误: 未找到快捷项 '${name}'。`);
204
+ }
205
+
206
+ const parsed = parseAliasArgs(options.openArgs ?? []);
207
+ const platform = options.platform ?? detectPlatform();
208
+ const launcher = launcherFor(platform, entry, { app: parsed.app });
209
+ if (!launcher) {
210
+ return fail(`错误: 当前平台不支持打开 '${entry.path}'。`);
211
+ }
212
+
213
+ const commandExists = options.commandExists ?? defaultCommandExists;
214
+ if (!commandExists(launcher.command)) {
215
+ return fail(`错误: 缺少打开目录命令 ${launcher.command}。`);
216
+ }
217
+
218
+ const runCommand = options.runCommand ?? defaultRunCommand;
219
+ const result = runCommand(launcher.command, launcher.args);
220
+ if (result.code === 0) {
221
+ return ok(`已打开快捷项: ${name}`);
222
+ }
223
+
224
+ return fail(`错误: 打开失败 ${result.stderr.trim()}`);
225
+ }
226
+
227
+ function removeAlias(name, options) {
228
+ if (!name) {
229
+ return fail(help());
230
+ }
231
+
232
+ const configPath = options.configPath ?? defaultConfigPath();
233
+ const config = loadConfig(configPath);
234
+ const entry = config.aliases[name];
235
+ if (!entry) {
236
+ return fail(`错误: 未找到快捷项 '${name}'。`);
237
+ }
238
+
239
+ delete config.aliases[name];
240
+ saveConfig(configPath, config);
241
+ return entry.type === 'dir' ? ok(`已删除目录快捷项: ${name}`) : ok(`已删除快捷项: ${name}`);
242
+ }
243
+
244
+ function updateAlias(args, options) {
245
+ const parsed = parseAliasArgs(args);
246
+ const [name, targetPath] = parsed.positionals;
247
+
248
+ if (!name || !targetPath || parsed.positionals.length !== 2) {
249
+ return fail(help());
250
+ }
251
+ if (parsed.type !== 'dir' && parsed.type !== 'file') {
252
+ return fail('错误: 更新快捷项必须使用 --type=dir 或 --type=file。');
253
+ }
254
+
255
+ const configPath = options.configPath ?? defaultConfigPath();
256
+ const config = loadConfig(configPath);
257
+ if (!config.aliases[name]) {
258
+ return fail(`错误: 未找到快捷项 '${name}'。`);
259
+ }
260
+
261
+ const resolved = resolveTarget(targetPath, parsed.type, options);
262
+ if (resolved.error) {
263
+ return fail(resolved.error);
264
+ }
265
+
266
+ config.aliases[name] = {
267
+ type: parsed.type,
268
+ path: resolved.absolutePath,
269
+ ...(parsed.type === 'file' && parsed.app ? { app: parsed.app } : {}),
270
+ };
271
+ saveConfig(configPath, config);
272
+
273
+ return ok(`已更新${typeLabel(parsed.type)}快捷项: ${name}\n路径: ${resolved.absolutePath}`);
274
+ }
275
+
276
+ export async function runAliasCommand(args, options = {}) {
277
+ const [command, ...rest] = args;
278
+
279
+ if (!command || command === '-a' || command === '--help' || command === 'help') {
280
+ return ok(help());
281
+ }
282
+
283
+ if (command === 'add') {
284
+ return addAlias(rest, options);
285
+ }
286
+ if (command === 'update') {
287
+ return updateAlias(rest, options);
288
+ }
289
+ if (command === 'open') {
290
+ const parsed = parseAliasArgs(rest);
291
+ return openAlias(parsed.positionals[0], { ...options, openArgs: rest });
292
+ }
293
+ if (command === 'ls' || command === 'list') {
294
+ return listAliases(options);
295
+ }
296
+ if (command === 'remove') {
297
+ return removeAlias(rest[0], options);
298
+ }
299
+
300
+ return fail(`错误: 未知动作 '${command}'\n${help()}`);
301
+ }
@@ -0,0 +1,318 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { detectPlatform } from '../platform/platform.mjs';
6
+ import { commandExists as defaultCommandExists, runCommand } from '../utils/process.mjs';
7
+ import { fail, ok } from '../utils/result.mjs';
8
+
9
+ const APPS = [
10
+ {
11
+ id: 'clash',
12
+ label: 'ClashX Pro',
13
+ macos: { name: 'ClashX Pro' },
14
+ windows: { name: 'Clash' },
15
+ linux: { name: 'clash' },
16
+ },
17
+ {
18
+ id: 'snipaste',
19
+ label: 'Snipaste',
20
+ macos: { name: 'Snipaste' },
21
+ windows: { name: 'Snipaste' },
22
+ linux: { name: 'snipaste' },
23
+ },
24
+ {
25
+ id: 'betterdisplay',
26
+ label: 'BetterDisplay',
27
+ macos: { name: 'BetterDisplay' },
28
+ },
29
+ {
30
+ id: 'netease',
31
+ label: '网易云音乐',
32
+ macos: { name: 'NeteaseMusic' },
33
+ windows: { name: '网易云音乐' },
34
+ linux: { name: 'netease-cloud-music' },
35
+ },
36
+ {
37
+ id: 'vscode',
38
+ label: 'VS Code',
39
+ macos: { name: 'Visual Studio Code' },
40
+ windows: { name: 'Code' },
41
+ linux: { name: 'code' },
42
+ },
43
+ ];
44
+
45
+ function help() {
46
+ return [
47
+ '--- edx-app 可用的指令集列表 ---',
48
+ '0. edx-app show 显示当前环境',
49
+ '1. edx-app start 启动所有应用',
50
+ '2. edx-app vscode 启动 VS Code',
51
+ '3. edx-app ls 列出可传给 --app 的应用值',
52
+ '4. edx-app run <APP> 打开指定应用',
53
+ '--------------------------',
54
+ ].join('\n');
55
+ }
56
+
57
+ function launcherFor(platform, app) {
58
+ const config = app[platform];
59
+ if (!config) {
60
+ return null;
61
+ }
62
+
63
+ if (platform === 'macos') {
64
+ return { command: 'open', args: ['-a', config.name] };
65
+ }
66
+ if (platform === 'windows') {
67
+ return {
68
+ command: 'powershell.exe',
69
+ args: ['-NoProfile', '-Command', `Start-Process ${JSON.stringify(config.name)}`],
70
+ };
71
+ }
72
+ if (platform === 'linux') {
73
+ return { command: config.name, args: [] };
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ function launcherForAppName(platform, appName) {
80
+ if (platform === 'macos') {
81
+ return { command: 'open', args: ['-a', appName] };
82
+ }
83
+ if (platform === 'windows') {
84
+ return {
85
+ command: 'powershell.exe',
86
+ args: ['-NoProfile', '-Command', `Start-Process -FilePath ${JSON.stringify(appName)}`],
87
+ };
88
+ }
89
+ if (platform === 'linux') {
90
+ return { command: appName, args: [] };
91
+ }
92
+
93
+ return null;
94
+ }
95
+
96
+ function show(options) {
97
+ const platform = options.platform ?? detectPlatform();
98
+ const lines = [`当前平台: ${platform}`, '----------------------------------------'];
99
+
100
+ for (const app of APPS) {
101
+ lines.push(`${app.label}: ${app[platform] ? '已配置' : '当前平台未配置'}`);
102
+ }
103
+
104
+ lines.push('----------------------------------------');
105
+ return ok(lines.join('\n'));
106
+ }
107
+
108
+ function defaultAppDirectories(platform) {
109
+ if (platform === 'macos') {
110
+ return [
111
+ '/Applications',
112
+ path.join(os.homedir(), 'Applications'),
113
+ ];
114
+ }
115
+ if (platform === 'linux') {
116
+ return [
117
+ '/usr/share/applications',
118
+ path.join(os.homedir(), '.local/share/applications'),
119
+ ];
120
+ }
121
+ if (platform === 'windows') {
122
+ return [
123
+ ...(process.env.ProgramData
124
+ ? [path.join(process.env.ProgramData, 'Microsoft/Windows/Start Menu/Programs')]
125
+ : []),
126
+ ...(process.env.APPDATA
127
+ ? [path.join(process.env.APPDATA, 'Microsoft/Windows/Start Menu/Programs')]
128
+ : []),
129
+ ];
130
+ }
131
+
132
+ return [];
133
+ }
134
+
135
+ function listDirectoryEntries(dir) {
136
+ if (!fs.existsSync(dir)) {
137
+ return [];
138
+ }
139
+
140
+ return fs.readdirSync(dir, { withFileTypes: true }).map((entry) => ({
141
+ name: entry.name,
142
+ isDirectory: entry.isDirectory(),
143
+ path: path.join(dir, entry.name),
144
+ }));
145
+ }
146
+
147
+ function readTextFile(filePath) {
148
+ return fs.readFileSync(filePath, 'utf8');
149
+ }
150
+
151
+ function extractDesktopExec(content) {
152
+ const execLine = content
153
+ .split(/\r?\n/)
154
+ .find((line) => line.startsWith('Exec='));
155
+ if (!execLine) {
156
+ return null;
157
+ }
158
+
159
+ const execValue = execLine
160
+ .slice('Exec='.length)
161
+ .replace(/\s+%[a-zA-Z]/g, '')
162
+ .trim();
163
+ const tokens = execValue.match(/"[^"]+"|'[^']+'|\S+/g) ?? [];
164
+ const command = tokens.find((token, index) => {
165
+ if (index === 0 && token === 'env') {
166
+ return false;
167
+ }
168
+ return !/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token);
169
+ });
170
+
171
+ return command ? command.replace(/^["']|["']$/g, '') : null;
172
+ }
173
+
174
+ function collectWindowsShortcuts(dir, listDirectory, names) {
175
+ for (const entry of listDirectory(dir)) {
176
+ const entryPath = entry.path ?? path.join(dir, entry.name);
177
+ if (entry.isDirectory) {
178
+ collectWindowsShortcuts(entryPath, listDirectory, names);
179
+ continue;
180
+ }
181
+ if (entry.name.endsWith('.lnk')) {
182
+ names.add(entryPath);
183
+ }
184
+ }
185
+ }
186
+
187
+ function listApps(options) {
188
+ const platform = options.platform ?? detectPlatform();
189
+ const appDirectories = options.appDirectories ?? defaultAppDirectories(platform);
190
+ const listDirectory = options.listDirectory ?? listDirectoryEntries;
191
+ const readFile = options.readFile ?? readTextFile;
192
+ const names = new Set();
193
+
194
+ if (platform === 'macos') {
195
+ for (const dir of appDirectories) {
196
+ for (const entry of listDirectory(dir)) {
197
+ if (!entry.isDirectory || !entry.name.endsWith('.app')) {
198
+ continue;
199
+ }
200
+ names.add(entry.name.slice(0, -'.app'.length));
201
+ }
202
+ }
203
+ } else if (platform === 'linux') {
204
+ for (const dir of appDirectories) {
205
+ for (const entry of listDirectory(dir)) {
206
+ if (entry.isDirectory || !entry.name.endsWith('.desktop')) {
207
+ continue;
208
+ }
209
+ const command = extractDesktopExec(readFile(entry.path ?? path.join(dir, entry.name)));
210
+ if (command) {
211
+ names.add(command);
212
+ }
213
+ }
214
+ }
215
+ } else if (platform === 'windows') {
216
+ for (const dir of appDirectories) {
217
+ collectWindowsShortcuts(dir, listDirectory, names);
218
+ }
219
+ } else {
220
+ return fail('错误: 当前平台不支持应用枚举。');
221
+ }
222
+
223
+ const sorted = [...names].sort((a, b) => a.localeCompare(b));
224
+ return sorted.length === 0 ? ok('未找到应用。') : ok(sorted.join('\n'));
225
+ }
226
+
227
+ function startApps(apps, options) {
228
+ const platform = options.platform ?? detectPlatform();
229
+ const commandExists = options.commandExists ?? defaultCommandExists;
230
+ const lines = [];
231
+ let failures = 0;
232
+
233
+ for (const app of apps) {
234
+ const launcher = launcherFor(platform, app);
235
+
236
+ if (!launcher) {
237
+ lines.push(`${app.label}: 当前平台未配置,跳过`);
238
+ continue;
239
+ }
240
+
241
+ if (!commandExists(launcher.command)) {
242
+ lines.push(`${app.label}: 缺少启动命令 ${launcher.command},跳过`);
243
+ failures++;
244
+ continue;
245
+ }
246
+
247
+ if (options.dryRun) {
248
+ lines.push(`${app.label}: 将执行 ${launcher.command} ${launcher.args.join(' ')}`);
249
+ continue;
250
+ }
251
+
252
+ const result = runCommand(launcher.command, launcher.args);
253
+ if (result.code === 0) {
254
+ lines.push(`${app.label}: 已启动`);
255
+ } else {
256
+ lines.push(`${app.label}: 启动失败 ${result.stderr.trim()}`);
257
+ failures++;
258
+ }
259
+ }
260
+
261
+ return failures > 0 ? fail(lines.join('\n')) : ok(lines.join('\n'));
262
+ }
263
+
264
+ function runApp(args, options) {
265
+ const appName = args.join(' ').trim();
266
+ if (!appName) {
267
+ return fail(`用法: edx-app run <APP>\n${help()}`);
268
+ }
269
+
270
+ const platform = options.platform ?? detectPlatform();
271
+ const launcher = launcherForAppName(platform, appName);
272
+ if (!launcher) {
273
+ return fail(`错误: 当前平台不支持打开应用 '${appName}'。`);
274
+ }
275
+
276
+ const commandExists = options.commandExists ?? defaultCommandExists;
277
+ if (!commandExists(launcher.command)) {
278
+ return fail(`错误: 缺少启动命令 ${launcher.command}。`);
279
+ }
280
+
281
+ if (options.dryRun) {
282
+ return ok(`将执行 ${launcher.command} ${launcher.args.join(' ')}`);
283
+ }
284
+
285
+ const execute = options.runCommand ?? runCommand;
286
+ const result = execute(launcher.command, launcher.args);
287
+ if (result.code === 0) {
288
+ return ok(`已打开应用: ${appName}`);
289
+ }
290
+
291
+ return fail(`错误: 打开应用失败 ${result.stderr.trim()}`);
292
+ }
293
+
294
+ export async function runAppCommand(args, options = {}) {
295
+ const [command, ...rest] = args;
296
+
297
+ if (!command || command === '-a' || command === '--help' || command === 'help') {
298
+ return ok(help());
299
+ }
300
+
301
+ if (command === 'show') {
302
+ return show(options);
303
+ }
304
+ if (command === 'ls' || command === 'list') {
305
+ return listApps(options);
306
+ }
307
+ if (command === 'run') {
308
+ return runApp(rest, options);
309
+ }
310
+ if (command === 'start') {
311
+ return startApps(APPS.filter((app) => app.id !== 'vscode'), options);
312
+ }
313
+ if (command === 'vscode') {
314
+ return startApps(APPS.filter((app) => app.id === 'vscode'), options);
315
+ }
316
+
317
+ return fail(`错误: 未知动作 '${command}'\n${help()}`);
318
+ }
@@ -0,0 +1,84 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import { commandExists as defaultCommandExists, runCommand } from '../utils/process.mjs';
5
+ import { fail, ok } from '../utils/result.mjs';
6
+
7
+ function help() {
8
+ return [
9
+ '--- edx-hw 可用的指令集列表 ---',
10
+ '0. edx-hw show 显示当前环境',
11
+ '1. edx-hw publish 向远端推送包',
12
+ '2. edx-hw debug 启动鸿蒙调试',
13
+ '--------------------------',
14
+ ].join('\n');
15
+ }
16
+
17
+ function listHarFiles(cwd) {
18
+ return fs.readdirSync(cwd).filter((entry) => entry.endsWith('.har') && fs.statSync(path.join(cwd, entry)).isFile());
19
+ }
20
+
21
+ function show(commandExists) {
22
+ const lines = ['正在检查开发环境...', '----------------------------------------'];
23
+
24
+ for (const command of ['node', 'npm', 'ohpm']) {
25
+ lines.push(`${command}: ${commandExists(command) ? '已安装' : '未安装'}`);
26
+ }
27
+
28
+ lines.push('----------------------------------------');
29
+ return ok(lines.join('\n'));
30
+ }
31
+
32
+ function publish(options) {
33
+ const commandExists = options.commandExists ?? defaultCommandExists;
34
+ const cwd = options.cwd ?? process.cwd();
35
+
36
+ if (!commandExists('ohpm')) {
37
+ return fail('错误: 未找到 ohpm,无法执行 ohpm publish。');
38
+ }
39
+
40
+ const harFiles = listHarFiles(cwd);
41
+ if (harFiles.length === 0) {
42
+ return fail('错误: 当前目录没有可发布的 .har 文件。');
43
+ }
44
+
45
+ if (options.dryRun) {
46
+ return ok(`将执行: ohpm publish ${harFiles.join(' ')}`);
47
+ }
48
+
49
+ return runCommand('ohpm', ['publish', ...harFiles], { cwd });
50
+ }
51
+
52
+ function debug(options) {
53
+ const commandExists = options.commandExists ?? defaultCommandExists;
54
+
55
+ if (!commandExists('hw-dev')) {
56
+ return fail('错误: 未找到 hw-dev,当前机器未安装鸿蒙调试工具。');
57
+ }
58
+
59
+ if (options.dryRun) {
60
+ return ok('将执行: hw-dev');
61
+ }
62
+
63
+ return runCommand('hw-dev');
64
+ }
65
+
66
+ export async function runHwCommand(args, options = {}) {
67
+ const [command] = args;
68
+
69
+ if (!command || command === '-a' || command === '--help' || command === 'help') {
70
+ return ok(help());
71
+ }
72
+
73
+ if (command === 'show') {
74
+ return show(options.commandExists ?? defaultCommandExists);
75
+ }
76
+ if (command === 'publish') {
77
+ return publish(options);
78
+ }
79
+ if (command === 'debug') {
80
+ return debug(options);
81
+ }
82
+
83
+ return fail(`错误: 未知动作 '${command}'\n${help()}`);
84
+ }