@hangox/pm-cli 0.2.3 → 0.2.5

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.js CHANGED
@@ -1148,10 +1148,183 @@ function parsePmLink(url) {
1148
1148
  return null;
1149
1149
  }
1150
1150
 
1151
+ // src/utils/issue-exporter.ts
1152
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, statSync } from "fs";
1153
+ import { join as join2 } from "path";
1154
+
1155
+ // src/utils/preset-extractor.ts
1156
+ var SUMMARY_CUSTOM_FIELD_IDS = [84];
1157
+ var STANDARD_CUSTOM_FIELD_IDS = [
1158
+ 84,
1159
+ // 预计提测时间
1160
+ 286,
1161
+ // 需求大类
1162
+ 186,
1163
+ // 业务目标
1164
+ 77,
1165
+ // 需求来源
1166
+ 274,
1167
+ // 开发结束时间
1168
+ 19,
1169
+ // 跟进QA
1170
+ 86,
1171
+ // 需求延误
1172
+ 87
1173
+ // 需求变更
1174
+ ];
1175
+ function extractSummaryFields(issue) {
1176
+ const rawIssue = issue;
1177
+ const rawStatus = rawIssue.status;
1178
+ const status = rawStatus ? { id: rawStatus.id ?? 0, name: rawStatus.name ?? "" } : { id: 0, name: "" };
1179
+ const rawAssignedTo = rawIssue.assigned_to;
1180
+ const assigned_to = rawAssignedTo ? { id: rawAssignedTo.id ?? 0, name: rawAssignedTo.name ?? "" } : void 0;
1181
+ const rawCustomFields = rawIssue.custom_fields;
1182
+ const custom_fields = (rawCustomFields || []).filter((cf) => SUMMARY_CUSTOM_FIELD_IDS.includes(cf.id)).map((cf) => ({ id: cf.id, name: cf.name, value: cf.value }));
1183
+ return {
1184
+ id: issue.id,
1185
+ subject: issue.subject,
1186
+ status,
1187
+ assigned_to,
1188
+ estimated_hours: issue.estimated_hours,
1189
+ spent_hours: issue.spent_hours,
1190
+ done_ratio: issue.done_ratio,
1191
+ custom_fields
1192
+ };
1193
+ }
1194
+ function extractStandardFields(issue) {
1195
+ const rawIssue = issue;
1196
+ const rawStatus = rawIssue.status;
1197
+ const status = rawStatus ? { id: rawStatus.id ?? 0, name: rawStatus.name ?? "", is_closed: rawStatus.is_closed } : { id: 0, name: "" };
1198
+ const rawAssignedTo = rawIssue.assigned_to;
1199
+ const assigned_to = rawAssignedTo ? { id: rawAssignedTo.id ?? 0, name: rawAssignedTo.name ?? "", mail: rawAssignedTo.mail } : void 0;
1200
+ const rawPriority = rawIssue.priority;
1201
+ const priority = rawPriority ? { id: rawPriority.id ?? 0, name: rawPriority.name ?? "" } : void 0;
1202
+ const rawTracker = rawIssue.tracker;
1203
+ const tracker = rawTracker ? { id: rawTracker.id ?? 0, name: rawTracker.name ?? "" } : void 0;
1204
+ const rawProject = rawIssue.project;
1205
+ const project = rawProject ? { id: rawProject.id ?? 0, name: rawProject.name ?? "" } : void 0;
1206
+ const rawCustomFields = rawIssue.custom_fields;
1207
+ const custom_fields = (rawCustomFields || []).filter((cf) => STANDARD_CUSTOM_FIELD_IDS.includes(cf.id)).map((cf) => ({ id: cf.id, name: cf.name, value: cf.value }));
1208
+ return {
1209
+ id: issue.id,
1210
+ subject: issue.subject,
1211
+ status,
1212
+ assigned_to,
1213
+ estimated_hours: issue.estimated_hours,
1214
+ spent_hours: issue.spent_hours,
1215
+ done_ratio: issue.done_ratio,
1216
+ priority,
1217
+ tracker,
1218
+ project,
1219
+ created_on: issue.created_on,
1220
+ updated_on: issue.updated_on,
1221
+ custom_fields
1222
+ };
1223
+ }
1224
+ function extractCompleteFields(issue) {
1225
+ const rawIssue = issue;
1226
+ const result = {};
1227
+ for (const key of Object.keys(rawIssue)) {
1228
+ if (key !== "children") {
1229
+ result[key] = rawIssue[key];
1230
+ }
1231
+ }
1232
+ return result;
1233
+ }
1234
+ function generateSummaryTree(issue, currentDepth = 0, maxDepth = -1) {
1235
+ const node = extractSummaryFields(issue);
1236
+ if (maxDepth !== -1 && currentDepth >= maxDepth) {
1237
+ node.children = [];
1238
+ return node;
1239
+ }
1240
+ if (issue.children && issue.children.length > 0) {
1241
+ node.children = issue.children.map(
1242
+ (child) => generateSummaryTree(child, currentDepth + 1, maxDepth)
1243
+ );
1244
+ } else {
1245
+ node.children = [];
1246
+ }
1247
+ return node;
1248
+ }
1249
+ function flattenIssues(issue, result = []) {
1250
+ result.push(issue);
1251
+ if (issue.children) {
1252
+ for (const child of issue.children) {
1253
+ flattenIssues(child, result);
1254
+ }
1255
+ }
1256
+ return result;
1257
+ }
1258
+
1259
+ // src/utils/issue-exporter.ts
1260
+ function generateExportDir(command, identifier) {
1261
+ const now = /* @__PURE__ */ new Date();
1262
+ 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");
1263
+ return `/tmp/pm-cli_${command}_${identifier}_${timestamp}`;
1264
+ }
1265
+ async function exportIssuePresets(issue, exportDir, options = {}) {
1266
+ const { depth = -1, pretty = false } = options;
1267
+ const indent = pretty ? 2 : 0;
1268
+ mkdirSync2(exportDir, { recursive: true });
1269
+ mkdirSync2(join2(exportDir, "standard"), { recursive: true });
1270
+ mkdirSync2(join2(exportDir, "complete"), { recursive: true });
1271
+ const resultFiles = [];
1272
+ const summaryTree = generateSummaryTree(issue, 0, depth);
1273
+ const summaryPath = join2(exportDir, "tree.summary.json");
1274
+ writeFileSync3(summaryPath, JSON.stringify(summaryTree, null, indent), "utf-8");
1275
+ const summarySize = statSync(summaryPath).size;
1276
+ resultFiles.push({ path: "tree.summary.json", size: summarySize, type: "tree" });
1277
+ const allIssues = flattenIssues(issue);
1278
+ for (const iss of allIssues) {
1279
+ const standardData = extractStandardFields(iss);
1280
+ const standardPath = join2(exportDir, "standard", `${iss.id}.json`);
1281
+ writeFileSync3(standardPath, JSON.stringify(standardData, null, indent), "utf-8");
1282
+ const standardSize = statSync(standardPath).size;
1283
+ resultFiles.push({ path: `standard/${iss.id}.json`, size: standardSize, type: "standard" });
1284
+ }
1285
+ for (const iss of allIssues) {
1286
+ const completeData = extractCompleteFields(iss);
1287
+ const completePath = join2(exportDir, "complete", `${iss.id}.json`);
1288
+ writeFileSync3(completePath, JSON.stringify(completeData, null, indent), "utf-8");
1289
+ const completeSize = statSync(completePath).size;
1290
+ resultFiles.push({ path: `complete/${iss.id}.json`, size: completeSize, type: "complete" });
1291
+ }
1292
+ const totalSize = resultFiles.reduce((sum, f) => sum + f.size, 0);
1293
+ return {
1294
+ success: true,
1295
+ exportDir,
1296
+ fileCount: resultFiles.length,
1297
+ totalSize,
1298
+ files: resultFiles
1299
+ };
1300
+ }
1301
+ function generateAIGuideOutput(exportDir, fileCount, totalSize) {
1302
+ const sizeKB = (totalSize / 1024).toFixed(2);
1303
+ return `
1304
+ \u2705 \u5BFC\u51FA\u5B8C\u6210\uFF01
1305
+ \u{1F4C2} ${exportDir}/
1306
+ \u{1F4CA} ${fileCount} \u4E2A\u6587\u4EF6, ${sizeKB} KB
1307
+
1308
+ \u{1F916} AI \u8BFB\u53D6\u6307\u5357:
1309
+ 1\uFE0F\u20E3 \u5148\u8BFB tree.summary.json \u2192 \u83B7\u53D6\u4EFB\u52A1\u6811 + \u7236\u5B50\u5173\u7CFB + \u57FA\u672C\u4FE1\u606F
1310
+ 2\uFE0F\u20E3 \u9700\u8981\u8BE6\u60C5\u65F6 \u2192 \u8BFB standard/{id}.json \u6216 complete/{id}.json
1311
+
1312
+ \u{1F4D6} \u5B57\u6BB5\u7EA7\u522B:
1313
+ \u2022 summary = id, subject, status, assigned_to, \u5DE5\u65F6, \u9884\u8BA1\u63D0\u6D4B\u65F6\u95F4
1314
+ \u2022 standard = summary + priority, tracker, project, \u65F6\u95F4\u6233, 8\u4E2A\u81EA\u5B9A\u4E49\u5B57\u6BB5
1315
+ \u2022 complete = \u5168\u90E8\u5B57\u6BB5\uFF08\u542B description, author, journals \u7B49\uFF09
1316
+
1317
+ \u{1F4A1} Token \u4F18\u5316:
1318
+ \u2022 \u53EA\u770B\u4EFB\u52A1\u5217\u8868/\u8FDB\u5EA6 \u2192 tree.summary.json (~5k tokens)
1319
+ \u2022 \u5355\u4EFB\u52A1\u4F18\u5148\u7EA7/\u6765\u6E90 \u2192 standard/{id}.json (~3k tokens)
1320
+ \u2022 \u4EFB\u52A1\u8BE6\u7EC6\u63CF\u8FF0/\u5386\u53F2 \u2192 complete/{id}.json (~11k tokens)
1321
+ `.trim();
1322
+ }
1323
+
1151
1324
  // src/commands/issue/index.ts
