@blamejs/blamejs-shop 0.0.50 → 0.0.51
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 +2 -0
- package/lib/storefront.js +73 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.0.x
|
|
10
10
|
|
|
11
|
+
- v0.0.51 (2026-05-22) — **SEO crawl surfaces — `/robots.txt` + `/sitemap.xml` listing every active product.** Crawlers landing on the live shop had no discovery surface — every product page was reachable only by being linked from the home grid, and there was no robots.txt to direct the crawl. This release wires `/robots.txt` (allow-everything-except-session-scoped, pointing at the sitemap) and `/sitemap.xml` (lists the home + /admin + every `status='active'` product with its `updated_at` lastmod, capped at 1000 rows). The XML is hand-rolled — no node:xml dep — with attribute-escaped values so a slug containing `&` or `<` can't break out of the document. **Added:** *`GET /robots.txt`* — `User-agent: *` + `Allow: /` for crawlable surfaces. Disallow paths cover the session-scoped + operator-only surfaces (`/admin`, `/cart`, `/checkout`, `/pay/`, `/orders/`, `/account`) — none of those have crawl value and exposing them risks crawlers tripping rate limits on the cart-bound DO. `Sitemap:` directive resolves from the request `Host` header so the same handler serves the right absolute URL for `localhost:8080` (dev), `blamejs-shop.coocoo.workers.dev` (live), or any custom domain. `Cache-Control: public, max-age=3600`. Operators with stricter requirements override by uploading a `robots.txt` key to R2 — the Worker's static-asset bridge serves R2 keys ahead of the storefront router, so the bucket file wins. · *`GET /sitemap.xml`* — Lists the home page (priority 1.0, daily changefreq), the `/admin` landing (priority 0.3, monthly changefreq), and every `status='active'` product (priority 0.8, weekly changefreq) with the product's `updated_at` or `created_at` as `lastmod` (ISO date). Capped at 1000 rows — the sitemap spec allows 50,000 but a shop at that scale should pre-segment into a sitemap index. The XML is hand-rolled, attribute-escape applies `&`, `<`, `>`, `"`, `'` so an operator-supplied slug can't break the document. `Cache-Control: public, max-age=300` so a catalog update propagates inside five minutes without an operator action.
|
|
12
|
+
|
|
11
13
|
- v0.0.50 (2026-05-22) — **OpenGraph + Twitter Card meta tags — shared links unfurl with proper previews.** Pages rendered without `og:*` or `twitter:*` tags. Sharing the live URL in Slack / iMessage / Twitter / Discord landed a bare URL with no preview, no image, no description. This release wires OpenGraph + Twitter Card meta tags into the LAYOUT head with sensible site-level defaults (brand logo + shop-level lede). The PDP overrides them with product-specific values — `og:title` is the product title, `og:description` is the catalog `description`, `og:image` is the first attached media URL — so a product share renders with the actual hero image + product copy. **Added:** *OpenGraph + Twitter Card tags in `LAYOUT`* — `<meta property="og:type">`, `og:site_name`, `og:title`, `og:description`, `og:image`, `og:url` + the Twitter Card analogues (`twitter:card=summary_large_image`, `twitter:title`, `twitter:description`, `twitter:image`). All templated through `{{og_*}}` placeholders so the `_render` HTML-escape protects against operator-supplied content breaking out of the attribute boundary. `<meta name="description">` also lands so non-OG crawlers see the same description. · *Default OG values on `_wrap(opts)`* — Every page renders with sensible defaults: `og:type=website`, `og:title=<page title> — <shop name>`, `og:description=Open-source ecommerce framework built on blamejs. Server-rendered HTML, post-quantum crypto, zero npm runtime dependencies.`, `og:image=/assets/brand/logo.png`. Per-renderer overrides via `opts.og_*` work surgically — only the field passed in overrides; the rest stay on defaults. · *Product-specific OG on the PDP* — `renderProduct` overrides four OG fields: `og_type=product`, `og_title=<product.title> — <shop_name>`, `og_description=<product.description>` (or a generated fallback when the description is empty), `og_image=<assetPrefix><heroMedia.r2_key>` (the first attached media row). A PDP share now renders the SVG hero + the product copy, not the brand logo + a generic shop description.
|
|
12
14
|
|
|
13
15
|
- v0.0.49 (2026-05-22) — **README refresh — scoped install command, live-demo URL, refreshed primitive table + migration list.** The README hadn't been touched since v0.0.43; the install snippet still pointed at the unscoped `blamejs-shop` name and the primitives table listed only the original commerce surface. Operators landing on the GitHub repo now see the correct `npm install @blamejs/blamejs-shop`, the live demo URL, badges for npm + license + SLSA L3, and a primitives table that includes `customers`, `subscriptions`, `newsletter`, and `theme` alongside the original commerce stack. **Changed:** *README — install snippet uses `@blamejs/blamejs-shop`* — Replaces the unscoped clone-and-smoke recipe with `npm install @blamejs/blamejs-shop` + the `require()` shape that resolves the framework + adapters. The local clone path stays as the secondary recipe for operators who want to develop against the repo. Three badges land in the header — npm version, Apache-2.0 license, SLSA Level 3. · *README — primitive table extended* — Adds rows for `customers` (passkey-only, email hash-only), `subscriptions` (Stripe-backed recurring), `newsletter` (signup primitive shipped in v0.0.41), and `theme` (file-backed templates). The `storefront` row gains the full surface description — utility bar, sticky header, dark hero with code-preview card, marquee, featured-product callout, collections grid, framework feature band, designed catalog grid, newsletter band, and the four-column footer. `admin` row mentions the subscription-plan routes. · *README — migration list updated* — Lists migrations 0001-0010 (catalog, cart, order, shop_config, webhooks, customers, inventory_thresholds, subscriptions, newsletter_signups) so operators don't have to grep the directory to know what schema lands. Adds the demo-seed recipe (`scripts/seed-sample-products.sql` + `scripts/seed-sample-product-media.sql`) for one-shot population of the live demo's four products + their SVG hero images. · *Live-demo URL pinned* — `https://blamejs-shop.coocoo.workers.dev/` lands in the README header so operators evaluating the framework can see a real deployed instance rendering the storefront design system end-to-end before committing to wiring their own Cloudflare account.
|
package/lib/storefront.js
CHANGED
|
@@ -2432,6 +2432,79 @@ function mount(router, deps) {
|
|
|
2432
2432
|
});
|
|
2433
2433
|
}
|
|
2434
2434
|
|
|
2435
|
+
// robots.txt — minimal crawl policy. Allow everything except
|
|
2436
|
+
// the admin API + cart + account + checkout / pay / orders (these
|
|
2437
|
+
// are session-scoped or operator-only, no crawl value), and
|
|
2438
|
+
// point at the sitemap. Operators with stricter requirements
|
|
2439
|
+
// replace the file at the same path via R2 (the Worker's
|
|
2440
|
+
// static-asset bridge would serve it ahead of this route if a
|
|
2441
|
+
// `robots.txt` key exists in the bucket).
|
|
2442
|
+
router.get("/robots.txt", function (req, res) {
|
|
2443
|
+
res.status(200);
|
|
2444
|
+
res.setHeader && res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
2445
|
+
res.setHeader && res.setHeader("cache-control", "public, max-age=3600");
|
|
2446
|
+
var hostHeader = req.headers && (req.headers.host || req.headers.Host) || "";
|
|
2447
|
+
var origin = hostHeader ? ("https://" + hostHeader) : "";
|
|
2448
|
+
var body =
|
|
2449
|
+
"User-agent: *\n" +
|
|
2450
|
+
"Allow: /\n" +
|
|
2451
|
+
"Disallow: /admin\n" +
|
|
2452
|
+
"Disallow: /cart\n" +
|
|
2453
|
+
"Disallow: /checkout\n" +
|
|
2454
|
+
"Disallow: /pay/\n" +
|
|
2455
|
+
"Disallow: /orders/\n" +
|
|
2456
|
+
"Disallow: /account\n" +
|
|
2457
|
+
(origin ? ("Sitemap: " + origin + "/sitemap.xml\n") : "");
|
|
2458
|
+
res.end ? res.end(body) : res.send(body);
|
|
2459
|
+
});
|
|
2460
|
+
|
|
2461
|
+
// sitemap.xml — lists every active product slug + the home page
|
|
2462
|
+
// + the framework-API landing. Cached short (5 minutes) so a
|
|
2463
|
+
// catalog update propagates without an operator action. The XML
|
|
2464
|
+
// is hand-rolled (no node:xml dep) because the surface is
|
|
2465
|
+
// ~3 fields per row and the XML-escape is trivial.
|
|
2466
|
+
router.get("/sitemap.xml", async function (req, res) {
|
|
2467
|
+
function _xmlEsc(s) {
|
|
2468
|
+
return String(s == null ? "" : s)
|
|
2469
|
+
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
2470
|
+
.replace(/"/g, """).replace(/'/g, "'");
|
|
2471
|
+
}
|
|
2472
|
+
var hostHeader = req.headers && (req.headers.host || req.headers.Host) || "";
|
|
2473
|
+
var origin = hostHeader ? ("https://" + hostHeader) : "";
|
|
2474
|
+
var urls = [];
|
|
2475
|
+
urls.push({ loc: origin + "/", changefreq: "daily", priority: "1.0" });
|
|
2476
|
+
urls.push({ loc: origin + "/admin", changefreq: "monthly", priority: "0.3" });
|
|
2477
|
+
try {
|
|
2478
|
+
var page = await deps.catalog.products.list({ status: "active", limit: 1000 });
|
|
2479
|
+
for (var i = 0; i < page.rows.length; i += 1) {
|
|
2480
|
+
var p = page.rows[i];
|
|
2481
|
+
var lastmod = new Date(p.updated_at || p.created_at || Date.now()).toISOString().slice(0, 10);
|
|
2482
|
+
urls.push({
|
|
2483
|
+
loc: origin + "/products/" + p.slug,
|
|
2484
|
+
lastmod: lastmod,
|
|
2485
|
+
changefreq: "weekly",
|
|
2486
|
+
priority: "0.8",
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
} catch (_e) { /* drop-silent — catalog unreachable means sitemap drops product rows */ }
|
|
2490
|
+
var body = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
|
2491
|
+
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
|
|
2492
|
+
for (var u = 0; u < urls.length; u += 1) {
|
|
2493
|
+
var item = urls[u];
|
|
2494
|
+
body += " <url>\n";
|
|
2495
|
+
body += " <loc>" + _xmlEsc(item.loc) + "</loc>\n";
|
|
2496
|
+
if (item.lastmod) body += " <lastmod>" + _xmlEsc(item.lastmod) + "</lastmod>\n";
|
|
2497
|
+
body += " <changefreq>" + _xmlEsc(item.changefreq) + "</changefreq>\n";
|
|
2498
|
+
body += " <priority>" + _xmlEsc(item.priority) + "</priority>\n";
|
|
2499
|
+
body += " </url>\n";
|
|
2500
|
+
}
|
|
2501
|
+
body += "</urlset>\n";
|
|
2502
|
+
res.status(200);
|
|
2503
|
+
res.setHeader && res.setHeader("content-type", "application/xml; charset=utf-8");
|
|
2504
|
+
res.setHeader && res.setHeader("cache-control", "public, max-age=300");
|
|
2505
|
+
res.end ? res.end(body) : res.send(body);
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2435
2508
|
// Designed admin landing — the rest of /admin/* is JSON. This
|
|
2436
2509
|
// single GET gives footer links + curious visitors a designed
|
|
2437
2510
|
// page explaining the API-only posture instead of a 404.
|
package/package.json
CHANGED