@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.
- package/dist/tendril-mcp.js +1 -1
- package/dist/tendril.js +38 -14
- package/package.json +1 -1
package/dist/tendril-mcp.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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;
|