@tokenoftrust/cli 1.4.0-rc.22 → 1.4.0-rc.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "1.4.0-rc.22",
3
+ "version": "1.4.0-rc.23",
4
4
  "description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Token of Trust",
@@ -444,6 +444,41 @@ export function baseCommitsBehind(git, branch) {
444
444
  }
445
445
  }
446
446
 
447
+ /**
448
+ * Resolve the git ref to diff HEAD against for the candidate's file patch (and its
449
+ * summary). The candidate PR is `preview + your file changes`, and `candidate_open`
450
+ * SERVER-CUTS the candidate branch back to the current preview tip and re-applies the
451
+ * patch — so the patch MUST be the FULL delta of your branch vs its fork point off
452
+ * `preview`, never "what changed since my last candidate push". Diffing against your
453
+ * own candidate tracking ref is what silently produced an empty patch (and the wrong
454
+ * "no file changes" skip, so no PR opened) after a push that landed but failed to open
455
+ * its PR: that ref already equals HEAD, so the since-last-push delta is empty even
456
+ * though the change vs preview is 19 files. Prefer the merge-base (fork point) with
457
+ * `origin/<baseBranch>`; fall back to the candidate tracking ref, then HEAD~1, then ""
458
+ * (single-commit `git show`) when no base ref resolves. Pure git I/O via the injected
459
+ * runner — unit-tested.
460
+ * @param {(cargs:string[])=>string} git
461
+ * @param {string} ref the candidate ref (e.g. "candidate/local-abc")
462
+ * @param {string} [baseBranch] the preview base branch (default FRESHNESS_BASE_BRANCH)
463
+ * @returns {string}
464
+ */
465
+ export function resolvePatchBase(git, ref, baseBranch = FRESHNESS_BASE_BRANCH) {
466
+ const verify = (r) => {
467
+ try { return git(["rev-parse", "--verify", "--quiet", r]).trim(); } catch { return ""; }
468
+ };
469
+ const previewRef = `refs/remotes/origin/${baseBranch}`;
470
+ if (verify(previewRef)) {
471
+ try {
472
+ const forkPoint = git(["merge-base", "HEAD", previewRef]).trim();
473
+ if (forkPoint) return forkPoint;
474
+ } catch { /* unrelated histories — fall through */ }
475
+ }
476
+ const trackingRef = `refs/remotes/origin/${ref}`;
477
+ if (verify(trackingRef)) return trackingRef;
478
+ if (verify("HEAD~1")) return "HEAD~1";
479
+ return "";
480
+ }
481
+
447
482
  // ─── born-rebased at submit (unit c3 — shift-left prevention #2) ─────────────────
448
483
 
449
484
  /** The three strategies the born-rebased rebuild accepts, mirroring `tot accept
@@ -1355,12 +1390,12 @@ export async function run(argv, ctx, { verb = "preview" } = {}) {
1355
1390
  };
1356
1391
  function buildSummaryAndPatch(ref) {
1357
1392
  const headSubject = gitSafe(["log", "-1", "--format=%s"]).trim();
1358
- const trackingRef = `refs/remotes/origin/${ref}`;
1359
- const base = gitSafe(["rev-parse", "--verify", "--quiet", trackingRef]).trim()
1360
- ? trackingRef
1361
- : gitSafe(["rev-parse", "--verify", "--quiet", "HEAD~1"]).trim()
1362
- ? "HEAD~1"
1363
- : "";
1393
+ // Diff against the fork point off `preview` — the FULL branch delta — so the patch
1394
+ // is complete for candidate_open's server-cut (which resets the branch to preview
1395
+ // and re-applies this patch). NOT the candidate's own tracking ref: after a push
1396
+ // that landed but failed to open its PR, that ref equals HEAD → empty patch →
1397
+ // wrong "no file changes" → no PR. See resolvePatchBase.
1398
+ const base = resolvePatchBase(gitSafe, ref);
1364
1399
  const statusCmd = base ? ["diff", "--name-status", `${base}..HEAD`] : ["show", "--name-status", "--format=", "HEAD"];
1365
1400
  const patchEntries = parseNameStatus(gitSafe(statusCmd));
1366
1401
  const files = patchEntries.map((e) => e.path);