@tendrilapp/cli 0.1.43 → 0.1.44

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.
@@ -108,7 +108,7 @@ var TOOLS = [
108
108
  },
109
109
  {
110
110
  name: "tendril_publish",
111
- description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim (they click Approve in the browser where they are signed in; when the page shows a terms checkbox, ticking it is part of their decision \u2014 never advise them to just tick it). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
111
+ description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim, along with which account the result says to be signed in as (they click Approve in the browser; approving includes accepting the design system's publishing terms, which is their decision to make, never yours to urge). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
112
112
  schema: z.object({
113
113
  bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
114
114
  portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
package/dist/tendril.js CHANGED
@@ -8468,6 +8468,9 @@ var init_publish_client = __esm({
8468
8468
  pollApproval(input) {
8469
8469
  return this.json("GET", `/api/publish-approvals/${encodeURIComponent(input.approvalId)}`, null, true);
8470
8470
  }
8471
+ whoami() {
8472
+ return this.json("GET", "/api/whoami", null, true);
8473
+ }
8471
8474
  begin(input) {
8472
8475
  return this.json("POST", "/api/publications", input, true);
8473
8476
  }
@@ -17247,7 +17250,7 @@ var init_server = __esm({
17247
17250
  },
17248
17251
  {
17249
17252
  name: "tendril_publish",
17250
- description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim (they click Approve in the browser where they are signed in; when the page shows a terms checkbox, ticking it is part of their decision \u2014 never advise them to just tick it). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
17253
+ description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim, along with which account the result says to be signed in as (they click Approve in the browser; approving includes accepting the design system's publishing terms, which is their decision to make, never yours to urge). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
17251
17254
  schema: z14.object({
17252
17255
  bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
17253
17256
  portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
@@ -18295,6 +18298,7 @@ var init_share = __esm({
18295
18298
  // packages/cli/src/commands/publish.ts
18296
18299
  var publish_exports = {};
18297
18300
  __export(publish_exports, {
18301
+ resolveOrigin: () => resolveOrigin,
18298
18302
  runPublish: () => runPublish
18299
18303
  });
18300
18304
  import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
@@ -18410,10 +18414,10 @@ async function runPublish(opts) {
18410
18414
  );
18411
18415
  return;
18412
18416
  }
18413
- const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18417
+ const origin = resolveOrigin(opts);
18414
18418
  const client = opts.client ?? httpClient(opts, origin);
18415
18419
  if (opts.approveWait === true) {
18416
- await approveWaitPhase(opts, client, bundleDir);
18420
+ await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
18417
18421
  }
18418
18422
  void reportRunPresence(componentName, "publishing");
18419
18423
  let opened = await client.begin({
@@ -18435,7 +18439,7 @@ async function runPublish(opts) {
18435
18439
  if (!opened.ok) {
18436
18440
  if (opened.needsConfirmation !== void 0) {
18437
18441
  fail(opts, ExitCode.ConfirmationRequired, {
18438
- error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
18442
+ error: opened.refusal,
18439
18443
  code: "first-publish-unconfirmed",
18440
18444
  remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
18441
18445
  });
@@ -18457,7 +18461,7 @@ async function runPublish(opts) {
18457
18461
  relPath: object.relPath,
18458
18462
  bytes: new Uint8Array(readFileSync41(file))
18459
18463
  });
18460
- if (!sent.ok) refuse2(opts, sent, "upload-refused");
18464
+ if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
18461
18465
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18462
18466
  }
18463
18467
  const committed = await client.commit({ publicationId: opened.value.publicationId });
@@ -18472,7 +18476,7 @@ async function runPublish(opts) {
18472
18476
  remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
18473
18477
  });
18474
18478
  }
18475
- refuse2(opts, committed, "commit-refused");
18479
+ refuse2(opts, committed, "commit-refused", true);
18476
18480
  }
18477
18481
  emitData(
18478
18482
  opts,
@@ -18544,6 +18548,12 @@ function readBundle(opts, bundleDir) {
18544
18548
  }
18545
18549
  return { manifest, report, reportText, files: bundleFiles(bundleDir) };
18546
18550
  }
18551
+ function resolveOrigin(opts) {
18552
+ const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18553
+ if (named !== "") return named;
18554
+ if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
18555
+ return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
18556
+ }
18547
18557
  function httpClient(opts, origin) {
18548
18558
  if (origin === "") {
18549
18559
  fail(opts, ExitCode.InputValidation, {
@@ -18576,12 +18586,13 @@ function httpClient(opts, origin) {
18576
18586
  }
18577
18587
  return new HttpPublishClient({ origin, token: found.token });
18578
18588
  }
18579
- function refuse2(opts, sent, code) {
18589
+ function refuse2(opts, sent, code, rejoins = false) {
18580
18590
  const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
18591
+ const retry = rejoins ? `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one` : `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again`;
18581
18592
  fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
18582
18593
  error: `${sent.refusal}${detail}`,
18583
18594
  code,
18584
- remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "")}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 a retry rejoins this same unfinished publication.` : `Fix what is named above and run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one.`
18595
+ remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${resolveOrigin(opts)}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then ${retry}.` : `Fix what is named above and ${retry}.`
18585
18596
  });
18586
18597
  }
18587
18598
  function pendingApprovalPath() {
@@ -18591,22 +18602,27 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
18591
18602
  const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
18592
18603
  if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
18593
18604
  const approval = requested.value;
18605
+ const who = await client.whoami?.();
18606
+ const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
18594
18607
  if (opts.approveStart === true) {
18595
- writeFileSync23(pendingApprovalPath(), `${JSON.stringify({ ...approval, bundleDir }, null, 2)}
18608
+ const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
18609
+ writeFileSync23(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
18596
18610
  `, { mode: 384 });
18611
+ await endRunPresence(input.componentName);
18597
18612
  emitData(
18598
18613
  opts,
18599
18614
  {
18600
18615
  status: "approval-pending",
18601
18616
  approveUrl: approval.approveUrl,
18602
18617
  componentName: input.componentName,
18618
+ ...asAccount === "" ? {} : { approveAsAccount: who?.ok === true ? who.value.email : "" },
18603
18619
  expiresAt: approval.expiresAt,
18604
- next: "open the approve page in the signed-in browser, click Approve (tick the terms box if shown), then finish with --approve-wait"
18620
+ next: `open the approve page in the browser${asAccount}, click Approve, then finish with --approve-wait`
18605
18621
  },
18606
18622
  () => {
18607
18623
  process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
18608
18624
  `);
18609
- process.stdout.write(`Approve it here: ${approval.approveUrl}
18625
+ process.stdout.write(`Approve it here${asAccount}: ${approval.approveUrl}
18610
18626
  `);
18611
18627
  process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
18612
18628
  `);
@@ -18614,16 +18630,17 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
18614
18630
  );
18615
18631
  return void 0;
18616
18632
  }
18617
- process.stderr.write(`This component's FIRST publish needs your approval in the browser:
18633
+ process.stderr.write(`This component's FIRST publish needs your approval in the browser${asAccount}:
18618
18634
  ${approval.approveUrl}
18619
18635
  `);
18620
18636
  process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
18621
18637
  `);
18622
18638
  const decided = await waitForApproval(opts, client, approval);
18623
18639
  if (decided === "approved") return input.begin();
18640
+ await endRunPresence(input.componentName);
18624
18641
  failDecision(opts, decided);
18625
18642
  }
18626
- async function approveWaitPhase(opts, client, bundleDir) {
18643
+ async function approveWaitPhase(opts, client, bundleDir, subject) {
18627
18644
  const file = pendingApprovalPath();
18628
18645
  let pending;
18629
18646
  if (existsSync44(file)) {
@@ -18642,6 +18659,13 @@ async function approveWaitPhase(opts, client, bundleDir) {
18642
18659
  remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
18643
18660
  });
18644
18661
  }
18662
+ if (pending.componentName !== void 0 && pending.componentName !== subject.componentName) {
18663
+ fail(opts, ExitCode.InputValidation, {
18664
+ error: `the waiting approval is for ${JSON.stringify(pending.componentName)}, and this publish is ${JSON.stringify(subject.componentName)}`,
18665
+ code: "pending-approval-mismatch",
18666
+ remediation: `Finish that one first (${tendrilCommand(`publish ${pending.bundleDir} --approve-wait`)}), or start this one fresh with --approve-start \u2014 starting one replaces the waiting slot.`
18667
+ });
18668
+ }
18645
18669
  const done = () => rmSync8(file, { force: true });
18646
18670
  const decided = await waitForApproval(opts, client, pending);
18647
18671
  done();
@@ -18670,7 +18694,7 @@ function failDecision(opts, decided) {
18670
18694
  fail(opts, ExitCode.ConfirmationRequired, {
18671
18695
  error: decided === "expired" ? "the approval request lapsed before anyone decided it" : "the approval request is gone \u2014 it lapsed and was cleaned up, or was already spent",
18672
18696
  code: "publish-approval-lapsed",
18673
- remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window."
18697
+ remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window. If the approve page showed nothing waiting, the browser is signed in to a DIFFERENT account than this CLI \u2014 the page lists only its own account's requests."
18674
18698
  });
18675
18699
  }
18676
18700
  var APPROVAL_WAIT_CAP_MS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tendrilapp/cli",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "Figma design systems → verified React components. CLI ruler + MCP server.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",