automata-cli 0.2.0-develop.31 → 0.2.0-develop.47
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 +261 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -404,6 +404,129 @@ function deleteLocalBranch(branch) {
|
|
|
404
404
|
throw new Error(`Failed to delete branch ${branch}: ${result.stderr.trim()}`);
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
|
+
var REVIEW_THREADS_QUERY = `
|
|
408
|
+
query($owner:String!,$repo:String!,$prNumber:Int!){
|
|
409
|
+
repository(owner:$owner,name:$repo){
|
|
410
|
+
pullRequest(number:$prNumber){
|
|
411
|
+
reviewThreads(first:100){
|
|
412
|
+
nodes{
|
|
413
|
+
isResolved
|
|
414
|
+
isOutdated
|
|
415
|
+
comments(first:1){
|
|
416
|
+
nodes{ author{login} body path line createdAt }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}`.trim();
|
|
423
|
+
function getPrCommentsGh(branch) {
|
|
424
|
+
const prView = run2("gh", ["pr", "view", branch, "--json", "number"]);
|
|
425
|
+
if (prView.status !== 0) {
|
|
426
|
+
if (prView.stderr.includes("no pull requests found") || prView.stderr.includes("Could not resolve")) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
throw new Error(prView.stderr.trim() || "Failed to query GitHub. Is `gh` installed and authenticated?");
|
|
430
|
+
}
|
|
431
|
+
const { number: prNumber } = JSON.parse(prView.stdout);
|
|
432
|
+
const ownerRepo = parseOwnerRepo();
|
|
433
|
+
if (!ownerRepo) {
|
|
434
|
+
throw new Error("Could not determine GitHub owner/repo from git remote. Is 'origin' set to a GitHub URL?");
|
|
435
|
+
}
|
|
436
|
+
const slashIdx = ownerRepo.indexOf("/");
|
|
437
|
+
const owner = ownerRepo.slice(0, slashIdx);
|
|
438
|
+
const repo = ownerRepo.slice(slashIdx + 1);
|
|
439
|
+
const gql = run2("gh", [
|
|
440
|
+
"api",
|
|
441
|
+
"graphql",
|
|
442
|
+
"-f",
|
|
443
|
+
`query=${REVIEW_THREADS_QUERY}`,
|
|
444
|
+
"-f",
|
|
445
|
+
`owner=${owner}`,
|
|
446
|
+
"-f",
|
|
447
|
+
`repo=${repo}`,
|
|
448
|
+
"-F",
|
|
449
|
+
`prNumber=${String(prNumber)}`
|
|
450
|
+
]);
|
|
451
|
+
if (gql.status !== 0) {
|
|
452
|
+
throw new Error(gql.stderr.trim() || "Failed to query GitHub GraphQL API.");
|
|
453
|
+
}
|
|
454
|
+
const response = JSON.parse(gql.stdout);
|
|
455
|
+
const threads = response.data.repository.pullRequest.reviewThreads.nodes;
|
|
456
|
+
return threads.filter((t) => !t.isResolved && t.comments.nodes.length > 0).map((t) => {
|
|
457
|
+
const c = t.comments.nodes[0];
|
|
458
|
+
return {
|
|
459
|
+
author: c.author.login,
|
|
460
|
+
body: c.body,
|
|
461
|
+
path: c.path,
|
|
462
|
+
line: c.line ?? null,
|
|
463
|
+
createdAt: c.createdAt
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
function getPrComments(branch) {
|
|
468
|
+
const config = readConfig();
|
|
469
|
+
if (config.remoteType === "azdo") {
|
|
470
|
+
return "unsupported";
|
|
471
|
+
}
|
|
472
|
+
return getPrCommentsGh(branch);
|
|
473
|
+
}
|
|
474
|
+
var SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/;
|
|
475
|
+
function getLatestTagOnMaster() {
|
|
476
|
+
const { stdout, status } = run2("git", [
|
|
477
|
+
"describe",
|
|
478
|
+
"--tags",
|
|
479
|
+
"--abbrev=0",
|
|
480
|
+
"--match",
|
|
481
|
+
"[0-9]*.[0-9]*.[0-9]*",
|
|
482
|
+
"--match",
|
|
483
|
+
"v[0-9]*.[0-9]*.[0-9]*",
|
|
484
|
+
"master"
|
|
485
|
+
]);
|
|
486
|
+
if (status !== 0) return null;
|
|
487
|
+
const tag = stdout.trim();
|
|
488
|
+
const m = SEMVER_RE.exec(tag);
|
|
489
|
+
if (!m) return null;
|
|
490
|
+
return `${m[1]}.${m[2]}.${m[3]}`;
|
|
491
|
+
}
|
|
492
|
+
function bumpMinorVersion(version2) {
|
|
493
|
+
const m = SEMVER_RE.exec(version2);
|
|
494
|
+
if (!m) throw new Error(`Invalid semver: ${version2}`);
|
|
495
|
+
return `${m[1]}.${String(Number(m[2]) + 1)}.0`;
|
|
496
|
+
}
|
|
497
|
+
function tagExists(version2) {
|
|
498
|
+
const { stdout, status, stderr } = run2("git", ["tag", "-l", version2]);
|
|
499
|
+
if (status !== 0) {
|
|
500
|
+
throw new Error(`Command failed: git tag -l ${version2}
|
|
501
|
+
${stderr.trim()}`);
|
|
502
|
+
}
|
|
503
|
+
return stdout.trim().length > 0;
|
|
504
|
+
}
|
|
505
|
+
function publishRelease(version2, dryRun) {
|
|
506
|
+
const releaseBranch = `release/${version2}`;
|
|
507
|
+
const steps = [
|
|
508
|
+
{ args: ["checkout", "-b", releaseBranch], desc: `git checkout -b ${releaseBranch}` },
|
|
509
|
+
{ args: ["checkout", "master"], desc: `git checkout master` },
|
|
510
|
+
{ args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
|
|
511
|
+
{ args: ["tag", version2], desc: `git tag ${version2}` },
|
|
512
|
+
{ args: ["checkout", "develop"], desc: `git checkout develop` },
|
|
513
|
+
{ args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
|
|
514
|
+
{ args: ["branch", "-d", releaseBranch], desc: `git branch -d ${releaseBranch}` },
|
|
515
|
+
{ args: ["push", "origin", "develop", "master", version2], desc: `git push origin develop master ${version2}` }
|
|
516
|
+
];
|
|
517
|
+
for (const step of steps) {
|
|
518
|
+
if (dryRun) {
|
|
519
|
+
process.stdout.write(`[dry-run] ${step.desc}
|
|
520
|
+
`);
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const { status, stderr } = run2("git", step.args);
|
|
524
|
+
if (status !== 0) {
|
|
525
|
+
throw new Error(`Command failed: ${step.desc}
|
|
526
|
+
${stderr.trim()}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
407
530
|
|
|
408
531
|
// src/commands/git.ts
|
|
409
532
|
var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
|
|
@@ -543,6 +666,64 @@ URL: ${pr.url}
|
|
|
543
666
|
process.stdout.write(formatChecks(pr.checks));
|
|
544
667
|
}
|
|
545
668
|
});
|
|
669
|
+
var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
|
|
670
|
+
var CONTROL_CHARS_RE = new RegExp("[\0-\b\v\f-\x7F]", "g");
|
|
671
|
+
function sanitizeText(text) {
|
|
672
|
+
return text.replace(ANSI_ESCAPE_RE, "").replace(CONTROL_CHARS_RE, "");
|
|
673
|
+
}
|
|
674
|
+
var getPrCommentsCmd = new Command2("get-pr-comments").description("List open (unresolved) review comments on the pull request for the current branch (GitHub only)").option("--json", "Output as JSON array").addHelpText(
|
|
675
|
+
"after",
|
|
676
|
+
`
|
|
677
|
+
Only GitHub (remoteType: gh) is supported. Azure DevOps is not supported.
|
|
678
|
+
See docs/azdo-gap.md for details.`
|
|
679
|
+
).action((options) => {
|
|
680
|
+
let branch;
|
|
681
|
+
try {
|
|
682
|
+
branch = getCurrentBranch();
|
|
683
|
+
} catch (err) {
|
|
684
|
+
process.stderr.write(`Error: ${err.message}
|
|
685
|
+
`);
|
|
686
|
+
process.exit(1);
|
|
687
|
+
}
|
|
688
|
+
let comments;
|
|
689
|
+
try {
|
|
690
|
+
comments = getPrComments(branch);
|
|
691
|
+
} catch (err) {
|
|
692
|
+
process.stderr.write(`Error: ${err.message}
|
|
693
|
+
`);
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
if (comments === "unsupported") {
|
|
697
|
+
process.stderr.write(
|
|
698
|
+
`Error: get-pr-comments is not supported for Azure DevOps. See docs/azdo-gap.md for details.
|
|
699
|
+
`
|
|
700
|
+
);
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
if (comments === null) {
|
|
704
|
+
process.stderr.write(`Error: No pull request found for branch: ${branch}
|
|
705
|
+
`);
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
if (options.json) {
|
|
709
|
+
process.stdout.write(JSON.stringify(comments, null, 2) + "\n");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (comments.length === 0) {
|
|
713
|
+
process.stdout.write(`No open comments.
|
|
714
|
+
`);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
const lines = [];
|
|
718
|
+
for (const c of comments) {
|
|
719
|
+
const loc = c.line !== null ? `${c.path}:${String(c.line)}` : `${c.path}:(file)`;
|
|
720
|
+
const safeBody = sanitizeText(c.body);
|
|
721
|
+
const safeAuthor = sanitizeText(c.author);
|
|
722
|
+
lines.push(`[${safeAuthor}] on ${loc}
|
|
723
|
+
${safeBody}`);
|
|
724
|
+
}
|
|
725
|
+
process.stdout.write(lines.join("\n\n") + "\n");
|
|
726
|
+
});
|
|
546
727
|
var finishFeatureCmd = new Command2("finish-feature").description("Clean up a merged feature branch: checkout develop, pull, and delete local branch").action(() => {
|
|
547
728
|
let branch;
|
|
548
729
|
try {
|
|
@@ -612,7 +793,86 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
|
|
|
612
793
|
process.exit(1);
|
|
613
794
|
}
|
|
614
795
|
});
|
|
615
|
-
var
|
|
796
|
+
var SEMVER_ARG_RE = /^\d+\.\d+\.\d+$/;
|
|
797
|
+
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(
|
|
798
|
+
"after",
|
|
799
|
+
`
|
|
800
|
+
Release sequence:
|
|
801
|
+
1. git checkout -b release/<version>
|
|
802
|
+
2. git checkout master && git merge --no-ff release/<version>
|
|
803
|
+
3. git tag <version>
|
|
804
|
+
4. git checkout develop && git merge --no-ff release/<version>
|
|
805
|
+
5. git branch -d release/<version>
|
|
806
|
+
6. git push origin develop master <version>
|
|
807
|
+
|
|
808
|
+
When [version] is omitted the latest semver tag on master is detected and the
|
|
809
|
+
minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).`
|
|
810
|
+
).action((version2, options) => {
|
|
811
|
+
const dryRun = options.dryRun ?? false;
|
|
812
|
+
let branch;
|
|
813
|
+
try {
|
|
814
|
+
branch = getCurrentBranch();
|
|
815
|
+
} catch (err) {
|
|
816
|
+
process.stderr.write(`Error: ${err.message}
|
|
817
|
+
`);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
if (branch !== "develop") {
|
|
821
|
+
process.stderr.write(
|
|
822
|
+
`Error: publish-release must be run from the 'develop' branch (currently on '${branch}').
|
|
823
|
+
`
|
|
824
|
+
);
|
|
825
|
+
process.exit(1);
|
|
826
|
+
}
|
|
827
|
+
if (hasUncommittedChanges()) {
|
|
828
|
+
process.stderr.write("Error: You have uncommitted changes. Commit or stash them before publishing a release.\n");
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
let resolvedVersion;
|
|
832
|
+
if (version2 !== void 0) {
|
|
833
|
+
if (!SEMVER_ARG_RE.test(version2)) {
|
|
834
|
+
process.stderr.write(`Error: Version '${version2}' is not valid semver. Use X.Y.Z format (e.g. 1.2.0).
|
|
835
|
+
`);
|
|
836
|
+
process.exit(1);
|
|
837
|
+
}
|
|
838
|
+
resolvedVersion = version2;
|
|
839
|
+
} else {
|
|
840
|
+
const latest = getLatestTagOnMaster();
|
|
841
|
+
if (latest === null) {
|
|
842
|
+
process.stderr.write(
|
|
843
|
+
"Error: No semver tag found on master. Pass a version explicitly: automata git publish-release <X.Y.Z>\n"
|
|
844
|
+
);
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
resolvedVersion = bumpMinorVersion(latest);
|
|
848
|
+
process.stdout.write(`Auto-detected version: ${latest} \u2192 ${resolvedVersion}
|
|
849
|
+
`);
|
|
850
|
+
}
|
|
851
|
+
if (tagExists(resolvedVersion)) {
|
|
852
|
+
process.stderr.write(`Error: Tag '${resolvedVersion}' already exists.
|
|
853
|
+
`);
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
if (dryRun) {
|
|
857
|
+
process.stdout.write(`Dry-run: release ${resolvedVersion}
|
|
858
|
+
`);
|
|
859
|
+
} else {
|
|
860
|
+
process.stdout.write(`Publishing release ${resolvedVersion}...
|
|
861
|
+
`);
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
publishRelease(resolvedVersion, dryRun);
|
|
865
|
+
} catch (err) {
|
|
866
|
+
process.stderr.write(`Error: ${err.message}
|
|
867
|
+
`);
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
if (!dryRun) {
|
|
871
|
+
process.stdout.write(`Release ${resolvedVersion} published successfully.
|
|
872
|
+
`);
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
var gitCommand = new Command2("git").description("Git workflow commands (some require gh CLI)").addCommand(getPrInfoCmd).addCommand(getPrCommentsCmd).addCommand(finishFeatureCmd).addCommand(publishReleaseCmd);
|
|
616
876
|
|
|
617
877
|
// src/commands/getReady.ts
|
|
618
878
|
import { Command as Command3 } from "commander";
|