@taprootio/site-authoring 0.10.1 → 0.10.2

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": "@taprootio/site-authoring",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -28,7 +28,7 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@taprootio/espalier": "4.16.0"
31
+ "@taprootio/espalier": "4.17.0"
32
32
  },
33
33
  "scripts": {
34
34
  "test": "node --test",
package/src/constants.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const CLI_NAME = "@taprootio/site-authoring";
2
- export const CLI_VERSION = "0.10.1";
2
+ export const CLI_VERSION = "0.10.2";
3
3
  export const CLI_BINARY_NAME = "taproot-site";
4
4
  export const RESULT_SCHEMA_VERSION = 1;
5
5
  export const CONFIG_FILE_NAME = "taproot-site.json";
@@ -256,6 +256,9 @@ const IMAGE_BANNER = Object.freeze({
256
256
  followingSpacing: enumOf("standard", "compact"),
257
257
  scrim: enumOf("auto", "none", "flat", "top", "bottom", "left", "right", "radial"),
258
258
  scrimStrength: enumOf("soft", "medium", "strong"),
259
+ // Finish a directional scrim's anchored edge at full alpha so the banner
260
+ // joins the adjoining band without a seam (Espalier 4.17.0 scrim-edge).
261
+ scrimEdge: enumOf("none", "flush"),
259
262
  bannerScheme: enumOf("auto", "light", "dark"),
260
263
  // Drop-shadow strength applied to the overlay text.
261
264
  textShadow: enumOf("none", "light", "medium", "strong"),
@@ -502,6 +505,7 @@ const COMPONENT_DEFINITIONS = Object.freeze({
502
505
  followingSpacing: "standard",
503
506
  scrim: "auto",
504
507
  scrimStrength: "medium",
508
+ scrimEdge: "none",
505
509
  bannerScheme: "auto",
506
510
  textShadow: "none",
507
511
  texture: "none",
@@ -512,6 +516,7 @@ const COMPONENT_DEFINITIONS = Object.freeze({
512
516
  "Set altText to describe meaningful banner imagery; leave it empty only when the image is decorative.",
513
517
  "Choose a scrim and scheme that keep overlay text readable across the image.",
514
518
  "For a full-bleed photograph with text aligned to the surrounding page well, set contentGutter to page. followingSpacing: compact reduces the top padding of an immediately following section so a bottom banner heading reads with its supporting paragraph. Use a banner-only contentPadding: none section followed by a standard text section; keep their theme contexts consistent. Defaults preserve frame-edge text and standard section spacing.",
519
+ "scrimEdge: flush finishes a directional scrim at full strength where it meets the page, so a full-bleed banner dissolves into the band below instead of ending at a visible line. It requires the arrangement above: a banner-only contentPadding: none section immediately followed by a section in the same theme context. It is inert when scrim is none, flat, or radial, and it stops being seamless when bannerScheme pins a polarity the page scheme does not share, because the scrim ink is then a derived color rather than the adjoining band's own. Default none; add it deliberately, per banner.",
515
520
  "Compose around the actual photograph: identify the meaningful subject and a quiet area for copy. Do not cover faces, product details, or high-detail edges. A focal point controls cropping, not text placement; inspect wide and compact crops separately. Keep the subject's face, ears, and other identifying details inside the compact frame, including at both ends of parallax travel. Adjust focus toward the subject rather than reusing a generic center; reduce motion or choose a roomier crop if preserving the subject requires it.",
516
521
  "Use contentPosition to place copy in that quiet area, not automatically in the center. Keep one dominant message and nearby supporting copy/actions; if the photo has no safe text area, use a split hero or a separate text section instead of forcing an overlay.",
517
522
  "Use the rule of thirds as a composition guide: consider the subject near one third and a compact text group in quiet space on the opposite side. Golden-ratio proportions are optional alternatives, not a proven engagement formula. Neither guide overrides legibility, the actual subject, or the compact crop. Current contentPosition values are edge/center anchors, not exact third or golden-ratio coordinates; do not invent unsupported positioning properties.",
package/src/transport.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  HTTP_TOO_MANY_REQUESTS,
11
11
  HTTP_UNAUTHORIZED,
12
12
  LIMITS,
13
+ LOCAL_API_BASE_URL,
13
14
  PLAN_LIMIT_REFUSAL_FIELD,
14
15
  PUBLISH_KEY_ENVIRONMENT_VARIABLE,
15
16
  REFUSAL_CAPABILITY_MISSING,
@@ -82,6 +83,18 @@ function isUndelivered(error) {
82
83
  return code !== undefined && UNDELIVERED_CAUSE_CODES.has(code);
83
84
  }
84
85
 
86
+ /**
87
+ * The certificate-verification codes, named once. `describeTransportCause`
88
+ * softens them into a sentence and `localCertificateHint` decides whether a
89
+ * remediation belongs with them, so the list must not be restated in either.
90
+ */
91
+ const CERTIFICATE_CAUSE_CODES = new Set([
92
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
93
+ "SELF_SIGNED_CERT_IN_CHAIN",
94
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
95
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
96
+ ]);
97
+
85
98
  /**
86
99
  * The underlying cause, named.
87
100
  *
@@ -94,12 +107,32 @@ function isUndelivered(error) {
94
107
  function describeTransportCause(error) {
95
108
  const code = transportCauseCode(error);
96
109
  if (code === undefined || !SAFE_CAUSE_CODE.test(code)) return undefined;
97
- return code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || code === "SELF_SIGNED_CERT_IN_CHAIN"
98
- || code === "UNABLE_TO_GET_ISSUER_CERT_LOCALLY" || code === "DEPTH_ZERO_SELF_SIGNED_CERT"
110
+ return CERTIFICATE_CAUSE_CODES.has(code)
99
111
  ? `${code}: the server's TLS certificate was not trusted`
100
112
  : code;
101
113
  }
102
114
 
115
+ /**
116
+ * The one remediation this error has, and only where it is the answer.
117
+ *
118
+ * Against the local stack an untrusted certificate is nearly always setup, not
119
+ * an attack: mkcert installs its root into the system and browser trust stores
120
+ * and Node reads neither, so the CLI fails while the same host loads fine in
121
+ * Chrome. Naming the fix here saves the round-trip the bare cause code costs.
122
+ *
123
+ * Deliberately silent for every other origin. On production the same code
124
+ * means an interception or a genuinely broken chain, and printing "here is how
125
+ * to trust it" would be advice to disable the check that just did its job.
126
+ */
127
+ function localCertificateHint(apiOrigin, error) {
128
+ const code = transportCauseCode(error);
129
+ if (code === undefined || !CERTIFICATE_CAUSE_CODES.has(code)) return "";
130
+ if (apiOrigin !== new URL(LOCAL_API_BASE_URL).origin) return "";
131
+ return " The local stack uses mkcert, whose root Node does not read from the system trust store."
132
+ + " Export NODE_EXTRA_CA_CERTS=\"$HOME/Library/Application Support/mkcert/rootCA.pem\""
133
+ + " (or NODE_USE_SYSTEM_CA=1 on Node 22.19+ or 24.6+) and run the command again.";
134
+ }
135
+
103
136
  const SAFE_CAUSE_CODE = /^[A-Z0-9_]{1,64}$/u;
104
137
 
105
138
  // Exported because the credential store's pre-mint size projection has to
@@ -742,7 +775,8 @@ export class SiteApiClient {
742
775
  (ambiguous
743
776
  ? "A Taproot API mutation ended without an authoritative response."
744
777
  : "A Taproot API request failed before a response was received.")
745
- + (cause === undefined ? "" : ` (${cause})`),
778
+ + (cause === undefined ? "" : ` (${cause})`)
779
+ + localCertificateHint(this.apiOrigin, error),
746
780
  );
747
781
  }
748
782
  if (response.status >= 200 && response.status < 300 && !response.redirected) {