@hangox/pm-cli 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/index.d.ts +2 -0
- package/dist/index.js +1449 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command6 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/test.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/utils/logger.ts
|
|
10
|
+
var currentLevel = "info";
|
|
11
|
+
var levels = {
|
|
12
|
+
debug: 0,
|
|
13
|
+
info: 1,
|
|
14
|
+
warn: 2,
|
|
15
|
+
error: 3,
|
|
16
|
+
silent: 4
|
|
17
|
+
};
|
|
18
|
+
function shouldLog(level) {
|
|
19
|
+
return levels[level] >= levels[currentLevel];
|
|
20
|
+
}
|
|
21
|
+
function formatMessage(level, message, data) {
|
|
22
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
23
|
+
const dataStr = data ? ` ${JSON.stringify(data)}` : "";
|
|
24
|
+
return `[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}`;
|
|
25
|
+
}
|
|
26
|
+
var logger = {
|
|
27
|
+
setLevel(level) {
|
|
28
|
+
currentLevel = level;
|
|
29
|
+
},
|
|
30
|
+
getLevel() {
|
|
31
|
+
return currentLevel;
|
|
32
|
+
},
|
|
33
|
+
debug(message, data) {
|
|
34
|
+
if (shouldLog("debug")) {
|
|
35
|
+
console.error(formatMessage("debug", message, data));
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
info(message, data) {
|
|
39
|
+
if (shouldLog("info")) {
|
|
40
|
+
console.error(formatMessage("info", message, data));
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
warn(message, data) {
|
|
44
|
+
if (shouldLog("warn")) {
|
|
45
|
+
console.error(formatMessage("warn", message, data));
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
error(message, data) {
|
|
49
|
+
if (shouldLog("error")) {
|
|
50
|
+
console.error(formatMessage("error", message, data));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var logger_default = logger;
|
|
55
|
+
|
|
56
|
+
// src/client/api-client.ts
|
|
57
|
+
var ApiClient = class _ApiClient {
|
|
58
|
+
static BASE_URL = "http://redmineapi.nie.netease.com/api";
|
|
59
|
+
static DEFAULT_TIMEOUT = 3e4;
|
|
60
|
+
// 30秒
|
|
61
|
+
/**
|
|
62
|
+
* 发送 GET 请求
|
|
63
|
+
*/
|
|
64
|
+
async get(endpoint, params = {}) {
|
|
65
|
+
try {
|
|
66
|
+
logger_default.debug(`API GET \u8BF7\u6C42: ${endpoint}`, { params });
|
|
67
|
+
const queryString = this.buildQueryString(params);
|
|
68
|
+
const url = `${_ApiClient.BASE_URL}/${endpoint}${queryString ? `?${queryString}` : ""}`;
|
|
69
|
+
const controller = new AbortController();
|
|
70
|
+
const timeoutId = setTimeout(() => controller.abort(), _ApiClient.DEFAULT_TIMEOUT);
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(url, {
|
|
73
|
+
method: "GET",
|
|
74
|
+
signal: controller.signal
|
|
75
|
+
});
|
|
76
|
+
clearTimeout(timeoutId);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
logger_default.error(`HTTP \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`);
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
message: `HTTP\u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801:${response.status}`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
logger_default.debug("API \u54CD\u5E94:", data);
|
|
86
|
+
return data;
|
|
87
|
+
} finally {
|
|
88
|
+
clearTimeout(timeoutId);
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return this.handleError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 发送 POST 请求
|
|
96
|
+
*/
|
|
97
|
+
async post(endpoint, params = {}) {
|
|
98
|
+
try {
|
|
99
|
+
logger_default.debug(`API POST \u8BF7\u6C42: ${endpoint}`, { params });
|
|
100
|
+
const url = `${_ApiClient.BASE_URL}/${endpoint}`;
|
|
101
|
+
const formData = this.buildFormData(params);
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeoutId = setTimeout(() => controller.abort(), _ApiClient.DEFAULT_TIMEOUT);
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
109
|
+
},
|
|
110
|
+
body: formData,
|
|
111
|
+
signal: controller.signal
|
|
112
|
+
});
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
logger_default.error(`HTTP \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`);
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
message: `HTTP\u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801:${response.status}`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
logger_default.debug("API \u54CD\u5E94:", data);
|
|
123
|
+
return data;
|
|
124
|
+
} finally {
|
|
125
|
+
clearTimeout(timeoutId);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return this.handleError(error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 处理错误
|
|
133
|
+
*/
|
|
134
|
+
handleError(error) {
|
|
135
|
+
if (error instanceof Error) {
|
|
136
|
+
if (error.name === "AbortError") {
|
|
137
|
+
logger_default.error("\u8BF7\u6C42\u8D85\u65F6");
|
|
138
|
+
return {
|
|
139
|
+
success: false,
|
|
140
|
+
message: "\u8BF7\u6C42\u8D85\u65F6"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
logger_default.error("\u8BF7\u6C42\u5F02\u5E38:", error);
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
message: `\u8BF7\u6C42\u5F02\u5E38:${error.message}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
message: "\u672A\u77E5\u9519\u8BEF"
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 构建查询字符串
|
|
156
|
+
*/
|
|
157
|
+
buildQueryString(params) {
|
|
158
|
+
return Object.entries(params).filter(([, value]) => value !== null && value !== void 0).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 构建表单数据
|
|
162
|
+
*/
|
|
163
|
+
buildFormData(params) {
|
|
164
|
+
return Object.entries(params).filter(([, value]) => value !== null && value !== void 0).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var apiClient = new ApiClient();
|
|
168
|
+
|
|
169
|
+
// src/services/user-service.ts
|
|
170
|
+
var UserService = class {
|
|
171
|
+
/**
|
|
172
|
+
* 测试连接 (通过获取项目列表来验证)
|
|
173
|
+
*/
|
|
174
|
+
async testConnection(token, host, project) {
|
|
175
|
+
logger_default.info("\u6D4B\u8BD5\u8FDE\u63A5", { host, project });
|
|
176
|
+
return await apiClient.get("project", { token, host });
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 获取项目列表
|
|
180
|
+
*/
|
|
181
|
+
async getProjects(token, host) {
|
|
182
|
+
logger_default.info("\u83B7\u53D6\u9879\u76EE\u5217\u8868", { host });
|
|
183
|
+
return await apiClient.get("project", { token, host });
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 获取项目用户
|
|
187
|
+
*/
|
|
188
|
+
async getProjectUsers(token, host, project) {
|
|
189
|
+
logger_default.info("\u83B7\u53D6\u9879\u76EE\u7528\u6237", { host, project });
|
|
190
|
+
return await apiClient.get("user", { token, host, project });
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 获取主机信息
|
|
194
|
+
*/
|
|
195
|
+
async getHostInfo(token, host) {
|
|
196
|
+
logger_default.info("\u83B7\u53D6\u4E3B\u673A\u4FE1\u606F", { host });
|
|
197
|
+
return await apiClient.get("host", { token, host });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
var userService = new UserService();
|
|
201
|
+
|
|
202
|
+
// src/utils/config.ts
|
|
203
|
+
import * as fs from "fs";
|
|
204
|
+
import * as path from "path";
|
|
205
|
+
import * as os from "os";
|
|
206
|
+
var DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".config", "pm-cli");
|
|
207
|
+
var DEFAULT_CONFIG_FILE = "config.json";
|
|
208
|
+
var CONFIG_KEY_MAP = {
|
|
209
|
+
"default-host": "host",
|
|
210
|
+
"default-project": "project",
|
|
211
|
+
"default-token": "token",
|
|
212
|
+
// 用户信息配置
|
|
213
|
+
"user-id": "userId",
|
|
214
|
+
"user-name": "userName",
|
|
215
|
+
"user-mail": "userMail"
|
|
216
|
+
};
|
|
217
|
+
function normalizeConfigKey(key) {
|
|
218
|
+
return CONFIG_KEY_MAP[key] || key;
|
|
219
|
+
}
|
|
220
|
+
function getConfigPath(customPath) {
|
|
221
|
+
if (customPath) {
|
|
222
|
+
return customPath;
|
|
223
|
+
}
|
|
224
|
+
if (process.env.PM_CLI_CONFIG) {
|
|
225
|
+
return process.env.PM_CLI_CONFIG;
|
|
226
|
+
}
|
|
227
|
+
return path.join(DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE);
|
|
228
|
+
}
|
|
229
|
+
function ensureConfigDir(configPath) {
|
|
230
|
+
const dir = path.dirname(configPath);
|
|
231
|
+
if (!fs.existsSync(dir)) {
|
|
232
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function readConfig(customPath) {
|
|
236
|
+
const configPath = getConfigPath(customPath);
|
|
237
|
+
if (!fs.existsSync(configPath)) {
|
|
238
|
+
return {
|
|
239
|
+
default: {},
|
|
240
|
+
profiles: {}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
245
|
+
return JSON.parse(content);
|
|
246
|
+
} catch {
|
|
247
|
+
return {
|
|
248
|
+
default: {},
|
|
249
|
+
profiles: {}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function writeConfig(config, customPath) {
|
|
254
|
+
const configPath = getConfigPath(customPath);
|
|
255
|
+
ensureConfigDir(configPath);
|
|
256
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
257
|
+
}
|
|
258
|
+
function getConfigValue(key, customPath) {
|
|
259
|
+
const config = readConfig(customPath);
|
|
260
|
+
let normalizedKey;
|
|
261
|
+
if (key.startsWith("default.")) {
|
|
262
|
+
normalizedKey = normalizeConfigKey(key.replace("default.", ""));
|
|
263
|
+
} else {
|
|
264
|
+
normalizedKey = normalizeConfigKey(key);
|
|
265
|
+
}
|
|
266
|
+
return config.default[normalizedKey];
|
|
267
|
+
}
|
|
268
|
+
function setConfigValue(key, value, customPath) {
|
|
269
|
+
const config = readConfig(customPath);
|
|
270
|
+
let normalizedKey;
|
|
271
|
+
if (key.startsWith("default.")) {
|
|
272
|
+
normalizedKey = normalizeConfigKey(key.replace("default.", ""));
|
|
273
|
+
} else {
|
|
274
|
+
normalizedKey = normalizeConfigKey(key);
|
|
275
|
+
}
|
|
276
|
+
config.default[normalizedKey] = value;
|
|
277
|
+
writeConfig(config, customPath);
|
|
278
|
+
}
|
|
279
|
+
function getProfile(name, customPath) {
|
|
280
|
+
const config = readConfig(customPath);
|
|
281
|
+
return config.profiles[name];
|
|
282
|
+
}
|
|
283
|
+
function setProfile(name, profile, customPath) {
|
|
284
|
+
const config = readConfig(customPath);
|
|
285
|
+
config.profiles[name] = profile;
|
|
286
|
+
writeConfig(config, customPath);
|
|
287
|
+
}
|
|
288
|
+
function removeProfile(name, customPath) {
|
|
289
|
+
const config = readConfig(customPath);
|
|
290
|
+
if (config.profiles[name]) {
|
|
291
|
+
delete config.profiles[name];
|
|
292
|
+
writeConfig(config, customPath);
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
function listProfiles(customPath) {
|
|
298
|
+
const config = readConfig(customPath);
|
|
299
|
+
return Object.keys(config.profiles);
|
|
300
|
+
}
|
|
301
|
+
function resolveCredentials(options) {
|
|
302
|
+
const config = readConfig(options.config);
|
|
303
|
+
const result = {
|
|
304
|
+
token: config.default.token,
|
|
305
|
+
host: config.default.host,
|
|
306
|
+
project: config.default.project
|
|
307
|
+
};
|
|
308
|
+
if (options.profile && config.profiles[options.profile]) {
|
|
309
|
+
const profile = config.profiles[options.profile];
|
|
310
|
+
if (profile.token) result.token = profile.token;
|
|
311
|
+
if (profile.host) result.host = profile.host;
|
|
312
|
+
if (profile.project) result.project = profile.project;
|
|
313
|
+
}
|
|
314
|
+
if (process.env.NETEASE_TOKEN) result.token = process.env.NETEASE_TOKEN;
|
|
315
|
+
if (process.env.NETEASE_HOST) result.host = process.env.NETEASE_HOST;
|
|
316
|
+
if (process.env.NETEASE_PROJECT) result.project = process.env.NETEASE_PROJECT;
|
|
317
|
+
if (options.token) result.token = options.token;
|
|
318
|
+
if (options.host) result.host = options.host;
|
|
319
|
+
if (options.project) result.project = options.project;
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
function validateCredentials(creds, requiredFields = ["token", "host", "project"]) {
|
|
323
|
+
const missing = [];
|
|
324
|
+
for (const field of requiredFields) {
|
|
325
|
+
if (!creds[field]) {
|
|
326
|
+
missing.push(field);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
valid: missing.length === 0,
|
|
331
|
+
missing
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/utils/output.ts
|
|
336
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
337
|
+
function generateOutputPath(command, identifier) {
|
|
338
|
+
const now = /* @__PURE__ */ new Date();
|
|
339
|
+
const timestamp = now.getFullYear().toString() + (now.getMonth() + 1).toString().padStart(2, "0") + now.getDate().toString().padStart(2, "0") + now.getHours().toString().padStart(2, "0") + now.getMinutes().toString().padStart(2, "0") + now.getSeconds().toString().padStart(2, "0");
|
|
340
|
+
return `/tmp/pm-cli_${command}_${identifier}_${timestamp}.json`;
|
|
341
|
+
}
|
|
342
|
+
function outputToFile(data, filePath) {
|
|
343
|
+
const result = {
|
|
344
|
+
success: true,
|
|
345
|
+
data
|
|
346
|
+
};
|
|
347
|
+
writeFileSync2(filePath, JSON.stringify(result, null, 2), "utf-8");
|
|
348
|
+
return filePath;
|
|
349
|
+
}
|
|
350
|
+
function smartOutput(data, options) {
|
|
351
|
+
if (options.stdout) {
|
|
352
|
+
outputSuccess(data);
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
const filePath = options.output || generateOutputPath(options.command, options.identifier);
|
|
356
|
+
outputToFile(data, filePath);
|
|
357
|
+
console.log(JSON.stringify({ success: true, output: filePath }, null, 2));
|
|
358
|
+
return filePath;
|
|
359
|
+
}
|
|
360
|
+
function outputSuccess(data) {
|
|
361
|
+
const result = {
|
|
362
|
+
success: true,
|
|
363
|
+
data
|
|
364
|
+
};
|
|
365
|
+
console.log(JSON.stringify(result, null, 2));
|
|
366
|
+
}
|
|
367
|
+
function outputError(error) {
|
|
368
|
+
const result = {
|
|
369
|
+
success: false,
|
|
370
|
+
error
|
|
371
|
+
};
|
|
372
|
+
console.log(JSON.stringify(result, null, 2));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/commands/test.ts
|
|
376
|
+
function createTestCommand() {
|
|
377
|
+
const testCmd = new Command("test").description("\u6D4B\u8BD5\u7F51\u6613\u6613\u534F\u4F5C\u8FDE\u63A5").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
378
|
+
const creds = resolveCredentials(options);
|
|
379
|
+
const validation = validateCredentials(creds);
|
|
380
|
+
if (!validation.valid) {
|
|
381
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const result = await userService.testConnection(
|
|
385
|
+
creds.token,
|
|
386
|
+
creds.host,
|
|
387
|
+
creds.project
|
|
388
|
+
);
|
|
389
|
+
if (result.success) {
|
|
390
|
+
outputSuccess({
|
|
391
|
+
message: "\u8FDE\u63A5\u6210\u529F",
|
|
392
|
+
host: creds.host,
|
|
393
|
+
project: creds.project,
|
|
394
|
+
data: result.data
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u8FDE\u63A5\u5931\u8D25");
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
return testCmd;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/commands/config/index.ts
|
|
405
|
+
import { Command as Command2 } from "commander";
|
|
406
|
+
function createConfigCommand() {
|
|
407
|
+
const configCmd = new Command2("config").description("\u914D\u7F6E\u7BA1\u7406");
|
|
408
|
+
configCmd.command("set <key> <value>").description("\u8BBE\u7F6E\u914D\u7F6E\u9879").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action((key, value, options) => {
|
|
409
|
+
setConfigValue(key, value, options.config);
|
|
410
|
+
outputSuccess({
|
|
411
|
+
message: `\u914D\u7F6E\u9879 ${key} \u5DF2\u8BBE\u7F6E`,
|
|
412
|
+
key,
|
|
413
|
+
value,
|
|
414
|
+
configPath: getConfigPath(options.config)
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
configCmd.command("get <key>").description("\u83B7\u53D6\u914D\u7F6E\u9879").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action((key, options) => {
|
|
418
|
+
const value = getConfigValue(key, options.config);
|
|
419
|
+
if (value !== void 0) {
|
|
420
|
+
outputSuccess({ key, value });
|
|
421
|
+
} else {
|
|
422
|
+
outputError(`\u914D\u7F6E\u9879 ${key} \u4E0D\u5B58\u5728`);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
configCmd.command("list").description("\u5217\u51FA\u6240\u6709\u914D\u7F6E").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action((options) => {
|
|
427
|
+
const config = readConfig(options.config);
|
|
428
|
+
outputSuccess({
|
|
429
|
+
configPath: getConfigPath(options.config),
|
|
430
|
+
config
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
const profileCmd = new Command2("profile").description("Profile \u7BA1\u7406");
|
|
434
|
+
profileCmd.command("add <name>").description("\u6DFB\u52A0 profile").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--token <token>", "API Token").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(
|
|
435
|
+
(name, options) => {
|
|
436
|
+
const profile = {};
|
|
437
|
+
if (options.host) profile.host = options.host;
|
|
438
|
+
if (options.project) profile.project = options.project;
|
|
439
|
+
if (options.token) profile.token = options.token;
|
|
440
|
+
if (Object.keys(profile).length === 0) {
|
|
441
|
+
outputError("\u8BF7\u81F3\u5C11\u6307\u5B9A\u4E00\u4E2A\u914D\u7F6E\u9879 (--host, --project, --token)");
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
setProfile(name, profile, options.config);
|
|
445
|
+
outputSuccess({
|
|
446
|
+
message: `Profile ${name} \u5DF2\u6DFB\u52A0`,
|
|
447
|
+
name,
|
|
448
|
+
profile
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
profileCmd.command("remove <name>").description("\u5220\u9664 profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action((name, options) => {
|
|
453
|
+
const removed = removeProfile(name, options.config);
|
|
454
|
+
if (removed) {
|
|
455
|
+
outputSuccess({ message: `Profile ${name} \u5DF2\u5220\u9664`, name });
|
|
456
|
+
} else {
|
|
457
|
+
outputError(`Profile ${name} \u4E0D\u5B58\u5728`);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
profileCmd.command("list").description("\u5217\u51FA\u6240\u6709 profiles").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action((options) => {
|
|
462
|
+
const profiles = listProfiles(options.config);
|
|
463
|
+
const profileDetails = {};
|
|
464
|
+
for (const name of profiles) {
|
|
465
|
+
profileDetails[name] = getProfile(name, options.config);
|
|
466
|
+
}
|
|
467
|
+
outputSuccess({
|
|
468
|
+
profiles: profileDetails,
|
|
469
|
+
count: profiles.length
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
configCmd.addCommand(profileCmd);
|
|
473
|
+
return configCmd;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/commands/issue/index.ts
|
|
477
|
+
import { Command as Command3 } from "commander";
|
|
478
|
+
|
|
479
|
+
// src/services/issue-service.ts
|
|
480
|
+
var IssueService = class {
|
|
481
|
+
/**
|
|
482
|
+
* 获取问题详情
|
|
483
|
+
*/
|
|
484
|
+
async getIssue(token, host, project, issueId, includeChildren, includeRelations) {
|
|
485
|
+
const params = {
|
|
486
|
+
token,
|
|
487
|
+
host,
|
|
488
|
+
issue_id: issueId
|
|
489
|
+
};
|
|
490
|
+
if (project) params.project = project;
|
|
491
|
+
const includes = [];
|
|
492
|
+
if (includeChildren) includes.push("children");
|
|
493
|
+
if (includeRelations) includes.push("relations");
|
|
494
|
+
if (includes.length > 0) {
|
|
495
|
+
params.include = JSON.stringify(includes);
|
|
496
|
+
}
|
|
497
|
+
logger_default.info("\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, issueId });
|
|
498
|
+
return await apiClient.get("issue", params);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* 创建问题
|
|
502
|
+
*/
|
|
503
|
+
async createIssue(params) {
|
|
504
|
+
logger_default.info("\u521B\u5EFA\u95EE\u9898", { params });
|
|
505
|
+
return await apiClient.post("create_issue", params);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* 更新问题
|
|
509
|
+
*/
|
|
510
|
+
async updateIssue(params) {
|
|
511
|
+
logger_default.info("\u66F4\u65B0\u95EE\u9898", { params });
|
|
512
|
+
return await apiClient.post("update_issue", params);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* 获取问题附件
|
|
516
|
+
*/
|
|
517
|
+
async getIssueAttachments(token, host, project, issueId) {
|
|
518
|
+
logger_default.info("\u83B7\u53D6\u95EE\u9898\u9644\u4EF6", { host, project, issueId });
|
|
519
|
+
return await apiClient.get("get_issue_attachments", {
|
|
520
|
+
token,
|
|
521
|
+
host,
|
|
522
|
+
project,
|
|
523
|
+
issue_id: issueId
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* 获取问题字段选项
|
|
528
|
+
*/
|
|
529
|
+
async getIssueFieldOptions(token, host, project) {
|
|
530
|
+
logger_default.info("\u83B7\u53D6\u95EE\u9898\u5B57\u6BB5\u9009\u9879", { host, project });
|
|
531
|
+
return await apiClient.post("get_issue_field_options", { token, host, project });
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* 自定义查询
|
|
535
|
+
*/
|
|
536
|
+
async customQuery(token, host, project, queryId, limit, offset) {
|
|
537
|
+
const params = {
|
|
538
|
+
token,
|
|
539
|
+
host,
|
|
540
|
+
project,
|
|
541
|
+
query_id: queryId
|
|
542
|
+
};
|
|
543
|
+
if (limit !== void 0) params.limit = limit;
|
|
544
|
+
if (offset !== void 0) params.offset = offset;
|
|
545
|
+
logger_default.info("\u81EA\u5B9A\u4E49\u67E5\u8BE2", { host, project, queryId });
|
|
546
|
+
return await apiClient.post("custom_query", params);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* V6 过滤器查询
|
|
550
|
+
*/
|
|
551
|
+
async filterQueryV6(token, host, project, mode, filterParams) {
|
|
552
|
+
const params = {
|
|
553
|
+
token,
|
|
554
|
+
host,
|
|
555
|
+
project,
|
|
556
|
+
mode,
|
|
557
|
+
...filterParams
|
|
558
|
+
};
|
|
559
|
+
logger_default.info("V6 \u8FC7\u6EE4\u5668\u67E5\u8BE2", { host, project, mode });
|
|
560
|
+
return await apiClient.post("filter_query_v6", params);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* 递归获取问题及其子单
|
|
564
|
+
* @param depth 递归深度,默认 10
|
|
565
|
+
* @param currentLevel 当前层级(内部使用)
|
|
566
|
+
*/
|
|
567
|
+
async getIssueWithChildren(token, host, project, issueId, depth = 10, currentLevel2 = 0) {
|
|
568
|
+
logger_default.info("\u9012\u5F52\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, issueId, depth, currentLevel: currentLevel2 });
|
|
569
|
+
const issueResult = await this.getIssue(token, host, project, issueId, true);
|
|
570
|
+
if (!issueResult.success || !issueResult.data) {
|
|
571
|
+
return issueResult;
|
|
572
|
+
}
|
|
573
|
+
const issue = issueResult.data;
|
|
574
|
+
issue.level = currentLevel2;
|
|
575
|
+
if (currentLevel2 >= depth) {
|
|
576
|
+
return { success: true, data: issue };
|
|
577
|
+
}
|
|
578
|
+
const childrenResult = await this.getDirectChildren(token, host, project, issueId);
|
|
579
|
+
if (!childrenResult.success || !childrenResult.data) {
|
|
580
|
+
return { success: true, data: issue };
|
|
581
|
+
}
|
|
582
|
+
const childrenIds = childrenResult.data;
|
|
583
|
+
if (childrenIds.length === 0) {
|
|
584
|
+
return { success: true, data: issue };
|
|
585
|
+
}
|
|
586
|
+
const childrenPromises = childrenIds.map(
|
|
587
|
+
(childId) => this.getIssueWithChildren(token, host, project, childId, depth, currentLevel2 + 1)
|
|
588
|
+
);
|
|
589
|
+
const childrenResults = await Promise.all(childrenPromises);
|
|
590
|
+
issue.children = childrenResults.filter((r) => r.success && r.data).map((r) => r.data);
|
|
591
|
+
return { success: true, data: issue };
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* 获取直接子单的 ID 列表
|
|
595
|
+
*/
|
|
596
|
+
async getDirectChildren(token, host, project, parentId) {
|
|
597
|
+
const filters = {
|
|
598
|
+
parent_id: { operator: "=", values: [parentId.toString()] }
|
|
599
|
+
};
|
|
600
|
+
const params = {
|
|
601
|
+
token,
|
|
602
|
+
host,
|
|
603
|
+
project,
|
|
604
|
+
filter_mode: "simple",
|
|
605
|
+
filters: JSON.stringify(filters),
|
|
606
|
+
c: JSON.stringify(["id"]),
|
|
607
|
+
per_page: 200
|
|
608
|
+
};
|
|
609
|
+
logger_default.info("\u67E5\u8BE2\u76F4\u63A5\u5B50\u5355", { host, project, parentId });
|
|
610
|
+
const result = await apiClient.get("filter_query_v6", params);
|
|
611
|
+
if (result.success && result.data) {
|
|
612
|
+
const data = result.data;
|
|
613
|
+
if (data.data?.list) {
|
|
614
|
+
const ids = data.data.list.map((item) => item.id);
|
|
615
|
+
return { success: true, data: ids };
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return { success: true, data: [] };
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* 查询子任务(按根任务和负责人过滤)
|
|
622
|
+
* 使用两步查询:先过滤获取 ID 列表,再批量获取详情
|
|
623
|
+
*/
|
|
624
|
+
async queryChildren(token, host, project, rootId, assignedToId, perPage = 100) {
|
|
625
|
+
const filters = {
|
|
626
|
+
root_id: { operator: "=", values: [rootId.toString()] }
|
|
627
|
+
};
|
|
628
|
+
if (assignedToId) {
|
|
629
|
+
filters.assigned_to_id = { operator: "=", values: [assignedToId.toString()] };
|
|
630
|
+
}
|
|
631
|
+
const params = {
|
|
632
|
+
token,
|
|
633
|
+
host,
|
|
634
|
+
project,
|
|
635
|
+
filter_mode: "simple",
|
|
636
|
+
filters: JSON.stringify(filters),
|
|
637
|
+
c: JSON.stringify(["id", "subject", "status", "tracker", "estimated_hours", "done_ratio", "assigned_to", "parent"]),
|
|
638
|
+
per_page: perPage
|
|
639
|
+
};
|
|
640
|
+
logger_default.info("\u67E5\u8BE2\u5B50\u4EFB\u52A1", { host, project, rootId, assignedToId });
|
|
641
|
+
const result = await apiClient.get("filter_query_v6", params);
|
|
642
|
+
if (result.success && result.data) {
|
|
643
|
+
const data = result.data;
|
|
644
|
+
if (data.data?.list && data.data.list.length > 0) {
|
|
645
|
+
const issueIds = data.data.list.map((item) => item.id);
|
|
646
|
+
const detailedIssues = await Promise.all(
|
|
647
|
+
issueIds.map(async (id) => {
|
|
648
|
+
const issueResult = await this.getIssue(token, host, project, id);
|
|
649
|
+
if (issueResult.success && issueResult.data) {
|
|
650
|
+
return {
|
|
651
|
+
id: issueResult.data.id,
|
|
652
|
+
subject: issueResult.data.subject,
|
|
653
|
+
status: issueResult.data.status?.name,
|
|
654
|
+
tracker: issueResult.data.tracker?.name,
|
|
655
|
+
estimated_hours: issueResult.data.estimated_hours,
|
|
656
|
+
done_ratio: issueResult.data.done_ratio,
|
|
657
|
+
assigned_to: issueResult.data.assigned_to?.name,
|
|
658
|
+
parent_id: issueResult.data.parent_id
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
return null;
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
return {
|
|
665
|
+
success: true,
|
|
666
|
+
data: {
|
|
667
|
+
total: detailedIssues.filter(Boolean).length,
|
|
668
|
+
issues: detailedIssues.filter(Boolean)
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
var issueService = new IssueService();
|
|
677
|
+
|
|
678
|
+
// src/utils/url-parser.ts
|
|
679
|
+
var PM_URL_PATH_PATTERN = /https?:\/\/([^/]+\.pm\.netease\.com)\/v6\/issues\/(\d+)/;
|
|
680
|
+
var PM_URL_QUERY_PATTERN = /https?:\/\/([^/]+\.pm\.netease\.com)\/v6\/issues\?/;
|
|
681
|
+
function parsePmLink(url) {
|
|
682
|
+
const pathMatch = url.match(PM_URL_PATH_PATTERN);
|
|
683
|
+
if (pathMatch) {
|
|
684
|
+
return {
|
|
685
|
+
host: pathMatch[1],
|
|
686
|
+
issueId: pathMatch[2]
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
const queryMatch = url.match(PM_URL_QUERY_PATTERN);
|
|
690
|
+
if (queryMatch) {
|
|
691
|
+
const host = queryMatch[1];
|
|
692
|
+
const issueIdMatch = url.match(/[?&]issue_id=(\d+)/);
|
|
693
|
+
if (issueIdMatch) {
|
|
694
|
+
return {
|
|
695
|
+
host,
|
|
696
|
+
issueId: issueIdMatch[1]
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/commands/issue/index.ts
|
|
704
|
+
function createIssueCommand() {
|
|
705
|
+
const issueCmd = new Command3("issue").description("\u95EE\u9898\u7BA1\u7406");
|
|
706
|
+
issueCmd.command("get [id]").description("\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5\uFF08\u9ED8\u8BA4\u9012\u5F52\u83B7\u53D6\u5B50\u5355\uFF0C\u8F93\u51FA\u5230\u6587\u4EF6\uFF09").option("--url <url>", "PM \u94FE\u63A5").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("--depth <depth>", "\u9012\u5F52\u83B7\u53D6\u5B50\u5355\u7684\u6DF1\u5EA6\uFF080 \u8868\u793A\u4E0D\u83B7\u53D6\u5B50\u5355\uFF09", "10").option("-o, --output <path>", "\u8F93\u51FA JSON \u5230\u6307\u5B9A\u6587\u4EF6\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230 /tmp\uFF09").option("--stdout", "\u5F3A\u5236\u8F93\u51FA\u5230\u63A7\u5236\u53F0\u800C\u975E\u6587\u4EF6").option("--include-relations", "\u5305\u542B\u5173\u8054\u95EE\u9898").action(async (id, options) => {
|
|
707
|
+
let issueId;
|
|
708
|
+
let host;
|
|
709
|
+
if (options.url) {
|
|
710
|
+
const linkInfo = parsePmLink(options.url);
|
|
711
|
+
if (!linkInfo) {
|
|
712
|
+
outputError("\u65E0\u6548\u7684 PM \u94FE\u63A5\u683C\u5F0F");
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
issueId = parseInt(linkInfo.issueId, 10);
|
|
716
|
+
host = linkInfo.host;
|
|
717
|
+
} else if (id) {
|
|
718
|
+
const cleanId = id.replace(/^#/, "");
|
|
719
|
+
issueId = parseInt(cleanId, 10);
|
|
720
|
+
if (isNaN(issueId)) {
|
|
721
|
+
outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
outputError("\u8BF7\u63D0\u4F9B\u95EE\u9898 ID \u6216 --url \u53C2\u6570");
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
const creds = resolveCredentials({
|
|
729
|
+
...options,
|
|
730
|
+
host: host || options.host
|
|
731
|
+
});
|
|
732
|
+
const validation = validateCredentials(creds, ["token", "host"]);
|
|
733
|
+
if (!validation.valid) {
|
|
734
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
const depth = parseInt(options.depth, 10);
|
|
738
|
+
if (depth > 0) {
|
|
739
|
+
const result = await issueService.getIssueWithChildren(
|
|
740
|
+
creds.token,
|
|
741
|
+
creds.host,
|
|
742
|
+
creds.project || "",
|
|
743
|
+
issueId,
|
|
744
|
+
depth
|
|
745
|
+
);
|
|
746
|
+
if (result.success && result.data) {
|
|
747
|
+
smartOutput(result.data, {
|
|
748
|
+
stdout: options.stdout,
|
|
749
|
+
output: options.output,
|
|
750
|
+
command: "issue-get",
|
|
751
|
+
identifier: issueId.toString()
|
|
752
|
+
});
|
|
753
|
+
} else {
|
|
754
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u95EE\u9898\u5931\u8D25");
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
const result = await issueService.getIssue(
|
|
759
|
+
creds.token,
|
|
760
|
+
creds.host,
|
|
761
|
+
creds.project || "",
|
|
762
|
+
issueId,
|
|
763
|
+
false,
|
|
764
|
+
options.includeRelations
|
|
765
|
+
);
|
|
766
|
+
if (result.success && result.data) {
|
|
767
|
+
smartOutput(result.data, {
|
|
768
|
+
stdout: options.stdout,
|
|
769
|
+
output: options.output,
|
|
770
|
+
command: "issue-get",
|
|
771
|
+
identifier: issueId.toString()
|
|
772
|
+
});
|
|
773
|
+
} else {
|
|
774
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u95EE\u9898\u5931\u8D25");
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
issueCmd.command("create").description("\u521B\u5EFA\u95EE\u9898").requiredOption("--subject <subject>", "\u95EE\u9898\u6807\u9898").option("--description <description>", "\u95EE\u9898\u63CF\u8FF0").option("--tracker-id <id>", "\u8DDF\u8E2A\u5668 ID").option("--priority-id <id>", "\u4F18\u5148\u7EA7 ID").option("--assigned-to-id <id>", "\u6307\u6D3E\u4EBA ID").option("--parent-id <id>", "\u7236\u95EE\u9898 ID").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").option("--estimated-hours <hours>", "\u9884\u4F30\u5DE5\u65F6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
780
|
+
const creds = resolveCredentials(options);
|
|
781
|
+
const validation = validateCredentials(creds);
|
|
782
|
+
if (!validation.valid) {
|
|
783
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
786
|
+
const params = {
|
|
787
|
+
token: creds.token,
|
|
788
|
+
host: creds.host,
|
|
789
|
+
project: creds.project,
|
|
790
|
+
subject: options.subject
|
|
791
|
+
};
|
|
792
|
+
if (options.description) params.description = options.description;
|
|
793
|
+
if (options.trackerId) params.tracker_id = parseInt(options.trackerId, 10);
|
|
794
|
+
if (options.priorityId) params.priority_id = parseInt(options.priorityId, 10);
|
|
795
|
+
if (options.assignedToId) params.assigned_to_id = parseInt(options.assignedToId, 10);
|
|
796
|
+
if (options.parentId) params.parent_issue_id = parseInt(options.parentId, 10);
|
|
797
|
+
if (options.startDate) params.start_date = options.startDate;
|
|
798
|
+
if (options.dueDate) params.due_date = options.dueDate;
|
|
799
|
+
if (options.estimatedHours) params.estimated_hours = parseFloat(options.estimatedHours);
|
|
800
|
+
const result = await issueService.createIssue(params);
|
|
801
|
+
if (result.success && result.data) {
|
|
802
|
+
outputSuccess(result.data);
|
|
803
|
+
} else {
|
|
804
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u521B\u5EFA\u95EE\u9898\u5931\u8D25");
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
issueCmd.command("update <id>").description("\u66F4\u65B0\u95EE\u9898").option("--subject <subject>", "\u95EE\u9898\u6807\u9898").option("--description <description>", "\u95EE\u9898\u63CF\u8FF0").option("--status <status>", "\u72B6\u6001\u540D\u79F0 (\u5982: \u65B0\u5EFA\u3001\u5F00\u53D1\u4E2D\u3001\u5DF2\u89E3\u51B3)").option("--tracker <tracker>", "\u8DDF\u8E2A\u6807\u7B7E\u540D\u79F0 (\u5982: BUG\u3001\u4EFB\u52A1)").option("--version <version>", "\u76EE\u6807\u7248\u672C\u540D\u79F0").option("--assigned-to-mail <email>", "\u6307\u6D3E\u4EBA\u90AE\u7BB1").option("--notes <notes>", "\u66F4\u65B0\u8BF4\u660E/\u5907\u6CE8").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F (\u683C\u5F0F: YYYY-MM-DD)").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F (\u683C\u5F0F: YYYY-MM-DD)").option("--estimated-hours <hours>", "\u9884\u4F30\u5DE5\u65F6").option("--follows <email>", "\u8DDF\u8FDBQA\u90AE\u7BB1").option("--url <url>", "PM \u94FE\u63A5 (\u81EA\u52A8\u89E3\u6790 host \u548C issue_id)").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
|
|
809
|
+
let issueId;
|
|
810
|
+
let urlHost;
|
|
811
|
+
if (options.url) {
|
|
812
|
+
const linkInfo = parsePmLink(options.url);
|
|
813
|
+
if (!linkInfo) {
|
|
814
|
+
outputError("\u65E0\u6548\u7684 PM \u94FE\u63A5\u683C\u5F0F");
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
issueId = parseInt(linkInfo.issueId, 10);
|
|
818
|
+
urlHost = linkInfo.host;
|
|
819
|
+
} else {
|
|
820
|
+
issueId = parseInt(id.replace(/^#/, ""), 10);
|
|
821
|
+
if (isNaN(issueId)) {
|
|
822
|
+
outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const creds = resolveCredentials({
|
|
827
|
+
...options,
|
|
828
|
+
host: urlHost || options.host
|
|
829
|
+
});
|
|
830
|
+
const validation = validateCredentials(creds);
|
|
831
|
+
if (!validation.valid) {
|
|
832
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
const params = {
|
|
836
|
+
token: creds.token,
|
|
837
|
+
host: creds.host,
|
|
838
|
+
project: creds.project,
|
|
839
|
+
issue_id: issueId
|
|
840
|
+
};
|
|
841
|
+
if (options.subject) params.subject = options.subject;
|
|
842
|
+
if (options.description) params.description = options.description;
|
|
843
|
+
if (options.status) params.status = options.status;
|
|
844
|
+
if (options.tracker) params.tracker = options.tracker;
|
|
845
|
+
if (options.version) params.version = options.version;
|
|
846
|
+
if (options.assignedToMail) params.assigned_to_mail = options.assignedToMail;
|
|
847
|
+
if (options.notes) params.notes = options.notes;
|
|
848
|
+
if (options.startDate) params.start_date = options.startDate;
|
|
849
|
+
if (options.dueDate) params.due_date = options.dueDate;
|
|
850
|
+
if (options.estimatedHours) params.estimated_hours = parseFloat(options.estimatedHours);
|
|
851
|
+
if (options.follows) params.follows = options.follows;
|
|
852
|
+
const result = await issueService.updateIssue(params);
|
|
853
|
+
if (result.success && result.data) {
|
|
854
|
+
outputSuccess(result.data);
|
|
855
|
+
} else {
|
|
856
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u66F4\u65B0\u95EE\u9898\u5931\u8D25");
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
issueCmd.command("query").description("\u81EA\u5B9A\u4E49\u67E5\u8BE2").requiredOption("--query-id <id>", "\u67E5\u8BE2 ID").option("--limit <limit>", "\u8FD4\u56DE\u6570\u91CF\u9650\u5236").option("--offset <offset>", "\u504F\u79FB\u91CF").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
861
|
+
const creds = resolveCredentials(options);
|
|
862
|
+
const validation = validateCredentials(creds);
|
|
863
|
+
if (!validation.valid) {
|
|
864
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
const queryId = parseInt(options.queryId, 10);
|
|
868
|
+
const limit = options.limit ? parseInt(options.limit, 10) : void 0;
|
|
869
|
+
const offset = options.offset ? parseInt(options.offset, 10) : void 0;
|
|
870
|
+
const result = await issueService.customQuery(
|
|
871
|
+
creds.token,
|
|
872
|
+
creds.host,
|
|
873
|
+
creds.project,
|
|
874
|
+
queryId,
|
|
875
|
+
limit,
|
|
876
|
+
offset
|
|
877
|
+
);
|
|
878
|
+
if (result.success && result.data) {
|
|
879
|
+
outputSuccess(result.data);
|
|
880
|
+
} else {
|
|
881
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u67E5\u8BE2\u5931\u8D25");
|
|
882
|
+
process.exit(1);
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
issueCmd.command("filter").description("V6 \u8FC7\u6EE4\u5668\u67E5\u8BE2").option("--mode <mode>", "\u67E5\u8BE2\u6A21\u5F0F (normal/simple/advanced)", "normal").option("--status <status>", "\u72B6\u6001\u8FC7\u6EE4").option("--tracker <tracker>", "\u8DDF\u8E2A\u5668\u8FC7\u6EE4").option("--assigned-to <user>", "\u6307\u6D3E\u4EBA\u8FC7\u6EE4").option("--limit <limit>", "\u8FD4\u56DE\u6570\u91CF\u9650\u5236").option("--offset <offset>", "\u504F\u79FB\u91CF").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
886
|
+
const creds = resolveCredentials(options);
|
|
887
|
+
const validation = validateCredentials(creds);
|
|
888
|
+
if (!validation.valid) {
|
|
889
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
const filterParams = {};
|
|
893
|
+
if (options.status) filterParams.status = options.status;
|
|
894
|
+
if (options.tracker) filterParams.tracker = options.tracker;
|
|
895
|
+
if (options.assignedTo) filterParams.assigned_to = options.assignedTo;
|
|
896
|
+
if (options.limit) filterParams.limit = parseInt(options.limit, 10);
|
|
897
|
+
if (options.offset) filterParams.offset = parseInt(options.offset, 10);
|
|
898
|
+
const result = await issueService.filterQueryV6(
|
|
899
|
+
creds.token,
|
|
900
|
+
creds.host,
|
|
901
|
+
creds.project,
|
|
902
|
+
options.mode,
|
|
903
|
+
filterParams
|
|
904
|
+
);
|
|
905
|
+
if (result.success && result.data) {
|
|
906
|
+
outputSuccess(result.data);
|
|
907
|
+
} else {
|
|
908
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u67E5\u8BE2\u5931\u8D25");
|
|
909
|
+
process.exit(1);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
issueCmd.command("field-options").description("\u83B7\u53D6\u95EE\u9898\u5B57\u6BB5\u9009\u9879").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
913
|
+
const creds = resolveCredentials(options);
|
|
914
|
+
const validation = validateCredentials(creds);
|
|
915
|
+
if (!validation.valid) {
|
|
916
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
const result = await issueService.getIssueFieldOptions(
|
|
920
|
+
creds.token,
|
|
921
|
+
creds.host,
|
|
922
|
+
creds.project
|
|
923
|
+
);
|
|
924
|
+
if (result.success) {
|
|
925
|
+
outputSuccess(result.data);
|
|
926
|
+
} else {
|
|
927
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u5B57\u6BB5\u9009\u9879\u5931\u8D25");
|
|
928
|
+
process.exit(1);
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
issueCmd.command("children <id>").description("\u67E5\u8BE2\u5B50\u4EFB\u52A1\uFF08\u652F\u6301\u6309\u8D1F\u8D23\u4EBA\u8FC7\u6EE4\uFF09").option("--url <url>", "PM \u94FE\u63A5").option("--assigned-to <name>", "\u8D1F\u8D23\u4EBA\u59D3\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF09").option("--assigned-to-id <id>", "\u8D1F\u8D23\u4EBA ID").option("--limit <limit>", "\u8FD4\u56DE\u6570\u91CF\u9650\u5236", "100").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
|
|
932
|
+
let issueId;
|
|
933
|
+
let host;
|
|
934
|
+
if (options.url) {
|
|
935
|
+
const linkInfo = parsePmLink(options.url);
|
|
936
|
+
if (!linkInfo) {
|
|
937
|
+
outputError("\u65E0\u6548\u7684 PM \u94FE\u63A5\u683C\u5F0F");
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
issueId = parseInt(linkInfo.issueId, 10);
|
|
941
|
+
host = linkInfo.host;
|
|
942
|
+
} else {
|
|
943
|
+
const cleanId = id.replace(/^#/, "");
|
|
944
|
+
issueId = parseInt(cleanId, 10);
|
|
945
|
+
if (isNaN(issueId)) {
|
|
946
|
+
outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
|
|
947
|
+
process.exit(1);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const creds = resolveCredentials({
|
|
951
|
+
...options,
|
|
952
|
+
host: host || options.host
|
|
953
|
+
});
|
|
954
|
+
const validation = validateCredentials(creds);
|
|
955
|
+
if (!validation.valid) {
|
|
956
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
957
|
+
process.exit(1);
|
|
958
|
+
}
|
|
959
|
+
let assignedToId;
|
|
960
|
+
if (options.assignedTo && !options.assignedToId) {
|
|
961
|
+
const usersResult = await userService.getProjectUsers(
|
|
962
|
+
creds.token,
|
|
963
|
+
creds.host,
|
|
964
|
+
creds.project
|
|
965
|
+
);
|
|
966
|
+
if (usersResult.success && usersResult.data) {
|
|
967
|
+
const users = usersResult.data;
|
|
968
|
+
const searchName = options.assignedTo.toLowerCase();
|
|
969
|
+
const matchedUser = users.find(
|
|
970
|
+
(u) => u.name?.toLowerCase().includes(searchName) || u.firstname?.toLowerCase().includes(searchName) || u.lastname?.toLowerCase().includes(searchName)
|
|
971
|
+
);
|
|
972
|
+
if (matchedUser) {
|
|
973
|
+
assignedToId = matchedUser.id;
|
|
974
|
+
} else {
|
|
975
|
+
outputError(`\u672A\u627E\u5230\u5339\u914D\u7684\u7528\u6237: ${options.assignedTo}`);
|
|
976
|
+
process.exit(1);
|
|
977
|
+
}
|
|
978
|
+
} else {
|
|
979
|
+
outputError("\u83B7\u53D6\u7528\u6237\u5217\u8868\u5931\u8D25");
|
|
980
|
+
process.exit(1);
|
|
981
|
+
}
|
|
982
|
+
} else if (options.assignedToId) {
|
|
983
|
+
assignedToId = parseInt(options.assignedToId, 10);
|
|
984
|
+
}
|
|
985
|
+
const perPage = parseInt(options.limit, 10) || 100;
|
|
986
|
+
const result = await issueService.queryChildren(
|
|
987
|
+
creds.token,
|
|
988
|
+
creds.host,
|
|
989
|
+
creds.project,
|
|
990
|
+
issueId,
|
|
991
|
+
assignedToId,
|
|
992
|
+
perPage
|
|
993
|
+
);
|
|
994
|
+
if (result.success && result.data) {
|
|
995
|
+
const data = result.data;
|
|
996
|
+
if (data.data?.list) {
|
|
997
|
+
outputSuccess({
|
|
998
|
+
total: data.data.list.length,
|
|
999
|
+
issues: data.data.list
|
|
1000
|
+
});
|
|
1001
|
+
} else {
|
|
1002
|
+
outputSuccess(result.data);
|
|
1003
|
+
}
|
|
1004
|
+
} else {
|
|
1005
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u67E5\u8BE2\u5B50\u4EFB\u52A1\u5931\u8D25");
|
|
1006
|
+
process.exit(1);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
return issueCmd;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/commands/time/index.ts
|
|
1013
|
+
import { Command as Command4 } from "commander";
|
|
1014
|
+
|
|
1015
|
+
// src/services/time-entry-service.ts
|
|
1016
|
+
var TimeEntryService = class {
|
|
1017
|
+
/**
|
|
1018
|
+
* 查询工时条目
|
|
1019
|
+
*/
|
|
1020
|
+
async queryTimeEntries(params) {
|
|
1021
|
+
const requestParams = {
|
|
1022
|
+
token: params.token,
|
|
1023
|
+
host: params.host,
|
|
1024
|
+
project: params.project
|
|
1025
|
+
};
|
|
1026
|
+
if (params.from_date) requestParams.from_date = params.from_date;
|
|
1027
|
+
if (params.to_date) requestParams.to_date = params.to_date;
|
|
1028
|
+
if (params.user_id) requestParams.user_id = params.user_id;
|
|
1029
|
+
if (params.activity_id) requestParams.activity_id = params.activity_id;
|
|
1030
|
+
if (params.member_of_group_id) requestParams.member_of_group_id = params.member_of_group_id;
|
|
1031
|
+
if (params.tracker_id) requestParams.tracker_id = params.tracker_id;
|
|
1032
|
+
if (params.version_id) requestParams.version_id = params.version_id;
|
|
1033
|
+
if (params.offset !== void 0) requestParams.offset = params.offset;
|
|
1034
|
+
if (params.limit !== void 0) requestParams.limit = params.limit;
|
|
1035
|
+
logger_default.info("\u67E5\u8BE2\u5DE5\u65F6\u6761\u76EE", { host: params.host, project: params.project });
|
|
1036
|
+
return await apiClient.get("query_time_entries", requestParams);
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* 获取工时条目选项
|
|
1040
|
+
*/
|
|
1041
|
+
async getTimeEntryOptions(token, host, project) {
|
|
1042
|
+
logger_default.info("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879", { host, project });
|
|
1043
|
+
return await apiClient.post("time_entry_options", { token, host, project });
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* 创建工时条目
|
|
1047
|
+
*/
|
|
1048
|
+
async createTimeEntry(params) {
|
|
1049
|
+
logger_default.info("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE", { params });
|
|
1050
|
+
return await apiClient.post("save_time_entry", params);
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* 更新工时条目
|
|
1054
|
+
*/
|
|
1055
|
+
async updateTimeEntry(params) {
|
|
1056
|
+
logger_default.info("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE", { params });
|
|
1057
|
+
return await apiClient.post("save_time_entry", params);
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* 删除工时条目
|
|
1061
|
+
*/
|
|
1062
|
+
async deleteTimeEntry(token, host, project, timeEntryId) {
|
|
1063
|
+
logger_default.info("\u5220\u9664\u5DE5\u65F6\u6761\u76EE", { host, project, timeEntryId });
|
|
1064
|
+
return await apiClient.get("delete_time_entry", {
|
|
1065
|
+
token,
|
|
1066
|
+
host,
|
|
1067
|
+
project,
|
|
1068
|
+
time_entry_id: timeEntryId
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
var timeEntryService = new TimeEntryService();
|
|
1073
|
+
|
|
1074
|
+
// src/commands/time/index.ts
|
|
1075
|
+
function getWeekDateRange(week) {
|
|
1076
|
+
const now = /* @__PURE__ */ new Date();
|
|
1077
|
+
let offset = 0;
|
|
1078
|
+
if (week === "current") {
|
|
1079
|
+
offset = 0;
|
|
1080
|
+
} else if (week === "last") {
|
|
1081
|
+
offset = -1;
|
|
1082
|
+
} else {
|
|
1083
|
+
offset = parseInt(week, 10) || 0;
|
|
1084
|
+
}
|
|
1085
|
+
const dayOfWeek = now.getDay();
|
|
1086
|
+
const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
|
|
1087
|
+
const monday = new Date(now);
|
|
1088
|
+
monday.setDate(now.getDate() + diffToMonday + offset * 7);
|
|
1089
|
+
monday.setHours(0, 0, 0, 0);
|
|
1090
|
+
const friday = new Date(monday);
|
|
1091
|
+
friday.setDate(monday.getDate() + 4);
|
|
1092
|
+
const formatDate = (d) => {
|
|
1093
|
+
const year = d.getFullYear();
|
|
1094
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
1095
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
1096
|
+
return `${year}-${month}-${day}`;
|
|
1097
|
+
};
|
|
1098
|
+
const weekLabel = offset === 0 ? "\u672C\u5468" : offset === -1 ? "\u4E0A\u5468" : `${Math.abs(offset)}\u5468\u524D`;
|
|
1099
|
+
return {
|
|
1100
|
+
from: formatDate(monday),
|
|
1101
|
+
to: formatDate(friday),
|
|
1102
|
+
weekLabel
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function createTimeCommand() {
|
|
1106
|
+
const timeCmd = new Command4("time").description("\u5DE5\u65F6\u7BA1\u7406");
|
|
1107
|
+
timeCmd.command("list").description("\u67E5\u8BE2\u5DE5\u65F6\u6761\u76EE").option("--from <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)").option("--to <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)").option("--user-id <id>", "\u7528\u6237 ID").option("--activity-id <id>", "\u6D3B\u52A8\u7C7B\u578B ID").option("--limit <limit>", "\u8FD4\u56DE\u6570\u91CF\u9650\u5236").option("--offset <offset>", "\u504F\u79FB\u91CF").option("--all-projects", "\u67E5\u8BE2\u6240\u6709\u9879\u76EE\u7684\u5DE5\u65F6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1108
|
+
const creds = resolveCredentials(options);
|
|
1109
|
+
const requiredFields = options.allProjects ? ["token", "host"] : ["token", "host", "project"];
|
|
1110
|
+
const validation = validateCredentials(creds, requiredFields);
|
|
1111
|
+
if (!validation.valid) {
|
|
1112
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1113
|
+
process.exit(1);
|
|
1114
|
+
}
|
|
1115
|
+
if (options.allProjects) {
|
|
1116
|
+
logger_default.info("\u67E5\u8BE2\u6240\u6709\u9879\u76EE\u7684\u5DE5\u65F6...");
|
|
1117
|
+
const projectsResult = await userService.getProjects(creds.token, creds.host);
|
|
1118
|
+
if (!projectsResult.success || !projectsResult.data) {
|
|
1119
|
+
outputError(projectsResult.message || projectsResult.msg || projectsResult.api_error_msg || "\u83B7\u53D6\u9879\u76EE\u5217\u8868\u5931\u8D25");
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
const projects = projectsResult.data;
|
|
1123
|
+
const projectNames = Object.values(projects).map((p) => p.name);
|
|
1124
|
+
logger_default.info(`\u627E\u5230 ${projectNames.length} \u4E2A\u9879\u76EE`);
|
|
1125
|
+
const allTimeEntries = [];
|
|
1126
|
+
let totalCount = 0;
|
|
1127
|
+
const projectSummary = [];
|
|
1128
|
+
for (const projectName of projectNames) {
|
|
1129
|
+
const result = await timeEntryService.queryTimeEntries({
|
|
1130
|
+
token: creds.token,
|
|
1131
|
+
host: creds.host,
|
|
1132
|
+
project: projectName,
|
|
1133
|
+
from_date: options.from,
|
|
1134
|
+
to_date: options.to,
|
|
1135
|
+
user_id: options.userId ? parseInt(options.userId, 10) : void 0,
|
|
1136
|
+
activity_id: options.activityId ? parseInt(options.activityId, 10) : void 0,
|
|
1137
|
+
limit: options.limit ? parseInt(options.limit, 10) : 100,
|
|
1138
|
+
offset: options.offset ? parseInt(options.offset, 10) : void 0
|
|
1139
|
+
});
|
|
1140
|
+
if (result.success && result.data) {
|
|
1141
|
+
const data = result.data;
|
|
1142
|
+
if (data.time_entries && data.time_entries.length > 0) {
|
|
1143
|
+
allTimeEntries.push(...data.time_entries);
|
|
1144
|
+
const projectHours = data.time_entries.reduce((sum, e) => sum + (e.hours || 0), 0);
|
|
1145
|
+
projectSummary.push({
|
|
1146
|
+
project: projectName,
|
|
1147
|
+
count: data.time_entries.length,
|
|
1148
|
+
hours: projectHours
|
|
1149
|
+
});
|
|
1150
|
+
totalCount += data.total_count || data.time_entries.length;
|
|
1151
|
+
logger_default.info(`${projectName}: ${data.time_entries.length} \u6761\u5DE5\u65F6\u8BB0\u5F55`);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
const totalHours = allTimeEntries.reduce((sum, e) => sum + (e.hours || 0), 0);
|
|
1156
|
+
outputSuccess({
|
|
1157
|
+
total_count: totalCount,
|
|
1158
|
+
total_hours: Math.round(totalHours * 100) / 100,
|
|
1159
|
+
project_summary: projectSummary,
|
|
1160
|
+
time_entries: allTimeEntries
|
|
1161
|
+
});
|
|
1162
|
+
} else {
|
|
1163
|
+
const result = await timeEntryService.queryTimeEntries({
|
|
1164
|
+
token: creds.token,
|
|
1165
|
+
host: creds.host,
|
|
1166
|
+
project: creds.project,
|
|
1167
|
+
from_date: options.from,
|
|
1168
|
+
to_date: options.to,
|
|
1169
|
+
user_id: options.userId ? parseInt(options.userId, 10) : void 0,
|
|
1170
|
+
activity_id: options.activityId ? parseInt(options.activityId, 10) : void 0,
|
|
1171
|
+
limit: options.limit ? parseInt(options.limit, 10) : void 0,
|
|
1172
|
+
offset: options.offset ? parseInt(options.offset, 10) : void 0
|
|
1173
|
+
});
|
|
1174
|
+
if (result.success && result.data) {
|
|
1175
|
+
outputSuccess(result.data);
|
|
1176
|
+
} else {
|
|
1177
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u67E5\u8BE2\u5DE5\u65F6\u5931\u8D25");
|
|
1178
|
+
process.exit(1);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
timeCmd.command("create").description("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").option("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID").option("--date <date>", "\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u4ECA\u5929").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1183
|
+
const creds = resolveCredentials(options);
|
|
1184
|
+
const validation = validateCredentials(creds);
|
|
1185
|
+
if (!validation.valid) {
|
|
1186
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1187
|
+
process.exit(1);
|
|
1188
|
+
}
|
|
1189
|
+
const issueId = parseInt(options.issue.replace(/^#/, ""), 10);
|
|
1190
|
+
if (isNaN(issueId)) {
|
|
1191
|
+
outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
|
|
1192
|
+
process.exit(1);
|
|
1193
|
+
}
|
|
1194
|
+
const days = parseFloat(options.days);
|
|
1195
|
+
if (isNaN(days) || days <= 0) {
|
|
1196
|
+
outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6570");
|
|
1197
|
+
process.exit(1);
|
|
1198
|
+
}
|
|
1199
|
+
const params = {
|
|
1200
|
+
token: creds.token,
|
|
1201
|
+
host: creds.host,
|
|
1202
|
+
project: creds.project,
|
|
1203
|
+
issue_id: issueId,
|
|
1204
|
+
hours: days,
|
|
1205
|
+
// API 参数名为 hours,但单位是天
|
|
1206
|
+
spent_on: options.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1207
|
+
};
|
|
1208
|
+
if (options.activity) params.activity_id = parseInt(options.activity, 10);
|
|
1209
|
+
if (options.comments) params.comments = options.comments;
|
|
1210
|
+
const result = await timeEntryService.createTimeEntry(params);
|
|
1211
|
+
if (result.success && result.data) {
|
|
1212
|
+
outputSuccess(result.data);
|
|
1213
|
+
} else {
|
|
1214
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u521B\u5EFA\u5DE5\u65F6\u5931\u8D25");
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
timeCmd.command("update <id>").description("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE").option("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").option("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID").option("--date <date>", "\u65E5\u671F (YYYY-MM-DD)").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
|
|
1219
|
+
const creds = resolveCredentials(options);
|
|
1220
|
+
const validation = validateCredentials(creds);
|
|
1221
|
+
if (!validation.valid) {
|
|
1222
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
const timeEntryId = parseInt(id, 10);
|
|
1226
|
+
if (isNaN(timeEntryId)) {
|
|
1227
|
+
outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6761\u76EE ID");
|
|
1228
|
+
process.exit(1);
|
|
1229
|
+
}
|
|
1230
|
+
const params = {
|
|
1231
|
+
token: creds.token,
|
|
1232
|
+
host: creds.host,
|
|
1233
|
+
project: creds.project,
|
|
1234
|
+
time_entry_id: timeEntryId
|
|
1235
|
+
};
|
|
1236
|
+
if (options.days) params.hours = parseFloat(options.days);
|
|
1237
|
+
if (options.activity) params.activity_id = parseInt(options.activity, 10);
|
|
1238
|
+
if (options.date) params.spent_on = options.date;
|
|
1239
|
+
if (options.comments) params.comments = options.comments;
|
|
1240
|
+
const result = await timeEntryService.updateTimeEntry(params);
|
|
1241
|
+
if (result.success && result.data) {
|
|
1242
|
+
outputSuccess(result.data);
|
|
1243
|
+
} else {
|
|
1244
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u66F4\u65B0\u5DE5\u65F6\u5931\u8D25");
|
|
1245
|
+
process.exit(1);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
timeCmd.command("delete <id>").description("\u5220\u9664\u5DE5\u65F6\u6761\u76EE").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
|
|
1249
|
+
const creds = resolveCredentials(options);
|
|
1250
|
+
const validation = validateCredentials(creds);
|
|
1251
|
+
if (!validation.valid) {
|
|
1252
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1253
|
+
process.exit(1);
|
|
1254
|
+
}
|
|
1255
|
+
const timeEntryId = parseInt(id, 10);
|
|
1256
|
+
if (isNaN(timeEntryId)) {
|
|
1257
|
+
outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6761\u76EE ID");
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
const result = await timeEntryService.deleteTimeEntry(
|
|
1261
|
+
creds.token,
|
|
1262
|
+
creds.host,
|
|
1263
|
+
creds.project,
|
|
1264
|
+
timeEntryId
|
|
1265
|
+
);
|
|
1266
|
+
if (result.success) {
|
|
1267
|
+
outputSuccess({ message: "\u5DE5\u65F6\u6761\u76EE\u5DF2\u5220\u9664", id: timeEntryId });
|
|
1268
|
+
} else {
|
|
1269
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u5220\u9664\u5DE5\u65F6\u5931\u8D25");
|
|
1270
|
+
process.exit(1);
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
timeCmd.command("options").description("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1274
|
+
const creds = resolveCredentials(options);
|
|
1275
|
+
const validation = validateCredentials(creds);
|
|
1276
|
+
if (!validation.valid) {
|
|
1277
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
const result = await timeEntryService.getTimeEntryOptions(
|
|
1281
|
+
creds.token,
|
|
1282
|
+
creds.host,
|
|
1283
|
+
creds.project
|
|
1284
|
+
);
|
|
1285
|
+
if (result.success) {
|
|
1286
|
+
outputSuccess(result.data);
|
|
1287
|
+
} else {
|
|
1288
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u9009\u9879\u5931\u8D25");
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
timeCmd.command("summary").description("\u5DE5\u65F6\u7EDF\u8BA1\u6C47\u603B\uFF08\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u6BB5\u7684\u5DE5\u65F6\u5E76\u8BA1\u7B97\u7F3A\u53E3\uFF09").option("--week <week>", "\u5468\u9009\u62E9: current (\u672C\u5468), last (\u4E0A\u5468), \u6216\u6570\u5B57\u504F\u79FB", "current").option("--from <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--to <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--target <days>", "\u76EE\u6807\u5DE5\u65F6\uFF08\u5929\uFF09\uFF0C\u9ED8\u8BA4 5", "5").option("--user-id <id>", "\u7528\u6237 ID\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 user-id\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1293
|
+
const creds = resolveCredentials(options);
|
|
1294
|
+
const validation = validateCredentials(creds, ["token", "host"]);
|
|
1295
|
+
if (!validation.valid) {
|
|
1296
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1297
|
+
process.exit(1);
|
|
1298
|
+
}
|
|
1299
|
+
let fromDate;
|
|
1300
|
+
let toDate;
|
|
1301
|
+
let periodLabel;
|
|
1302
|
+
if (options.from && options.to) {
|
|
1303
|
+
fromDate = options.from;
|
|
1304
|
+
toDate = options.to;
|
|
1305
|
+
periodLabel = `${fromDate} ~ ${toDate}`;
|
|
1306
|
+
} else {
|
|
1307
|
+
const weekRange = getWeekDateRange(options.week);
|
|
1308
|
+
fromDate = weekRange.from;
|
|
1309
|
+
toDate = weekRange.to;
|
|
1310
|
+
periodLabel = `${weekRange.weekLabel} (${fromDate} ~ ${toDate})`;
|
|
1311
|
+
}
|
|
1312
|
+
let userId;
|
|
1313
|
+
if (options.userId) {
|
|
1314
|
+
userId = parseInt(options.userId, 10);
|
|
1315
|
+
} else {
|
|
1316
|
+
const configUserId = getConfigValue("userId", options.config);
|
|
1317
|
+
if (configUserId) {
|
|
1318
|
+
userId = parseInt(configUserId, 10);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
logger_default.info(`\u67E5\u8BE2\u5DE5\u65F6\u7EDF\u8BA1: ${periodLabel}`);
|
|
1322
|
+
if (userId) {
|
|
1323
|
+
logger_default.info(`\u7528\u6237 ID: ${userId}`);
|
|
1324
|
+
}
|
|
1325
|
+
const projectsResult = await userService.getProjects(creds.token, creds.host);
|
|
1326
|
+
if (!projectsResult.success || !projectsResult.data) {
|
|
1327
|
+
outputError(projectsResult.message || projectsResult.msg || projectsResult.api_error_msg || "\u83B7\u53D6\u9879\u76EE\u5217\u8868\u5931\u8D25");
|
|
1328
|
+
process.exit(1);
|
|
1329
|
+
}
|
|
1330
|
+
const projects = projectsResult.data;
|
|
1331
|
+
const projectNames = Object.values(projects).map((p) => p.name);
|
|
1332
|
+
logger_default.info(`\u67E5\u8BE2 ${projectNames.length} \u4E2A\u9879\u76EE...`);
|
|
1333
|
+
const projectSummary = [];
|
|
1334
|
+
let totalHours = 0;
|
|
1335
|
+
for (const projectName of projectNames) {
|
|
1336
|
+
const result = await timeEntryService.queryTimeEntries({
|
|
1337
|
+
token: creds.token,
|
|
1338
|
+
host: creds.host,
|
|
1339
|
+
project: projectName,
|
|
1340
|
+
from_date: fromDate,
|
|
1341
|
+
to_date: toDate,
|
|
1342
|
+
user_id: userId,
|
|
1343
|
+
limit: 1e3
|
|
1344
|
+
});
|
|
1345
|
+
if (result.success && result.data) {
|
|
1346
|
+
const data = result.data;
|
|
1347
|
+
if (data.time_entries && data.time_entries.length > 0) {
|
|
1348
|
+
const projectHours = data.time_entries.reduce((sum, e) => sum + (e.hours || 0), 0);
|
|
1349
|
+
if (projectHours > 0) {
|
|
1350
|
+
projectSummary.push({
|
|
1351
|
+
project: projectName,
|
|
1352
|
+
hours: Math.round(projectHours * 100) / 100
|
|
1353
|
+
});
|
|
1354
|
+
totalHours += projectHours;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
const targetHours = parseFloat(options.target) || 5;
|
|
1360
|
+
const remainingHours = Math.max(0, targetHours - totalHours);
|
|
1361
|
+
totalHours = Math.round(totalHours * 100) / 100;
|
|
1362
|
+
outputSuccess({
|
|
1363
|
+
period: periodLabel,
|
|
1364
|
+
userId: userId || "\u672A\u6307\u5B9A",
|
|
1365
|
+
totalHours,
|
|
1366
|
+
targetHours,
|
|
1367
|
+
remainingHours: Math.round(remainingHours * 100) / 100,
|
|
1368
|
+
isFilled: remainingHours === 0,
|
|
1369
|
+
projectBreakdown: projectSummary.sort((a, b) => b.hours - a.hours)
|
|
1370
|
+
});
|
|
1371
|
+
});
|
|
1372
|
+
return timeCmd;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// src/commands/project/index.ts
|
|
1376
|
+
import { Command as Command5 } from "commander";
|
|
1377
|
+
function createProjectCommand() {
|
|
1378
|
+
const projectCmd = new Command5("project").description("\u9879\u76EE\u7BA1\u7406");
|
|
1379
|
+
projectCmd.command("list").description("\u83B7\u53D6\u9879\u76EE\u5217\u8868").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1380
|
+
const creds = resolveCredentials(options);
|
|
1381
|
+
const validation = validateCredentials(creds, ["token", "host"]);
|
|
1382
|
+
if (!validation.valid) {
|
|
1383
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1384
|
+
process.exit(1);
|
|
1385
|
+
}
|
|
1386
|
+
const result = await userService.getProjects(creds.token, creds.host);
|
|
1387
|
+
if (result.success && result.data) {
|
|
1388
|
+
outputSuccess(result.data);
|
|
1389
|
+
} else {
|
|
1390
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u9879\u76EE\u5217\u8868\u5931\u8D25");
|
|
1391
|
+
process.exit(1);
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
projectCmd.command("users").description("\u83B7\u53D6\u9879\u76EE\u7528\u6237\u5217\u8868").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1395
|
+
const creds = resolveCredentials(options);
|
|
1396
|
+
const validation = validateCredentials(creds);
|
|
1397
|
+
if (!validation.valid) {
|
|
1398
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
const result = await userService.getProjectUsers(
|
|
1402
|
+
creds.token,
|
|
1403
|
+
creds.host,
|
|
1404
|
+
creds.project
|
|
1405
|
+
);
|
|
1406
|
+
if (result.success && result.data) {
|
|
1407
|
+
outputSuccess(result.data);
|
|
1408
|
+
} else {
|
|
1409
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u7528\u6237\u5217\u8868\u5931\u8D25");
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
projectCmd.command("info").description("\u83B7\u53D6\u4E3B\u673A\u4FE1\u606F").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1414
|
+
const creds = resolveCredentials(options);
|
|
1415
|
+
const validation = validateCredentials(creds, ["token", "host"]);
|
|
1416
|
+
if (!validation.valid) {
|
|
1417
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
const result = await userService.getHostInfo(creds.token, creds.host);
|
|
1421
|
+
if (result.success) {
|
|
1422
|
+
outputSuccess(result.data);
|
|
1423
|
+
} else {
|
|
1424
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u4E3B\u673A\u4FE1\u606F\u5931\u8D25");
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
return projectCmd;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/index.ts
|
|
1432
|
+
var program = new Command6();
|
|
1433
|
+
program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase) \u547D\u4EE4\u884C\u5DE5\u5177").version("0.1.0").option("--verbose", "\u8BE6\u7EC6\u8F93\u51FA").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F").hook("preAction", (thisCommand) => {
|
|
1434
|
+
const opts = thisCommand.opts();
|
|
1435
|
+
if (opts.debug) {
|
|
1436
|
+
logger_default.setLevel("debug");
|
|
1437
|
+
} else if (opts.verbose) {
|
|
1438
|
+
logger_default.setLevel("info");
|
|
1439
|
+
} else {
|
|
1440
|
+
logger_default.setLevel("silent");
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
program.addCommand(createTestCommand());
|
|
1444
|
+
program.addCommand(createConfigCommand());
|
|
1445
|
+
program.addCommand(createIssueCommand());
|
|
1446
|
+
program.addCommand(createTimeCommand());
|
|
1447
|
+
program.addCommand(createProjectCommand());
|
|
1448
|
+
program.parse();
|
|
1449
|
+
//# sourceMappingURL=index.js.map
|