ai-project-manage-cli 6.0.105 → 6.0.107
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 +508 -355
- 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,168 @@ 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
|
-
|
|
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
|
+
CLOSED: "\u5DF2\u5173\u95ED",
|
|
1232
|
+
SUSPENDED: "\u5DF2\u6682\u505C"
|
|
1233
|
+
};
|
|
1234
|
+
function taskStatusLabel(status) {
|
|
1235
|
+
if (status === null) {
|
|
1236
|
+
return "\u5E73\u53F0\u65E0\u4EFB\u52A1";
|
|
1209
1237
|
}
|
|
1238
|
+
return TASK_STATUS_LABEL[status] ?? status;
|
|
1210
1239
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1240
|
+
function syncTagForBranch(hasLocal, hasRemote) {
|
|
1241
|
+
if (hasLocal && hasRemote) {
|
|
1242
|
+
return "\u53CC\u7AEF";
|
|
1243
|
+
}
|
|
1244
|
+
if (hasLocal) {
|
|
1245
|
+
return "\u4EC5\u672C\u5730";
|
|
1246
|
+
}
|
|
1247
|
+
return "\u4EC5\u8FDC\u7A0B";
|
|
1213
1248
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1249
|
+
function shouldDeleteMode1(input) {
|
|
1250
|
+
if (input.merged) {
|
|
1251
|
+
return { delete: true, reason: "\u5DF2\u5408\u5E76" };
|
|
1252
|
+
}
|
|
1253
|
+
if (input.taskStatus === null) {
|
|
1254
|
+
return { delete: true, reason: "\u5E73\u53F0\u65E0\u4EFB\u52A1" };
|
|
1255
|
+
}
|
|
1256
|
+
return { delete: false, reason: "" };
|
|
1216
1257
|
}
|
|
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}`);
|
|
1258
|
+
function shouldDeleteMode2(input) {
|
|
1259
|
+
const mode1 = shouldDeleteMode1(input);
|
|
1260
|
+
if (mode1.delete) {
|
|
1261
|
+
return mode1;
|
|
1224
1262
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
|
|
1263
|
+
if (input.taskStatus === "COMPLETED") {
|
|
1264
|
+
return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5B8C\u6210" };
|
|
1228
1265
|
}
|
|
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
|
-
);
|
|
1266
|
+
if (input.taskStatus === "FAILED") {
|
|
1267
|
+
return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5931\u8D25" };
|
|
1234
1268
|
}
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
async function isBranchBasedOnBaseline(cwd, branch, baselineBranch) {
|
|
1238
|
-
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1239
|
-
try {
|
|
1240
|
-
await execGit3(
|
|
1241
|
-
cwd,
|
|
1242
|
-
["merge-base", "--is-ancestor", `origin/${baselineBranch}`, ref],
|
|
1243
|
-
true
|
|
1244
|
-
);
|
|
1245
|
-
return true;
|
|
1246
|
-
} catch {
|
|
1247
|
-
return false;
|
|
1269
|
+
if (input.taskStatus === "CLOSED") {
|
|
1270
|
+
return { delete: true, reason: "\u4EFB\u52A1\u5DF2\u5173\u95ED" };
|
|
1248
1271
|
}
|
|
1272
|
+
return { delete: false, reason: "" };
|
|
1249
1273
|
}
|
|
1250
|
-
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1274
|
+
function buildSessionBranchRow(input) {
|
|
1275
|
+
const mode1 = shouldDeleteMode1(input);
|
|
1276
|
+
const mode2 = shouldDeleteMode2(input);
|
|
1277
|
+
return {
|
|
1278
|
+
branch: input.branch,
|
|
1279
|
+
sessionId: input.sessionId,
|
|
1280
|
+
hasLocal: input.hasLocal,
|
|
1281
|
+
hasRemote: input.hasRemote,
|
|
1282
|
+
syncTag: syncTagForBranch(input.hasLocal, input.hasRemote),
|
|
1283
|
+
merged: input.merged,
|
|
1284
|
+
taskStatus: input.taskStatus,
|
|
1285
|
+
taskLabel: taskStatusLabel(input.taskStatus),
|
|
1286
|
+
deleteMode1: mode1.delete,
|
|
1287
|
+
deleteMode2: mode2.delete,
|
|
1288
|
+
deleteReasonMode1: mode1.reason,
|
|
1289
|
+
deleteReasonMode2: mode2.reason
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function rowsForDeleteMode(rows, mode) {
|
|
1293
|
+
return rows.filter((row) => mode === 1 ? row.deleteMode1 : row.deleteMode2);
|
|
1294
|
+
}
|
|
1295
|
+
function yn(value) {
|
|
1296
|
+
return value ? "\u662F" : "\u2014";
|
|
1297
|
+
}
|
|
1298
|
+
function pad(value, width) {
|
|
1299
|
+
const chars = [...value];
|
|
1300
|
+
const len = chars.length;
|
|
1301
|
+
if (len >= width) {
|
|
1302
|
+
return value;
|
|
1256
1303
|
}
|
|
1257
|
-
return
|
|
1304
|
+
return value + " ".repeat(width - len);
|
|
1305
|
+
}
|
|
1306
|
+
function formatSessionBranchTable(meta, rows) {
|
|
1307
|
+
const lines = [
|
|
1308
|
+
`[apm] \u5DE5\u4F5C\u76EE\u5F55: ${meta.workdirPath}`,
|
|
1309
|
+
`[apm] Git \u6839\u76EE\u5F55: ${meta.gitRoot}`,
|
|
1310
|
+
`[apm] \u4ED3\u5E93 ID: ${meta.repositoryId}`,
|
|
1311
|
+
`[apm] \u57FA\u7EBF\u5206\u652F: ${meta.baselineBranch}`,
|
|
1312
|
+
""
|
|
1313
|
+
];
|
|
1314
|
+
if (rows.length === 0) {
|
|
1315
|
+
lines.push(
|
|
1316
|
+
`[apm] \u672A\u53D1\u73B0\u4ECE ${meta.baselineBranch} \u884D\u751F\u7684 feat/session-* \u5206\u652F\uFF08\u672C\u5730\u4E0E\u8FDC\u7A0B\uFF09`
|
|
1317
|
+
);
|
|
1318
|
+
return lines.join("\n");
|
|
1319
|
+
}
|
|
1320
|
+
const headers = [
|
|
1321
|
+
"\u5206\u652F",
|
|
1322
|
+
"\u672C\u5730",
|
|
1323
|
+
"\u8FDC\u7A0B",
|
|
1324
|
+
"\u540C\u6B65",
|
|
1325
|
+
"\u5DF2\u5408\u5E76",
|
|
1326
|
+
"\u4EFB\u52A1\u72B6\u6001",
|
|
1327
|
+
"\u6A21\u5F0F1",
|
|
1328
|
+
"\u6A21\u5F0F2"
|
|
1329
|
+
];
|
|
1330
|
+
const widths = [36, 4, 4, 6, 6, 10, 6, 6];
|
|
1331
|
+
lines.push(headers.map((h, i) => pad(h, widths[i])).join(" "));
|
|
1332
|
+
lines.push(widths.map((w) => "-".repeat(w)).join(" "));
|
|
1333
|
+
for (const row of rows) {
|
|
1334
|
+
const cells = [
|
|
1335
|
+
row.branch,
|
|
1336
|
+
yn(row.hasLocal),
|
|
1337
|
+
yn(row.hasRemote),
|
|
1338
|
+
row.syncTag,
|
|
1339
|
+
yn(row.merged),
|
|
1340
|
+
row.taskLabel,
|
|
1341
|
+
row.deleteMode1 ? row.deleteReasonMode1 || "\u662F" : "\u2014",
|
|
1342
|
+
row.deleteMode2 ? row.deleteReasonMode2 || "\u662F" : "\u2014"
|
|
1343
|
+
];
|
|
1344
|
+
lines.push(cells.map((c, i) => pad(c, widths[i])).join(" "));
|
|
1345
|
+
}
|
|
1346
|
+
const mode1Count = rows.filter((r) => r.deleteMode1).length;
|
|
1347
|
+
const mode2Count = rows.filter((r) => r.deleteMode2).length;
|
|
1348
|
+
lines.push("");
|
|
1349
|
+
lines.push(
|
|
1350
|
+
`[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`
|
|
1351
|
+
);
|
|
1352
|
+
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");
|
|
1353
|
+
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");
|
|
1354
|
+
lines.push("[apm] \u6267\u884C\u6E05\u7406: apm branch prune --mode 1|2 [--dry-run]");
|
|
1355
|
+
return lines.join("\n");
|
|
1258
1356
|
}
|
|
1259
1357
|
async function listLocalSessionBranches(cwd) {
|
|
1260
|
-
const out = await
|
|
1358
|
+
const out = await execGit(
|
|
1261
1359
|
cwd,
|
|
1262
1360
|
[
|
|
1263
1361
|
"for-each-ref",
|
|
1264
1362
|
"--format=%(refname:short)",
|
|
1265
1363
|
"refs/heads/",
|
|
1266
|
-
SESSION_BRANCH_PREFIX
|
|
1364
|
+
`${SESSION_BRANCH_PREFIX}*`
|
|
1267
1365
|
],
|
|
1268
1366
|
true
|
|
1269
1367
|
);
|
|
1270
1368
|
return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1271
1369
|
}
|
|
1272
1370
|
async function listRemoteSessionBranches(cwd) {
|
|
1273
|
-
const out = await
|
|
1371
|
+
const out = await execGit(
|
|
1274
1372
|
cwd,
|
|
1275
1373
|
[
|
|
1276
1374
|
"for-each-ref",
|
|
1277
1375
|
"--format=%(refname:short)",
|
|
1278
1376
|
"refs/remotes/origin/",
|
|
1279
|
-
SESSION_BRANCH_PREFIX
|
|
1377
|
+
`${SESSION_BRANCH_PREFIX}*`
|
|
1280
1378
|
],
|
|
1281
1379
|
true
|
|
1282
1380
|
);
|
|
@@ -1284,7 +1382,7 @@ async function listRemoteSessionBranches(cwd) {
|
|
|
1284
1382
|
}
|
|
1285
1383
|
async function localBranchExists2(cwd, branch) {
|
|
1286
1384
|
try {
|
|
1287
|
-
await
|
|
1385
|
+
await execGit(
|
|
1288
1386
|
cwd,
|
|
1289
1387
|
["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
1290
1388
|
true
|
|
@@ -1294,29 +1392,25 @@ async function localBranchExists2(cwd, branch) {
|
|
|
1294
1392
|
return false;
|
|
1295
1393
|
}
|
|
1296
1394
|
}
|
|
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";
|
|
1395
|
+
async function isBranchBasedOnBaseline(cwd, branch, baselineBranch) {
|
|
1396
|
+
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1397
|
+
try {
|
|
1398
|
+
await execGit(
|
|
1399
|
+
cwd,
|
|
1400
|
+
["merge-base", "--is-ancestor", `origin/${baselineBranch}`, ref],
|
|
1401
|
+
true
|
|
1402
|
+
);
|
|
1403
|
+
return true;
|
|
1404
|
+
} catch {
|
|
1405
|
+
return false;
|
|
1311
1406
|
}
|
|
1312
|
-
return "\u4FDD\u7559";
|
|
1313
1407
|
}
|
|
1314
|
-
async function
|
|
1408
|
+
async function isBranchMergedIntoBaseline(cwd, branch, baselineBranch) {
|
|
1315
1409
|
const ref = await localBranchExists2(cwd, branch) ? branch : `origin/${branch}`;
|
|
1316
1410
|
try {
|
|
1317
|
-
await
|
|
1411
|
+
await execGit(
|
|
1318
1412
|
cwd,
|
|
1319
|
-
["merge-base", "--is-ancestor", ref, `origin/${
|
|
1413
|
+
["merge-base", "--is-ancestor", ref, `origin/${baselineBranch}`],
|
|
1320
1414
|
true
|
|
1321
1415
|
);
|
|
1322
1416
|
return true;
|
|
@@ -1324,79 +1418,129 @@ async function isBranchMergedIntoDefault(cwd, branch, defaultBranch) {
|
|
|
1324
1418
|
return false;
|
|
1325
1419
|
}
|
|
1326
1420
|
}
|
|
1421
|
+
async function resolveSessionBranchScanMeta(cwd, api) {
|
|
1422
|
+
const workdirPath = resolveWorkdirPath(cwd);
|
|
1423
|
+
const baseline = await resolveWorkspaceBaseline(api, workdirPath);
|
|
1424
|
+
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
1425
|
+
await ensureGitRepo(gitRoot);
|
|
1426
|
+
await execGit(gitRoot, ["fetch", "--prune", "origin"], true);
|
|
1427
|
+
await ensureRemoteBaselineBranch(gitRoot, baseline.defaultBranch);
|
|
1428
|
+
return {
|
|
1429
|
+
workdirPath,
|
|
1430
|
+
gitRoot,
|
|
1431
|
+
baselineBranch: baseline.defaultBranch,
|
|
1432
|
+
repositoryId: baseline.repositoryId
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
async function scanSessionBranches(meta, sessionStatusById) {
|
|
1436
|
+
const { gitRoot, baselineBranch } = meta;
|
|
1437
|
+
const names = /* @__PURE__ */ new Set();
|
|
1438
|
+
for (const branch of await listLocalSessionBranches(gitRoot)) {
|
|
1439
|
+
names.add(branch);
|
|
1440
|
+
}
|
|
1441
|
+
for (const branch of await listRemoteSessionBranches(gitRoot)) {
|
|
1442
|
+
names.add(branch);
|
|
1443
|
+
}
|
|
1444
|
+
const rows = [];
|
|
1445
|
+
for (const branch of [...names].sort((a, b) => a.localeCompare(b))) {
|
|
1446
|
+
if (!await isBranchBasedOnBaseline(gitRoot, branch, baselineBranch)) {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const sessionId = sessionIdFromBranchName(branch);
|
|
1450
|
+
if (!sessionId) {
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
const hasLocal = await localBranchExists2(gitRoot, branch);
|
|
1454
|
+
const hasRemote = await remoteBranchExists(gitRoot, branch);
|
|
1455
|
+
const merged = await isBranchMergedIntoBaseline(
|
|
1456
|
+
gitRoot,
|
|
1457
|
+
branch,
|
|
1458
|
+
baselineBranch
|
|
1459
|
+
);
|
|
1460
|
+
const rawStatus = sessionStatusById.get(sessionId);
|
|
1461
|
+
const taskStatus = rawStatus ? rawStatus : null;
|
|
1462
|
+
rows.push(
|
|
1463
|
+
buildSessionBranchRow({
|
|
1464
|
+
branch,
|
|
1465
|
+
sessionId,
|
|
1466
|
+
hasLocal,
|
|
1467
|
+
hasRemote,
|
|
1468
|
+
merged,
|
|
1469
|
+
taskStatus
|
|
1470
|
+
})
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
return rows;
|
|
1474
|
+
}
|
|
1475
|
+
async function deleteSessionBranch(gitRoot, baselineBranch, branch, currentBranchRef) {
|
|
1476
|
+
if (currentBranchRef.value === branch) {
|
|
1477
|
+
await execGit(gitRoot, ["checkout", baselineBranch], true);
|
|
1478
|
+
currentBranchRef.value = baselineBranch;
|
|
1479
|
+
}
|
|
1480
|
+
if (await localBranchExists2(gitRoot, branch)) {
|
|
1481
|
+
await execGit(gitRoot, ["branch", "-D", branch], true);
|
|
1482
|
+
console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
|
|
1483
|
+
}
|
|
1484
|
+
if (await remoteBranchExists(gitRoot, branch)) {
|
|
1485
|
+
await execGit(gitRoot, ["push", "origin", "--delete", branch], true);
|
|
1486
|
+
console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async function loadSessionStatusMap(api) {
|
|
1490
|
+
const { sessions } = await api.cli.listSessionsForBranchCleanup({});
|
|
1491
|
+
return new Map(sessions.map((item) => [item.sessionId, item.taskStatus]));
|
|
1492
|
+
}
|
|
1493
|
+
async function getGitCurrentBranch(gitRoot) {
|
|
1494
|
+
return getCurrentBranch(gitRoot);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/commands/clean-branches.ts
|
|
1327
1498
|
async function runCleanBranches(options = {}) {
|
|
1328
1499
|
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
1500
|
const cfg = await ensureLoggedConfig();
|
|
1334
1501
|
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
|
-
);
|
|
1502
|
+
const meta = await resolveSessionBranchScanMeta(cwd, api);
|
|
1503
|
+
const sessionStatusById = await loadSessionStatusMap(api);
|
|
1504
|
+
const rows = await scanSessionBranches(meta, sessionStatusById);
|
|
1505
|
+
console.log(formatSessionBranchTable(meta, rows));
|
|
1506
|
+
if (!options.mode) {
|
|
1351
1507
|
return;
|
|
1352
1508
|
}
|
|
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));
|
|
1509
|
+
const toDelete = rowsForDeleteMode(rows, options.mode);
|
|
1364
1510
|
if (toDelete.length === 0) {
|
|
1365
|
-
console.log(
|
|
1511
|
+
console.log(`[apm] \u6A21\u5F0F ${options.mode} \u4E0B\u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684\u5206\u652F`);
|
|
1366
1512
|
return;
|
|
1367
1513
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1514
|
+
if (options.dryRun) {
|
|
1515
|
+
for (const row of toDelete) {
|
|
1516
|
+
const reason = options.mode === 1 ? row.deleteReasonMode1 : row.deleteReasonMode2;
|
|
1517
|
+
const parts = [
|
|
1518
|
+
row.hasLocal ? "\u672C\u5730" : null,
|
|
1519
|
+
row.hasRemote ? "\u8FDC\u7A0B" : null
|
|
1520
|
+
].filter(Boolean).join("+");
|
|
1521
|
+
console.log(
|
|
1522
|
+
`[apm] [dry-run] \u6A21\u5F0F ${options.mode} \u5C06\u5220\u9664 ${row.branch}\uFF08${parts || "\u65E0"}\uFF1B${reason}\uFF09`
|
|
1377
1523
|
);
|
|
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
1524
|
}
|
|
1525
|
+
console.log(
|
|
1526
|
+
`[apm] [dry-run] \u6A21\u5F0F ${options.mode} \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`
|
|
1527
|
+
);
|
|
1528
|
+
return;
|
|
1394
1529
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1530
|
+
const currentBranchRef = {
|
|
1531
|
+
value: await getGitCurrentBranch(meta.gitRoot)
|
|
1532
|
+
};
|
|
1533
|
+
for (const row of toDelete) {
|
|
1534
|
+
await deleteSessionBranch(
|
|
1535
|
+
meta.gitRoot,
|
|
1536
|
+
meta.baselineBranch,
|
|
1537
|
+
row.branch,
|
|
1538
|
+
currentBranchRef
|
|
1539
|
+
);
|
|
1399
1540
|
}
|
|
1541
|
+
console.log(
|
|
1542
|
+
`[apm] \u6A21\u5F0F ${options.mode} \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`
|
|
1543
|
+
);
|
|
1400
1544
|
}
|
|
1401
1545
|
|
|
1402
1546
|
// src/commands/pull.ts
|
|
@@ -1649,7 +1793,7 @@ function isRuleUpToDate(entry, rule, dest) {
|
|
|
1649
1793
|
}
|
|
1650
1794
|
async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
1651
1795
|
const api = createApmApiClient(cfg);
|
|
1652
|
-
const baseline = await api
|
|
1796
|
+
const baseline = await resolveBranchBaseline(api, sessionId, workdirPath);
|
|
1653
1797
|
const repositoryId = baseline.repositoryId;
|
|
1654
1798
|
const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
1655
1799
|
await ensureDirExists(rulesDir);
|
|
@@ -2881,15 +3025,25 @@ function posixBasename(p) {
|
|
|
2881
3025
|
}
|
|
2882
3026
|
|
|
2883
3027
|
// src/commands/deploy/internal/deploy-hints.ts
|
|
3028
|
+
function isProductionDeployEnv(env) {
|
|
3029
|
+
const normalized = env.trim().toLowerCase();
|
|
3030
|
+
return normalized === "online" || normalized === "production" || normalized === "prod";
|
|
3031
|
+
}
|
|
2884
3032
|
function formatMissingWisdomDeployLines() {
|
|
2885
3033
|
return [
|
|
2886
3034
|
"[apm] \u65E0\u6CD5\u6267\u884C apm deploy\uFF1A\u8BF7\u5728 apm.config.json \u4E2D\u914D\u7F6E wisdomDeploy\uFF08host\u3001remotePath\uFF09"
|
|
2887
3035
|
];
|
|
2888
3036
|
}
|
|
2889
3037
|
function formatWisdomFrontendBuildNotConfiguredLines(env, packageJsonPath) {
|
|
3038
|
+
if (isProductionDeployEnv(env)) {
|
|
3039
|
+
return [
|
|
3040
|
+
`[apm] \u65E0\u6CD5\u6267\u884C\u524D\u7AEF\u90E8\u7F72\uFF1A${packageJsonPath} \u4E2D\u672A\u627E\u5230 build:${env} \u6216 build \u811A\u672C`,
|
|
3041
|
+
"[apm] \u8BF7\u914D\u7F6E npm run build:online \u6216 npm run build \u540E\u518D\u8BD5"
|
|
3042
|
+
];
|
|
3043
|
+
}
|
|
2890
3044
|
return [
|
|
2891
3045
|
`[apm] \u65E0\u6CD5\u6267\u884C\u524D\u7AEF\u90E8\u7F72\uFF1A${packageJsonPath} \u4E2D\u672A\u627E\u5230 build:${env} \u811A\u672C`,
|
|
2892
|
-
"[apm] \u8BF7\u914D\u7F6E npm run build:test
|
|
3046
|
+
"[apm] \u8BF7\u914D\u7F6E npm run build:test \u540E\u518D\u8BD5"
|
|
2893
3047
|
];
|
|
2894
3048
|
}
|
|
2895
3049
|
|
|
@@ -3054,8 +3208,8 @@ var MinioClient = class {
|
|
|
3054
3208
|
// src/commands/deploy/internal/deploy-artifact-minio.ts
|
|
3055
3209
|
var APM_DEPLOYMENT_RUN_ID_ENV = "APM_DEPLOYMENT_RUN_ID";
|
|
3056
3210
|
function formatDeployArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
3057
|
-
const
|
|
3058
|
-
return `${date.getFullYear()}${
|
|
3211
|
+
const pad2 = (n) => String(n).padStart(2, "0");
|
|
3212
|
+
return `${date.getFullYear()}${pad2(date.getMonth() + 1)}${pad2(date.getDate())}${pad2(date.getHours())}${pad2(date.getMinutes())}${pad2(date.getSeconds())}`;
|
|
3059
3213
|
}
|
|
3060
3214
|
function sanitizeDeployProjectName(name) {
|
|
3061
3215
|
const trimmed = name.trim().replace(/\\/g, "/");
|
|
@@ -5144,6 +5298,9 @@ function resolveFrontendBuildCommand(env, cwd) {
|
|
|
5144
5298
|
if (scripts[buildKey]?.trim()) {
|
|
5145
5299
|
return `npm run build:${env}`;
|
|
5146
5300
|
}
|
|
5301
|
+
if (isProductionDeployEnv(env) && scripts.build?.trim()) {
|
|
5302
|
+
return "npm run build";
|
|
5303
|
+
}
|
|
5147
5304
|
return null;
|
|
5148
5305
|
}
|
|
5149
5306
|
function resolveFrontendDistDir(cwd) {
|
|
@@ -5254,19 +5411,10 @@ var LOG_PREFIX2 = "[apm] \u90E8\u7F72\u524D\u57FA\u7EBF\u540C\u6B65:";
|
|
|
5254
5411
|
function formatGitError(error) {
|
|
5255
5412
|
return error instanceof Error ? error.message : String(error);
|
|
5256
5413
|
}
|
|
5257
|
-
async function
|
|
5414
|
+
async function resolveBaselineBranch(cwd, api) {
|
|
5258
5415
|
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;
|
|
5416
|
+
const baseline = await resolveWorkspaceBaseline(api, workdirPath);
|
|
5417
|
+
return baseline.defaultBranch;
|
|
5270
5418
|
}
|
|
5271
5419
|
async function isMergeInProgress(cwd) {
|
|
5272
5420
|
try {
|
|
@@ -5346,21 +5494,17 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
|
|
|
5346
5494
|
}
|
|
5347
5495
|
await ensureGitRepo(cwd);
|
|
5348
5496
|
const api = createApmApiClient(cfg);
|
|
5349
|
-
const baselineBranch = await
|
|
5497
|
+
const baselineBranch = await resolveBaselineBranch(cwd, api);
|
|
5498
|
+
const gitRoot = await resolveGitRepoRoot(cwd);
|
|
5350
5499
|
note(`${LOG_PREFIX2} \u57FA\u7EBF\u5206\u652F ${baselineBranch}`);
|
|
5351
5500
|
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
|
-
}
|
|
5501
|
+
await ensureRemoteBaselineBranch(gitRoot, baselineBranch);
|
|
5358
5502
|
note(`${LOG_PREFIX2} fetch \u5B8C\u6210`);
|
|
5359
|
-
const currentBranch = await getCurrentBranch(
|
|
5503
|
+
const currentBranch = await getCurrentBranch(gitRoot);
|
|
5360
5504
|
let stashed = false;
|
|
5361
|
-
if (await isWorkingTreeDirty(
|
|
5505
|
+
if (await isWorkingTreeDirty(gitRoot)) {
|
|
5362
5506
|
note(`${LOG_PREFIX2} \u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u5148 stash`);
|
|
5363
|
-
await execGit(
|
|
5507
|
+
await execGit(gitRoot, [
|
|
5364
5508
|
"stash",
|
|
5365
5509
|
"push",
|
|
5366
5510
|
"-u",
|
|
@@ -5371,12 +5515,12 @@ async function syncDeployBaselineBeforeDeploy(cwd) {
|
|
|
5371
5515
|
note(`${LOG_PREFIX2} \u5DF2 stash \u672A\u63D0\u4EA4\u6539\u52A8`);
|
|
5372
5516
|
}
|
|
5373
5517
|
const { merged, conflictRollback } = await mergeBaselineBranch(
|
|
5374
|
-
|
|
5518
|
+
gitRoot,
|
|
5375
5519
|
baselineBranch,
|
|
5376
5520
|
currentBranch,
|
|
5377
5521
|
note
|
|
5378
5522
|
);
|
|
5379
|
-
const stashConflict = await restoreStashIfNeeded(
|
|
5523
|
+
const stashConflict = await restoreStashIfNeeded(gitRoot, stashed, note);
|
|
5380
5524
|
if (conflictRollback || stashConflict) {
|
|
5381
5525
|
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
5526
|
} else if (merged) {
|
|
@@ -8258,14 +8402,23 @@ function buildProgram() {
|
|
|
8258
8402
|
});
|
|
8259
8403
|
const branch = program.command("branch").description("\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>");
|
|
8260
8404
|
branch.command("prune").description(
|
|
8261
|
-
"\
|
|
8262
|
-
).option(
|
|
8263
|
-
"
|
|
8264
|
-
"\
|
|
8265
|
-
).action(async (opts) => {
|
|
8405
|
+
"\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"
|
|
8406
|
+
).option(
|
|
8407
|
+
"--mode <n>",
|
|
8408
|
+
"\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"
|
|
8409
|
+
).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) => {
|
|
8410
|
+
const modeRaw = opts.mode?.trim();
|
|
8411
|
+
let mode;
|
|
8412
|
+
if (modeRaw) {
|
|
8413
|
+
if (modeRaw !== "1" && modeRaw !== "2") {
|
|
8414
|
+
console.error("[apm] --mode \u4EC5\u652F\u6301 1 \u6216 2");
|
|
8415
|
+
process.exit(1);
|
|
8416
|
+
}
|
|
8417
|
+
mode = Number(modeRaw);
|
|
8418
|
+
}
|
|
8266
8419
|
await runCleanBranches({
|
|
8267
8420
|
dryRun: opts.dryRun,
|
|
8268
|
-
|
|
8421
|
+
mode
|
|
8269
8422
|
});
|
|
8270
8423
|
});
|
|
8271
8424
|
branch.description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
|