@bettercms-ai/convert 0.25.1 → 0.26.0

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/index.d.ts CHANGED
@@ -957,7 +957,7 @@ declare function attrsFor(path: string, kind: string, props?: AttrBinding[], sco
957
957
  declare function propsAttribute(props: AttrBinding[]): string;
958
958
 
959
959
  /** Bumped whenever the emitted source changes. `isKnownHelper` upgrades anything older. */
960
- declare const HELPER_VERSION = 13;
960
+ declare const HELPER_VERSION = 14;
961
961
  /** Where each dialect's helper lives. Null = this dialect reads nothing at build time. */
962
962
  declare const HELPER_PATH: Record<Dialect, string | null>;
963
963
  /** The directory the snapshots live in, at the repository root. */
package/dist/index.js CHANGED
@@ -4199,7 +4199,7 @@ async function bindImages(ctx, targets) {
4199
4199
 
4200
4200
  // src/helper.ts
4201
4201
  import { createHash as createHash2 } from "crypto";
4202
- var HELPER_VERSION = 13;
4202
+ var HELPER_VERSION = 14;
4203
4203
  var HEADER = "// @bettercms-ai/convert helper v";
4204
4204
  var HELPER_PATH = Object.fromEntries(
4205
4205
  Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
@@ -4273,11 +4273,11 @@ function render(value: unknown): string | undefined {
4273
4273
  *
4274
4274
  * Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
4275
4275
  * runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
4276
- * read keeps the last good copy; it is checked at most once a second.
4276
+ * read keeps the last good copy. Its modification time is asked on EVERY read (a stat, microseconds;
4277
+ * the file is parsed again only when it changed), so a publish shows on the very next request.
4277
4278
  */
4278
- type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
4279
+ type BcmsLiveContent = { helper: string; stamp: number; bySlug: Map<string, unknown>; entries: Record<string, unknown>; layout: unknown };
4279
4280
  let bcmsLive: BcmsLiveContent | null = null;
4280
- let bcmsLiveCheckedAt = 0;
4281
4281
 
4282
4282
  function liveContent(): BcmsLiveContent | null {
4283
4283
  const proc = (globalThis as {
@@ -4285,9 +4285,6 @@ function liveContent(): BcmsLiveContent | null {
4285
4285
  }).process;
4286
4286
  const file = proc?.env?.BCMS_CONTENT_FILE;
4287
4287
  if (!file || typeof proc?.getBuiltinModule !== "function") return null;
4288
- const now = Date.now();
4289
- if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
4290
- bcmsLiveCheckedAt = now;
4291
4288
  try {
4292
4289
  const fs = proc.getBuiltinModule("node:fs") as {
4293
4290
  statSync(path: string): { mtimeMs: number };
@@ -4301,13 +4298,30 @@ function liveContent(): BcmsLiveContent | null {
4301
4298
  for (const rows of Object.values(collections)) {
4302
4299
  if (!Array.isArray(rows)) continue;
4303
4300
  for (const row of rows) {
4304
- if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) bySlug.set(row.slug, row);
4301
+ // A published row carries its values under \`data\` (the delivery API's shape); the snapshot a
4302
+ // template reads is the values themselves. Read flat, as before, where a row has none.
4303
+ if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) {
4304
+ bySlug.set(row.slug, isRecord(row.data) ? { ...row.data, slug: row.slug } : row);
4305
+ }
4305
4306
  }
4306
4307
  }
4308
+ // A dynamic route reads \`page.entries[slug]\`: every published row, by its slug.
4309
+ const entries: Record<string, unknown> = Object.fromEntries(bySlug);
4310
+ for (const row of bySlug.values()) if (isRecord(row) && !("entries" in row)) row.entries = entries;
4307
4311
  const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
4308
4312
  const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
4309
- const layout = isRecord(withLayout) ? withLayout.layout : isRecord(snapshot) ? snapshot.layout : undefined;
4310
- bcmsLive = { stamp, bySlug, layout };
4313
+ // The site's Layout first; a page's own (its overrides resolved in) only where there is no other.
4314
+ const delivered = isRecord(snapshot) && isRecord(snapshot.layout) ? snapshot.layout : isRecord(withLayout) ? withLayout.layout : undefined;
4315
+ // A template addresses a Layout value as \`<section id>:<field id>\`; the published Layout holds
4316
+ // each Section's values under \`sections.<slug>.fields\`. Both spellings are answered.
4317
+ const layout: Record<string, unknown> | undefined = isRecord(delivered) ? { ...delivered } : undefined;
4318
+ for (const section of layout && isRecord(delivered) && isRecord(delivered.sections) ? Object.values(delivered.sections) : []) {
4319
+ if (!isRecord(section) || typeof section.id !== "string" || !isRecord(section.fields)) continue;
4320
+ for (const [field, value] of Object.entries(section.fields)) layout![section.id + ":" + field] = value;
4321
+ }
4322
+ // \`helper\` names this reader in the built bundle: the platform reads it to know the app shows a
4323
+ // publish on its next request, and an image BetterCMS copied beside it (@see bcmsImage).
4324
+ bcmsLive = { helper: "bcms-helper@${HELPER_VERSION}", stamp, bySlug, entries, layout };
4311
4325
  } catch {
4312
4326
  // Unreadable, or caught mid-write: keep serving the last good copy.
4313
4327
  }
@@ -4319,7 +4333,10 @@ function live(page: unknown): unknown {
4319
4333
  const content = liveContent();
4320
4334
  if (!content) return page;
4321
4335
  if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
4322
- if (isRecord(page) && typeof page.slug === "string") return content.bySlug.get(page.slug) ?? page;
4336
+ if (isRecord(page) && typeof page.slug === "string") {
4337
+ // A collection's page has no row of its own: its entries are the published ones all the same.
4338
+ return content.bySlug.get(page.slug) ?? ("entries" in page ? page : { ...page, entries: content.entries });
4339
+ }
4323
4340
  return page;
4324
4341
  }
4325
4342
 
@@ -4333,7 +4350,10 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
4333
4350
 
4334
4351
  /** One field of the shared layout \u2014 the header and footer every route renders. */
4335
4352
  export function bcmsLayout(path: string, fallback: string): string {
4336
- return render(readPath(live(layoutSnapshot), path)) ?? fallback;
4353
+ const layout = live(layoutSnapshot);
4354
+ // The whole address first: a field id may carry a dot of its own (\`derived.nav-cta\`).
4355
+ const whole = isRecord(layout) ? layout[path] : undefined;
4356
+ return render(whole !== undefined ? whole : readPath(layout, path)) ?? fallback;
4337
4357
  }
4338
4358
 
4339
4359
  /**
@@ -4410,13 +4430,24 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
4410
4430
  * release write (\`writeImage\`) puts the editor's image on the \`data-bcms-field\` element, which
4411
4431
  * is where every published value lands. A plain string fallback (\`<img src="/hero.png">\`) has
4412
4432
  * no such rule and takes the CMS url directly.
4433
+ *
4434
+ * \u{1F534} ON A HOSTED SERVER THE EDITOR'S IMAGE IS A LOCAL FILE. A page the app renders per request has no
4435
+ * release write: what this returns is the page. BetterCMS copies a replaced image beside the app and
4436
+ * names the copy on the published value (\`local\`, with its size), and it is returned in the shape
4437
+ * of an import (\`{ src, width, height, format }\`), so \`<Image>\` optimises it like the site's own.
4438
+ * Absent (a build, or the site's own picture) the import stands.
4413
4439
  */
4414
4440
  export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
4415
4441
  page = live(page);
4416
- if (fallback !== null && typeof fallback === "object") return fallback;
4417
4442
  const entries = isRecord(page) ? page.entries : undefined;
4418
4443
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4419
- return render(readPath(root, path)) ?? fallback;
4444
+ const value = readPath(root, path);
4445
+ if (fallback !== null && typeof fallback === "object") {
4446
+ if (!isRecord(value) || typeof value.local !== "string" || typeof value.width !== "number" || typeof value.height !== "number") return fallback;
4447
+ const format = /\\.([a-z0-9]+)$/i.exec(value.local)?.[1]?.toLowerCase() ?? "";
4448
+ return { src: value.local, width: value.width, height: value.height, format: format === "jpg" ? "jpeg" : format } as unknown as T;
4449
+ }
4450
+ return render(value) ?? fallback;
4420
4451
  }
4421
4452
 
4422
4453
  /**
@@ -4749,7 +4780,8 @@ var HISTORICAL = {
4749
4780
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4750
4781
  10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4751
4782
  11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
4752
- 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
4783
+ 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528",
4784
+ 13: "cd8bdd74dbc944f2331503bdf8e995cb7f74f0d5b307d08aee4690e1695ae798"
4753
4785
  },
4754
4786
  "src/lib/bcms-content.ts": {
4755
4787
  1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
@@ -4763,7 +4795,8 @@ var HISTORICAL = {
4763
4795
  9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
4764
4796
  10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
4765
4797
  11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0",
4766
- 12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208"
4798
+ 12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208",
4799
+ 13: "06ee70c9f0bb25e03f3930d2250da8dae172ade457f27ebba20382b6482ad3f4"
4767
4800
  },
4768
4801
  "composables/bcms-content.ts": {
4769
4802
  1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
@@ -4777,7 +4810,8 @@ var HISTORICAL = {
4777
4810
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4778
4811
  10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4779
4812
  11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
4780
- 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
4813
+ 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528",
4814
+ 13: "cd8bdd74dbc944f2331503bdf8e995cb7f74f0d5b307d08aee4690e1695ae798"
4781
4815
  }
4782
4816
  };
4783
4817
  var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");