@tokenoftrust/cli 2.0.6 → 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.6",
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
@@ -159,7 +159,15 @@ export function forgeShimPath(env = process.env) {
159
159
  /** The shim's contents: pin the ABSOLUTE node + CLI entry active at write time, with a
160
160
  * PATH-search fallback for node, and FAIL LOUD to stderr (never silently) when no node
161
161
  * is found — so a broken helper says how to fix itself instead of yielding an opaque
162
- * "Repository not found". Pure — unit-tested. */
162
+ * "Repository not found". Pure — unit-tested.
163
+ *
164
+ * The pinned ENTRY gets the same fail-loud treatment as NODE, because the path pinned at
165
+ * write time can stop existing: running the CLI from a dev checkout or a git worktree pins
166
+ * the shim to THAT tree, and reaping it breaks forge git auth for every checkout on the
167
+ * machine — including ones created by the globally-installed `tot`. Unguarded, the failure
168
+ * surfaces three layers away as a raw node MODULE_NOT_FOUND followed by git's
169
+ * "could not read Username" / "Repository not found", which names neither the stale path
170
+ * nor the fix. */
163
171
  export function renderForgeShim(nodePath, entryPath) {
164
172
  return [
165
173
  "#!/bin/sh",
@@ -171,7 +179,18 @@ export function renderForgeShim(nodePath, entryPath) {
171
179
  ' echo "tot: no node runtime for the git credential helper — reinstall: npm i -g @tokenoftrust/cli@latest" >&2',
172
180
  " exit 1",
173
181
  "fi",
174
- `exec "$NODE" ${shq(entryPath)} git-credential "$@"`,
182
+ `ENTRY=${shq(entryPath)}`,
183
+ 'if [ ! -f "$ENTRY" ]; then',
184
+ ' echo "tot: the git credential helper points at a CLI that no longer exists:" >&2',
185
+ ' echo " $ENTRY" >&2',
186
+ ' echo " This shim was pinned by whichever tot ran last — if that was a dev" >&2',
187
+ ' echo " checkout or a git worktree that has since been removed, forge git auth" >&2',
188
+ ' echo " is broken for EVERY checkout on this machine until it is re-pinned." >&2',
189
+ ' echo " Fix: run any tot command that touches git (e.g. \\`tot clone <tenant>\\`)" >&2',
190
+ ' echo " from an installed tot, or reinstall: npm i -g @tokenoftrust/cli@latest" >&2',
191
+ " exit 1",
192
+ "fi",
193
+ 'exec "$NODE" "$ENTRY" git-credential "$@"',
175
194
  "",
176
195
  ].join("\n");
177
196
  }