@bettercms-ai/convert 0.23.0 → 0.24.1

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,17 @@
1
1
  # @bettercms-ai/convert
2
2
 
3
+ ## 0.24.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 15e446a: Helper v13: a repository image path (`@/images/blog/post-1.avif`, `~/…`, `../…`, `/src/…`) is never rendered as a value; the template's own imported picture shows instead. Converted sites get it on their next BetterCMS build through the helper upgrade.
8
+
9
+ ## 0.24.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 86c0c9f: Helper v12: on a server BetterCMS hosts, each content snapshot resolves at request time to its published counterpart in the file the platform rewrites on every publish (`BCMS_CONTENT_FILE`), so a server-rendered page shows a publish in seconds without a rebuild. Builds, dev servers, browsers and edge runtimes are unchanged. New export `helperUpgrades()` lists the current helper per path with the digests of every older one it may replace.
14
+
3
15
  ## 0.23.0
4
16
 
5
17
  ### Minor Changes
package/dist/cli.js CHANGED
@@ -4190,7 +4190,7 @@ async function bindImages(ctx, targets) {
4190
4190
 
4191
4191
  // src/helper.ts
4192
4192
  import { createHash as createHash2 } from "crypto";
4193
- var HELPER_VERSION = 11;
4193
+ var HELPER_VERSION = 13;
4194
4194
  var HEADER = "// @bettercms-ai/convert helper v";
4195
4195
  var HELPER_PATH = Object.fromEntries(
4196
4196
  Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
@@ -4236,9 +4236,16 @@ function readPath(root: unknown, path: string): unknown {
4236
4236
  return cursor;
4237
4237
  }
4238
4238
 
4239
+ /**
4240
+ * A repository image path \u2014 \`@/images/x.avif\`, \`~/assets/x.png\`, \`../assets/x.jpg\`, \`/src/x.png\`.
4241
+ * The bundler resolves one at build and nothing else can, so as a value it is never rendered: the
4242
+ * template's own fallback (the picture the build already imported) shows instead of a broken image.
4243
+ */
4244
+ const REPO_IMAGE_PATH = /^(?:@[\\w-]*|~|\\.{1,2}|\\/?src)\\/[^\\s?#"'<>]*\\.(?:avif|png|jpe?g|webp|gif|svg|bmp|ico|tiff?|jxl|heic)(?:[?#]\\S*)?$/i;
4245
+
4239
4246
  /** A stored value as a template renders it: richtext is its html, an image is its url. */
4240
4247
  function render(value: unknown): string | undefined {
4241
- if (typeof value === "string") return value;
4248
+ if (typeof value === "string") return REPO_IMAGE_PATH.test(value.trim()) ? undefined : value;
4242
4249
  if (typeof value === "number" || typeof value === "boolean") return String(value);
4243
4250
  if (isRecord(value)) {
4244
4251
  if (typeof value.html === "string") return value.html;
@@ -4247,8 +4254,69 @@ function render(value: unknown): string | undefined {
4247
4254
  return undefined;
4248
4255
  }
4249
4256
 
4257
+ /**
4258
+ * The PUBLISHED copy, on a server BetterCMS hosts \u2014 read at request time, not at build time.
4259
+ *
4260
+ * BetterCMS keeps one file current for a running server app (\`BCMS_CONTENT_FILE\`, the same
4261
+ * bcms-content.json the build was given) and rewrites it on every publish, so a server-rendered
4262
+ * page shows a publish within seconds instead of after a rebuild. Each snapshot this module reads
4263
+ * resolves to its live counterpart: the shared layout by identity, a page by its \`slug\`.
4264
+ *
4265
+ * Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
4266
+ * runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
4267
+ * read keeps the last good copy; it is checked at most once a second.
4268
+ */
4269
+ type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
4270
+ let bcmsLive: BcmsLiveContent | null = null;
4271
+ let bcmsLiveCheckedAt = 0;
4272
+
4273
+ function liveContent(): BcmsLiveContent | null {
4274
+ const proc = (globalThis as {
4275
+ process?: { env?: Record<string, string | undefined>; getBuiltinModule?: (id: string) => unknown };
4276
+ }).process;
4277
+ const file = proc?.env?.BCMS_CONTENT_FILE;
4278
+ if (!file || typeof proc?.getBuiltinModule !== "function") return null;
4279
+ const now = Date.now();
4280
+ if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
4281
+ bcmsLiveCheckedAt = now;
4282
+ try {
4283
+ const fs = proc.getBuiltinModule("node:fs") as {
4284
+ statSync(path: string): { mtimeMs: number };
4285
+ readFileSync(path: string, encoding: "utf8"): string;
4286
+ };
4287
+ const stamp = fs.statSync(file).mtimeMs;
4288
+ if (bcmsLive && bcmsLive.stamp === stamp) return bcmsLive;
4289
+ const snapshot: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
4290
+ const bySlug = new Map<string, unknown>();
4291
+ const collections = isRecord(snapshot) && isRecord(snapshot.collections) ? snapshot.collections : {};
4292
+ for (const rows of Object.values(collections)) {
4293
+ if (!Array.isArray(rows)) continue;
4294
+ for (const row of rows) {
4295
+ if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) bySlug.set(row.slug, row);
4296
+ }
4297
+ }
4298
+ const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
4299
+ const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
4300
+ const layout = isRecord(withLayout) ? withLayout.layout : isRecord(snapshot) ? snapshot.layout : undefined;
4301
+ bcmsLive = { stamp, bySlug, layout };
4302
+ } catch {
4303
+ // Unreadable, or caught mid-write: keep serving the last good copy.
4304
+ }
4305
+ return bcmsLive;
4306
+ }
4307
+
4308
+ /** The published counterpart of a snapshot this module was handed, or the snapshot itself. */
4309
+ function live(page: unknown): unknown {
4310
+ const content = liveContent();
4311
+ if (!content) return page;
4312
+ if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
4313
+ if (isRecord(page) && typeof page.slug === "string") return content.bySlug.get(page.slug) ?? page;
4314
+ return page;
4315
+ }
4316
+
4250
4317
  /** One field of one page. \`slug\` selects the entry on a dynamic route. */
4251
4318
  export function bcms(page: unknown, path: string, fallback: string, slug?: string): string {
4319
+ page = live(page);
4252
4320
  const entries = isRecord(page) ? page.entries : undefined;
4253
4321
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4254
4322
  return render(readPath(root, path)) ?? fallback;
@@ -4256,7 +4324,7 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
4256
4324
 
4257
4325
  /** One field of the shared layout \u2014 the header and footer every route renders. */
4258
4326
  export function bcmsLayout(path: string, fallback: string): string {
4259
- return render(readPath(layoutSnapshot, path)) ?? fallback;
4327
+ return render(readPath(live(layoutSnapshot), path)) ?? fallback;
4260
4328
  }
4261
4329
 
4262
4330
  /**
@@ -4264,6 +4332,7 @@ export function bcmsLayout(path: string, fallback: string): string {
4264
4332
  * an empty snapshot still renders the same number of cards it always did.
4265
4333
  */
4266
4334
  export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: string): T[] {
4335
+ page = live(page);
4267
4336
  const entries = isRecord(page) ? page.entries : undefined;
4268
4337
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4269
4338
  const value = readPath(root, path);
@@ -4309,6 +4378,7 @@ export function bcmsRowsAs<T>(
4309
4378
  * this out loud rather than leaving an editor to discover it.
4310
4379
  */
4311
4380
  export function bcmsList(page: unknown, paths: string[], fallback: string[], slug?: string): string[] {
4381
+ page = live(page);
4312
4382
  const entries = isRecord(page) ? page.entries : undefined;
4313
4383
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4314
4384
  return paths.map((path, index) => render(readPath(root, path)) ?? fallback[index] ?? "");
@@ -4333,6 +4403,7 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
4333
4403
  * no such rule and takes the CMS url directly.
4334
4404
  */
4335
4405
  export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
4406
+ page = live(page);
4336
4407
  if (fallback !== null && typeof fallback === "object") return fallback;
4337
4408
  const entries = isRecord(page) ? page.entries : undefined;
4338
4409
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
@@ -4361,6 +4432,7 @@ export function bcmsSection(
4361
4432
  overrides?: unknown,
4362
4433
  bindPaths?: Record<string, string>,
4363
4434
  ): Record<string, unknown> {
4435
+ page = live(page);
4364
4436
  const group = bind ? readPath(page, bind) : undefined;
4365
4437
  const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
4366
4438
  // A locale copy on its twin's component spells its fields its own way: each prop's copy is
@@ -4432,6 +4504,7 @@ export function bcmsSectionPaths(
4432
4504
  map: BcmsPathMap,
4433
4505
  bindPaths?: Record<string, string>,
4434
4506
  ): Record<string, unknown> {
4507
+ page = live(page);
4435
4508
  const out: Record<string, unknown> = isRecord(page) ? { ...page } : {};
4436
4509
  const own = isRecord(overrides) ? overrides : {};
4437
4510
  const separator = prefix.slice(-1);
@@ -4665,7 +4738,9 @@ var HISTORICAL = {
4665
4738
  7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
4666
4739
  8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
4667
4740
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4668
- 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
4741
+ 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4742
+ 11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
4743
+ 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
4669
4744
  },
4670
4745
  "src/lib/bcms-content.ts": {
4671
4746
  1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
@@ -4677,7 +4752,9 @@ var HISTORICAL = {
4677
4752
  7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7",
4678
4753
  8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5",
4679
4754
  9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
4680
- 10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e"
4755
+ 10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
4756
+ 11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0",
4757
+ 12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208"
4681
4758
  },
4682
4759
  "composables/bcms-content.ts": {
4683
4760
  1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
@@ -4689,7 +4766,9 @@ var HISTORICAL = {
4689
4766
  7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
4690
4767
  8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
4691
4768
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4692
- 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
4769
+ 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4770
+ 11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
4771
+ 12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
4693
4772
  }
4694
4773
  };
4695
4774
  var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
@@ -10705,7 +10784,7 @@ function styleEdits(before, after) {
10705
10784
  // package.json
10706
10785
  var package_default = {
10707
10786
  name: "@bettercms-ai/convert",
10708
- version: "0.23.0",
10787
+ version: "0.24.1",
10709
10788
  type: "module",
10710
10789
  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.",
10711
10790
  bin: {