ai-project-manage-cli 6.0.105 → 6.0.106
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 +490 -354
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -535,26 +535,194 @@ async function tryReadGitOriginUrl(cwd) {
|
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
+
// src/git-utils.ts
|
|
539
|
+
import { execFile as execFile2 } from "child_process";
|
|
540
|
+
import { promisify as promisify2 } from "util";
|
|
541
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
542
|
+
async function execGit(cwd, args, quiet = false) {
|
|
543
|
+
try {
|
|
544
|
+
const { stdout, stderr } = await execFileAsync2("git", args, {
|
|
545
|
+
cwd,
|
|
546
|
+
encoding: "utf8",
|
|
547
|
+
maxBuffer: 10 * 1024 * 1024
|
|
548
|
+
});
|
|
549
|
+
if (!quiet && stderr.trim()) {
|
|
550
|
+
process.stderr.write(stderr);
|
|
551
|
+
}
|
|
552
|
+
return stdout;
|
|
553
|
+
} catch (err) {
|
|
554
|
+
const e = err;
|
|
555
|
+
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
556
|
+
throw new Error(
|
|
557
|
+
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function isGitRepo(cwd) {
|
|
562
|
+
try {
|
|
563
|
+
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
564
|
+
return true;
|
|
565
|
+
} catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function ensureGitRepo(cwd) {
|
|
570
|
+
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
571
|
+
}
|
|
572
|
+
async function getCurrentBranch(cwd) {
|
|
573
|
+
return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
574
|
+
}
|
|
575
|
+
async function isWorkingTreeDirty(cwd) {
|
|
576
|
+
const out = await execGit(cwd, ["status", "--porcelain"], true);
|
|
577
|
+
return out.trim().length > 0;
|
|
578
|
+
}
|
|
579
|
+
async function remoteBranchExists(cwd, branch) {
|
|
580
|
+
const out = await execGit(
|
|
581
|
+
cwd,
|
|
582
|
+
["ls-remote", "--heads", "origin", branch],
|
|
583
|
+
true
|
|
584
|
+
);
|
|
585
|
+
return out.trim().length > 0;
|
|
586
|
+
}
|
|
587
|
+
async function resolveGitRepoRoot(cwd) {
|
|
588
|
+
return (await execGit(cwd, ["rev-parse", "--show-toplevel"], true)).trim();
|
|
589
|
+
}
|
|
590
|
+
async function ensureRemoteBaselineBranch(cwd, baselineBranch) {
|
|
591
|
+
await execGit(cwd, ["fetch", "origin", baselineBranch], true);
|
|
592
|
+
if (await remoteBranchExists(cwd, baselineBranch)) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
throw new Error(
|
|
596
|
+
`[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
async function hasUpstream(cwd) {
|
|
600
|
+
try {
|
|
601
|
+
await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
|
|
602
|
+
return true;
|
|
603
|
+
} catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
|
|
608
|
+
async function commitAndPushGitignore(workdir) {
|
|
609
|
+
if (!await isGitRepo(workdir)) {
|
|
610
|
+
console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
await execGit(workdir, ["add", "--", ".gitignore"]);
|
|
614
|
+
await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
|
|
615
|
+
console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
|
|
616
|
+
const originUrl = await tryReadGitOriginUrl(workdir);
|
|
617
|
+
if (!originUrl) {
|
|
618
|
+
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (await hasUpstream(workdir)) {
|
|
622
|
+
await execGit(workdir, ["push"]);
|
|
623
|
+
} else {
|
|
624
|
+
await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
|
|
625
|
+
}
|
|
626
|
+
console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/baseline-resolve.ts
|
|
630
|
+
function formatBaselineDiagnostic(workdirPath, baselineWorkdirPath, diagnostic) {
|
|
631
|
+
return diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baselineWorkdirPath}\uFF09
|
|
632
|
+
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
|
|
633
|
+
}
|
|
634
|
+
async function matchRepositoryByGitRemote(api, workdirPath) {
|
|
635
|
+
const gitRoot = await resolveGitRepoRoot(workdirPath);
|
|
636
|
+
const gitUrl = await tryReadGitOriginUrl(gitRoot);
|
|
637
|
+
if (!gitUrl) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const matched = await api.cli.matchRepository({ url: gitUrl });
|
|
641
|
+
const repositoryId = matched.repositoryId?.trim();
|
|
642
|
+
const defaultBranch = matched.defaultBranch?.trim();
|
|
643
|
+
if (!repositoryId || !defaultBranch) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
console.log(
|
|
647
|
+
`[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
|
|
648
|
+
);
|
|
649
|
+
return { repositoryId, defaultBranch };
|
|
650
|
+
}
|
|
651
|
+
async function resolveWorkspaceBaseline(api, workdirPath) {
|
|
652
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
653
|
+
const repositoryId = baseline.repositoryId?.trim();
|
|
654
|
+
const defaultBranch = baseline.defaultBranch?.trim();
|
|
655
|
+
if (repositoryId && defaultBranch) {
|
|
656
|
+
return {
|
|
657
|
+
repositoryId,
|
|
658
|
+
defaultBranch,
|
|
659
|
+
workdirPath: baseline.workdirPath,
|
|
660
|
+
matchedViaGitRemote: false
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
|
|
664
|
+
if (viaGit) {
|
|
665
|
+
return {
|
|
666
|
+
...viaGit,
|
|
667
|
+
workdirPath: baseline.workdirPath,
|
|
668
|
+
matchedViaGitRemote: true
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
throw new Error(
|
|
672
|
+
`[apm] ${formatBaselineDiagnostic(
|
|
673
|
+
workdirPath,
|
|
674
|
+
baseline.workdirPath,
|
|
675
|
+
baseline.diagnostic
|
|
676
|
+
)}`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
async function resolveBranchBaseline(api, sessionId, workdirPath) {
|
|
680
|
+
const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
|
|
681
|
+
const repositoryId = baseline.repositoryId?.trim();
|
|
682
|
+
const defaultBranch = baseline.defaultBranch?.trim();
|
|
683
|
+
if (repositoryId && defaultBranch) {
|
|
684
|
+
return {
|
|
685
|
+
sessionId: baseline.sessionId,
|
|
686
|
+
taskId: baseline.taskId,
|
|
687
|
+
repositoryId,
|
|
688
|
+
defaultBranch,
|
|
689
|
+
workdirPath: baseline.workdirPath,
|
|
690
|
+
matchedViaGitRemote: false
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
const viaGit = await matchRepositoryByGitRemote(api, workdirPath);
|
|
694
|
+
if (viaGit) {
|
|
695
|
+
return {
|
|
696
|
+
sessionId: baseline.sessionId,
|
|
697
|
+
taskId: baseline.taskId,
|
|
698
|
+
...viaGit,
|
|
699
|
+
workdirPath: baseline.workdirPath,
|
|
700
|
+
matchedViaGitRemote: true
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
throw new Error(
|
|
704
|
+
`[apm] ${formatBaselineDiagnostic(
|
|
705
|
+
workdirPath,
|
|
706
|
+
baseline.workdirPath,
|
|
707
|
+
baseline.diagnostic
|
|
708
|
+
)}`
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
538
712
|
// src/deployment-config-sync.ts
|
|
539
713
|
var TEMPLATE_HINT = "\u4FDD\u7559\u6A21\u677F .apm/apm.config.json";
|
|
540
714
|
var SYNC_HINT = "\u767B\u8BB0\u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u3001\u7ED1\u5B9A\u4ED3\u5E93\u540E\uFF0C\u53EF\u6267\u884C: apm sync-deploy-config";
|
|
541
715
|
async function resolveRepositoryIdForSync(api, workdirPath) {
|
|
542
|
-
|
|
543
|
-
|
|
716
|
+
try {
|
|
717
|
+
const baseline = await resolveWorkspaceBaseline(api, workdirPath);
|
|
544
718
|
return { repositoryId: baseline.repositoryId, diagnostic: null };
|
|
719
|
+
} catch (err) {
|
|
720
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
721
|
+
return {
|
|
722
|
+
repositoryId: null,
|
|
723
|
+
diagnostic: detail.replace(/^\[apm\]\s*/, "")
|
|
724
|
+
};
|
|
545
725
|
}
|
|
546
|
-
const gitUrl = await tryReadGitOriginUrl(workdirPath);
|
|
547
|
-
if (gitUrl) {
|
|
548
|
-
const matched = await api.cli.matchRepository({ url: gitUrl });
|
|
549
|
-
if (matched.repositoryId) {
|
|
550
|
-
console.log(
|
|
551
|
-
`[apm] \u5DE5\u4F5C\u7A7A\u95F4\u8DEF\u5F84\u672A\u5339\u914D\uFF0C\u5DF2\u901A\u8FC7 git remote \u5173\u8054\u4ED3\u5E93: ${gitUrl}`
|
|
552
|
-
);
|
|
553
|
-
return { repositoryId: matched.repositoryId, diagnostic: null };
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const detail = baseline.diagnostic?.message ?? `\u5F53\u524D\u8DEF\u5F84\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09\u672A\u5339\u914D\u5230\u5DF2\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\u3002`;
|
|
557
|
-
return { repositoryId: null, diagnostic: detail };
|
|
558
726
|
}
|
|
559
727
|
async function syncRemoteDeploymentConfig(workdirPath, apmDir) {
|
|
560
728
|
const cfg = await tryReadApmConfig();
|
|
@@ -816,85 +984,6 @@ async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
|
|
|
816
984
|
return synced;
|
|
817
985
|
}
|
|
818
986
|
|
|
819
|
-
// src/git-utils.ts
|
|
820
|
-
import { execFile as execFile2 } from "child_process";
|
|
821
|
-
import { promisify as promisify2 } from "util";
|
|
822
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
823
|
-
async function execGit(cwd, args, quiet = false) {
|
|
824
|
-
try {
|
|
825
|
-
const { stdout, stderr } = await execFileAsync2("git", args, {
|
|
826
|
-
cwd,
|
|
827
|
-
encoding: "utf8",
|
|
828
|
-
maxBuffer: 10 * 1024 * 1024
|
|
829
|
-
});
|
|
830
|
-
if (!quiet && stderr.trim()) {
|
|
831
|
-
process.stderr.write(stderr);
|
|
832
|
-
}
|
|
833
|
-
return stdout;
|
|
834
|
-
} catch (err) {
|
|
835
|
-
const e = err;
|
|
836
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
837
|
-
throw new Error(
|
|
838
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
async function isGitRepo(cwd) {
|
|
843
|
-
try {
|
|
844
|
-
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
845
|
-
return true;
|
|
846
|
-
} catch {
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
async function ensureGitRepo(cwd) {
|
|
851
|
-
await execGit(cwd, ["rev-parse", "--git-dir"], true);
|
|
852
|
-
}
|
|
853
|
-
async function getCurrentBranch(cwd) {
|
|
854
|
-
return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
855
|
-
}
|
|
856
|
-
async function isWorkingTreeDirty(cwd) {
|
|
857
|
-
const out = await execGit(cwd, ["status", "--porcelain"], true);
|
|
858
|
-
return out.trim().length > 0;
|
|
859
|
-
}
|
|
860
|
-
async function remoteBranchExists(cwd, branch) {
|
|
861
|
-
const out = await execGit(
|
|
862
|
-
cwd,
|
|
863
|
-
["ls-remote", "--heads", "origin", branch],
|
|
864
|
-
true
|
|
865
|
-
);
|
|
866
|
-
return out.trim().length > 0;
|
|
867
|
-
}
|
|
868
|
-
async function hasUpstream(cwd) {
|
|
869
|
-
try {
|
|
870
|
-
await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
|
|
871
|
-
return true;
|
|
872
|
-
} catch {
|
|
873
|
-
return false;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
var GITIGNORE_COMMIT_MESSAGE = "chore(apm): ignore .apm directory";
|
|
877
|
-
async function commitAndPushGitignore(workdir) {
|
|
878
|
-
if (!await isGitRepo(workdir)) {
|
|
879
|
-
console.log("[apm] \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8BF7\u624B\u52A8\u63D0\u4EA4 .gitignore");
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
await execGit(workdir, ["add", "--", ".gitignore"]);
|
|
883
|
-
await execGit(workdir, ["commit", "-m", GITIGNORE_COMMIT_MESSAGE]);
|
|
884
|
-
console.log(`[apm] \u5DF2\u63D0\u4EA4 .gitignore: ${GITIGNORE_COMMIT_MESSAGE}`);
|
|
885
|
-
const originUrl = await tryReadGitOriginUrl(workdir);
|
|
886
|
-
if (!originUrl) {
|
|
887
|
-
console.log("[apm] \u672A\u914D\u7F6E remote.origin\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8 push .gitignore");
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
if (await hasUpstream(workdir)) {
|
|
891
|
-
await execGit(workdir, ["push"]);
|
|
892
|
-
} else {
|
|
893
|
-
await execGit(workdir, ["push", "-u", "origin", "HEAD"]);
|
|
894
|
-
}
|
|
895
|
-
console.log("[apm] \u5DF2\u63A8\u9001 .gitignore");
|
|
896
|
-
}
|
|
897
|
-
|
|
898
987
|
// src/commands/init.ts
|
|
899
988
|
async function ensureWorkspaceInitialized(workdir, options) {
|
|
900
989
|
if (isWorkspaceApmInitialized(workdir)) {
|
|
@@ -1010,9 +1099,6 @@ async function runLogin(opts) {
|
|
|
1010
1099
|
}
|
|
1011
1100
|
|
|
1012
1101
|
// src/commands/branch.ts
|
|
1013
|
-
import { execFile as execFile3 } from "child_process";
|
|
1014
|
-
import { promisify as promisify3 } from "util";
|
|
1015
|
-
var execFileAsync3 = promisify3(execFile3);
|
|
1016
1102
|
var SESSION_BRANCH_PREFIX = "feat/session-";
|
|
1017
1103
|
function branchNameForSession(sessionId) {
|
|
1018
1104
|
const id = sessionId.trim();
|
|
@@ -1034,47 +1120,9 @@ function sessionIdFromBranchName(branch) {
|
|
|
1034
1120
|
const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
|
|
1035
1121
|
return sessionId || null;
|
|
1036
1122
|
}
|
|
1037
|
-
async function execGit2(cwd, args, quiet) {
|
|
1038
|
-
try {
|
|
1039
|
-
const { stdout, stderr } = await execFileAsync3("git", args, {
|
|
1040
|
-
cwd,
|
|
1041
|
-
encoding: "utf8",
|
|
1042
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1043
|
-
});
|
|
1044
|
-
if (!quiet && stderr.trim()) {
|
|
1045
|
-
process.stderr.write(stderr);
|
|
1046
|
-
}
|
|
1047
|
-
return stdout;
|
|
1048
|
-
} catch (err) {
|
|
1049
|
-
const e = err;
|
|
1050
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1051
|
-
throw new Error(
|
|
1052
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
async function ensureGitRepo2(cwd) {
|
|
1057
|
-
await execGit2(cwd, ["rev-parse", "--git-dir"], true);
|
|
1058
|
-
}
|
|
1059
|
-
async function getCurrentBranch2(cwd) {
|
|
1060
|
-
const name = (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
|
|
1061
|
-
return name;
|
|
1062
|
-
}
|
|
1063
|
-
async function isWorkingTreeDirty2(cwd) {
|
|
1064
|
-
const out = await execGit2(cwd, ["status", "--porcelain"], true);
|
|
1065
|
-
return out.trim().length > 0;
|
|
1066
|
-
}
|
|
1067
|
-
async function remoteHeadBranchExists(cwd, branch) {
|
|
1068
|
-
const out = await execGit2(
|
|
1069
|
-
cwd,
|
|
1070
|
-
["ls-remote", "--heads", "origin", branch],
|
|
1071
|
-
true
|
|
1072
|
-
);
|
|
1073
|
-
return out.trim().length > 0;
|
|
1074
|
-
}
|
|
1075
1123
|
async function localBranchExists(cwd, branch) {
|
|
1076
1124
|
try {
|
|
1077
|
-
await
|
|
1125
|
+
await execGit(
|
|
1078
1126
|
cwd,
|
|
1079
1127
|
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
1080
1128
|
true
|
|
@@ -1085,32 +1133,29 @@ async function localBranchExists(cwd, branch) {
|
|
|
1085
1133
|
}
|
|
1086
1134
|
}
|
|
1087
1135
|
async function commitWorkingTreeIfDirty(cwd, message) {
|
|
1088
|
-
await
|
|
1089
|
-
if (!await
|
|
1136
|
+
await ensureGitRepo(cwd);
|
|
1137
|
+
if (!await isWorkingTreeDirty(cwd)) {
|
|
1090
1138
|
return false;
|
|
1091
1139
|
}
|
|
1092
|
-
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await
|
|
1093
|
-
await
|
|
1094
|
-
await
|
|
1140
|
+
const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
|
|
1141
|
+
await execGit(cwd, ["add", "-A"]);
|
|
1142
|
+
await execGit(cwd, ["commit", "-m", commitMessage]);
|
|
1095
1143
|
console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
|
|
1096
1144
|
return true;
|
|
1097
1145
|
}
|
|
1098
1146
|
async function ensureFeatureBranch(branch, baselineBranch, options) {
|
|
1099
1147
|
const cwd = options.cwd ?? process.cwd();
|
|
1148
|
+
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
1100
1149
|
const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
|
|
1101
|
-
await
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
|
-
const current = await getCurrentBranch2(cwd);
|
|
1108
|
-
const dirty = await isWorkingTreeDirty2(cwd);
|
|
1150
|
+
await ensureGitRepo(gitRoot);
|
|
1151
|
+
await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
|
|
1152
|
+
const current = await getCurrentBranch(gitRoot);
|
|
1153
|
+
const dirty = await isWorkingTreeDirty(gitRoot);
|
|
1109
1154
|
if (dirty) {
|
|
1110
1155
|
if (current === branch) {
|
|
1111
|
-
await commitWorkingTreeIfDirty(
|
|
1156
|
+
await commitWorkingTreeIfDirty(gitRoot, commitMessage);
|
|
1112
1157
|
} else {
|
|
1113
|
-
await
|
|
1158
|
+
await execGit(gitRoot, [
|
|
1114
1159
|
"stash",
|
|
1115
1160
|
"push",
|
|
1116
1161
|
"-u",
|
|
@@ -1119,34 +1164,34 @@ async function ensureFeatureBranch(branch, baselineBranch, options) {
|
|
|
1119
1164
|
]);
|
|
1120
1165
|
}
|
|
1121
1166
|
}
|
|
1122
|
-
const onTargetBranch = await
|
|
1167
|
+
const onTargetBranch = await getCurrentBranch(gitRoot) === branch;
|
|
1123
1168
|
if (onTargetBranch) {
|
|
1124
|
-
await
|
|
1125
|
-
await
|
|
1169
|
+
await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
|
|
1170
|
+
await execGit(gitRoot, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
|
|
1126
1171
|
} else {
|
|
1127
|
-
const remoteExists = await
|
|
1172
|
+
const remoteExists = await remoteBranchExists(gitRoot, branch);
|
|
1128
1173
|
if (remoteExists) {
|
|
1129
|
-
await
|
|
1130
|
-
await
|
|
1131
|
-
} else if (await localBranchExists(
|
|
1132
|
-
if (await
|
|
1133
|
-
await
|
|
1174
|
+
await execGit(gitRoot, ["fetch", "origin", branch]);
|
|
1175
|
+
await execGit(gitRoot, ["checkout", "-B", branch, `origin/${branch}`]);
|
|
1176
|
+
} else if (await localBranchExists(gitRoot, branch)) {
|
|
1177
|
+
if (await getCurrentBranch(gitRoot) !== branch) {
|
|
1178
|
+
await execGit(gitRoot, ["checkout", branch]);
|
|
1134
1179
|
}
|
|
1135
1180
|
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
1136
1181
|
} else {
|
|
1137
|
-
await
|
|
1182
|
+
await execGit(gitRoot, ["fetch", "origin", baselineBranch]);
|
|
1138
1183
|
try {
|
|
1139
|
-
await
|
|
1184
|
+
await execGit(gitRoot, [
|
|
1140
1185
|
"checkout",
|
|
1141
1186
|
"-b",
|
|
1142
1187
|
branch,
|
|
1143
1188
|
`origin/${baselineBranch}`
|
|
1144
1189
|
]);
|
|
1145
|
-
await
|
|
1190
|
+
await execGit(gitRoot, ["push", "-u", "origin", branch]);
|
|
1146
1191
|
} catch (err) {
|
|
1147
|
-
if (await localBranchExists(
|
|
1148
|
-
if (await
|
|
1149
|
-
await
|
|
1192
|
+
if (await localBranchExists(gitRoot, branch)) {
|
|
1193
|
+
if (await getCurrentBranch(gitRoot) !== branch) {
|
|
1194
|
+
await execGit(gitRoot, ["checkout", branch]);
|
|
1150
1195
|
}
|
|
1151
1196
|
console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
|
|
1152
1197
|
} else {
|
|
@@ -1168,115 +1213,164 @@ async function runBranch(sessionId, options = {}) {
|
|
|
1168
1213
|
const api = createApmApiClient(cfg);
|
|
1169
1214
|
const cwd = options.cwd ?? process.cwd();
|
|
1170
1215
|
const workdirPath = resolveWorkdirPath(cwd);
|
|
1171
|
-
const baseline = await
|
|
1172
|
-
|
|
1216
|
+
const baseline = await resolveBranchBaseline(
|
|
1217
|
+
api,
|
|
1218
|
+
trimmedSessionId,
|
|
1173
1219
|
workdirPath
|
|
1174
|
-
|
|
1175
|
-
if (!baseline.repositoryId) {
|
|
1176
|
-
const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
|
|
1177
|
-
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
|
|
1178
|
-
throw new Error(`[apm] ${detail}`);
|
|
1179
|
-
}
|
|
1180
|
-
const baselineBranch = (baseline.defaultBranch ?? "").trim();
|
|
1181
|
-
if (!baselineBranch) {
|
|
1182
|
-
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
1183
|
-
}
|
|
1220
|
+
);
|
|
1184
1221
|
const branch = branchNameForSession(trimmedSessionId);
|
|
1185
|
-
return ensureFeatureBranch(branch,
|
|
1222
|
+
return ensureFeatureBranch(branch, baseline.defaultBranch, options);
|
|
1186
1223
|
}
|
|
1187
1224
|
|
|
1188
|
-
// src/commands/
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
if (!quiet && stderr.trim()) {
|
|
1200
|
-
process.stderr.write(stderr);
|
|
1201
|
-
}
|
|
1202
|
-
return stdout;
|
|
1203
|
-
} catch (err) {
|
|
1204
|
-
const e = err;
|
|
1205
|
-
const detail = (e.stderr ?? e.message ?? String(err)).trim();
|
|
1206
|
-
throw new Error(
|
|
1207
|
-
`[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
|
|
1208
|
-
);
|
|
1225
|
+
// src/commands/session-branch-prune.ts
|
|
1226
|
+
var TASK_STATUS_LABEL = {
|
|
1227
|
+
OPEN: "\u5F85\u5F00\u59CB",
|
|
1228
|
+
IN_PROGRESS: "\u8FDB\u884C\u4E2D",
|
|
1229
|
+
COMPLETED: "\u5DF2\u5B8C\u6210",
|
|
1230
|
+
FAILED: "\u5DF2\u5931\u8D25",
|
|
1231
|
+
SUSPENDED: "\u5DF2\u6682\u505C"
|
|
1232
|
+
};
|
|
1233
|
+
function taskStatusLabel(status) {
|
|
1234
|
+
if (status === null) {
|
|
1235
|
+
return "\u5E73\u53F0\u65E0\u4EFB\u52A1";
|
|
1209
1236
|
}
|
|
1237
|
+
return TASK_STATUS_LABEL[status] ?? status;
|
|
1210
1238
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1239
|
+
function syncTagForBranch(hasLocal, hasRemote) {
|
|
1240
|
+
if (hasLocal && hasRemote) {
|
|
1241
|
+
return "\u53CC\u7AEF";
|
|
1242
|
+
}
|
|
1243
|
+
if (hasLocal) {
|
|
1244
|
+
return "\u4EC5\u672C\u5730";
|
|
1245
|
+
}
|
|
1246
|
+
return "\u4EC5\u8FDC\u7A0B";
|
|
1213
1247
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1248
|
+
function shouldDeleteMode1(input) {
|
|
1249
|
+
if (input.merged) {
|
|
1250
|
+
return { delete: true, reason: "\u5DF2\u5408\u5E76" };
|
|
1251
|
+
}
|
|
1252
|
+
if (input.taskStatus === null) {
|
|
1253
|
+
return { delete: true, reason: "\u5E73\u53F0\u65E0\u4EFB\u52A1" };
|
|
1254
|
+
}
|
|
1255
|
+
return { delete: false, reason: "" };
|
|
1216
1256
|
}
|
|
1217
|
-
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
|
|
1222
|
-
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
|
|
1223
|
-
throw new Error(`[apm] ${detail}`);
|
|
1257
|
+
function shouldDeleteMode2(input) {
|
|
1258
|
+
const mode1 = shouldDeleteMode1(input);
|
|
1259
|
+
if (mode1.delete) {
|
|
1260
|
+
return mode1;
|
|
1224
1261
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
1262
|
+
if (input.taskStatus === "COMPLETED") {
|
|
1263
|
+
return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5B8C\u6210" };
|
|
1228
1264
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
throw new Error(
|
|
1232
|
-
`[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
|
|
1233
|
-
);
|
|
1265
|
+
if (input.taskStatus === "FAILED") {
|
|
1266
|
+
return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5931\u8D25" };
|
|
1234
1267
|
}
|
|
1235
|
-
return
|
|
1268
|
+
return { delete: false, reason: "" };
|
|
1236
1269
|
}
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1270
|
+
function buildSessionBranchRow(input) {
|
|
1271
|
+
const mode1 = shouldDeleteMode1(input);
|
|
1272
|
+
const mode2 = shouldDeleteMode2(input);
|
|
1273
|
+
return {
|
|
1274
|
+
branch: input.branch,
|
|
1275
|
+
sessionId: input.sessionId,
|
|
1276
|
+
hasLocal: input.hasLocal,
|
|
1277
|
+
hasRemote: input.hasRemote,
|
|
1278
|
+
syncTag: syncTagForBranch(input.hasLocal, input.hasRemote),
|
|
1279
|
+
merged: input.merged,
|
|
1280
|
+
taskStatus: input.taskStatus,
|
|
1281
|
+
taskLabel: taskStatusLabel(input.taskStatus),
|
|
1282
|
+
deleteMode1: mode1.delete,
|
|
1283
|
+
deleteMode2: mode2.delete,
|
|
1284
|
+
deleteReasonMode1: mode1.reason,
|
|
1285
|
+
deleteReasonMode2: mode2.reason
|
|
1286
|
+
};
|
|
1249
1287
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1288
|
+
function rowsForDeleteMode(rows, mode) {
|
|
1289
|
+
return rows.filter((row) => mode === 1 ? row.deleteMode1 : row.deleteMode2);
|
|
1290
|
+
}
|
|
1291
|
+
function yn(value) {
|
|
1292
|
+
return value ? "\u662F" : "\u2014";
|
|
1293
|
+
}
|
|
1294
|
+
function pad(value, width) {
|
|
1295
|
+
const chars = [...value];
|
|
1296
|
+
const len = chars.length;
|
|
1297
|
+
if (len >= width) {
|
|
1298
|
+
return value;
|
|
1256
1299
|
}
|
|
1257
|
-
return
|
|
1300
|
+
return value + " ".repeat(width - len);
|
|
1301
|
+
}
|
|
1302
|
+
function formatSessionBranchTable(meta, rows) {
|
|
1303
|
+
const lines = [
|
|
1304
|
+
`[apm] \u5DE5\u4F5C\u76EE\u5F55: ${meta.workdirPath}`,
|
|
1305
|
+
`[apm] Git \u6839\u76EE\u5F55: ${meta.gitRoot}`,
|
|
1306
|
+
`[apm] \u4ED3\u5E93 ID: ${meta.repositoryId}`,
|
|
1307
|
+
`[apm] \u57FA\u7EBF\u5206\u652F: ${meta.baselineBranch}`,
|
|
1308
|
+
""
|
|
1309
|
+
];
|
|
1310
|
+
if (rows.length === 0) {
|
|
1311
|
+
lines.push(
|
|
1312
|
+
`[apm] \u672A\u53D1\u73B0\u4ECE ${meta.baselineBranch} \u884D\u751F\u7684 feat/session-* \u5206\u652F\uFF08\u672C\u5730\u4E0E\u8FDC\u7A0B\uFF09`
|
|
1313
|
+
);
|
|
1314
|
+
return lines.join("\n");
|
|
1315
|
+
}
|
|
1316
|
+
const headers = [
|
|
1317
|
+
"\u5206\u652F",
|
|
1318
|
+
"\u672C\u5730",
|
|
1319
|
+
"\u8FDC\u7A0B",
|
|
1320
|
+
"\u540C\u6B65",
|
|
1321
|
+
"\u5DF2\u5408\u5E76",
|
|
1322
|
+
"\u4EFB\u52A1\u72B6\u6001",
|
|
1323
|
+
"\u6A21\u5F0F1",
|
|
1324
|
+
"\u6A21\u5F0F2"
|
|
1325
|
+
];
|
|
1326
|
+
const widths = [36, 4, 4, 6, 6, 10, 6, 6];
|
|
1327
|
+
lines.push(headers.map((h, i) => pad(h, widths[i])).join(" "));
|
|
1328
|
+
lines.push(widths.map((w) => "-".repeat(w)).join(" "));
|
|
1329
|
+
for (const row of rows) {
|
|
1330
|
+
const cells = [
|
|
1331
|
+
row.branch,
|
|
1332
|
+
yn(row.hasLocal),
|
|
1333
|
+
yn(row.hasRemote),
|
|
1334
|
+
row.syncTag,
|
|
1335
|
+
yn(row.merged),
|
|
1336
|
+
row.taskLabel,
|
|
1337
|
+
row.deleteMode1 ? row.deleteReasonMode1 || "\u662F" : "\u2014",
|
|
1338
|
+
row.deleteMode2 ? row.deleteReasonMode2 || "\u662F" : "\u2014"
|
|
1339
|
+
];
|
|
1340
|
+
lines.push(cells.map((c, i) => pad(c, widths[i])).join(" "));
|
|
1341
|
+
}
|
|
1342
|
+
const mode1Count = rows.filter((r) => r.deleteMode1).length;
|
|
1343
|
+
const mode2Count = rows.filter((r) => r.deleteMode2).length;
|
|
1344
|
+
lines.push("");
|
|
1345
|
+
lines.push(
|
|
1346
|
+
`[apm] \u5171 ${rows.length} \u4E2A\u5206\u652F\uFF1B\u6A21\u5F0F 1 \u53EF\u6E05\u7406 ${mode1Count} \u4E2A\uFF0C\u6A21\u5F0F 2 \u53EF\u6E05\u7406 ${mode2Count} \u4E2A`
|
|
1347
|
+
);
|
|
1348
|
+
lines.push("[apm] \u6A21\u5F0F 1\uFF1A\u5DF2\u5408\u5E76\u8FDB\u57FA\u7EBF\uFF0C\u6216\u5E73\u53F0\u5DF2\u65E0\u8BE5\u6C9F\u901A\u7FA4\u4EFB\u52A1");
|
|
1349
|
+
lines.push("[apm] \u6A21\u5F0F 2\uFF1A\u5728\u6A21\u5F0F 1 \u57FA\u7840\u4E0A\uFF0C\u53E6\u542B\u4EFB\u52A1\u5DF2\u5B8C\u6210\u6216\u5DF2\u5931\u8D25\u7684\u5206\u652F");
|
|
1350
|
+
lines.push("[apm] \u6267\u884C\u6E05\u7406: apm branch prune --mode 1|2 [--dry-run]");
|
|
1351
|
+
return lines.join("\n");
|
|
1258
1352
|
}
|
|
1259
1353
|
async function listLocalSessionBranches(cwd) {
|
|
1260
|
-
const out = await
|
|
1354
|
+
const out = await execGit(
|
|
1261
1355
|
cwd,
|
|
1262
1356
|
[
|
|
1263
1357
|
"for-each-ref",
|
|
1264
1358
|
"--format=%(refname:short)",
|
|
1265
1359
|
"refs/heads/",
|
|
1266
|
-
SESSION_BRANCH_PREFIX
|
|
1360
|
+
`${SESSION_BRANCH_PREFIX}*`
|
|
1267
1361
|
],
|
|
1268
1362
|
true
|
|
1269
1363
|
);
|
|
1270
1364
|
return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1271
1365
|
}
|
|
1272
1366
|
async function listRemoteSessionBranches(cwd) {
|
|
1273
|
-
const out = await
|
|
1367
|
+
const out = await execGit(
|
|
1274
1368
|
cwd,
|
|
1275
1369
|
[
|
|
1276
1370
|
"for-each-ref",
|
|
1277
1371
|
"--format=%(refname:short)",
|
|
1278
1372
|
"refs/remotes/origin/",
|
|
1279
|
-
SESSION_BRANCH_PREFIX
|
|
1373
|
+
`${SESSION_BRANCH_PREFIX}*`
|
|
1280
1374
|
],
|
|
1281
1375
|
true
|
|
1282
1376
|
);
|
|
@@ -1284,7 +1378,7 @@ async function listRemoteSessionBranches(cwd) {
|
|
|
1284
1378
|
}
|
|
1285
1379
|
async function localBranchExists2(cwd, branch) {
|
|
1286
1380
|
try {
|
|
1287
|
-
await
|
|
1381
|
+
await execGit(
|
|
1288
1382
|
cwd,
|
|
1289
1383
|
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
1290
1384
|
true
|
|
@@ -1294,29 +1388,25 @@ async function localBranchExists2(cwd, branch) {
|
|
|
1294
1388
|
return false;
|
|
1295
1389
|
}
|
|
1296
1390
|
}
|
|
1297
|
-
async function
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
return
|
|
1308
|
-
}
|
|
1309
|
-
if (sessionStatusById.get(sessionId) === "COMPLETED") {
|
|
1310
|
-
return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
|
|
1391
|
+
async function isBranchBasedOnBaseline(cwd, branch, baselineBranch) {
|
|
1392
|
+
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1393
|
+
try {
|
|
1394
|
+
await execGit(
|
|
1395
|
+
cwd,
|
|
1396
|
+
["merge-base", "--is-ancestor", `origin/${baselineBranch}`, ref],
|
|
1397
|
+
true
|
|
1398
|
+
);
|
|
1399
|
+
return true;
|
|
1400
|
+
} catch {
|
|
1401
|
+
return false;
|
|
1311
1402
|
}
|
|
1312
|
-
return "\u4FDD\u7559";
|
|
1313
1403
|
}
|
|
1314
|
-
async function
|
|
1404
|
+
async function isBranchMergedIntoBaseline(cwd, branch, baselineBranch) {
|
|
1315
1405
|
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1316
1406
|
try {
|
|
1317
|
-
await
|
|
1407
|
+
await execGit(
|
|
1318
1408
|
cwd,
|
|
1319
|
-
["merge-base", "--is-ancestor", ref, `origin/${
|
|
1409
|
+
["merge-base", "--is-ancestor", ref, `origin/${baselineBranch}`],
|
|
1320
1410
|
true
|
|
1321
1411
|
);
|
|
1322
1412
|
return true;
|
|
@@ -1324,79 +1414,129 @@ async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
|
|
|
1324
1414
|
return false;
|
|
1325
1415
|
}
|
|
1326
1416
|
}
|
|
1417
|
+
async function resolveSessionBranchScanMeta(cwd, api) {
|
|
1418
|
+
const workdirPath = resolveWorkdirPath(cwd);
|
|
1419
|
+
const baseline = await resolveWorkspaceBaseline(api, workdirPath);
|
|
1420
|
+
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
1421
|
+
await ensureGitRepo(gitRoot);
|
|
1422
|
+
await execGit(gitRoot, ["fetch", "--prune", "origin"], true);
|
|
1423
|
+
await ensureRemoteBaselineBranch(gitRoot, baseline.defaultBranch);
|
|
1424
|
+
return {
|
|
1425
|
+
workdirPath,
|
|
1426
|
+
gitRoot,
|
|
1427
|
+
baselineBranch: baseline.defaultBranch,
|
|
1428
|
+
repositoryId: baseline.repositoryId
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
async function scanSessionBranches(meta, sessionStatusById) {
|
|
1432
|
+
const { gitRoot, baselineBranch } = meta;
|
|
1433
|
+
const names = /* @__PURE__ */ new Set();
|
|
1434
|
+
for (const branch of await listLocalSessionBranches(gitRoot)) {
|
|
1435
|
+
names.add(branch);
|
|
1436
|
+
}
|
|
1437
|
+
for (const branch of await listRemoteSessionBranches(gitRoot)) {
|
|
1438
|
+
names.add(branch);
|
|
1439
|
+
}
|
|
1440
|
+
const rows = [];
|
|
1441
|
+
for (const branch of [...names].sort((a, b) => a.localeCompare(b))) {
|
|
1442
|
+
if (!await isBranchBasedOnBaseline(gitRoot, branch, baselineBranch)) {
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
const sessionId = sessionIdFromBranchName(branch);
|
|
1446
|
+
if (!sessionId) {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const hasLocal = await localBranchExists2(gitRoot, branch);
|
|
1450
|
+
const hasRemote = await remoteBranchExists(gitRoot, branch);
|
|
1451
|
+
const merged = await isBranchMergedIntoBaseline(
|
|
1452
|
+
gitRoot,
|
|
1453
|
+
branch,
|
|
1454
|
+
baselineBranch
|
|
1455
|
+
);
|
|
1456
|
+
const rawStatus = sessionStatusById.get(sessionId);
|
|
1457
|
+
const taskStatus = rawStatus ? rawStatus : null;
|
|
1458
|
+
rows.push(
|
|
1459
|
+
buildSessionBranchRow({
|
|
1460
|
+
branch,
|
|
1461
|
+
sessionId,
|
|
1462
|
+
hasLocal,
|
|
1463
|
+
hasRemote,
|
|
1464
|
+
merged,
|
|
1465
|
+
taskStatus
|
|
1466
|
+
})
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
return rows;
|
|
1470
|
+
}
|
|
1471
|
+
async function deleteSessionBranch(gitRoot, baselineBranch, branch, currentBranchRef) {
|
|
1472
|
+
if (currentBranchRef.value === branch) {
|
|
1473
|
+
await execGit(gitRoot, ["checkout", baselineBranch], true);
|
|
1474
|
+
currentBranchRef.value = baselineBranch;
|
|
1475
|
+
}
|
|
1476
|
+
if (await localBranchExists2(gitRoot, branch)) {
|
|
1477
|
+
await execGit(gitRoot, ["branch", "-D", branch], true);
|
|
1478
|
+
console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
|
|
1479
|
+
}
|
|
1480
|
+
if (await remoteBranchExists(gitRoot, branch)) {
|
|
1481
|
+
await execGit(gitRoot, ["push", "origin", "--delete", branch], true);
|
|
1482
|
+
console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
async function loadSessionStatusMap(api) {
|
|
1486
|
+
const { sessions } = await api.cli.listSessionsForBranchCleanup({});
|
|
1487
|
+
return new Map(sessions.map((item) => [item.sessionId, item.taskStatus]));
|
|
1488
|
+
}
|
|
1489
|
+
async function getGitCurrentBranch(gitRoot) {
|
|
1490
|
+
return getCurrentBranch(gitRoot);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/commands/clean-branches.ts
|
|
1327
1494
|
async function runCleanBranches(options = {}) {
|
|
1328
1495
|
const cwd = options.cwd ?? process.cwd();
|
|
1329
|
-
const dryRun = options.dryRun ?? false;
|
|
1330
|
-
const includeRemote = options.includeRemote ?? false;
|
|
1331
|
-
await ensureGitRepo3(cwd);
|
|
1332
|
-
await execGit3(cwd, ["fetch", "--prune", "origin"], true);
|
|
1333
1496
|
const cfg = await ensureLoggedConfig();
|
|
1334
1497
|
const api = createApmApiClient(cfg);
|
|
1335
|
-
const
|
|
1336
|
-
const
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
)
|
|
1340
|
-
const candidates = await listCandidateSessionBranches(cwd, includeRemote);
|
|
1341
|
-
const branchNames = [];
|
|
1342
|
-
for (const branch of candidates) {
|
|
1343
|
-
if (await isBranchBasedOnBaseline(cwd, branch, baselineBranch)) {
|
|
1344
|
-
branchNames.push(branch);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
if (branchNames.length === 0) {
|
|
1348
|
-
console.log(
|
|
1349
|
-
`[apm] \u672A\u53D1\u73B0\u4ECE ${baselineBranch} \u884D\u751F\u7684\u672C\u5730 feat/session-* \u5206\u652F${includeRemote ? "\uFF08\u542B\u8FDC\u7A0B\uFF09" : ""}`
|
|
1350
|
-
);
|
|
1498
|
+
const meta = await resolveSessionBranchScanMeta(cwd, api);
|
|
1499
|
+
const sessionStatusById = await loadSessionStatusMap(api);
|
|
1500
|
+
const rows = await scanSessionBranches(meta, sessionStatusById);
|
|
1501
|
+
console.log(formatSessionBranchTable(meta, rows));
|
|
1502
|
+
if (!options.mode) {
|
|
1351
1503
|
return;
|
|
1352
1504
|
}
|
|
1353
|
-
const toDelete =
|
|
1354
|
-
const sessionId = sessionIdFromBranchName(branch);
|
|
1355
|
-
if (!sessionId) {
|
|
1356
|
-
return null;
|
|
1357
|
-
}
|
|
1358
|
-
const reason = reasonForCleanup(sessionId, sessionStatusById);
|
|
1359
|
-
if (reason === "\u4FDD\u7559") {
|
|
1360
|
-
return null;
|
|
1361
|
-
}
|
|
1362
|
-
return { branch, sessionId, reason };
|
|
1363
|
-
}).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
|
|
1505
|
+
const toDelete = rowsForDeleteMode(rows, options.mode);
|
|
1364
1506
|
if (toDelete.length === 0) {
|
|
1365
|
-
console.log(
|
|
1507
|
+
console.log(`[apm] \u6A21\u5F0F ${options.mode} \u4E0B\u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684\u5206\u652F`);
|
|
1366
1508
|
return;
|
|
1367
1509
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1510
|
+
if (options.dryRun) {
|
|
1511
|
+
for (const row of toDelete) {
|
|
1512
|
+
const reason = options.mode === 1 ? row.deleteReasonMode1 : row.deleteReasonMode2;
|
|
1513
|
+
const parts = [
|
|
1514
|
+
row.hasLocal ? "\u672C\u5730" : null,
|
|
1515
|
+
row.hasRemote ? "\u8FDC\u7A0B" : null
|
|
1516
|
+
].filter(Boolean).join("+");
|
|
1517
|
+
console.log(
|
|
1518
|
+
`[apm] [dry-run] \u6A21\u5F0F ${options.mode} \u5C06\u5220\u9664 ${row.branch}\uFF08${parts || "\u65E0"}\uFF1B${reason}\uFF09`
|
|
1377
1519
|
);
|
|
1378
|
-
const mergeTag = merged ? "\u5DF2\u5408\u5E76" : "\u672A\u5408\u5E76";
|
|
1379
|
-
console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label} [${mergeTag}]`);
|
|
1380
|
-
continue;
|
|
1381
|
-
}
|
|
1382
|
-
if (currentBranch === branch) {
|
|
1383
|
-
await execGit3(cwd, ["checkout", baselineBranch], true);
|
|
1384
|
-
currentBranch = baselineBranch;
|
|
1385
|
-
}
|
|
1386
|
-
if (await localBranchExists2(cwd, branch)) {
|
|
1387
|
-
await execGit3(cwd, ["branch", "-D", branch], true);
|
|
1388
|
-
console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
|
|
1389
|
-
}
|
|
1390
|
-
if (await remoteBranchExists2(cwd, branch)) {
|
|
1391
|
-
await execGit3(cwd, ["push", "origin", "--delete", branch], true);
|
|
1392
|
-
console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
|
|
1393
1520
|
}
|
|
1521
|
+
console.log(
|
|
1522
|
+
`[apm] [dry-run] \u6A21\u5F0F ${options.mode} \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`
|
|
1523
|
+
);
|
|
1524
|
+
return;
|
|
1394
1525
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1526
|
+
const currentBranchRef = {
|
|
1527
|
+
value: await getGitCurrentBranch(meta.gitRoot)
|
|
1528
|
+
};
|
|
1529
|
+
for (const row of toDelete) {
|
|
1530
|
+
await deleteSessionBranch(
|
|
1531
|
+
meta.gitRoot,
|
|
1532
|
+
meta.baselineBranch,
|
|
1533
|
+
row.branch,
|
|
1534
|
+
currentBranchRef
|
|
1535
|
+
);
|
|
1399
1536
|
}
|
|
1537
|
+
console.log(
|
|
1538
|
+
`[apm] \u6A21\u5F0F ${options.mode} \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`
|
|
1539
|
+
);
|
|
1400
1540
|
}
|
|
1401
1541
|
|
|
1402
1542
|
// src/commands/pull.ts
|
|
@@ -1649,7 +1789,7 @@ function isRuleUpToDate(entry, rule, dest) {
|
|
|
1649
1789
|
}
|
|
1650
1790
|
async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
1651
1791
|
const api = createApmApiClient(cfg);
|
|
1652
|
-
const baseline = await api
|
|
1792
|
+
const baseline = await resolveBranchBaseline(api, sessionId, workdirPath);
|
|
1653
1793
|
const repositoryId = baseline.repositoryId;
|
|
1654
1794
|
const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
1655
1795
|
await ensureDirExists(rulesDir);
|
|
@@ -3054,8 +3194,8 @@ var MinioClient = class {
|
|
|
3054
3194
|
// src/commands/deploy/internal/deploy-artifact-minio.ts
|
|
3055
3195
|
var APM_DEPLOYMENT_RUN_ID_ENV = "APM_DEPLOYMENT_RUN_ID";
|
|
3056
3196
|
function formatDeployArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
3057
|
-
const
|
|
3058
|
-
return `${date.getFullYear()}${
|
|
3197
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
3198
|
+
return `${date.getFullYear()}${pad2(date.getMonth() + 1)}${pad2(date.getDate())}${pad2(date.getHours())}${pad2(date.getMinutes())}${pad2(date.getSeconds())}`;
|
|
3059
3199
|
}
|
|
3060
3200
|
function sanitizeDeployProjectName(name) {
|
|
3061
3201
|
const trimmed = name.trim().replace(/\\/g, "/");
|
|
@@ -5254,19 +5394,10 @@ var LOG_PREFIX2 = "[apm] \u90E8\u7F72\u524D\u57FA\u7EBF\u540C\u6B65:";
|
|
|
5254
5394
|
function formatGitError(error) {
|
|
5255
5395
|
return error instanceof Error ? error.message : String(error);
|
|
5256
5396
|
}
|
|
5257
|
-
async function
|
|
5397
|
+
async function resolveBaselineBranch(cwd, api) {
|
|
5258
5398
|
const workdirPath = resolveWorkdirPath(cwd);
|
|
5259
|
-
const baseline = await api
|
|
5260
|
-
|
|
5261
|
-
const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
|
|
5262
|
-
\u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
|
|
5263
|
-
throw new Error(detail);
|
|
5264
|
-
}
|
|
5265
|
-
const baselineBranch = (baseline.defaultBranch ?? "").trim();
|
|
5266
|
-
if (!baselineBranch) {
|
|
5267
|
-
throw new Error("\u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
5268
|
-
}
|
|
5269
|
-
return baselineBranch;
|
|
5399
|
+
const baseline = await resolveWorkspaceBaseline(api, workdirPath);
|
|
5400
|
+
return baseline.defaultBranch;
|
|
5270
5401
|
}
|
|
5271
5402
|
async function isMergeInProgress(cwd) {
|
|
5272
5403
|
try {
|
|
@@ -5346,21 +5477,17 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
|
|
|
5346
5477
|
}
|
|
5347
5478
|
await ensureGitRepo(cwd);
|
|
5348
5479
|
const api = createApmApiClient(cfg);
|
|
5349
|
-
const baselineBranch = await
|
|
5480
|
+
const baselineBranch = await resolveBaselineBranch(cwd, api);
|
|
5481
|
+
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
5350
5482
|
note(`${LOG_PREFIX2} \u57FA\u7EBF\u5206\u652F ${baselineBranch}`);
|
|
5351
5483
|
note(`${LOG_PREFIX2} \u6B63\u5728 fetch origin ${baselineBranch}`);
|
|
5352
|
-
await
|
|
5353
|
-
if (!await remoteBranchExists(cwd, baselineBranch)) {
|
|
5354
|
-
throw new Error(
|
|
5355
|
-
`\u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
|
|
5356
|
-
);
|
|
5357
|
-
}
|
|
5484
|
+
await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
|
|
5358
5485
|
note(`${LOG_PREFIX2} fetch \u5B8C\u6210`);
|
|
5359
|
-
const currentBranch = await getCurrentBranch(
|
|
5486
|
+
const currentBranch = await getCurrentBranch(gitRoot);
|
|
5360
5487
|
let stashed = false;
|
|
5361
|
-
if (await isWorkingTreeDirty(
|
|
5488
|
+
if (await isWorkingTreeDirty(gitRoot)) {
|
|
5362
5489
|
note(`${LOG_PREFIX2} \u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u5148 stash`);
|
|
5363
|
-
await execGit(
|
|
5490
|
+
await execGit(gitRoot, [
|
|
5364
5491
|
"stash",
|
|
5365
5492
|
"push",
|
|
5366
5493
|
"-u",
|
|
@@ -5371,12 +5498,12 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
|
|
|
5371
5498
|
note(`${LOG_PREFIX2} \u5DF2 stash \u672A\u63D0\u4EA4\u6539\u52A8`);
|
|
5372
5499
|
}
|
|
5373
5500
|
const { merged, conflictRollback } = await mergeBaselineBranch(
|
|
5374
|
-
|
|
5501
|
+
gitRoot,
|
|
5375
5502
|
baselineBranch,
|
|
5376
5503
|
currentBranch,
|
|
5377
5504
|
note
|
|
5378
5505
|
);
|
|
5379
|
-
const stashConflict = await restoreStashIfNeeded(
|
|
5506
|
+
const stashConflict = await restoreStashIfNeeded(gitRoot, stashed, note);
|
|
5380
5507
|
if (conflictRollback || stashConflict) {
|
|
5381
5508
|
note(`${LOG_PREFIX2} \u57FA\u7EBF\u540C\u6B65\u672A\u5B8C\u6210\uFF08\u5B58\u5728\u51B2\u7A81\u5E76\u5DF2\u56DE\u9000\uFF09\uFF0C\u7EE7\u7EED\u6267\u884C\u90E8\u7F72`);
|
|
5382
5509
|
} else if (merged) {
|
|
@@ -8258,14 +8385,23 @@ function buildProgram() {
|
|
|
8258
8385
|
});
|
|
8259
8386
|
const branch = program.command("branch").description("\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>");
|
|
8260
8387
|
branch.command("prune").description(
|
|
8261
|
-
"\
|
|
8262
|
-
).option(
|
|
8263
|
-
"
|
|
8264
|
-
"\
|
|
8265
|
-
).action(async (opts) => {
|
|
8388
|
+
"\u626B\u63CF feat/session-* \u5206\u652F\u5E76\u8F93\u51FA\u72B6\u6001\u8868\uFF1B\u52A0 --mode \u6309\u89C4\u5219\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B\u5206\u652F"
|
|
8389
|
+
).option(
|
|
8390
|
+
"--mode <n>",
|
|
8391
|
+
"\u6E05\u7406\u6A21\u5F0F\uFF1A1=\u5DF2\u5408\u5E76\u6216\u5E73\u53F0\u65E0\u4EFB\u52A1\uFF1B2=\u6A21\u5F0F1+\u4EFB\u52A1\u5DF2\u5B8C\u6210/\u5DF2\u5931\u8D25"
|
|
8392
|
+
).option("--dry-run", "\u914D\u5408 --mode \u4EC5\u9884\u89C8\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
|
|
8393
|
+
const modeRaw = opts.mode?.trim();
|
|
8394
|
+
let mode;
|
|
8395
|
+
if (modeRaw) {
|
|
8396
|
+
if (modeRaw !== "1" && modeRaw !== "2") {
|
|
8397
|
+
console.error("[apm] --mode \u4EC5\u652F\u6301 1 \u6216 2");
|
|
8398
|
+
process.exit(1);
|
|
8399
|
+
}
|
|
8400
|
+
mode = Number(modeRaw);
|
|
8401
|
+
}
|
|
8266
8402
|
await runCleanBranches({
|
|
8267
8403
|
dryRun: opts.dryRun,
|
|
8268
|
-
|
|
8404
|
+
mode
|
|
8269
8405
|
});
|
|
8270
8406
|
});
|
|
8271
8407
|
branch.description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
|