@blamejs/blamejs-shop 0.0.79 → 0.0.82

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/CHANGELOG.md CHANGED
@@ -8,6 +8,12 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.0.x
10
10
 
11
+ - v0.0.82 (2026-05-23) — **`vendor-update.sh --check` skips gracefully when upstream is unreachable (Cloudflare Workers Builds fix).** The vendor-drift gate inside the container smoke test was failing in build environments that can't reach `api.github.com` — `_latest_tag()` returned empty and the script reported a phantom drift against an empty version string. The committed `lib/vendor/blamejs/` tree is already the source of truth at build time; freshness can only meaningfully be checked when the upstream tag is reachable. The gate now skips with a warning to stderr when the upstream lookup returns empty, instead of failing the build. **Fixed:** *`scripts/vendor-update.sh --check` no longer fails the build when upstream is unreachable* — When `_latest_tag()` returns an empty string (sandboxed CI runner, rate-limited anonymous GitHub API request, air-gapped image), the gate emits `[vendor-check] SKIPPED — could not resolve upstream tag (offline / rate-limited); committed v<X> is the source of truth` to stderr and exits 0. The next operator-run smoke against a network-reachable environment re-verifies freshness. Online behavior is unchanged — when the upstream tag resolves, the gate compares as before and fails on actual drift.
12
+
13
+ - v0.0.81 (2026-05-23) — **Comprehensive `codebase-patterns` detector catalog for primitive composition + shape alignment with the vendored framework's catalog.** Extends the `codebase-patterns` detector with five additional reinvention catchers (`manual-random-uuid`, `manual-random-bytes`, `weak-hash-sha2`, `manual-createhmac`, `worker-direct-vendor-import`) and aligns every entry's data shape with the vendored framework's canonical catalog at `lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js` — `id` / `primitive` (one-line replacement) / `regex` / `allowlist` / `reason`. Existing `console-direct`, `math-random`, `todo-fixme-hack-xxx`, `empty-catch-swallow` detectors expand from the `lib`-only scope to a new `shop` scope (lib/ + worker/) so the Worker substrate gets the same hygiene gates. The runner prints both the canonical primitive and the deeper reason on failure so the operator-facing fail message points directly at the b.* call that should have been composed. **Added:** *Five additional `codebase-patterns` detectors for blamejs primitive composition* — `manual-random-uuid` (`crypto.randomUUID()` → `b.uuid.v7()` or `b.uuid.v4()`), `manual-random-bytes` (`crypto.randomBytes(n)` → `b.crypto.generateBytes(n)`), `weak-hash-sha2` (`createHash("sha256"|"sha384"|"sha512")` → `b.crypto.sha3Hash(data)` outside explicit protocol exceptions), `manual-createhmac` (`createHmac(...)` → `b.crypto.hmacSha3` / `b.crypto.hmacSha256`), `worker-direct-vendor-import` (Worker code reaching for `lib/vendor/blamejs/lib/*.js` leaf modules outside `worker/b.js` → use the adapter). The four detectors that already existed for `lib`-only enforcement (`console-direct`, `math-random`, `todo-fixme-hack-xxx`, `empty-catch-swallow`) expand to the new `shop` scope covering both `lib/` and `worker/`. **Changed:** *Detector entry shape aligned with the vendored framework's catalog* — Every entry now carries `id`, `primitive` (the canonical one-line replacement), `regex`, `allowlist`, and `reason` — matching the shape blamejs's own `codebase-patterns.test.js` uses for its 95 internal detectors. The runner prints both the primitive line and the deeper reason on failure so operators see what to compose AND why, not just the regex match. · *Allow markers on the documented exceptions (`worker/index.js` console.*, `lib/pixel-events.js` SHA-256)* — `worker/index.js` carries per-line `allow:console-direct` markers on every `console.log/error` call — Workers have no framework observability sink; `console.*` IS the structured log emission point auto-routed to wrangler tail / Logpush. `lib/pixel-events.js#_sha256Hex` carries inline `allow:weak-hash-sha2` (and the existing `allow:non-shop-require`) markers — Meta CAPI / Google EC / TikTok / Pinterest / Snap CAPI mandate SHA-256 of the normalised identifier on the wire and `b.crypto.sha3Hash` is not a valid substitute.
14
+
15
+ - v0.0.80 (2026-05-23) — **Worker code composes blamejs primitives through a single `worker/b.js` adapter + four new codebase-patterns detectors catch reinvention.** The Worker now imports the framework primitives it needs through a single validated adapter at `worker/b.js`. The adapter pulls leaf modules from `lib/vendor/blamejs/lib/` directly (the framework entry's `node:tls.DEFAULT_MIN_VERSION` write has no Worker analogue) and re-exports them under the canonical `b.<namespace>` shape. Worker code now reads `b.template.escapeHtml(s)`, `b.money.of(n, c).format("en-US")`, `b.crypto.timingSafeEqual(a, b)`, and `b.crypto.hmacSha256(secret, message)` — same call shape as server-side code. Four new `codebase-patterns` detectors catch the reinvention shapes (per-character HTML-escape regex, `Intl.NumberFormat({style:"currency"})`, hand-rolled timing-safe comparison loop, inline `crypto.subtle.sign("HMAC")`) so future Worker / lib code can't reach for these without being flagged at smoke time. Eight pre-existing reinvention sites in `lib/storefront.js` (the 5-char `_esc`, `_escAttr`, `_orderEsc`, `_xmlEsc` helpers), `lib/wishlist-digest.js`, and `lib/barcodes.js` were refactored to compose `b.template.escapeHtml` — bringing the apostrophe escape (`&#x27;`) defense the previous 4-char shape was missing. **Added:** *`worker/b.js` — single validated blamejs adapter for the Worker substrate* — Imports `crypto`, `template`, `money`, `uuid`, `safe-url`, `safe-sql`, `fsm` leaf modules from `lib/vendor/blamejs/lib/` and re-exports them under the canonical `b.<namespace>` shape. Adds one Worker-side `b.crypto.hmacSha256` extension (the framework's PQC-first policy ships only `b.crypto.hmacSha3` publicly; Stripe webhook verify mandates SHA-256 per their published protocol). The extension composes `node:crypto.createHmac` — the same primitive the framework's internal `hmac()` helper uses. · *Four new `codebase-patterns` detectors — flags blamejs-primitive reinvention* — Adds `worker-render-reinvented-primitive` (per-char HTML escape regex → `b.template.escapeHtml`), `intl-numberformat-currency-reinvented` (`new Intl.NumberFormat({style:"currency"})` → `b.money.of(amount, currency).format(locale)`), `manual-timing-safe-equal` (hand-rolled `diff |= a.charCodeAt(i) ^ b.charCodeAt(i)` loop → `b.crypto.timingSafeEqual`), and `inline-hmac-subtle-crypto` (`crypto.subtle.sign("HMAC", ...)` → `b.crypto.hmacSha256` or the framework's webhook-verify primitive). The detectors scan a new `shop` scope (lib/ + worker/) so they catch reinvention in both surfaces. **Changed:** *Worker render + Stripe verify compose blamejs primitives through `worker/b.js`* — `worker/render/_lib.js` now delegates `escapeHtml`, `escapeAttr`, and the per-value substitution in `renderTemplate` to `b.template.escapeHtml`, and routes `formatPrice` through `b.money.of(BigInt(minor), currency).format("en-US")`. `worker/index.js`'s `_timingSafeEqual` delegates to `b.crypto.timingSafeEqual`. `_verifyStripeSignature` swaps its inline `crypto.subtle.sign("HMAC", ...)` block for `b.crypto.hmacSha256(secret, message)`. The warming-page canonical-URL escape now uses `b.template.escapeHtml`. · *Eight pre-existing inline HTML-escape helpers refactored to `b.template.escapeHtml`* — `lib/storefront.js` had six per-function helpers (`_esc`, `_escAttr` × 3, `_orderEsc`, `_xmlEsc`) that hand-rolled the 4- or 5-char escape. `lib/wishlist-digest.js#_htmlEscape` and `lib/barcodes.js`'s inline barcode-label escape used the same shape. All eight now compose `b.template.escapeHtml` — the four-character helpers gain the apostrophe (`&#x27;`) escape they were missing, closing a small defense-in-depth gap on attribute interpolation against single-quoted attributes.
16
+
11
17
  - v0.0.79 (2026-05-23) — **Container Dockerfile uses the committed vendored tree instead of re-fetching from GitHub at build time.** The container image's `vendor` stage previously ran `bash scripts/vendor-update.sh blamejs latest` to re-fetch the vendored blamejs at image-build time. That stage fails in build environments without outbound `api.github.com` access (rate-limited anonymous requests, sandboxed CI runners, etc.) — most visibly on Cloudflare Workers Builds. The committed `lib/vendor/blamejs/` tree is already the source of truth, and the smoke gate's `vendor-update.sh --check` already enforces it matches the latest upstream release tag, so the in-build re-fetch is redundant. The vendor stage now just `COPY`s the committed tree. **Fixed:** *`Dockerfile` vendor stage no longer requires `api.github.com` access at image-build time* — Removed the `RUN bash scripts/vendor-update.sh blamejs "${BLAMEJS_TAG}"` step and the `BLAMEJS_TAG` build arg. The vendor stage now copies `lib/vendor/` from the build context directly. Image builds in network-restricted environments (Cloudflare Workers Builds, air-gapped CI runners) now succeed where they previously errored on `could not resolve latest blamejs release tag`. The committed vendor stays the source of truth; freshness is enforced by the smoke gate's `vendor-update.sh --check`, not the image build.
12
18
 
13
19
  - v0.0.78 (2026-05-23) — **Edge HTML cache + SmartPlacement — unauthenticated storefront reads drop to ~10-30ms TTFB.** Two complementary perf wins layer on top of the v0.0.77 edge render. The Worker now wraps `/`, `/search`, and `/products/:slug` with a `caches.default` lookup keyed on the full request URL; only unauthenticated requests (no `shop_sid` / `shop_auth` cookie) are eligible. Cached responses carry `cache-control: public, max-age=60, stale-while-revalidate=300` so the edge keeps serving HTML while a background revalidate refreshes the data. Authenticated requests skip the cache entirely so per-session content stays correct. Separately, `wrangler.toml` gains `[placement] mode = "smart"` so the Worker compute runs close to D1 rather than close to the user — eliminates the cross-region D1 round-trip on cache miss. **Added:** *Edge HTML cache for unauthenticated storefront reads — 60s TTL + 300s stale-while-revalidate* — `worker/index.js` wraps `_edgeRender` with a `caches.default` lookup. Requests carrying any `shop_sid` / `shop_auth` cookie bypass the cache and hit the renderer directly. Cache misses store the rendered HTML via `ctx.waitUntil(cache.put(...))` so subsequent visitors get an edge-cache hit at the same PoP. Stale-while-revalidate lets the edge serve last-known-good HTML during the 5-minute background-revalidate window. Cached responses carry an `x-edge-cache: hit` header for observability; misses carry `x-edge-cache: miss`. · *SmartPlacement enabled — Worker runs close to D1, not close to the user* — `[placement] mode = "smart"` in `wrangler.toml`. Cloudflare's placement engine routes the Worker compute to a region close to the bound D1 database instead of the user-nearest PoP. The edge still serves the eventual response to the user via standard routing — only Worker execution moves. Cuts the D1 round-trip on cache miss from ~30-50ms (cross-region) to sub-ms (intra-region).
package/lib/barcodes.js CHANGED
@@ -367,14 +367,7 @@ function _renderSvg(modules, label, opts) {
367
367
  }
368
368
  var labelXml = "";
369
369
  if (label) {
370
- // Escape & < > " for the human-readable line. (' is rare in
371
- // barcode values but cheap to cover.)
372
- var safe = String(label)
373
- .replace(/&/g, "&amp;")
374
- .replace(/</g, "&lt;")
375
- .replace(/>/g, "&gt;")
376
- .replace(/"/g, "&quot;")
377
- .replace(/'/g, "&#39;");
370
+ var safe = _b().template.escapeHtml(String(label));
378
371
  labelXml = "<text x=\"" + (totalW / 2).toFixed(3) + "\" y=\"" + (heightPx - 2) +
379
372
  "\" font-family=\"monospace\" font-size=\"10\" text-anchor=\"middle\" fill=\"#000\">" + safe + "</text>";
380
373
  }
@@ -96,7 +96,7 @@
96
96
  * @related b.uuid, b.guardUuid
97
97
  */
98
98
 
99
- var nodeCrypto = require("node:crypto"); // allow:non-shop-require — Provider spec (Meta CAPI, Google EC, TikTok Events, Pinterest CAPI, Snap CAPI) mandates SHA-256 of the normalised identifier; blamejs.crypto exposes SHA3-512 / SHAKE256 / namespaceHash, none of which match the wire format ad platforms accept. Node's stdlib createHash("sha256") is the minimum-surface route.
99
+ var nodeCrypto = require("node:crypto"); // allow:non-shop-require allow:weak-hash-sha2 — Provider spec (Meta CAPI, Google EC, TikTok Events, Pinterest CAPI, Snap CAPI) mandates SHA-256 of the normalised identifier; blamejs.crypto exposes SHA3-512 / SHAKE256 / namespaceHash, none of which match the wire format ad platforms accept. Node's stdlib SHA-256 hash is the minimum-surface route.
100
100
 
101
101
  var bShop;
102
102
  function _b() {
@@ -478,6 +478,7 @@ function _normalisePhone(s) {
478
478
  }
479
479
 
480
480
  function _sha256Hex(s) {
481
+ // allow:weak-hash-sha2 — Meta CAPI / Google EC / TikTok / Pinterest / Snap CAPI all mandate SHA-256 of the normalised identifier on the wire; b.crypto.sha3Hash (SHA3-512) is not a valid substitute.
481
482
  return nodeCrypto.createHash("sha256").update(s, "utf8").digest("hex");
482
483
  }
483
484
 
package/lib/storefront.js CHANGED
@@ -513,10 +513,7 @@ function renderHome(opts) {
513
513
  // than the dense 6-tile collections grid below. Operators that
514
514
  // want a different selection rule (top-seller, newest, manually
515
515
  // pinned) wrap renderHome and override `opts.featured`.
516
- function _esc(s) {
517
- return String(s == null ? "" : s)
518
- .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
519
- }
516
+ function _esc(s) { return _b().template.escapeHtml(s); }
520
517
  var featuredProduct = null;
521
518
  if (opts.featured) {
522
519
  featuredProduct = opts.featured;
@@ -691,13 +688,7 @@ var PRODUCT_PAGE =
691
688
  // freshly-seeded product never renders an empty square.
692
689
  function _buildPdpGallery(product, media, assetPrefix) {
693
690
  var prefix = assetPrefix || "/assets/";
694
- function _escAttr(s) {
695
- return String(s == null ? "" : s)
696
- .replace(/&/g, "&amp;")
697
- .replace(/</g, "&lt;")
698
- .replace(/>/g, "&gt;")
699
- .replace(/"/g, "&quot;");
700
- }
691
+ function _escAttr(s) { return _b().template.escapeHtml(s); }
701
692
  if (!media || media.length === 0) {
702
693
  var initial = (product.title || "?").trim().charAt(0).toUpperCase() || "?";
703
694
  return "<figure class=\"pdp__media\" aria-hidden=\"true\">" +
@@ -1047,10 +1038,7 @@ function renderOrder(opts) {
1047
1038
  asset_css_main: opts.theme.assetUrl("css/main.css"),
1048
1039
  });
1049
1040
  }
1050
- function _orderEsc(s) {
1051
- return String(s == null ? "" : s)
1052
- .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1053
- }
1041
+ function _orderEsc(s) { return _b().template.escapeHtml(s); }
1054
1042
  var rows = rendered.map(function (l) {
1055
1043
  var thumb = l.image_url
1056
1044
  ? "<span class=\"cart-line__thumb\"><img src=\"" + _orderEsc(l.image_url) + "\" alt=\"" + _orderEsc(l.image_alt) + "\" loading=\"lazy\"></span>"
@@ -1180,10 +1168,7 @@ function renderCart(opts) {
1180
1168
  asset_css_main: opts.theme.assetUrl("css/main.css"),
1181
1169
  });
1182
1170
  }
1183
- function _escAttr(s) {
1184
- return String(s == null ? "" : s)
1185
- .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1186
- }
1171
+ function _escAttr(s) { return _b().template.escapeHtml(s); }
1187
1172
  var body;
1188
1173
  if (rendered.length === 0) {
1189
1174
  body = CART_EMPTY_PAGE;
@@ -1628,10 +1613,7 @@ function renderAccount(opts) {
1628
1613
  if (s === "completed" || s === "shipped" || s === "delivered") return "pdp__badge--ok";
1629
1614
  return "";
1630
1615
  }
1631
- function _escAttr(s) {
1632
- return String(s == null ? "" : s)
1633
- .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1634
- }
1616
+ function _escAttr(s) { return _b().template.escapeHtml(s); }
1635
1617
 
1636
1618
  var rows = orders.map(function (o) {
1637
1619
  var products = orderLookup[o.id] || [];
@@ -2496,11 +2478,10 @@ function mount(router, deps) {
2496
2478
  // is hand-rolled (no node:xml dep) because the surface is
2497
2479
  // ~3 fields per row and the XML-escape is trivial.
2498
2480
  router.get("/sitemap.xml", async function (req, res) {
2499
- function _xmlEsc(s) {
2500
- return String(s == null ? "" : s)
2501
- .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
2502
- .replace(/"/g, "&quot;").replace(/'/g, "&apos;");
2503
- }
2481
+ // XML 1.0 §4.6 names `&apos;` for apostrophe but also accepts the
2482
+ // numeric reference `&#x27;` which is what b.template.escapeHtml
2483
+ // emits, so the same primitive works for the sitemap.
2484
+ function _xmlEsc(s) { return _b().template.escapeHtml(s); }
2504
2485
  var hostHeader = req.headers && (req.headers.host || req.headers.Host) || "";
2505
2486
  var origin = hostHeader ? ("https://" + hostHeader) : "";
2506
2487
  var urls = [];
@@ -136,14 +136,7 @@ var DAY_MS = 24 * 60 * 60 * 1000;
136
136
  // the mail primitive). Same rules — five named entities, no tag/attr
137
137
  // context awareness because we only embed in text nodes / hrefs that
138
138
  // we control directly.
139
- function _htmlEscape(s) {
140
- return String(s)
141
- .replace(/&/g, "&amp;")
142
- .replace(/</g, "&lt;")
143
- .replace(/>/g, "&gt;")
144
- .replace(/"/g, "&quot;")
145
- .replace(/'/g, "&#39;");
146
- }
139
+ function _htmlEscape(s) { return _b().template.escapeHtml(s); }
147
140
 
148
141
  // ---- validators ---------------------------------------------------------
149
142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.0.79",
3
+ "version": "0.0.82",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {