ai-project-manage-cli 3.0.2 → 3.0.4

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.
Files changed (2) hide show
  1. package/dist/index.js +153 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -95,6 +95,10 @@ var requestConfig = {
95
95
  method: "GET",
96
96
  path: "/cli/requirements/pull"
97
97
  }),
98
+ branchBaseline: defineEndpoint({
99
+ method: "GET",
100
+ path: "/cli/requirements/branch-baseline"
101
+ }),
98
102
  comment: defineEndpoint({
99
103
  method: "POST",
100
104
  path: "/cli/requirements/comment"
@@ -307,6 +311,144 @@ async function runLogin(opts) {
307
311
  console.log(JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2));
308
312
  }
309
313
 
314
+ // src/commands/branch.ts
315
+ import { execFile } from "child_process";
316
+ import { resolve as resolve2 } from "path";
317
+ import { promisify } from "util";
318
+ var execFileAsync = promisify(execFile);
319
+ async function fetchBaselineBranchFromApi(requirementId, cwd) {
320
+ const cfg = await ensureLoggedConfig();
321
+ const api = createApmApiClient(cfg);
322
+ const workdirPath = resolve2(cwd);
323
+ const { baselineBranch } = await api.cliRequirements.branchBaseline({
324
+ requirementId,
325
+ workdirPath
326
+ });
327
+ const name = baselineBranch.trim();
328
+ if (!name) {
329
+ throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
330
+ }
331
+ return name;
332
+ }
333
+ function branchNameForRequirement(requirementId) {
334
+ const id = requirementId.trim();
335
+ if (!id) {
336
+ throw new Error("[apm] \u9700\u6C42 ID \u4E0D\u80FD\u4E3A\u7A7A");
337
+ }
338
+ if (/[\s/\\]/.test(id)) {
339
+ throw new Error(
340
+ "[apm] \u9700\u6C42 ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
341
+ );
342
+ }
343
+ return `feat/req-${id}`;
344
+ }
345
+ async function execGit(cwd, args, quiet) {
346
+ try {
347
+ const { stdout, stderr } = await execFileAsync("git", args, {
348
+ cwd,
349
+ encoding: "utf8",
350
+ maxBuffer: 10 * 1024 * 1024
351
+ });
352
+ if (!quiet && stderr.trim()) {
353
+ process.stderr.write(stderr);
354
+ }
355
+ return stdout;
356
+ } catch (err) {
357
+ const e = err;
358
+ const detail = (e.stderr ?? e.message ?? String(err)).trim();
359
+ throw new Error(
360
+ `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
361
+ );
362
+ }
363
+ }
364
+ async function ensureGitRepo(cwd) {
365
+ await execGit(cwd, ["rev-parse", "--git-dir"], true);
366
+ }
367
+ async function getCurrentBranch(cwd) {
368
+ const name = (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
369
+ return name;
370
+ }
371
+ async function isWorkingTreeDirty(cwd) {
372
+ const out = await execGit(cwd, ["status", "--porcelain"], true);
373
+ return out.trim().length > 0;
374
+ }
375
+ async function remoteHeadBranchExists(cwd, branch) {
376
+ const out = await execGit(
377
+ cwd,
378
+ ["ls-remote", "--heads", "origin", branch],
379
+ true
380
+ );
381
+ return out.trim().length > 0;
382
+ }
383
+ async function localBranchExists(cwd, branch) {
384
+ try {
385
+ await execGit(
386
+ cwd,
387
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
388
+ true
389
+ );
390
+ return true;
391
+ } catch {
392
+ return false;
393
+ }
394
+ }
395
+ async function runBranch(requirementId, options = {}) {
396
+ const cwd = options.cwd ?? process.cwd();
397
+ const branch = branchNameForRequirement(requirementId);
398
+ const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
399
+ await ensureGitRepo(cwd);
400
+ const current = await getCurrentBranch(cwd);
401
+ const dirty = await isWorkingTreeDirty(cwd);
402
+ if (dirty) {
403
+ if (current === branch) {
404
+ await execGit(cwd, ["add", "-A"]);
405
+ await execGit(cwd, ["commit", "-m", commitMessage]);
406
+ } else {
407
+ await execGit(cwd, [
408
+ "stash",
409
+ "push",
410
+ "-u",
411
+ "-m",
412
+ `apm: switch to ${branch}`
413
+ ]);
414
+ }
415
+ }
416
+ const remoteExists = await remoteHeadBranchExists(cwd, branch);
417
+ if (remoteExists) {
418
+ await execGit(cwd, ["fetch", "origin", branch]);
419
+ const hasLocal = await localBranchExists(cwd, branch);
420
+ if (hasLocal) {
421
+ await execGit(cwd, ["checkout", branch]);
422
+ await execGit(cwd, ["pull", "--no-edit"]);
423
+ } else {
424
+ await execGit(cwd, ["checkout", "-b", branch, `origin/${branch}`]);
425
+ }
426
+ } else {
427
+ const onBranch = await getCurrentBranch(cwd) === branch;
428
+ if (!onBranch) {
429
+ const hasLocal = await localBranchExists(cwd, branch);
430
+ if (hasLocal) {
431
+ await execGit(cwd, ["checkout", branch]);
432
+ } else {
433
+ const baselineBranch = await fetchBaselineBranchFromApi(
434
+ requirementId,
435
+ cwd
436
+ );
437
+ await execGit(cwd, ["fetch", "origin", baselineBranch]);
438
+ await execGit(cwd, [
439
+ "checkout",
440
+ "-b",
441
+ branch,
442
+ `origin/${baselineBranch}`
443
+ ]);
444
+ }
445
+ }
446
+ await execGit(cwd, ["push", "-u", "origin", branch]);
447
+ }
448
+ console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
449
+ return branch;
450
+ }
451
+
310
452
  // src/commands/pull.ts
311
453
  import { writeFileSync as writeFileSync3 } from "fs";
312
454
  import { join as join4 } from "path";
@@ -364,9 +506,8 @@ async function runPull(requirementId) {
364
506
  {
365
507
  id: req.id,
366
508
  status: req.status,
367
- ...req.urgency != null && req.urgency !== "" ? { urgency: req.urgency } : {},
368
509
  title: req.title,
369
- ...req.pauseReason != null && req.pauseReason.trim() !== "" ? { pauseReason: req.pauseReason } : {},
510
+ env: req.envName || "",
370
511
  tasks: tasksForStatusYaml(data.tasks ?? [])
371
512
  },
372
513
  { lineWidth: 0 }
@@ -476,6 +617,16 @@ function buildProgram() {
476
617
  program.command("pull").description("GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u5230 .apm \u76EE\u5F55").argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
477
618
  await runPull(requirementId);
478
619
  });
620
+ program.command("branch").description(
621
+ "\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<ID>\uFF1A\u8FDC\u7AEF\u5B58\u5728\u5219\u62C9\u53D6\u6700\u65B0\uFF1B\u8FDC\u7AEF\u5C1A\u65E0\u8BE5\u5206\u652F\u4E14\u672C\u5730\u4E5F\u65E0\u540C\u540D\u5206\u652F\u65F6\uFF0C\u9700\u5DF2 login\uFF0C\u5E76\u7531\u5E73\u53F0\u6839\u636E\u5F53\u524D\u76EE\u5F55\u8DEF\u5F84\u89E3\u6790\u4ED3\u5E93\u57FA\u7EBF\u5206\u652F\u540E\u4ECE origin \u68C0\u51FA\u518D\u63A8\u9001\uFF1B\u6709\u672C\u5730\u672A\u63D0\u4EA4\u6539\u52A8\u65F6\u5728\u975E\u76EE\u6807\u5206\u652F\u5148 stash\uFF08\u4E0D\u81EA\u52A8\u6062\u590D\uFF09\uFF0C\u5728\u76EE\u6807\u5206\u652F\u5219\u5148 commit"
622
+ ).argument("<requirementId>", "\u9700\u6C42 ID").option(
623
+ "-m, --message <text>",
624
+ "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\uFF09"
625
+ ).action(
626
+ async (requirementId, opts) => {
627
+ await runBranch(requirementId, { message: opts.message });
628
+ }
629
+ );
479
630
  program.command("comment").description("POST /api/cli/requirements/comment\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--file <path>", "\u8BC4\u8BBA\u6B63\u6587\u6587\u4EF6\u8DEF\u5F84").option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B").action(
480
631
  async (requirementId, options) => {
481
632
  await runComment(requirementId, options.file, options.model);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,