@bettercms-ai/convert 0.23.0 → 0.24.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.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+
3
9
  ## 0.23.0
4
10
 
5
11
  ### 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 = 12;
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])
@@ -4247,8 +4247,69 @@ function render(value: unknown): string | undefined {
4247
4247
  return undefined;
4248
4248
  }
4249
4249
 
4250
+ /**
4251
+ * The PUBLISHED copy, on a server BetterCMS hosts \u2014 read at request time, not at build time.
4252
+ *
4253
+ * BetterCMS keeps one file current for a running server app (\`BCMS_CONTENT_FILE\`, the same
4254
+ * bcms-content.json the build was given) and rewrites it on every publish, so a server-rendered
4255
+ * page shows a publish within seconds instead of after a rebuild. Each snapshot this module reads
4256
+ * resolves to its live counterpart: the shared layout by identity, a page by its \`slug\`.
4257
+ *
4258
+ * Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
4259
+ * runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
4260
+ * read keeps the last good copy; it is checked at most once a second.
4261
+ */
4262
+ type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
4263
+ let bcmsLive: BcmsLiveContent | null = null;
4264
+ let bcmsLiveCheckedAt = 0;
4265
+
4266
+ function liveContent(): BcmsLiveContent | null {
4267
+ const proc = (globalThis as {
4268
+ process?: { env?: Record<string, string | undefined>; getBuiltinModule?: (id: string) => unknown };
4269
+ }).process;
4270
+ const file = proc?.env?.BCMS_CONTENT_FILE;
4271
+ if (!file || typeof proc?.getBuiltinModule !== "function") return null;
4272
+ const now = Date.now();
4273
+ if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
4274
+ bcmsLiveCheckedAt = now;
4275
+ try {
4276
+ const fs = proc.getBuiltinModule("node:fs") as {
4277
+ statSync(path: string): { mtimeMs: number };
4278
+ readFileSync(path: string, encoding: "utf8"): string;
4279
+ };
4280
+ const stamp = fs.statSync(file).mtimeMs;
4281
+ if (bcmsLive && bcmsLive.stamp === stamp) return bcmsLive;
4282
+ const snapshot: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
4283
+ const bySlug = new Map<string, unknown>();
4284
+ const collections = isRecord(snapshot) && isRecord(snapshot.collections) ? snapshot.collections : {};
4285
+ for (const rows of Object.values(collections)) {
4286
+ if (!Array.isArray(rows)) continue;
4287
+ for (const row of rows) {
4288
+ if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) bySlug.set(row.slug, row);
4289
+ }
4290
+ }
4291
+ const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
4292
+ const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
4293
+ const layout = isRecord(withLayout) ? withLayout.layout : isRecord(snapshot) ? snapshot.layout : undefined;
4294
+ bcmsLive = { stamp, bySlug, layout };
4295
+ } catch {
4296
+ // Unreadable, or caught mid-write: keep serving the last good copy.
4297
+ }
4298
+ return bcmsLive;
4299
+ }
4300
+
4301
+ /** The published counterpart of a snapshot this module was handed, or the snapshot itself. */
4302
+ function live(page: unknown): unknown {
4303
+ const content = liveContent();
4304
+ if (!content) return page;
4305
+ if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
4306
+ if (isRecord(page) && typeof page.slug === "string") return content.bySlug.get(page.slug) ?? page;
4307
+ return page;
4308
+ }
4309
+
4250
4310
  /** One field of one page. \`slug\` selects the entry on a dynamic route. */
