@tokenoftrust/cli 2.0.7 → 2.0.8

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": "2.0.7",
3
+ "version": "2.0.8",
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",
@@ -46,7 +46,7 @@
46
46
  *
47
47
  * Dependency-free (global fetch + `git` via child_process).
48
48
  */
49
- import { execFile } from "node:child_process";
49
+ import { execFile, execFileSync } from "node:child_process";
50
50
  import { promisify } from "node:util";
51
51
  import { createMcpClient } from "../mcp.mjs";
52
52
  import { establishSession, AuthUnavailableError } from "../auth.mjs";
@@ -54,7 +54,7 @@ import { offerSignIn } from "./login.mjs";
54
54
  import { CliError, fail, formatError } from "../errors.mjs";
55
55
  import { writeNvmrc } from "../sample.mjs";
56
56
  import { emitObstacle } from "../obstacle.mjs";
57
- import { CREDENTIAL_HELPER, splitAuthedRemote, basicAuthExtraHeader } from "../git-credential.mjs";
57
+ import { splitAuthedRemote, basicAuthExtraHeader, installForgeCredentialHelper } from "../git-credential.mjs";
58
58
 
59
59
  const execFileP = promisify(execFile);
60
60
 
@@ -366,11 +366,19 @@ async function cloneRepo(gitRemote, dir, redact) {
366
366
  next: `check the target dir is empty and you can reach the remote, then re-run`,
367
367
  });
368
368
  }
369
+ // Install the HOST-SCOPED helper, not a bare `credential.helper`. A bare one is appended to
370
+ // whatever is inherited, and on a Mac that inherits `credential.helper = osxkeychain` — which
371
+ // runs FIRST, answers with a stale forge credential, and shadows ours. Gitea reports the
372
+ // resulting unauthorized fetch as "Repository not found", so a freshly cloned checkout looks
373
+ // like a missing repo rather than a credential problem. The host-scoped form writes
374
+ // ["", <helper>]: the empty reset clears the inherited helper for this host only.
369
375
  try {
370
- await git(["-C", dir, "config", "--local", "credential.helper", CREDENTIAL_HELPER]);
376
+ const gitSync = (/** @type {string[]} */ cargs) =>
377
+ execFileSync("git", ["-C", dir, ...cargs], { encoding: "utf8" });
378
+ installForgeCredentialHelper(gitSync, { host: new URL(gitRemote).host });
371
379
  } catch {
372
- // Best-effort — a checkout missing the helper still WORKS (the token just
373
- // isn't self-healing yet); the next `tot preview`/`tot sync` migrates it.
380
+ // Best-effort — never fail a good clone over credential plumbing. The next
381
+ // `tot preview`/`tot sync` installs it, and the fetch that needs it says why.
374
382
  }
375
383
  const head = (await git(["-C", dir, "log", "-1", "--oneline"])).trim();
376
384
  writeNvmrc(dir); // version-manager hooks land on a supported Node on cd
@@ -166,8 +166,13 @@ export async function run(argv, ctx) {
166
166
  // used to 401 even right after signing back in. Best-effort, never blocks sync.
167
167
  try {
168
168
  ensureTokenlessRemote(git);
169
- } catch {
170
- /* best-effort see above */
169
+ } catch (e) {
170
+ // Best-effort, but never SILENT: this call installs the host-scoped credential helper, and
171
+ // without it the fetch below fails as "Repository not found" — Gitea's wording for an
172
+ // unauthorized read. Swallowing the reason turns a credential problem into a phantom
173
+ // missing repo, with nothing pointing back here.
174
+ console.error(`~ could not repair this checkout's forge credentials: ${e?.message || e}`);
175
+ console.error(" a fetch failure below is likely this, not a missing repo.");
171
176
  }
172
177
 
173
178
  // Refuse a dirty tree up front — a merge on top of uncommitted edits is how