@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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @bettercms-ai/convert
2
2
 
3
+ ## 0.26.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 69dfcfa: A server app hosted on BetterCMS shows a publish on its very next request: the converted-site helper (v14) and `readPublishedContent` ask the content file's time on every read instead of once a second. Helper v14 also renders an image an editor replaced on a page the app renders per request: `bcmsImage` returns the local copy BetterCMS puts beside the app, in the shape of an import, where it used to keep the build's picture. A site built with an older helper gets v14 on its next BetterCMS build, with no commit.
8
+
3
9
  ## 0.25.1
4
10
 
5
11
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -4203,7 +4203,7 @@ async function bindImages(ctx, targets) {
4203
4203
 
4204
4204
  // src/helper.ts
4205
4205
  import { createHash as createHash2 } from "crypto";
4206
- var HELPER_VERSION = 13;
4206
+ var HELPER_VERSION = 14;
4207
4207
  var HEADER = "// @bettercms-ai/convert helper v";
4208
4208
  var HELPER_PATH = Object.fromEntries(
4209
4209
  Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
@@ -4277,11 +4277,11 @@ function render(value: unknown): string | undefined {
4277
4277
  *
4278
4278
  * Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
4279
4279
  * runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
4280
- * read keeps the last good copy; it is checked at most once a second.
4280
+ * read keeps the last good copy. Its modification time is asked on EVERY read (a stat, microseconds;
4281
+ * the file is parsed again only when it changed), so a publish shows on the very next request.
4281
4282
  */
4282
- type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
4283
+ type BcmsLiveContent = { helper: string; stamp: number; bySlug: Map<string, unknown>; entries: Record<string, unknown>; layout: unknown };
4283
4284
  let bcmsLive: BcmsLiveContent | null = null;
4284
- let bcmsLiveCheckedAt = 0;
4285
4285
 
4286
4286
  function liveContent(): BcmsLiveContent | null {
4287
4287
  const proc = (globalThis as {
@@ -4289,9 +4289,6 @@ function liveContent(): BcmsLiveContent | null {
4289
4289
  }).process;
4290
4290
  const file = proc?.env?.BCMS_CONTENT_FILE;
4291
4291
  if (!file || typeof proc?.getBuiltinModule !== "function") return null;
4292
- const now = Date.now();
4293
- if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
4294
- bcmsLiveCheckedAt = now;
4295
4292
  try {
4296
4293
  const fs = proc.getBuiltinModule("node:fs") as {
4297
4294
  statSync(path: string): { mtimeMs: number };
@@ -4305,13 +4302,30 @@ function liveContent(): BcmsLiveContent | null {
4305
4302
  for (const rows of Object.values(collections)) {
4306
4303
  if (!Array.isArray(rows)) continue;
4307
4304
  for (const row of rows) {
4308
- if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) bySlug.set(row.slug, row);
4305
+ // A published row carries its values under \`data\` (the delivery API's shape); the snapshot a
4306
+ // template reads is the values themselves. Read flat, as before, where a row has none.
4307
+ if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) {
4308
+ bySlug.set(row.slug, isRecord(row.data) ? { ...row.data, slug: row.slug } : row);
4309
+ }
4309
4310
  }
4310
4311
  }
4312
+ // A dynamic route reads \`page.entries[slug]\`: every published row, by its slug.
4313
+ const entries: Record<string, unknown> = Object.fromEntries(bySlug);
4314
+ for (const row of bySlug.values()) if (isRecord(row) && !("entries" in row)) row.entries = entries;
4311
4315
  const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
4312
4316
  const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
4313
- const layout = isRecord(withLayout) ? withLayout.layout : isRecord(snapshot) ? snapshot.layout : undefined;
4314
- bcmsLive = { stamp, bySlug, layout };
4317
+ // The site's Layout first; a page's own (its overrides resolved in) only where there is no other.
4318
+ const delivered = isRecord(snapshot) && isRecord(snapshot.layout) ? snapshot.layout : isRecord(withLayout) ? withLayout.layout : undefined;
4319
+ // A template addresses a Layout value as \`<section id>:<field id>\`; the published Layout holds
4320
+ // each Section's values under \`sections.<slug>.fields\`. Both spellings are answered.
4321
+ const layout: Record<string, unknown> | undefined = isRecord(delivered) ? { ...delivered } : undefined;
4322
+ for (const section of layout && isRecord(delivered) && isRecord(delivered.sections) ? Object.values(delivered.sections) : []) {
4323
+ if (!isRecord(section) || typeof section.id !== "string" || !isRecord(section.fields)) continue;
4324
+ for (const [field, value] of Object.entries(section.fields)) layout![section.id + ":" + field] = value;
4325
+ }
4326
+ // \`helper\` names this reader in the built bundle: the platform reads it to know the app shows a
4327
+ // publish on its next request, and an image BetterCMS copied beside it (@see bcmsImage).
4328
+ bcmsLive = { helper: "bcms-helper@${HELPER_VERSION}", stamp, bySlug, entries, layout };
4315
4329
  } catch {
4316
4330
  // Unreadable, or caught mid-write: keep serving the last good copy.
4317
4331
  }
@@ -4323,7 +4337,10 @@ function live(page: unknown): unknown {
4323
4337
  const content = liveContent();
4324
4338
  if (!content) return page;
4325
4339
  if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
4326
- if (isRecord(page) && typeof page.slug === "string") return content.bySlug.get(page.slug) ?? page;
4340
+ if (isRecord(page) && typeof page.slug === "string") {
4341
+ // A collection's page has no row of its own: its entries are the published ones all the same.
4342
+ return content.bySlug.get(page.slug) ?? ("entries" in page ? page : { ...page, entries: content.entries });
4343
+ }
4327
4344
  return page;
4328
4345
  }
4329
4346
 
@@ -4337,7 +4354,10 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
4337
4354
 
4338
4355
  /** One field of the shared layout \u2014 the header and footer every route renders. */
4339
4356
  export function bcmsLayout(path: string, fallback: string): string {
4340
- return render(readPath(live(layoutSnapshot), path)) ?? fallback;
4357
+ const layout = live(layoutSnapshot);
4358
+ // The whole address first: a field id may carry a dot of its own (\`derived.nav-cta\`).
4359
+ const whole = isRecord(layout) ? layout[path] : undefined;
4360
+ return render(whole !== undefined ? whole : readPath(layout, path)) ?? fallback;
4341
4361
  }
4342
4362
 
4343
4363
  /**
@@ -4414,13 +4434,24 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
4414
4434
  * release write (\`writeImage\`) puts the editor's image on the \`data-bcms-field\` element, which
4415
4435
  * is where every published value lands. A plain string fallback (\`<img src="/hero.png">\`) has
4416
4436
  * no such rule and takes the CMS url directly.
4437
+ *
4438
+ * \u{1F534} ON A HOSTED SERVER THE EDITOR'S IMAGE IS A LOCAL FILE. A page the app renders per request has no
4439
+ * release write: what this returns is the page. BetterCMS copies a replaced image beside the app and
4440
+ * names the copy on the published value (\`local\`, with its size), and it is returned in the shape
4441
+ * of an import (\`{ src, width, height, format }\`), so \`<Image>\` optimises it like the site's own.
4442
+ * Absent (a build, or the site's own picture) the import stands.
4417
4443
  */
4418
4444
  export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
4419
4445
  page = live(page);
4420
- if (fallback !== null && typeof fallback === "object") return fallback;
4421
4446
  const entries = isRecord(page) ? page.entries : undefined;
4422
4447
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4423
- return render(readPath(root, path)) ?? fallback;
4448
+ const value = readPath(root, path);
4449
+ if (fallback !== null && typeof fallback === "object") {
4450
+ if (!isRecord(value) || typeof value.local !== "string" || typeof value.width !== "number" || typeof value.height !== "number") return fallback;
4451
+ const format = /\\.([a-z0-9]+)$/i.exec(value.local)?.[1]?.toLowerCase() ?? "";
4452
+ return { src: value.local, width: value.width, height: value.height, format: format === "jpg" ? "jpeg" : format } as unknown as T;
4453
+ }
4454
+ return render(value) ?? fallback;
4424
4455
  }
4425
4456
 
4426
4457
  /**
@@ -4753,7 +4784,8 @@ var HISTORICAL = {
4753
4784
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4754
4785
  10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4755
4786
  11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
4756
- 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
4787
+ 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528",
4788
+ 13: "cd8bdd74dbc944f2331503bdf8e995cb7f74f0d5b307d08aee4690e1695ae798"
4757
4789
  },
4758
4790
  "src/lib/bcms-content.ts": {
4759
4791
  1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
@@ -4767,7 +4799,8 @@ var HISTORICAL = {
4767
4799
  9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
4768
4800
  10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
4769
4801
  11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0",
4770
- 12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208"
4802
+ 12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208",
4803
+ 13: "06ee70c9f0bb25e03f3930d2250da8dae172ade457f27ebba20382b6482ad3f4"
4771
4804
  },
4772
4805
  "composables/bcms-content.ts": {
4773
4806
  1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
@@ -4781,7 +4814,8 @@ var HISTORICAL = {
4781
4814
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4782
4815
  10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4783
4816
  11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
4784
- 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
4817
+ 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528",
4818
+ 13: "cd8bdd74dbc944f2331503bdf8e995cb7f74f0d5b307d08aee4690e1695ae798"
4785
4819
  }
4786
4820
  };
4787
4821
  var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
@@ -10863,7 +10897,7 @@ function styleEdits(before, after) {
10863
10897
  // package.json
10864
10898
  var package_default = {
10865
10899
  name: "@bettercms-ai/convert",
10866
- version: "0.25.1",
10900
+ version: "0.26.0",
10867
10901
  type: "module",
10868
10902
  description: "Deterministic codemod that turns an imported site's text-matched bindings into declared ones \u2014 parser offsets in, one magic-string pass out, plus a receipt of everything it could not do.",
10869
10903
  bin: {