4251
4311
  export function bcms(page: unknown, path: string, fallback: string, slug?: string): string {
4312
+ page = live(page);
4252
4313
  const entries = isRecord(page) ? page.entries : undefined;
4253
4314
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4254
4315
  return render(readPath(root, path)) ?? fallback;
@@ -4256,7 +4317,7 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
4256
4317
 
4257
4318
  /** One field of the shared layout \u2014 the header and footer every route renders. */
4258
4319
  export function bcmsLayout(path: string, fallback: string): string {
4259
- return render(readPath(layoutSnapshot, path)) ?? fallback;
4320
+ return render(readPath(live(layoutSnapshot), path)) ?? fallback;
4260
4321
  }
4261
4322
 
4262
4323
  /**
@@ -4264,6 +4325,7 @@ export function bcmsLayout(path: string, fallback: string): string {
4264
4325
  * an empty snapshot still renders the same number of cards it always did.
4265
4326
  */
4266
4327
  export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: string): T[] {
4328
+ page = live(page);
4267
4329
  const entries = isRecord(page) ? page.entries : undefined;
4268
4330
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4269
4331
  const value = readPath(root, path);
@@ -4309,6 +4371,7 @@ export function bcmsRowsAs<T>(
4309
4371
  * this out loud rather than leaving an editor to discover it.
4310
4372
  */
4311
4373
  export function bcmsList(page: unknown, paths: string[], fallback: string[], slug?: string): string[] {
4374
+ page = live(page);
4312
4375
  const entries = isRecord(page) ? page.entries : undefined;
4313
4376
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
4314
4377
  return paths.map((path, index) => render(readPath(root, path)) ?? fallback[index] ?? "");
@@ -4333,6 +4396,7 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
4333
4396
  * no such rule and takes the CMS url directly.
4334
4397
  */
4335
4398
  export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
4399
+ page = live(page);
4336
4400
  if (fallback !== null && typeof fallback === "object") return fallback;
4337
4401
  const entries = isRecord(page) ? page.entries : undefined;
4338
4402
  const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
@@ -4361,6 +4425,7 @@ export function bcmsSection(
4361
4425
  overrides?: unknown,
4362
4426
  bindPaths?: Record<string, string>,
4363
4427
  ): Record<string, unknown> {
4428
+ page = live(page);
4364
4429
  const group = bind ? readPath(page, bind) : undefined;
4365
4430
  const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
4366
4431
  // A locale copy on its twin's component spells its fields its own way: each prop's copy is
@@ -4432,6 +4497,7 @@ export function bcmsSectionPaths(
4432
4497
  map: BcmsPathMap,
4433
4498
  bindPaths?: Record<string, string>,
4434
4499
  ): Record<string, unknown> {
4500
+ page = live(page);
4435
4501
  const out: Record<string, unknown> = isRecord(page) ? { ...page } : {};
4436
4502
  const own = isRecord(overrides) ? overrides : {};
4437
4503
  const separator = prefix.slice(-1);
@@ -4665,7 +4731,8 @@ var HISTORICAL = {
4665
4731
  7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
4666
4732
  8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
4667
4733
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4668
- 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
4734
+ 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4735
+ 11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e"
4669
4736
  },
4670
4737
  "src/lib/bcms-content.ts": {
4671
4738
  1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
@@ -4677,7 +4744,8 @@ var HISTORICAL = {
4677
4744
  7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7",
4678
4745
  8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5",
4679
4746
  9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
4680
- 10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e"
4747
+ 10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
4748
+ 11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0"
4681
4749
  },
4682
4750
  "composables/bcms-content.ts": {
4683
4751
  1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
@@ -4689,7 +4757,8 @@ var HISTORICAL = {
4689
4757
  7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
4690
4758
  8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
4691
4759
  9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
4692
- 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
4760
+ 10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
4761
+ 11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e"
4693
4762
  }
4694
4763
  };
4695
4764
  var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
@@ -10705,7 +10774,7 @@ function styleEdits(before, after) {
10705
10774
  // package.json
10706
10775
  var package_default = {
10707
10776
  name: "@bettercms-ai/convert",
10708
- version: "0.23.0",
10777
+ version: "0.24.0",
10709
10778
  type: "module",
10710
10779
  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
10780
  bin: {