@denisvieiradev/gitwise-core 0.1.0 → 1.1.1

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.d.ts CHANGED
@@ -721,7 +721,12 @@ interface FinishReleaseOptions {
721
721
  * failed strategy merge (typically gitflow's develop merge when develop has
722
722
  * advanced) surfaces as `FINISH_MERGE_CONFLICT` — the repo is left mid-merge
723
723
  * for manual recovery (`git merge --continue` then tag + push by hand) since
724
- * the plan can no longer be re-run.
724
+ * the plan can no longer be re-run. A failure in step 9 (tag creation, or a
725
+ * rejected push — typically a non-fast-forward because origin's mainBranch
726
+ * advanced, e.g. a CI bot commit, while this release was being prepared or
727
+ * finished) surfaces as `FINISH_PUSH_FAILED` with the exact fetch/merge/push
728
+ * recovery commands embedded in the message; merge (never rebase) is required
729
+ * because the tag, if already created, is pinned to the release commit's hash.
725
730
  */
726
731
  declare function finishRelease(opts: FinishReleaseOptions): Promise<void>;
727
732
  interface AbortReleaseOptions {
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ var __export = (target, all) => {
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@denisvieiradev/gitwise-core",
10
- version: "0.1.0",
10
+ version: "1.1.0",
11
11
  description: "Shared logic for gitwise: non-interactive commit/review/pr/release commands, LLM providers, git/github primitives, prompt templates.",
12
12
  type: "module",
13
13
  main: "./dist/index.js",
@@ -890,12 +890,10 @@ function defaultIsProcessAlive(pid) {
890
890
  }
891
891
 
892
892
  // src/providers/claude-code.ts
893
- import { execFile as execFile3, execSync, spawn } from "child_process";
893
+ import { execSync, spawn } from "child_process";
894
894
  import fs from "fs";
895
895
  import os from "os";
896
896
  import path2 from "path";
897
- import { promisify as promisify3 } from "util";
898
- var execFileAsync = promisify3(execFile3);
899
897
  var LARGE_PROMPT_THRESHOLD = 1e5;
900
898
  var DEFAULT_TIMEOUT_MS = 12e4;
901
899
  var COMMON_CLAUDE_PATHS = [
@@ -973,35 +971,12 @@ var ClaudeCodeProvider = class {
973
971
  return args;
974
972
  }
975
973
  async callViaCli(args) {
976
- try {
977
- const { stdout } = await execFileAsync(this.claudeBinaryPath, args, {
978
- timeout: DEFAULT_TIMEOUT_MS,
979
- maxBuffer: 10 * 1024 * 1024,
980
- ...{ input: "" }
981
- });
982
- return this.parseResponse(stdout);
983
- } catch (err) {
984
- const execErr = err;
985
- if (execErr.stdout) {
986
- try {
987
- const parsed = JSON.parse(execErr.stdout);
988
- if (parsed.is_error) {
989
- throw new Error(`Claude CLI error: ${parsed.result}`);
990
- }
991
- } catch (parseErr) {
992
- if (parseErr instanceof Error && parseErr.message.startsWith("Claude CLI error:")) {
993
- throw parseErr;
994
- }
995
- }
996
- }
997
- const stderr = execErr.stderr?.replace(/Warning: no stdin data.*\n?/g, "").trim();
998
- if (stderr) {
999
- throw new Error(`Claude CLI failed: ${stderr}`);
1000
- }
1001
- throw this.wrapError(err);
1002
- }
974
+ return this.spawnClaude(args, "");
1003
975
  }
1004
976
  async callViaStdin(args, input) {
977
+ return this.spawnClaude(args, input);
978
+ }
979
+ async spawnClaude(args, input) {
1005
980
  return new Promise((resolve, reject) => {
1006
981
  const child = spawn(this.claudeBinaryPath, args, {
1007
982
  stdio: ["pipe", "pipe", "pipe"],
@@ -1740,9 +1715,9 @@ ${stderr}`;
1740
1715
  }
1741
1716
 
1742
1717
  // src/commands/pr.ts
1743
- import { execFile as execFile4 } from "child_process";
1744
- import { promisify as promisify4 } from "util";
1745
- var exec3 = promisify4(execFile4);
1718
+ import { execFile as execFile3 } from "child_process";
1719
+ import { promisify as promisify3 } from "util";
1720
+ var exec3 = promisify3(execFile3);
1746
1721
  function parsePrResponse(content) {
1747
1722
  const titleMatch = content.match(/^TITLE:\s*(.+)$/m);
1748
1723
  const title = titleMatch ? titleMatch[1].trim() : "Update";
@@ -2473,6 +2448,28 @@ ${dirty}`,
2473
2448
  await saveReleasePlan(cwd, persistedPlan);
2474
2449
  await finishRelease({ cwd, tagAndPush, createGhRelease, workspacePropagation, signTags });
2475
2450
  }
2451
+ function finishPushFailure(opts) {
2452
+ const { stage, tag, mainBranch, developBranch, newVersion, err } = opts;
2453
+ const cause = err instanceof Error ? err.message : String(err);
2454
+ const action = stage === "tag" ? `create the tag "${tag}"` : stage === "push-main" ? `push "${mainBranch}" (with tags) to origin` : `push "${developBranch}" to origin`;
2455
+ const recoverySteps = stage === "tag" ? [
2456
+ `git tag -a ${tag} -F .gitwise/release-${newVersion}.md`,
2457
+ `git push origin ${mainBranch} --follow-tags`
2458
+ ] : [
2459
+ `git ls-remote --tags origin ${tag} # check whether the tag already reached origin`,
2460
+ `git fetch origin`,
2461
+ `git merge origin/${mainBranch} # do NOT rebase \u2014 that changes the hash ${tag} points to`,
2462
+ `git push origin ${mainBranch} --follow-tags`
2463
+ ];
2464
+ return new GitwiseError({
2465
+ code: "FINISH_PUSH_FAILED",
2466
+ message: `Failed to ${action} while finishing v${newVersion}: ${cause}. The release plan file has already been deleted, so "gw release finish" cannot be re-run, and the release commit already exists locally, so "gw release prepare" will refuse with NO_COMMITS. Recover manually:
2467
+ ${recoverySteps.map((s) => ` ${s}`).join("\n")}`,
2468
+ exitCode: EXIT_CODES.GIT_FAILED,
2469
+ cause: err,
2470
+ details: { stage, tag, mainBranch, developBranch, newVersion }
2471
+ });
2472
+ }
2476
2473
  async function finishRelease(opts) {
2477
2474
  const {
2478
2475
  cwd,
@@ -2663,15 +2660,27 @@ ${cause}`,
2663
2660
  await checkout(cwd, mainBranch);
2664
2661
  }
2665
2662
  if (tagAndPush) {
2666
- await createTag(cwd, tag, notes, { signed: signTags !== false });
2667
- await pushWithTags(cwd, "origin", mainBranch);
2663
+ try {
2664
+ await createTag(cwd, tag, notes, { signed: signTags !== false });
2665
+ } catch (err) {
2666
+ throw finishPushFailure({ stage: "tag", tag, mainBranch, newVersion: plan.newVersion, err });
2667
+ }
2668
+ try {
2669
+ await pushWithTags(cwd, "origin", mainBranch);
2670
+ } catch (err) {
2671
+ throw finishPushFailure({ stage: "push-main", tag, mainBranch, newVersion: plan.newVersion, err });
2672
+ }
2668
2673
  debug("release.finish.tag.pushed", {
2669
2674
  tag,
2670
2675
  branch: mainBranch,
2671
2676
  remote: "origin"
2672
2677
  });
2673
2678
  if (strategy.requiresDevelop()) {
2674
- await push(cwd, "origin", developBranch);
2679
+ try {
2680
+ await push(cwd, "origin", developBranch);
2681
+ } catch (err) {
2682
+ throw finishPushFailure({ stage: "push-develop", tag, mainBranch, developBranch, newVersion: plan.newVersion, err });
2683
+ }
2675
2684
  }
2676
2685
  }
2677
2686
  if (createGhRelease) {