1152
1325
  function createIssueCommand() {
1153
1326
  const issueCmd = new Command3("issue").description("\u95EE\u9898\u7BA1\u7406");
1154
- 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").option("--pretty", "\u683C\u5F0F\u5316\u8F93\u51FA JSON\uFF08\u9ED8\u8BA4\u538B\u7F29\uFF09").action(async (id, options) => {
1327
+ issueCmd.command("get [id]").description("\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5\uFF08\u9ED8\u8BA4\u5206\u7EA7\u5BFC\u51FA\uFF0C--raw \u8F93\u51FA\u5B8C\u6574 JSON\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\u76EE\u5F55\u6216\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 /tmp\uFF09").option("--stdout", "\u5F3A\u5236\u8F93\u51FA\u5230\u63A7\u5236\u53F0\u800C\u975E\u6587\u4EF6").option("--raw", "\u8F93\u51FA\u5B8C\u6574 JSON \u6587\u4EF6\uFF08\u4E0D\u5206\u7EA7\uFF0C\u539F\u884C\u4E3A\uFF09").option("--include-relations", "\u5305\u542B\u5173\u8054\u95EE\u9898").option("--pretty", "\u683C\u5F0F\u5316\u8F93\u51FA JSON\uFF08\u9ED8\u8BA4\u538B\u7F29\uFF09").action(async (id, options) => {
1155
1328
  let issueId;
1156
1329
  let host;
1157
1330
  if (options.url) {
@@ -1192,13 +1365,31 @@ function createIssueCommand() {
1192
1365
  depth
1193
1366
  );
1194
1367
  if (result.success && result.data) {
1195
- smartOutput(result.data, {
1196
- stdout: options.stdout,
1197
- output: options.output,
1198
- command: "issue-get",
1199
- identifier: issueId.toString(),
1200
- pretty: options.pretty
1201
- });
1368
+ if (options.raw || options.stdout) {
1369
+ smartOutput(result.data, {
1370
+ stdout: options.stdout,
1371
+ output: options.output,
1372
+ command: "issue-get",
1373
+ identifier: issueId.toString(),
1374
+ pretty: options.pretty
1375
+ });
1376
+ } else {
1377
+ const exportDir = options.output || generateExportDir("issue-get", issueId.toString());
1378
+ const exportResult = await exportIssuePresets(result.data, exportDir, {
1379
+ depth,
1380
+ pretty: options.pretty
1381
+ });
1382
+ if (exportResult.success) {
1383
+ console.log(generateAIGuideOutput(
1384
+ exportResult.exportDir,
1385
+ exportResult.fileCount,
1386
+ exportResult.totalSize
1387
+ ));
1388
+ } else {
1389
+ outputError("\u5BFC\u51FA\u5931\u8D25", options.pretty);
1390
+ process.exit(1);
1391
+ }
1392
+ }
1202
1393
  } else {
1203
1394
  outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u95EE\u9898\u5931\u8D25", options.pretty);
1204
1395
  process.exit(1);
@@ -1847,7 +2038,7 @@ function getWeekDateRange(week) {
1847
2038
  }
1848
2039
  function createTimeCommand() {
1849
2040
  const timeCmd = new Command4("time").description("\u5DE5\u65F6\u7BA1\u7406");
1850
- 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").option("--pretty", "\u683C\u5F0F\u5316\u8F93\u51FA JSON\uFF08\u9ED8\u8BA4\u538B\u7F29\uFF09").action(async (options) => {
2041
+ 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("--all-user", "\u67E5\u8BE2\u6240\u6709\u7528\u6237\u7684\u5DE5\u65F6\uFF08\u4E0D\u7B5B\u9009\uFF0C\u9ED8\u8BA4\u53EA\u67E5\u8BE2\u5F53\u524D\u7528\u6237\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").option("--pretty", "\u683C\u5F0F\u5316\u8F93\u51FA JSON\uFF08\u9ED8\u8BA4\u538B\u7F29\uFF09").action(async (options) => {
1851
2042
  const creds = resolveCredentials(options);
1852
2043
  const requiredFields = options.allProjects ? ["token", "host"] : ["token", "host", "project"];
1853
2044
  const validation = validateCredentials(creds, requiredFields);
@@ -1865,6 +2056,24 @@ function createTimeCommand() {
1865
2056
  const projects = projectsResult.data;
1866
2057
  const projectNames = Object.values(projects).map((p) => p.name);
1867
2058
  logger_default.info(`\u627E\u5230 ${projectNames.length} \u4E2A\u9879\u76EE`);
2059
+ let userId;
2060
+ if (!options.allUser) {
2061
+ if (options.userId) {
2062
+ userId = parseInt(options.userId, 10);
2063
+ } else {
2064
+ const configUserId = getConfigValue("userId", options.config);
2065
+ if (configUserId) {
2066
+ userId = parseInt(configUserId, 10);
2067
+ logger_default.info(`\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684\u7528\u6237 ID: ${userId}`);
2068
+ } else {
2069
+ outputError("\u672A\u914D\u7F6E\u7528\u6237 ID\uFF0C\u8BF7\u5148\u8BBE\u7F6E: pm-cli config set user-id <\u60A8\u7684\u7528\u6237ID>\uFF0C\u6216\u4F7F\u7528 --all-user \u67E5\u8BE2\u6240\u6709\u7528\u6237", options.pretty);
2070
+ process.exit(1);
2071
+ }
2072
+ }
2073
+ } else if (options.userId) {
2074
+ userId = parseInt(options.userId, 10);
2075
+ logger_default.info(`\u4F7F\u7528\u6307\u5B9A\u7684\u7528\u6237 ID: ${userId}`);
2076
+ }
1868
2077
  const allTimeEntries = [];
1869
2078
  let totalCount = 0;
1870
2079
  const projectSummary = [];
@@ -1875,7 +2084,7 @@ function createTimeCommand() {
1875
2084
  project: projectName,
1876
2085
  from_date: options.from,
1877
2086
  to_date: options.to,
1878
- user_id: options.userId ? parseInt(options.userId, 10) : void 0,
2087
+ user_id: userId,
1879
2088
  activity_id: options.activityId ? parseInt(options.activityId, 10) : void 0,
1880
2089
  limit: options.limit ? parseInt(options.limit, 10) : 100,
1881
2090
  offset: options.offset ? parseInt(options.offset, 10) : void 0
@@ -2233,11 +2442,11 @@ function createProjectCommand() {
2233
2442
 
2234
2443
  // src/index.ts
2235
2444
  import { readFileSync as readFileSync2 } from "fs";
2236
- import { dirname as dirname2, join as join2 } from "path";
2445
+ import { dirname as dirname2, join as join3 } from "path";
2237
2446
  import { fileURLToPath } from "url";
2238
2447
  var __filename = fileURLToPath(import.meta.url);
2239
2448
  var __dirname = dirname2(__filename);
2240
- var packageJson = JSON.parse(readFileSync2(join2(__dirname, "../package.json"), "utf-8"));
2449
+ var packageJson = JSON.parse(readFileSync2(join3(__dirname, "../package.json"), "utf-8"));
2241
2450
  var version = packageJson.version;
2242
2451
  var program = new Command6();
2243
2452
  program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase) \u547D\u4EE4\u884C\u5DE5\u5177").version(version).option("--verbose", "\u8BE6\u7EC6\u8F93\u51FA").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F").option("--no-mapping", "\u4E0D\u8F93\u51FA key \u6620\u5C04\u8868\uFF08\u9ED8\u8BA4\u8F93\u51FA\uFF09").hook("preAction", (thisCommand) => {