automata-cli 0.8.0-develop.331 → 0.8.0-develop.353
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/README.md +2 -2
- package/dist/{ConfigWizard-D6NYL2E6.js → ConfigWizard-GULICHML.js} +19 -1
- package/dist/index.js +367 -59
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -74,12 +74,12 @@ Checks that the PR exists and is merged, the working tree is clean, and the remo
|
|
|
74
74
|
Execute the full GitFlow release sequence and push to `origin`. Only requires `git`.
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
automata git publish-release # auto-detect version from
|
|
77
|
+
automata git publish-release # auto-detect version from the trunk tag
|
|
78
78
|
automata git publish-release 2.0.0 # explicit version
|
|
79
79
|
automata git publish-release --dry-run # preview without executing
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
When no version is given, the latest semver tag on `
|
|
82
|
+
When no version is given, the trunk branch is resolved from `origin` (`main`, `master` or whatever the remote calls it), tags are fetched, and the latest semver tag on `origin/<trunk>` is detected and the minor segment incremented (e.g. `1.2.0 → 1.3.0`). See [docs/git.md](docs/git.md#automata-git-publish-release).
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
@@ -36,7 +36,7 @@ var EXECUTOR_OPTIONS = [
|
|
|
36
36
|
{ label: "Claude Code", value: "claude" },
|
|
37
37
|
{ label: "Codex", value: "codex" }
|
|
38
38
|
];
|
|
39
|
-
var MAIN_MENU_OPTIONS = ["Remote / Mode", "Implement-Next", "Prompts", "Issue Watch", "Do Work"];
|
|
39
|
+
var MAIN_MENU_OPTIONS = ["Remote / Mode", "Implement-Next", "Prompts", "Issue Watch", "Do Work", "Git"];
|
|
40
40
|
var PROMPTS_MENU_OPTIONS = [
|
|
41
41
|
"Sonar",
|
|
42
42
|
"Fix-Comments",
|
|
@@ -173,6 +173,7 @@ function ConfigWizard() {
|
|
|
173
173
|
const [doWorkPrOrphanPrompt, setDoWorkPrOrphanPrompt] = useState(
|
|
174
174
|
existing.doWork?.prompts?.prOrphan ?? DEFAULT_DO_WORK_PR_ORPHAN_PROMPT
|
|
175
175
|
);
|
|
176
|
+
const [gitTrunkBranch, setGitTrunkBranch] = useState(existing.git?.trunkBranch ?? "");
|
|
176
177
|
const [pendingRemote, setPendingRemote] = useState(existing.remoteType ?? "gh");
|
|
177
178
|
const [pendingTechnique, setPendingTechnique] = useState(
|
|
178
179
|
existing.issueDiscoveryTechnique ?? "label"
|
|
@@ -379,6 +380,16 @@ function ConfigWizard() {
|
|
|
379
380
|
setScreen("prompts-menu");
|
|
380
381
|
},
|
|
381
382
|
onBack: () => setScreen("prompts-menu")
|
|
383
|
+
},
|
|
384
|
+
"git-trunk-branch": {
|
|
385
|
+
setValue: setGitTrunkBranch,
|
|
386
|
+
onSubmit: () => {
|
|
387
|
+
const branch = gitTrunkBranch.trim();
|
|
388
|
+
const current = readRawConfig();
|
|
389
|
+
writeConfig({ ...current, git: { ...current.git, trunkBranch: branch || void 0 } });
|
|
390
|
+
exit();
|
|
391
|
+
},
|
|
392
|
+
onBack: () => setScreen("main")
|
|
382
393
|
}
|
|
383
394
|
};
|
|
384
395
|
const menus = {
|
|
@@ -392,6 +403,7 @@ function ConfigWizard() {
|
|
|
392
403
|
else if (chosen === "Implement-Next") setScreen("technique");
|
|
393
404
|
else if (chosen === "Issue Watch") setScreen("allowed-users");
|
|
394
405
|
else if (chosen === "Do Work") setScreen("do-work-base-branch");
|
|
406
|
+
else if (chosen === "Git") setScreen("git-trunk-branch");
|
|
395
407
|
else setScreen("prompts-menu");
|
|
396
408
|
}
|
|
397
409
|
},
|
|
@@ -550,6 +562,12 @@ function ConfigWizard() {
|
|
|
550
562
|
label: "Instructions for a pull request with no linked issue:",
|
|
551
563
|
value: doWorkPrOrphanPrompt,
|
|
552
564
|
hint: `Type prompt \xB7 Enter to save \xB7 ${BACK}`
|
|
565
|
+
},
|
|
566
|
+
"git-trunk-branch": {
|
|
567
|
+
title: "Git \u2014 Trunk Branch",
|
|
568
|
+
label: "Branch publish-release releases to (blank = detect it from the remote):",
|
|
569
|
+
value: gitTrunkBranch,
|
|
570
|
+
hint: `Type branch \xB7 Enter to save \xB7 ${BACK}`
|
|
553
571
|
}
|
|
554
572
|
};
|
|
555
573
|
const menuViews = {
|
package/dist/index.js
CHANGED
|
@@ -214,12 +214,23 @@ var configSetDoWorkPrompt = new Command("do-work-prompt").description("Set the t
|
|
|
214
214
|
process.stdout.write(`do-work ${turnKind} prompt set.
|
|
215
215
|
`);
|
|
216
216
|
});
|
|
217
|
-
var
|
|
217
|
+
var configSetGitTrunkBranch = new Command("git-trunk-branch").description("Pin the branch `publish-release` releases to, instead of detecting it").argument("<value>", "Branch name (unset = detect it from the remote)").action((value) => {
|
|
218
|
+
const branch = value.trim();
|
|
219
|
+
if (branch.length === 0) {
|
|
220
|
+
process.stderr.write("Error: git-trunk-branch requires a non-empty branch name.\n");
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
const current = readRawConfig();
|
|
224
|
+
writeConfig({ ...current, git: { ...current.git, trunkBranch: branch } });
|
|
225
|
+
process.stdout.write(`git trunk branch set to: ${branch}
|
|
226
|
+
`);
|
|
227
|
+
});
|
|
228
|
+
var configSet = new Command("set").description("Set a configuration value").addCommand(configSetType).addCommand(configSetIssueDiscoveryTechnique).addCommand(configSetIssueDiscoveryValue).addCommand(configSetClaudeSystemPrompt).addCommand(configSetAllowedUsers).addCommand(configSetAgentUser).addCommand(configSetDoWorkBaseBranch).addCommand(configSetDoWorkProtectedBranches).addCommand(configSetDoWorkExecutor).addCommand(configSetDoWorkModel).addCommand(configSetDoWorkEffort).addCommand(configSetDoWorkMaxRuns).addCommand(configSetDoWorkLockStaleMinutes).addCommand(configSetDoWorkPrompt).addCommand(configSetGitTrunkBranch);
|
|
218
229
|
var configCommand = new Command("config").description("Configure automata settings").addCommand(configSet).action(async () => {
|
|
219
230
|
const [{ render }, React, { ConfigWizard }] = await Promise.all([
|
|
220
231
|
import("ink"),
|
|
221
232
|
import("react"),
|
|
222
|
-
import("./ConfigWizard-
|
|
233
|
+
import("./ConfigWizard-GULICHML.js")
|
|
223
234
|
]);
|
|
224
235
|
const { waitUntilExit } = render(React.createElement(ConfigWizard));
|
|
225
236
|
await waitUntilExit();
|
|
@@ -230,6 +241,7 @@ import { Command as Command2 } from "commander";
|
|
|
230
241
|
|
|
231
242
|
// src/git/gitService.ts
|
|
232
243
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
244
|
+
import { existsSync } from "fs";
|
|
233
245
|
|
|
234
246
|
// src/config/azdoService.ts
|
|
235
247
|
import { spawnSync } from "child_process";
|
|
@@ -279,6 +291,44 @@ function getPrInfo() {
|
|
|
279
291
|
};
|
|
280
292
|
}
|
|
281
293
|
|
|
294
|
+
// src/git/trunkDetection.ts
|
|
295
|
+
var TRUNK_CANDIDATES = ["main", "master"];
|
|
296
|
+
var TRUNK_BRANCH_CONFIG_KEY = "git.trunkBranch";
|
|
297
|
+
var ORIGIN_HEAD_PREFIX = "refs/remotes/origin/";
|
|
298
|
+
var SYMREF_PREFIX = "ref: refs/heads/";
|
|
299
|
+
function parseOriginHeadRef(stdout) {
|
|
300
|
+
const ref = stdout.trim();
|
|
301
|
+
if (!ref.startsWith(ORIGIN_HEAD_PREFIX)) return null;
|
|
302
|
+
const branch = ref.slice(ORIGIN_HEAD_PREFIX.length);
|
|
303
|
+
return branch.length > 0 ? branch : null;
|
|
304
|
+
}
|
|
305
|
+
function parseLsRemoteSymref(stdout) {
|
|
306
|
+
for (const line of stdout.split("\n")) {
|
|
307
|
+
const trimmed2 = line.trim();
|
|
308
|
+
if (!trimmed2.startsWith(SYMREF_PREFIX)) continue;
|
|
309
|
+
const branch = trimmed2.slice(SYMREF_PREFIX.length).split(/\s+/)[0];
|
|
310
|
+
if (branch.length > 0) return branch;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function describeTrunkSource(source, branch) {
|
|
315
|
+
switch (source) {
|
|
316
|
+
case "config":
|
|
317
|
+
return `configured as ${TRUNK_BRANCH_CONFIG_KEY}`;
|
|
318
|
+
case "origin-head":
|
|
319
|
+
return "from origin/HEAD";
|
|
320
|
+
case "ls-remote":
|
|
321
|
+
return "from the remote's advertised HEAD";
|
|
322
|
+
case "probe":
|
|
323
|
+
return `probed as origin/${branch}`;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function unresolvedTrunkMessage(attempted) {
|
|
327
|
+
return `Could not determine the trunk branch of 'origin'.
|
|
328
|
+
Tried: ${attempted.join(", ")}.
|
|
329
|
+
Set it explicitly with: automata config set git-trunk-branch <branch>`;
|
|
330
|
+
}
|
|
331
|
+
|
|
282
332
|
// src/git/gitService.ts
|
|
283
333
|
function run2(cmd, args) {
|
|
284
334
|
const result = spawnSync2(cmd, args, { encoding: "utf8" });
|
|
@@ -752,7 +802,7 @@ function checkoutAndPull(targetBranch) {
|
|
|
752
802
|
if (checkout.status !== 0) {
|
|
753
803
|
throw new Error(`Failed to checkout ${targetBranch}: ${checkout.stderr.trim()}`);
|
|
754
804
|
}
|
|
755
|
-
const pull = run2("git", ["pull"]);
|
|
805
|
+
const pull = run2("git", ["pull", "--ff-only"]);
|
|
756
806
|
if (pull.status !== 0) {
|
|
757
807
|
throw new Error(`Failed to pull ${targetBranch}: ${pull.stderr.trim()}`);
|
|
758
808
|
}
|
|
@@ -785,6 +835,41 @@ function isAncestorCommit(maybeAncestor, descendant) {
|
|
|
785
835
|
function resetHardTo(ref) {
|
|
786
836
|
return gitCommand(["reset", "--hard", ref]);
|
|
787
837
|
}
|
|
838
|
+
var CHERRY_LINE = /^([+-]) ([0-9a-f]+)$/;
|
|
839
|
+
function describeDivergence(upstream, head) {
|
|
840
|
+
const cherry = run2("git", ["cherry", upstream, head]);
|
|
841
|
+
if (cherry.status !== 0) return null;
|
|
842
|
+
const commits = [];
|
|
843
|
+
for (const line of cherry.stdout.split("\n")) {
|
|
844
|
+
const trimmed2 = line.trim();
|
|
845
|
+
if (trimmed2.length === 0) continue;
|
|
846
|
+
const match = CHERRY_LINE.exec(trimmed2);
|
|
847
|
+
if (match === null) return null;
|
|
848
|
+
commits.push({ sha: match[2], alreadyUpstream: match[1] === "-" });
|
|
849
|
+
}
|
|
850
|
+
const merges = run2("git", ["rev-list", "--count", "--merges", `${upstream}..${head}`]);
|
|
851
|
+
if (merges.status !== 0) return null;
|
|
852
|
+
const count = Number.parseInt(merges.stdout.trim(), 10);
|
|
853
|
+
if (!Number.isInteger(count)) return null;
|
|
854
|
+
return { commits, merges: count };
|
|
855
|
+
}
|
|
856
|
+
function rebaseOnto(ref) {
|
|
857
|
+
const { stdout, stderr, status } = run2("git", ["rebase", "--no-reapply-cherry-picks", ref]);
|
|
858
|
+
const detail = [stdout.trim(), stderr.trim()].filter((part) => part.length > 0).join("\n");
|
|
859
|
+
return { ok: status === 0, stderr: detail };
|
|
860
|
+
}
|
|
861
|
+
function abortRebase() {
|
|
862
|
+
return gitCommand(["rebase", "--abort"]);
|
|
863
|
+
}
|
|
864
|
+
var REBASE_STATE_PATHS = ["rebase-merge", "rebase-apply/rebasing"];
|
|
865
|
+
function isRebaseInProgress() {
|
|
866
|
+
return REBASE_STATE_PATHS.some((name) => {
|
|
867
|
+
const { stdout, status } = run2("git", ["rev-parse", "--git-path", name]);
|
|
868
|
+
if (status !== 0) return false;
|
|
869
|
+
const path = stdout.trim();
|
|
870
|
+
return path.length > 0 && existsSync(path);
|
|
871
|
+
});
|
|
872
|
+
}
|
|
788
873
|
function fetchPrune() {
|
|
789
874
|
const result = run2("git", ["fetch", "--prune"]);
|
|
790
875
|
if (result.status !== 0) {
|
|
@@ -927,7 +1012,63 @@ function resolveCurrentBranchComments() {
|
|
|
927
1012
|
return { ok: true, branch, comments: raw };
|
|
928
1013
|
}
|
|
929
1014
|
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
930
|
-
function
|
|
1015
|
+
function remoteBranchExists(branch) {
|
|
1016
|
+
return !isUpstreamGone(branch);
|
|
1017
|
+
}
|
|
1018
|
+
function resolveTrunkBranch() {
|
|
1019
|
+
const configured = readRawConfig().git?.trunkBranch?.trim();
|
|
1020
|
+
if (configured) {
|
|
1021
|
+
return { ok: true, branch: configured, source: "config" };
|
|
1022
|
+
}
|
|
1023
|
+
const attempted = [];
|
|
1024
|
+
attempted.push("origin/HEAD");
|
|
1025
|
+
const head = run2("git", ["symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"]);
|
|
1026
|
+
if (head.status === 0) {
|
|
1027
|
+
const branch = parseOriginHeadRef(head.stdout);
|
|
1028
|
+
if (branch !== null) return { ok: true, branch, source: "origin-head" };
|
|
1029
|
+
}
|
|
1030
|
+
attempted.push("git ls-remote --symref origin HEAD");
|
|
1031
|
+
const symref = run2("git", ["ls-remote", "--symref", "origin", "HEAD"]);
|
|
1032
|
+
if (symref.status === 0) {
|
|
1033
|
+
const branch = parseLsRemoteSymref(symref.stdout);
|
|
1034
|
+
if (branch !== null) return { ok: true, branch, source: "ls-remote" };
|
|
1035
|
+
}
|
|
1036
|
+
for (const candidate of TRUNK_CANDIDATES) {
|
|
1037
|
+
attempted.push(`origin/${candidate}`);
|
|
1038
|
+
if (remoteBranchExists(candidate)) {
|
|
1039
|
+
return { ok: true, branch: candidate, source: "probe" };
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return { ok: false, attempted };
|
|
1043
|
+
}
|
|
1044
|
+
function fetchTrunkAndTags(trunk) {
|
|
1045
|
+
const { status, stderr } = run2("git", [
|
|
1046
|
+
"fetch",
|
|
1047
|
+
"--tags",
|
|
1048
|
+
"origin",
|
|
1049
|
+
`+refs/heads/${trunk}:refs/remotes/origin/${trunk}`
|
|
1050
|
+
]);
|
|
1051
|
+
if (status !== 0) {
|
|
1052
|
+
return { ok: false, message: stderr.trim() || `git fetch --tags origin ${trunk} failed.` };
|
|
1053
|
+
}
|
|
1054
|
+
return { ok: true };
|
|
1055
|
+
}
|
|
1056
|
+
function localBranchExists(branch) {
|
|
1057
|
+
const { status } = run2("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
1058
|
+
return status === 0;
|
|
1059
|
+
}
|
|
1060
|
+
function trunkBehindCount(trunk) {
|
|
1061
|
+
if (!localBranchExists(trunk)) return 0;
|
|
1062
|
+
const { stdout, status } = run2("git", [
|
|
1063
|
+
"rev-list",
|
|
1064
|
+
"--count",
|
|
1065
|
+
`refs/heads/${trunk}..refs/remotes/origin/${trunk}`
|
|
1066
|
+
]);
|
|
1067
|
+
if (status !== 0) return 0;
|
|
1068
|
+
const count = Number.parseInt(stdout.trim(), 10);
|
|
1069
|
+
return Number.isNaN(count) ? 0 : count;
|
|
1070
|
+
}
|
|
1071
|
+
function getLatestTagOnTrunk(ref) {
|
|
931
1072
|
const { stdout, status } = run2("git", [
|
|
932
1073
|
"describe",
|
|
933
1074
|
"--tags",
|
|
@@ -936,7 +1077,7 @@ function getLatestTagOnMaster() {
|
|
|
936
1077
|
"[0-9]*.[0-9]*.[0-9]*",
|
|
937
1078
|
"--match",
|
|
938
1079
|
"v[0-9]*.[0-9]*.[0-9]*",
|
|
939
|
-
|
|
1080
|
+
ref
|
|
940
1081
|
]);
|
|
941
1082
|
if (status !== 0) return null;
|
|
942
1083
|
const tag = stdout.trim();
|
|
@@ -957,17 +1098,45 @@ ${stderr.trim()}`);
|
|
|
957
1098
|
}
|
|
958
1099
|
return stdout.trim().length > 0;
|
|
959
1100
|
}
|
|
960
|
-
function
|
|
1101
|
+
function checkReleasePreconditions() {
|
|
1102
|
+
let branch;
|
|
1103
|
+
try {
|
|
1104
|
+
branch = getCurrentBranch();
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
return { ok: false, message: err.message };
|
|
1107
|
+
}
|
|
1108
|
+
if (branch !== "develop") {
|
|
1109
|
+
return {
|
|
1110
|
+
ok: false,
|
|
1111
|
+
message: `publish-release must be run from the 'develop' branch (currently on '${branch}').`
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
if (hasUncommittedChanges()) {
|
|
1115
|
+
return {
|
|
1116
|
+
ok: false,
|
|
1117
|
+
message: "You have uncommitted changes. Commit or stash them before publishing a release."
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
return { ok: true };
|
|
1121
|
+
}
|
|
1122
|
+
function publishRelease(version2, dryRun, trunk) {
|
|
961
1123
|
const releaseBranch = `release/${version2}`;
|
|
1124
|
+
const checkoutTrunk = localBranchExists(trunk) ? { args: ["checkout", trunk], desc: `git checkout ${trunk}` } : {
|
|
1125
|
+
args: ["checkout", "-b", trunk, `origin/${trunk}`],
|
|
1126
|
+
desc: `git checkout -b ${trunk} origin/${trunk}`
|
|
1127
|
+
};
|
|
962
1128
|
const steps = [
|
|
963
1129
|
{ args: ["checkout", "-b", releaseBranch], desc: `git checkout -b ${releaseBranch}` },
|
|
964
|
-
|
|
1130
|
+
checkoutTrunk,
|
|
965
1131
|
{ args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
|
|
966
1132
|
{ args: ["tag", version2], desc: `git tag ${version2}` },
|
|
967
1133
|
{ args: ["checkout", "develop"], desc: `git checkout develop` },
|
|
968
1134
|
{ args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
|
|
969
1135
|
{ args: ["branch", "-d", releaseBranch], desc: `git branch -d ${releaseBranch}` },
|
|
970
|
-
{
|
|
1136
|
+
{
|
|
1137
|
+
args: ["push", "origin", "develop", trunk, version2],
|
|
1138
|
+
desc: `git push origin develop ${trunk} ${version2}`
|
|
1139
|
+
}
|
|
971
1140
|
];
|
|
972
1141
|
for (const step of steps) {
|
|
973
1142
|
if (dryRun) {
|
|
@@ -983,6 +1152,29 @@ ${stderr.trim()}`);
|
|
|
983
1152
|
}
|
|
984
1153
|
}
|
|
985
1154
|
|
|
1155
|
+
// src/git/releaseVersion.ts
|
|
1156
|
+
var SEMVER_ARG_RE = /^\d+\.\d+\.\d+$/;
|
|
1157
|
+
function resolveReleaseVersion(requested, trunkRef, latestTag) {
|
|
1158
|
+
if (requested !== void 0) {
|
|
1159
|
+
if (!SEMVER_ARG_RE.test(requested)) {
|
|
1160
|
+
return {
|
|
1161
|
+
ok: false,
|
|
1162
|
+
message: `Version '${requested}' is not valid semver. Use X.Y.Z format (e.g. 1.2.0).`
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return { ok: true, version: requested, notice: null };
|
|
1166
|
+
}
|
|
1167
|
+
const latest = latestTag();
|
|
1168
|
+
if (latest === null) {
|
|
1169
|
+
return {
|
|
1170
|
+
ok: false,
|
|
1171
|
+
message: `No semver tag found on ${trunkRef}. Pass a version explicitly: automata git publish-release <X.Y.Z>`
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
const version2 = bumpMinorVersion(latest);
|
|
1175
|
+
return { ok: true, version: version2, notice: `Auto-detected version: ${latest} \u2192 ${version2}` };
|
|
1176
|
+
}
|
|
1177
|
+
|
|
986
1178
|
// src/commands/git.ts
|
|
987
1179
|
var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
|
|
988
1180
|
var SKIP_CONCLUSIONS = /* @__PURE__ */ new Set(["SKIPPED", "NEUTRAL"]);
|
|
@@ -1347,59 +1539,58 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
1347
1539
|
process.exit(1);
|
|
1348
1540
|
}
|
|
1349
1541
|
});
|
|
1350
|
-
var
|
|
1351
|
-
var publishReleaseCmd = new Command2("publish-release").description("Execute the full GitFlow release sequence and push to origin").argument("[version]", "Release version in X.Y.Z format (auto-detected from master tag if omitted)").option("--dry-run", "Print git commands without executing them").addHelpText(
|
|
1542
|
+
var publishReleaseCmd = new Command2("publish-release").description("Execute the full GitFlow release sequence and push to origin").argument("[version]", "Release version in X.Y.Z format (auto-detected from the trunk tag if omitted)").option("--dry-run", "Print git commands without executing them").addHelpText(
|
|
1352
1543
|
"after",
|
|
1353
1544
|
`
|
|
1354
1545
|
Release sequence:
|
|
1355
1546
|
1. git checkout -b release/<version>
|
|
1356
|
-
2. git checkout
|
|
1547
|
+
2. git checkout <trunk> && git merge --no-ff release/<version>
|
|
1357
1548
|
3. git tag <version>
|
|
1358
1549
|
4. git checkout develop && git merge --no-ff release/<version>
|
|
1359
1550
|
5. git branch -d release/<version>
|
|
1360
|
-
6. git push origin develop
|
|
1551
|
+
6. git push origin develop <trunk> <version>
|
|
1361
1552
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1553
|
+
<trunk> is resolved from origin \u2014 git.trunkBranch in .automata/config.json if
|
|
1554
|
+
set, else origin/HEAD, the remote's advertised HEAD, or a probe of main/master.
|
|
1555
|
+
Tags are fetched from origin first, in --dry-run too, so the version a dry run
|
|
1556
|
+
prints is the one a real run would use.
|
|
1557
|
+
|
|
1558
|
+
When [version] is omitted the latest semver tag on origin/<trunk> is detected
|
|
1559
|
+
and the minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
|
|
1364
1560
|
).action((version2, options) => {
|
|
1365
1561
|
const dryRun = options.dryRun ?? false;
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
} catch (err) {
|
|
1370
|
-
process.stderr.write(`Error: ${err.message}
|
|
1562
|
+
const preconditions = checkReleasePreconditions();
|
|
1563
|
+
if (!preconditions.ok) {
|
|
1564
|
+
process.stderr.write(`Error: ${preconditions.message}
|
|
1371
1565
|
`);
|
|
1372
1566
|
process.exit(1);
|
|
1373
1567
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
`
|
|
1378
|
-
);
|
|
1568
|
+
const trunk = resolveTrunkBranch();
|
|
1569
|
+
if (!trunk.ok) {
|
|
1570
|
+
process.stderr.write(`Error: ${unresolvedTrunkMessage(trunk.attempted)}
|
|
1571
|
+
`);
|
|
1379
1572
|
process.exit(1);
|
|
1380
1573
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1574
|
+
const trunkBranch = trunk.branch;
|
|
1575
|
+
const trunkRef = `origin/${trunkBranch}`;
|
|
1576
|
+
process.stdout.write(`Trunk branch: ${trunkBranch} (${describeTrunkSource(trunk.source, trunkBranch)})
|
|
1577
|
+
`);
|
|
1578
|
+
const fetched = fetchTrunkAndTags(trunkBranch);
|
|
1579
|
+
if (!fetched.ok) {
|
|
1580
|
+
process.stderr.write(`Error: Failed to fetch ${trunkBranch} and tags from origin.
|
|
1581
|
+
${fetched.message}
|
|
1582
|
+
`);
|
|
1383
1583
|
process.exit(1);
|
|
1384
1584
|
}
|
|
1385
|
-
|
|
1386
|
-
if (
|
|
1387
|
-
|
|
1388
|
-
process.stderr.write(`Error: Version '${version2}' is not valid semver. Use X.Y.Z format (e.g. 1.2.0).
|
|
1585
|
+
const versionResult = resolveReleaseVersion(version2, trunkRef, () => getLatestTagOnTrunk(trunkRef));
|
|
1586
|
+
if (!versionResult.ok) {
|
|
1587
|
+
process.stderr.write(`Error: ${versionResult.message}
|
|
1389
1588
|
`);
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
if (latest === null) {
|
|
1396
|
-
process.stderr.write(
|
|
1397
|
-
"Error: No semver tag found on master. Pass a version explicitly: automata git publish-release <X.Y.Z>\n"
|
|
1398
|
-
);
|
|
1399
|
-
process.exit(1);
|
|
1400
|
-
}
|
|
1401
|
-
resolvedVersion = bumpMinorVersion(latest);
|
|
1402
|
-
process.stdout.write(`Auto-detected version: ${latest} \u2192 ${resolvedVersion}
|
|
1589
|
+
process.exit(1);
|
|
1590
|
+
}
|
|
1591
|
+
const resolvedVersion = versionResult.version;
|
|
1592
|
+
if (versionResult.notice !== null) {
|
|
1593
|
+
process.stdout.write(`${versionResult.notice}
|
|
1403
1594
|
`);
|
|
1404
1595
|
}
|
|
1405
1596
|
if (tagExists(resolvedVersion)) {
|
|
@@ -1407,6 +1598,14 @@ minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
|
|
|
1407
1598
|
`);
|
|
1408
1599
|
process.exit(1);
|
|
1409
1600
|
}
|
|
1601
|
+
const behind = trunkBehindCount(trunkBranch);
|
|
1602
|
+
if (behind > 0) {
|
|
1603
|
+
process.stderr.write(
|
|
1604
|
+
`Error: Local branch '${trunkBranch}' is ${String(behind)} commit(s) behind ${trunkRef}. Update it (git checkout ${trunkBranch} && git merge --ff-only ${trunkRef}) or delete it, then re-run.
|
|
1605
|
+
`
|
|
1606
|
+
);
|
|
1607
|
+
process.exit(1);
|
|
1608
|
+
}
|
|
1410
1609
|
if (dryRun) {
|
|
1411
1610
|
process.stdout.write(`Dry-run: release ${resolvedVersion}
|
|
1412
1611
|
`);
|
|
@@ -1415,7 +1614,7 @@ minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
|
|
|
1415
1614
|
`);
|
|
1416
1615
|
}
|
|
1417
1616
|
try {
|
|
1418
|
-
publishRelease(resolvedVersion, dryRun);
|
|
1617
|
+
publishRelease(resolvedVersion, dryRun, trunkBranch);
|
|
1419
1618
|
} catch (err) {
|
|
1420
1619
|
process.stderr.write(`Error: ${err.message}
|
|
1421
1620
|
`);
|
|
@@ -2035,7 +2234,7 @@ function parseCreatedPrUrl(stdout, head) {
|
|
|
2035
2234
|
// src/claude/claudeService.ts
|
|
2036
2235
|
import { spawn, spawnSync as spawnSync5 } from "child_process";
|
|
2037
2236
|
import { createInterface } from "readline";
|
|
2038
|
-
import { existsSync } from "fs";
|
|
2237
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2039
2238
|
import { delimiter, join } from "path";
|
|
2040
2239
|
|
|
2041
2240
|
// src/cli/spawnUtils.ts
|
|
@@ -2129,7 +2328,7 @@ function resolveCommand(name) {
|
|
|
2129
2328
|
const pathDirs = (process.env["PATH"] ?? "").split(delimiter);
|
|
2130
2329
|
for (const dir of pathDirs) {
|
|
2131
2330
|
const candidate = join(dir, name);
|
|
2132
|
-
if (
|
|
2331
|
+
if (existsSync2(candidate)) return candidate;
|
|
2133
2332
|
}
|
|
2134
2333
|
return name;
|
|
2135
2334
|
}
|
|
@@ -3668,7 +3867,7 @@ function prepareBaseBranch(baseBranch) {
|
|
|
3668
3867
|
if (!pull.ok) {
|
|
3669
3868
|
return { ok: false, reason: "pull-failed", detail: pull.stderr };
|
|
3670
3869
|
}
|
|
3671
|
-
return { ok: true, branch: baseBranch };
|
|
3870
|
+
return { ok: true, branch: baseBranch, strategy: "fast-forward" };
|
|
3672
3871
|
}
|
|
3673
3872
|
function resetToForcePushedRemote(headRefName, previousRemoteSha) {
|
|
3674
3873
|
const localSha = revParse(`refs/heads/${headRefName}`);
|
|
@@ -3678,7 +3877,81 @@ function resetToForcePushedRemote(headRefName, previousRemoteSha) {
|
|
|
3678
3877
|
if (!reset.ok) {
|
|
3679
3878
|
return { ok: false, reason: "pull-failed", detail: reset.stderr };
|
|
3680
3879
|
}
|
|
3681
|
-
return { ok: true, branch: headRefName };
|
|
3880
|
+
return { ok: true, branch: headRefName, strategy: "reset-to-remote" };
|
|
3881
|
+
}
|
|
3882
|
+
function rebaseOntoAlreadyAppliedRemote(headRefName) {
|
|
3883
|
+
const upstream = `refs/remotes/origin/${headRefName}`;
|
|
3884
|
+
const divergence = describeDivergence(upstream, `refs/heads/${headRefName}`);
|
|
3885
|
+
if (divergence === null) return null;
|
|
3886
|
+
if (divergence.merges > 0) return null;
|
|
3887
|
+
if (divergence.commits.length === 0) return null;
|
|
3888
|
+
if (divergence.commits.some((commit) => !commit.alreadyUpstream)) return null;
|
|
3889
|
+
if (isRebaseInProgress()) {
|
|
3890
|
+
return {
|
|
3891
|
+
ok: false,
|
|
3892
|
+
reason: "pull-failed",
|
|
3893
|
+
detail: `a rebase is already in progress in this checkout and automata did not start it, so ${headRefName} was left untouched; finish it with \`git rebase --continue\` or drop it with \`git rebase --abort\``
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
const rebase = rebaseOnto(upstream);
|
|
3897
|
+
if (!rebase.ok) {
|
|
3898
|
+
if (!isRebaseInProgress()) {
|
|
3899
|
+
return {
|
|
3900
|
+
ok: false,
|
|
3901
|
+
reason: "pull-failed",
|
|
3902
|
+
detail: `${rebase.stderr} \u2014 the rebase never started, so ${headRefName} is untouched`
|
|
3903
|
+
};
|
|
3904
|
+
}
|
|
3905
|
+
const aborted = abortRebase();
|
|
3906
|
+
const restored = aborted.ok ? `the rebase was aborted, so ${headRefName} is back where it was` : `the rebase could NOT be aborted (${aborted.stderr}) \u2014 run \`git rebase --abort\` in the checkout`;
|
|
3907
|
+
return {
|
|
3908
|
+
ok: false,
|
|
3909
|
+
reason: "rebase-conflict",
|
|
3910
|
+
detail: `${rebase.stderr} \u2014 ${restored}`
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
const local = revParse(`refs/heads/${headRefName}`);
|
|
3914
|
+
const remote = revParse(upstream);
|
|
3915
|
+
if (local === null || remote === null || local !== remote) {
|
|
3916
|
+
return {
|
|
3917
|
+
ok: false,
|
|
3918
|
+
reason: "pull-failed",
|
|
3919
|
+
detail: `the rebase of ${headRefName} onto origin/${headRefName} reported success but did not land on the remote tip, so the branch is still out of sync; compare them with \`git log origin/${headRefName}..${headRefName}\``
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
return { ok: true, branch: headRefName, strategy: "rebase" };
|
|
3923
|
+
}
|
|
3924
|
+
function divergenceRefusal(headRefName, pullError) {
|
|
3925
|
+
const divergence = describeDivergence(
|
|
3926
|
+
`refs/remotes/origin/${headRefName}`,
|
|
3927
|
+
`refs/heads/${headRefName}`
|
|
3928
|
+
);
|
|
3929
|
+
if (divergence === null) {
|
|
3930
|
+
return {
|
|
3931
|
+
ok: false,
|
|
3932
|
+
reason: "pull-failed",
|
|
3933
|
+
detail: `${pullError} \u2014 how the local ${headRefName} relates to origin/${headRefName} could not be established, so nothing was changed and ${headRefName} is untouched; inspect it with \`git log --oneline origin/${headRefName}...${headRefName}\` before deciding what to do`
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
const unpushedCount = divergence.commits.filter((c) => !c.alreadyUpstream).length + divergence.merges;
|
|
3937
|
+
if (unpushedCount === 0) {
|
|
3938
|
+
return {
|
|
3939
|
+
ok: false,
|
|
3940
|
+
reason: "pull-failed",
|
|
3941
|
+
detail: `${pullError} \u2014 no commit of the local ${headRefName} is missing from origin/${headRefName}, so this is not a divergence; ${headRefName} is untouched and git's own error above is the cause to fix`
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
let unpushed;
|
|
3945
|
+
if (unpushedCount === 1) {
|
|
3946
|
+
unpushed = `1 of its commits is not on origin/${headRefName}`;
|
|
3947
|
+
} else {
|
|
3948
|
+
unpushed = `${String(unpushedCount)} of its commits are not on origin/${headRefName}`;
|
|
3949
|
+
}
|
|
3950
|
+
return {
|
|
3951
|
+
ok: false,
|
|
3952
|
+
reason: "pull-failed",
|
|
3953
|
+
detail: `${pullError} \u2014 the local ${headRefName} has diverged from origin/${headRefName} and ${unpushed}, so it was not synchronized automatically; inspect them with \`git log origin/${headRefName}..${headRefName}\` and \`git reset --hard origin/${headRefName}\` yourself once they are safe to lose`
|
|
3954
|
+
};
|
|
3682
3955
|
}
|
|
3683
3956
|
function preparePrBranch(headRefName) {
|
|
3684
3957
|
if (hasUncommittedChanges([RUN_LOCK_RELATIVE_PATH])) return dirtyTree();
|
|
@@ -3687,25 +3960,28 @@ function preparePrBranch(headRefName) {
|
|
|
3687
3960
|
if (!fetched.ok) {
|
|
3688
3961
|
return { ok: false, reason: "checkout-failed", detail: fetched.stderr };
|
|
3689
3962
|
}
|
|
3963
|
+
const localExisted = revParse(`refs/heads/${headRefName}`) !== null;
|
|
3690
3964
|
const checkout = checkoutBranch(headRefName);
|
|
3691
3965
|
if (!checkout.ok) {
|
|
3692
3966
|
const created = createTrackingBranch(headRefName);
|
|
3693
3967
|
if (!created.ok) {
|
|
3694
3968
|
return { ok: false, reason: "checkout-failed", detail: created.stderr };
|
|
3695
3969
|
}
|
|
3696
|
-
return { ok: true, branch: headRefName };
|
|
3970
|
+
return { ok: true, branch: headRefName, strategy: "tracking-branch" };
|
|
3697
3971
|
}
|
|
3698
3972
|
const pull = pullFastForwardOnly(headRefName);
|
|
3699
3973
|
if (!pull.ok) {
|
|
3700
3974
|
const reset = resetToForcePushedRemote(headRefName, previousRemoteSha);
|
|
3701
3975
|
if (reset !== null) return reset;
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
detail: `${pull.stderr} \u2014 the local ${headRefName} has commits origin/${headRefName} does not, so it was not reset automatically; inspect them and \`git reset --hard origin/${headRefName}\` yourself`
|
|
3706
|
-
};
|
|
3976
|
+
const rebased = rebaseOntoAlreadyAppliedRemote(headRefName);
|
|
3977
|
+
if (rebased !== null) return rebased;
|
|
3978
|
+
return divergenceRefusal(headRefName, pull.stderr);
|
|
3707
3979
|
}
|
|
3708
|
-
return {
|
|
3980
|
+
return {
|
|
3981
|
+
ok: true,
|
|
3982
|
+
branch: headRefName,
|
|
3983
|
+
strategy: localExisted ? "fast-forward" : "tracking-branch"
|
|
3984
|
+
};
|
|
3709
3985
|
}
|
|
3710
3986
|
|
|
3711
3987
|
// src/git/repoHygiene.ts
|
|
@@ -4073,6 +4349,18 @@ function repoField(repo) {
|
|
|
4073
4349
|
const flat = oneLine(repo);
|
|
4074
4350
|
return flat.length === 0 ? "-" : flat;
|
|
4075
4351
|
}
|
|
4352
|
+
var QUIET_SYNC = /* @__PURE__ */ new Set(["fast-forward", "tracking-branch"]);
|
|
4353
|
+
function summarizeSync(items) {
|
|
4354
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4355
|
+
for (const item of items) {
|
|
4356
|
+
if (item.sync === void 0) continue;
|
|
4357
|
+
const key = oneLine(item.sync);
|
|
4358
|
+
if (key.length === 0 || QUIET_SYNC.has(key)) continue;
|
|
4359
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
4360
|
+
}
|
|
4361
|
+
if (counts.size === 0) return null;
|
|
4362
|
+
return [...counts].map(([strategy, count]) => `${strategy}:${String(count)}`).join(",");
|
|
4363
|
+
}
|
|
4076
4364
|
function formatExecutionLine(tick) {
|
|
4077
4365
|
const counts = {
|
|
4078
4366
|
answered: 0,
|
|
@@ -4096,6 +4384,8 @@ function formatExecutionLine(tick) {
|
|
|
4096
4384
|
`exit=${String(tick.exitCode)}`,
|
|
4097
4385
|
`dur=${(tick.durationMs / 1e3).toFixed(1)}s`
|
|
4098
4386
|
];
|
|
4387
|
+
const sync = summarizeSync(tick.items);
|
|
4388
|
+
if (sync !== null) fields.push(`sync=${sync}`);
|
|
4099
4389
|
if (tick.note !== void 0 && tick.note.length > 0) fields.push(`note=${tick.note}`);
|
|
4100
4390
|
return fields.join(" ") + "\n";
|
|
4101
4391
|
}
|
|
@@ -4112,13 +4402,18 @@ function describeItemExecution(item) {
|
|
|
4112
4402
|
const effort = item.effort === void 0 ? "" : ` effort=${oneLine(item.effort)}`;
|
|
4113
4403
|
return ` [${oneLine(item.executor)}${model}${effort}]`;
|
|
4114
4404
|
}
|
|
4405
|
+
function describeItemSync(item) {
|
|
4406
|
+
if (item.sync === void 0) return "";
|
|
4407
|
+
const flat = oneLine(item.sync);
|
|
4408
|
+
return flat.length === 0 ? "" : ` sync=${flat}`;
|
|
4409
|
+
}
|
|
4115
4410
|
function formatWorkRecord(tick) {
|
|
4116
4411
|
const ran = tick.items.filter((item) => item.ranExecutor);
|
|
4117
4412
|
if (ran.length === 0) return null;
|
|
4118
4413
|
const header = `=== ${tick.timestamp.toISOString()} ${repoField(tick.repo)} ===
|
|
4119
4414
|
`;
|
|
4120
4415
|
const lines = ran.map(
|
|
4121
|
-
(item) => `${oneLine(item.subject)} ${item.turn === null ? "-" : oneLine(item.turn)} ${item.outcome}${describeItemExecution(item)} \u2014 ${briefDetail(item.detail)}
|
|
4416
|
+
(item) => `${oneLine(item.subject)} ${item.turn === null ? "-" : oneLine(item.turn)} ${item.outcome}${describeItemExecution(item)}${describeItemSync(item)} \u2014 ${briefDetail(item.detail)}
|
|
4122
4417
|
`
|
|
4123
4418
|
);
|
|
4124
4419
|
return header + lines.join("") + "\n";
|
|
@@ -4202,6 +4497,7 @@ function recordTick(tick, dir = operationLogDirectory()) {
|
|
|
4202
4497
|
|
|
4203
4498
|
// src/commands/doWork.ts
|
|
4204
4499
|
var inFlightMarker = null;
|
|
4500
|
+
var inFlightSync = null;
|
|
4205
4501
|
function subjectLabel(issue, pr) {
|
|
4206
4502
|
if (issue !== null) return `#${String(issue)}`;
|
|
4207
4503
|
return pr === null ? "#?" : `PR #${String(pr)}`;
|
|
@@ -4840,6 +5136,7 @@ async function processItem(planned, settings, silent, preflightCauses) {
|
|
|
4840
5136
|
progress(`
|
|
4841
5137
|
${itemLabel(planned)} ${planned.turn}: ${planned.reason}
|
|
4842
5138
|
`);
|
|
5139
|
+
inFlightSync = null;
|
|
4843
5140
|
const refreshed = refreshItem(planned, settings);
|
|
4844
5141
|
if (refreshed.kind === "skip") {
|
|
4845
5142
|
progress(` skipped: ${refreshed.detail}
|
|
@@ -4872,9 +5169,18 @@ ${itemLabel(planned)} ${planned.turn}: ${planned.reason}
|
|
|
4872
5169
|
return {
|
|
4873
5170
|
...base,
|
|
4874
5171
|
outcome: "skipped",
|
|
4875
|
-
detail: `${prepared.reason}: ${prepared.detail}${why}
|
|
5172
|
+
detail: `${prepared.reason}: ${prepared.detail}${why}`,
|
|
5173
|
+
sync: prepared.reason
|
|
4876
5174
|
};
|
|
4877
5175
|
}
|
|
5176
|
+
base.sync = prepared.strategy;
|
|
5177
|
+
inFlightSync = prepared.strategy;
|
|
5178
|
+
if (prepared.strategy !== "fast-forward" && prepared.strategy !== "tracking-branch") {
|
|
5179
|
+
progress(
|
|
5180
|
+
` ${prepared.branch} had diverged from origin; synchronized by ${prepared.strategy}.
|
|
5181
|
+
`
|
|
5182
|
+
);
|
|
5183
|
+
}
|
|
4878
5184
|
claimIssue(item, settings);
|
|
4879
5185
|
if (item.turn === "pr-work" && item.pr !== null && item.prNeedsAssignment) {
|
|
4880
5186
|
claimPr(item.pr.number, settings.participants.agentUser);
|
|
@@ -5077,7 +5383,8 @@ function toTickLogItem(report) {
|
|
|
5077
5383
|
ranExecutor: report.ranExecutor ?? false,
|
|
5078
5384
|
executor: report.execution?.executor,
|
|
5079
5385
|
model: report.execution?.model,
|
|
5080
|
-
effort: report.execution?.effort
|
|
5386
|
+
effort: report.execution?.effort,
|
|
5387
|
+
sync: report.sync
|
|
5081
5388
|
};
|
|
5082
5389
|
}
|
|
5083
5390
|
function logTick(reports, exitCode, startedAt, note) {
|
|
@@ -5183,7 +5490,8 @@ ${describePlan(decisions)}`;
|
|
|
5183
5490
|
title: itemTitle(item),
|
|
5184
5491
|
turn: item.turn,
|
|
5185
5492
|
outcome: "failed",
|
|
5186
|
-
detail: err.message
|
|
5493
|
+
detail: err.message,
|
|
5494
|
+
sync: inFlightSync ?? void 0
|
|
5187
5495
|
};
|
|
5188
5496
|
}
|
|
5189
5497
|
if (report.ranExecutor === true) runsUsed++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automata-cli",
|
|
3
|
-
"version": "0.8.0-develop.
|
|
3
|
+
"version": "0.8.0-develop.353",
|
|
4
4
|
"description": "Automata CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@eslint/js": "^10.0.1",
|
|
40
|
-
"@types/node": "^26.
|
|
40
|
+
"@types/node": "^26.6.1",
|
|
41
41
|
"@types/react": "^19.3.0",
|
|
42
42
|
"eslint": "^10.10.0",
|
|
43
43
|
"ink-testing-library": "^4.0.0",
|
|
44
|
-
"prettier": "^3.9.
|
|
44
|
+
"prettier": "^3.9.7",
|
|
45
45
|
"tsup": "^8.5.1",
|
|
46
46
|
"typescript": "^5.9.3",
|
|
47
47
|
"typescript-eslint": "^8.70.0",
|
|
48
|
-
"vitest": "^5.0.
|
|
48
|
+
"vitest": "^5.0.1"
|
|
49
49
|
}
|
|
50
50
|
}
|