@riverbankcms/sdk 0.5.0 → 0.5.2

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.
Files changed (54) hide show
  1. package/dist/cli/index.js +567 -209
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/client/client.d.mts +2 -2
  4. package/dist/client/client.d.ts +2 -2
  5. package/dist/client/client.js +23 -1
  6. package/dist/client/client.js.map +1 -1
  7. package/dist/client/client.mjs +23 -1
  8. package/dist/client/client.mjs.map +1 -1
  9. package/dist/client/hooks.d.mts +2 -2
  10. package/dist/client/hooks.d.ts +2 -2
  11. package/dist/client/usePage-BXjk8BhD.d.mts +6704 -0
  12. package/dist/client/usePage-BiOReg0_.d.ts +6704 -0
  13. package/dist/server/{Layout-Cc5HUXAH.d.mts → Layout-BClXUTsd.d.mts} +1 -1
  14. package/dist/server/{Layout-B-q2Py4v.d.ts → Layout-UXGjXv8M.d.ts} +1 -1
  15. package/dist/server/{chunk-6OSNCH4F.js → chunk-6PWMOREA.js} +24 -2
  16. package/dist/server/chunk-6PWMOREA.js.map +1 -0
  17. package/dist/server/{chunk-4HIRA33Z.mjs → chunk-XY66HIB4.mjs} +24 -2
  18. package/dist/server/{chunk-6OSNCH4F.js.map → chunk-XY66HIB4.mjs.map} +1 -1
  19. package/dist/server/{components-CU46ZkAv.d.mts → components-BmaJxgCV.d.mts} +3 -3
  20. package/dist/server/{components-DvozDwRN.d.ts → components-DppHY5oD.d.ts} +3 -3
  21. package/dist/server/components.d.mts +5 -5
  22. package/dist/server/components.d.ts +5 -5
  23. package/dist/server/data.d.mts +2 -2
  24. package/dist/server/data.d.ts +2 -2
  25. package/dist/server/{index-Q7RLMAQ6.d.mts → index-Bucs6UqG.d.mts} +1 -1
  26. package/dist/server/{index-CJfMXZQr.d.ts → index-Cp7tJuRt.d.ts} +1 -1
  27. package/dist/server/index.d.mts +24 -3
  28. package/dist/server/index.d.ts +24 -3
  29. package/dist/server/index.js +2 -2
  30. package/dist/server/index.mjs +1 -1
  31. package/dist/server/{loadContent-DgpSKWqY.d.mts → loadContent-BS-3wesN.d.mts} +3 -3
  32. package/dist/server/{loadContent-GPvUI1bN.d.ts → loadContent-Buvmudee.d.ts} +3 -3
  33. package/dist/server/{loadPage-DGnIK7s4.d.mts → loadPage-B8mQUUSo.d.mts} +2 -2
  34. package/dist/server/{loadPage-DW9WB-u9.d.ts → loadPage-DP3nrHBi.d.ts} +2 -2
  35. package/dist/server/metadata.d.mts +3 -3
  36. package/dist/server/metadata.d.ts +3 -3
  37. package/dist/server/navigation.d.mts +2 -2
  38. package/dist/server/navigation.d.ts +2 -2
  39. package/dist/server/rendering/server.d.mts +4 -4
  40. package/dist/server/rendering/server.d.ts +4 -4
  41. package/dist/server/rendering.d.mts +7 -7
  42. package/dist/server/rendering.d.ts +7 -7
  43. package/dist/server/routing.d.mts +3 -3
  44. package/dist/server/routing.d.ts +3 -3
  45. package/dist/server/server.d.mts +5 -5
  46. package/dist/server/server.d.ts +5 -5
  47. package/dist/server/server.js +2 -2
  48. package/dist/server/server.mjs +1 -1
  49. package/dist/server/{types-0f4PIlgx.d.mts → types-1cLz0vnq.d.mts} +1 -1
  50. package/dist/server/{types-kOQyCFXO.d.ts → types-BvcJU7zk.d.ts} +1 -1
  51. package/dist/server/{types-C28kMfa1.d.ts → types-CVykEqXN.d.ts} +35 -1
  52. package/dist/server/{types-DuzJZKJI.d.mts → types-Dsu9wsUh.d.mts} +35 -1
  53. package/package.json +3 -1
  54. package/dist/server/chunk-4HIRA33Z.mjs.map +0 -1
package/dist/cli/index.js CHANGED
@@ -7,7 +7,8 @@ var fs = require('fs');
7
7
  var dotenv = require('dotenv');
8
8
  var commander = require('commander');
9
9
  var zod = require('zod');
10
- var fs5 = require('fs/promises');
10
+ var prompts = require('prompts');
11
+ var fs3 = require('fs/promises');
11
12
  var readline = require('readline');
12
13
  var equal = require('fast-deep-equal');
13
14
  var os = require('os');
@@ -35,7 +36,8 @@ function _interopNamespace(e) {
35
36
  }
36
37
 
37
38
  var path9__namespace = /*#__PURE__*/_interopNamespace(path9);
38
- var fs5__namespace = /*#__PURE__*/_interopNamespace(fs5);
39
+ var prompts__default = /*#__PURE__*/_interopDefault(prompts);
40
+ var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
39
41
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
40
42
  var equal__default = /*#__PURE__*/_interopDefault(equal);
41
43
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
@@ -5480,66 +5482,6 @@ Examples:
5480
5482
  const dashboard = resolveDashboardUrl(options.dashboard);
5481
5483
  return pushConfigAction({ ...options, dashboard });
5482
5484
  });
5483
- async function ensureDir(dirPath) {
5484
- try {
5485
- await fs5__namespace.mkdir(dirPath, { recursive: true });
5486
- } catch (error) {
5487
- if (error.code !== "EEXIST") {
5488
- throw error;
5489
- }
5490
- }
5491
- }
5492
- async function writeJsonFile(filePath, data) {
5493
- const content = JSON.stringify(data, null, 2) + "\n";
5494
- await fs5__namespace.writeFile(filePath, content, "utf-8");
5495
- }
5496
- async function writeEntries(contentDir, pulledEntries) {
5497
- const entriesDir = path9__namespace.join(contentDir, "entries");
5498
- const metaDir = path9__namespace.join(contentDir, ".meta");
5499
- await ensureDir(entriesDir);
5500
- await ensureDir(metaDir);
5501
- const { contentType, entries, meta } = pulledEntries;
5502
- const entriesFile = {
5503
- contentType,
5504
- entries: entries.map((entry) => ({
5505
- identifier: entry.identifier,
5506
- data: entry.data,
5507
- status: entry.status,
5508
- hasUnpublishedChanges: entry.hasUnpublishedChanges,
5509
- slug: entry.slug
5510
- }))
5511
- };
5512
- const filePath = path9__namespace.join(entriesDir, `${contentType}.json`);
5513
- await writeJsonFile(filePath, entriesFile);
5514
- const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
5515
- await writeJsonFile(metaPath, meta);
5516
- return { filePath, metaPath };
5517
- }
5518
- async function writePages(contentDir, pulledPages) {
5519
- const pagesDir = path9__namespace.join(contentDir, "pages");
5520
- await ensureDir(pagesDir);
5521
- const filePaths = [];
5522
- for (const page of pulledPages.pages) {
5523
- const filePath = path9__namespace.join(pagesDir, `${page.identifier}.json`);
5524
- await writeJsonFile(filePath, page);
5525
- filePaths.push(filePath);
5526
- }
5527
- return filePaths;
5528
- }
5529
- async function writeNavigation(contentDir, pulledNavigation) {
5530
- await ensureDir(contentDir);
5531
- const filePath = path9__namespace.join(contentDir, "navigation.json");
5532
- await writeJsonFile(filePath, {
5533
- menus: pulledNavigation.menus
5534
- });
5535
- return filePath;
5536
- }
5537
- async function writeSettings(contentDir, pulledSettings) {
5538
- await ensureDir(contentDir);
5539
- const filePath = path9__namespace.join(contentDir, "settings.json");
5540
- await writeJsonFile(filePath, pulledSettings.settings);
5541
- return filePath;
5542
- }
5543
5485
 
5544
5486
  // src/client/management/http.ts
5545
5487
  var ManagementApiError = class extends Error {
@@ -5828,12 +5770,15 @@ function createPullOperations(http) {
5828
5770
  http.get("/pull/settings")
5829
5771
  ]);
5830
5772
  return {
5831
- entries: entriesResult.entries,
5832
- pages: pagesResult.pages,
5833
- navigation: navigationResult.menus,
5834
- settings: settingsResult.settings,
5773
+ entries: entriesResult?.entries || {},
5774
+ pages: pagesResult?.pages || [],
5775
+ navigation: navigationResult?.menus || [],
5776
+ settings: settingsResult?.settings || {},
5835
5777
  meta: {
5836
- pulledAt: (/* @__PURE__ */ new Date()).toISOString()
5778
+ pulledAt: (/* @__PURE__ */ new Date()).toISOString(),
5779
+ entries: entriesResult?.meta?.entries,
5780
+ truncated: entriesResult?.meta?.truncated,
5781
+ truncationMessage: entriesResult?.meta?.truncationMessage
5837
5782
  }
5838
5783
  };
5839
5784
  }
@@ -5849,6 +5794,15 @@ function createPreviewOperations(http) {
5849
5794
  };
5850
5795
  }
5851
5796
 
5797
+ // src/client/management/identifiers.ts
5798
+ function createIdentifiersOperations(http) {
5799
+ return {
5800
+ async backfill() {
5801
+ return http.post("/identifiers/backfill");
5802
+ }
5803
+ };
5804
+ }
5805
+
5852
5806
  // src/client/management/index.ts
5853
5807
  function createManagementClient(config3) {
5854
5808
  if (!config3.dashboardUrl) {
@@ -5879,7 +5833,8 @@ function createManagementClient(config3) {
5879
5833
  navigation: createNavigationOperations(http),
5880
5834
  settings: createSettingsOperations(http),
5881
5835
  pull: createPullOperations(http),
5882
- preview: createPreviewOperations(http)
5836
+ preview: createPreviewOperations(http),
5837
+ identifiers: createIdentifiersOperations(http)
5883
5838
  };
5884
5839
  }
5885
5840
 
@@ -6063,6 +6018,18 @@ function formatError(error) {
6063
6018
  }
6064
6019
 
6065
6020
  // src/cli/helpers.ts
6021
+ function mapEntryForOutput(entry) {
6022
+ const result = {
6023
+ identifier: entry.identifier,
6024
+ data: entry.data,
6025
+ status: entry.status,
6026
+ hasUnpublishedChanges: entry.hasUnpublishedChanges
6027
+ };
6028
+ if (entry.slug !== void 0) {
6029
+ result.slug = entry.slug;
6030
+ }
6031
+ return result;
6032
+ }
6066
6033
  function parsePaginationOptions(options) {
6067
6034
  return {
6068
6035
  limit: parseInt(options.limit ?? "20", 10),
@@ -6075,7 +6042,7 @@ function formatDateShort(isoDate) {
6075
6042
  async function parseJsonData(options) {
6076
6043
  if (options.file) {
6077
6044
  const filePath = path9__namespace.resolve(options.file);
6078
- const content = await fs5__namespace.readFile(filePath, "utf-8");
6045
+ const content = await fs3__namespace.readFile(filePath, "utf-8");
6079
6046
  return JSON.parse(content);
6080
6047
  }
6081
6048
  if (options.data) {
@@ -6280,20 +6247,253 @@ function createListCommand(config3) {
6280
6247
  );
6281
6248
  }
6282
6249
 
6250
+ // src/cli/content/writer.ts
6251
+ async function ensureDir(dirPath) {
6252
+ try {
6253
+ await fs3__namespace.mkdir(dirPath, { recursive: true });
6254
+ } catch (error) {
6255
+ if (error.code !== "EEXIST") {
6256
+ throw error;
6257
+ }
6258
+ }
6259
+ }
6260
+ async function writeJsonFile(filePath, data) {
6261
+ const content = JSON.stringify(data, null, 2) + "\n";
6262
+ await fs3__namespace.writeFile(filePath, content, "utf-8");
6263
+ }
6264
+ async function writeEntries(contentDir, pulledEntries) {
6265
+ const entriesDir = path9__namespace.join(contentDir, "entries");
6266
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6267
+ await ensureDir(entriesDir);
6268
+ await ensureDir(metaDir);
6269
+ const { contentType, entries, meta } = pulledEntries;
6270
+ const entriesFile = {
6271
+ contentType,
6272
+ entries: entries.map(mapEntryForOutput)
6273
+ };
6274
+ const filePath = path9__namespace.join(entriesDir, `${contentType}.json`);
6275
+ await writeJsonFile(filePath, entriesFile);
6276
+ const metaPath = path9__namespace.join(metaDir, `${contentType}.json`);
6277
+ await writeJsonFile(metaPath, meta);
6278
+ return { filePath, metaPath };
6279
+ }
6280
+ async function writePages(contentDir, pulledPages) {
6281
+ const pagesDir = path9__namespace.join(contentDir, "pages");
6282
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6283
+ await ensureDir(pagesDir);
6284
+ await ensureDir(metaDir);
6285
+ const filePaths = [];
6286
+ const pagesMeta = {};
6287
+ for (const page of pulledPages.pages) {
6288
+ const filePath = path9__namespace.join(pagesDir, `${page.identifier}.json`);
6289
+ await writeJsonFile(filePath, page);
6290
+ filePaths.push(filePath);
6291
+ pagesMeta[page.identifier] = {
6292
+ createdAt: page.createdAt,
6293
+ updatedAt: page.updatedAt
6294
+ };
6295
+ }
6296
+ const metaPath = path9__namespace.join(metaDir, "pages.json");
6297
+ await writeJsonFile(metaPath, {
6298
+ pulledAt: pulledPages.meta.pulledAt,
6299
+ pages: pagesMeta
6300
+ });
6301
+ return { filePaths, metaPath };
6302
+ }
6303
+ async function writeNavigation(contentDir, pulledNavigation) {
6304
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6305
+ await ensureDir(contentDir);
6306
+ await ensureDir(metaDir);
6307
+ const filePath = path9__namespace.join(contentDir, "navigation.json");
6308
+ await writeJsonFile(filePath, {
6309
+ menus: pulledNavigation.menus
6310
+ });
6311
+ const menusMeta = {};
6312
+ for (const menu of pulledNavigation.menus) {
6313
+ menusMeta[menu.name] = {
6314
+ createdAt: menu.createdAt,
6315
+ updatedAt: menu.updatedAt
6316
+ };
6317
+ }
6318
+ const metaPath = path9__namespace.join(metaDir, "navigation.json");
6319
+ await writeJsonFile(metaPath, {
6320
+ pulledAt: pulledNavigation.meta.pulledAt,
6321
+ menus: menusMeta
6322
+ });
6323
+ return { filePath, metaPath };
6324
+ }
6325
+ async function writeSettings(contentDir, pulledSettings) {
6326
+ await ensureDir(contentDir);
6327
+ const filePath = path9__namespace.join(contentDir, "settings.json");
6328
+ await writeJsonFile(filePath, pulledSettings.settings);
6329
+ return filePath;
6330
+ }
6331
+ async function fileExists(filePath) {
6332
+ try {
6333
+ await fs3__namespace.access(filePath);
6334
+ return true;
6335
+ } catch {
6336
+ return false;
6337
+ }
6338
+ }
6339
+ async function readJsonFile(filePath) {
6340
+ const content = await fs3__namespace.readFile(filePath, "utf-8");
6341
+ return JSON.parse(content);
6342
+ }
6343
+ async function listFiles(dirPath, extension) {
6344
+ try {
6345
+ const files = await fs3__namespace.readdir(dirPath);
6346
+ return files.filter((f) => f.endsWith(extension)).map((f) => path9__namespace.join(dirPath, f));
6347
+ } catch {
6348
+ return [];
6349
+ }
6350
+ }
6351
+ async function readEntries(contentDir, contentType) {
6352
+ const entriesDir = path9__namespace.join(contentDir, "entries");
6353
+ const result = /* @__PURE__ */ new Map();
6354
+ {
6355
+ const files = await listFiles(entriesDir, ".json");
6356
+ for (const filePath of files) {
6357
+ try {
6358
+ const file = await readJsonFile(filePath);
6359
+ result.set(file.contentType, file.entries);
6360
+ } catch (error) {
6361
+ console.warn(`Warning: Could not parse ${filePath}:`, error);
6362
+ }
6363
+ }
6364
+ }
6365
+ return result;
6366
+ }
6367
+ async function readPages(contentDir) {
6368
+ const pagesDir = path9__namespace.join(contentDir, "pages");
6369
+ const pages = [];
6370
+ const files = await listFiles(pagesDir, ".json");
6371
+ for (const filePath of files) {
6372
+ try {
6373
+ const page = await readJsonFile(filePath);
6374
+ pages.push(page);
6375
+ } catch (error) {
6376
+ console.warn(`Warning: Could not parse ${filePath}:`, error);
6377
+ }
6378
+ }
6379
+ return pages;
6380
+ }
6381
+ async function readNavigation(contentDir) {
6382
+ const filePath = path9__namespace.join(contentDir, "navigation.json");
6383
+ if (!await fileExists(filePath)) {
6384
+ return null;
6385
+ }
6386
+ return readJsonFile(filePath);
6387
+ }
6388
+ async function readSettings(contentDir) {
6389
+ const filePath = path9__namespace.join(contentDir, "settings.json");
6390
+ if (!await fileExists(filePath)) {
6391
+ return null;
6392
+ }
6393
+ return readJsonFile(filePath);
6394
+ }
6395
+ async function readAllContent(contentDir) {
6396
+ const [entries, pages, navigation, settings] = await Promise.all([
6397
+ readEntries(contentDir),
6398
+ readPages(contentDir),
6399
+ readNavigation(contentDir),
6400
+ readSettings(contentDir)
6401
+ ]);
6402
+ return { entries, pages, navigation, settings };
6403
+ }
6404
+ async function contentDirExists(contentDir) {
6405
+ return fileExists(contentDir);
6406
+ }
6407
+ async function getContentSummary(contentDir) {
6408
+ const content = await readAllContent(contentDir);
6409
+ const entryTypes = Array.from(content.entries.keys());
6410
+ let totalEntries = 0;
6411
+ for (const entries of content.entries.values()) {
6412
+ totalEntries += entries.length;
6413
+ }
6414
+ return {
6415
+ hasEntries: content.entries.size > 0,
6416
+ entryTypes,
6417
+ totalEntries,
6418
+ hasPages: content.pages.length > 0,
6419
+ pageCount: content.pages.length,
6420
+ hasNavigation: content.navigation !== null && content.navigation.menus.length > 0,
6421
+ menuCount: content.navigation?.menus.length ?? 0,
6422
+ hasSettings: content.settings !== null
6423
+ };
6424
+ }
6425
+ async function readEntriesMeta(contentDir, contentType) {
6426
+ const metaPath = path9__namespace.join(contentDir, ".meta", `${contentType}.json`);
6427
+ try {
6428
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
6429
+ return JSON.parse(content);
6430
+ } catch (error) {
6431
+ if (error.code === "ENOENT") {
6432
+ return null;
6433
+ }
6434
+ throw error;
6435
+ }
6436
+ }
6437
+ async function readAllMeta(contentDir) {
6438
+ const metaDir = path9__namespace.join(contentDir, ".meta");
6439
+ const metaMap = /* @__PURE__ */ new Map();
6440
+ try {
6441
+ const files = await fs3__namespace.readdir(metaDir);
6442
+ for (const file of files) {
6443
+ if (file.endsWith(".json") && file !== "pages.json" && file !== "navigation.json") {
6444
+ const contentType = file.replace(".json", "");
6445
+ const meta = await readEntriesMeta(contentDir, contentType);
6446
+ if (meta) metaMap.set(contentType, meta);
6447
+ }
6448
+ }
6449
+ } catch (error) {
6450
+ if (error.code !== "ENOENT") throw error;
6451
+ }
6452
+ return metaMap;
6453
+ }
6454
+ async function readPagesMeta(contentDir) {
6455
+ const metaPath = path9__namespace.join(contentDir, ".meta", "pages.json");
6456
+ try {
6457
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
6458
+ return JSON.parse(content);
6459
+ } catch (error) {
6460
+ if (error.code === "ENOENT") {
6461
+ return null;
6462
+ }
6463
+ throw error;
6464
+ }
6465
+ }
6466
+ async function readNavigationMeta(contentDir) {
6467
+ const metaPath = path9__namespace.join(contentDir, ".meta", "navigation.json");
6468
+ try {
6469
+ const content = await fs3__namespace.readFile(metaPath, "utf-8");
6470
+ return JSON.parse(content);
6471
+ } catch (error) {
6472
+ if (error.code === "ENOENT") {
6473
+ return null;
6474
+ }
6475
+ throw error;
6476
+ }
6477
+ }
6478
+
6283
6479
  // src/cli/commands/pull.ts
6284
6480
  var DEFAULT_PAGE_LIMIT = 500;
6285
6481
  async function pullEntriesWithPagination(client, contentType, output) {
6286
6482
  const allEntries = [];
6483
+ const aggregatedMeta = {};
6287
6484
  let page = 1;
6288
6485
  let hasMore = true;
6289
- let lastResult = null;
6486
+ let pulledAt = (/* @__PURE__ */ new Date()).toISOString();
6290
6487
  while (hasMore) {
6291
6488
  const result = await client.pull.entries(contentType, {
6292
6489
  page,
6293
6490
  limit: DEFAULT_PAGE_LIMIT
6294
6491
  });
6295
6492
  allEntries.push(...result.entries);
6296
- lastResult = result;
6493
+ if (result.meta.entries) {
6494
+ Object.assign(aggregatedMeta, result.meta.entries);
6495
+ }
6496
+ pulledAt = result.meta.pulledAt;
6297
6497
  hasMore = result.pagination.hasMore;
6298
6498
  if (hasMore) {
6299
6499
  output.info(`Fetched ${allEntries.length} entries (page ${page})...`);
@@ -6303,7 +6503,7 @@ async function pullEntriesWithPagination(client, contentType, output) {
6303
6503
  return {
6304
6504
  contentType,
6305
6505
  entries: allEntries,
6306
- meta: lastResult?.meta ?? { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} },
6506
+ meta: { pulledAt, entries: aggregatedMeta },
6307
6507
  pagination: {
6308
6508
  page: 1,
6309
6509
  limit: allEntries.length,
@@ -6312,20 +6512,21 @@ async function pullEntriesWithPagination(client, contentType, output) {
6312
6512
  }
6313
6513
  };
6314
6514
  }
6315
- async function writeAllEntries(contentDir, entriesByType, pulledAt) {
6515
+ async function writeAllEntries(contentDir, entriesByType, pulledAt, entriesMeta) {
6316
6516
  let totalCount = 0;
6317
6517
  const files = [];
6318
6518
  for (const [contentType, entries] of Object.entries(entriesByType)) {
6519
+ const ctMeta = {};
6520
+ for (const entry of entries) {
6521
+ const key = `${contentType}:${entry.identifier}`;
6522
+ if (entriesMeta?.[key]) {
6523
+ ctMeta[entry.identifier] = entriesMeta[key];
6524
+ }
6525
+ }
6319
6526
  const { filePath } = await writeEntries(contentDir, {
6320
6527
  contentType,
6321
- entries: entries.map((e) => ({
6322
- identifier: e.identifier,
6323
- data: e.data,
6324
- status: e.status,
6325
- hasUnpublishedChanges: e.hasUnpublishedChanges,
6326
- slug: e.slug
6327
- })),
6328
- meta: { pulledAt, entries: {} },
6528
+ entries,
6529
+ meta: { pulledAt, entries: ctMeta },
6329
6530
  pagination: { limit: entries.length, total: entries.length}
6330
6531
  });
6331
6532
  totalCount += entries.length;
@@ -6335,16 +6536,14 @@ async function writeAllEntries(contentDir, entriesByType, pulledAt) {
6335
6536
  }
6336
6537
  async function fetchAllContentPaginated(client, contentTypes, output) {
6337
6538
  const allEntries = {};
6539
+ const allMeta = {};
6338
6540
  for (const contentType of contentTypes) {
6339
6541
  output.info(`Fetching ${contentType} entries...`);
6340
6542
  const result = await pullEntriesWithPagination(client, contentType, output);
6341
- allEntries[contentType] = result.entries.map((e) => ({
6342
- identifier: e.identifier,
6343
- data: e.data,
6344
- status: e.status,
6345
- hasUnpublishedChanges: e.hasUnpublishedChanges,
6346
- slug: e.slug
6347
- }));
6543
+ allEntries[contentType] = result.entries.map(mapEntryForOutput);
6544
+ for (const [id, meta] of Object.entries(result.meta.entries || {})) {
6545
+ allMeta[`${contentType}:${id}`] = meta;
6546
+ }
6348
6547
  output.info(` ${result.entries.length} entries`);
6349
6548
  }
6350
6549
  output.info("Fetching pages...");
@@ -6358,10 +6557,10 @@ async function fetchAllContentPaginated(client, contentTypes, output) {
6358
6557
  pages: pagesResult.pages,
6359
6558
  navigation: navigationResult.menus,
6360
6559
  settings: settingsResult.settings,
6361
- meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString() }
6560
+ meta: { pulledAt: (/* @__PURE__ */ new Date()).toISOString(), entries: allMeta }
6362
6561
  };
6363
6562
  }
6364
- var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").addHelpText("after", `
6563
+ var pullCommand = new commander.Command("pull").description("Pull content from CMS").argument("[scope]", "What to pull: entries, pages, navigation, settings, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--output <dir>", "Output directory", "./content").option("--force", "Overwrite existing files without prompting").option("--yes", "Skip confirmation prompt (same as --force)").addHelpText("after", `
6365
6564
  Examples:
6366
6565
  $ riverbankcms pull # Pull all content
6367
6566
  $ riverbankcms pull --remote # Pull from production
@@ -6376,6 +6575,24 @@ Examples:
6376
6575
  async (scope, type, options, command) => {
6377
6576
  const { output, client } = createCommandContext(command);
6378
6577
  const contentDir = path9__namespace.resolve(options.output ?? "./content");
6578
+ if (await contentDirExists(contentDir) && !options.force && !options.yes) {
6579
+ if (!process.stdin.isTTY) {
6580
+ output.error("Content directory already exists and --yes not specified", {
6581
+ suggestion: "Use --yes or --force to overwrite in non-interactive mode"
6582
+ });
6583
+ return;
6584
+ }
6585
+ const response = await prompts__default.default({
6586
+ type: "confirm",
6587
+ name: "overwrite",
6588
+ message: `Content directory '${path9__namespace.basename(contentDir)}' already exists. Overwrite?`,
6589
+ initial: false
6590
+ });
6591
+ if (!response.overwrite) {
6592
+ output.info("Aborted.");
6593
+ return;
6594
+ }
6595
+ }
6379
6596
  const pullScope = scope?.toLowerCase() ?? "all";
6380
6597
  switch (pullScope) {
6381
6598
  case "entries": {
@@ -6400,7 +6617,8 @@ Examples:
6400
6617
  const { totalCount, files } = await writeAllEntries(
6401
6618
  contentDir,
6402
6619
  result.entries,
6403
- result.meta.pulledAt
6620
+ result.meta.pulledAt,
6621
+ result.meta.entries
6404
6622
  );
6405
6623
  output.success(`Pulled ${totalCount} entries across ${Object.keys(result.entries).length} content types`, {
6406
6624
  files
@@ -6411,15 +6629,15 @@ Examples:
6411
6629
  case "pages": {
6412
6630
  output.info("Pulling pages");
6413
6631
  const result = await client.pull.pages();
6414
- const files = await writePages(contentDir, result);
6415
- output.success(`Pulled ${result.pages.length} pages`, { files });
6632
+ const { filePaths, metaPath } = await writePages(contentDir, result);
6633
+ output.success(`Pulled ${result.pages.length} pages`, { files: filePaths, meta: metaPath });
6416
6634
  break;
6417
6635
  }
6418
6636
  case "navigation": {
6419
6637
  output.info("Pulling navigation menus");
6420
6638
  const result = await client.pull.navigation();
6421
- const filePath = await writeNavigation(contentDir, result);
6422
- output.success(`Pulled ${result.menus.length} navigation menus`, { file: filePath });
6639
+ const { filePath, metaPath } = await writeNavigation(contentDir, result);
6640
+ output.success(`Pulled ${result.menus.length} navigation menus`, { file: filePath, meta: metaPath });
6423
6641
  break;
6424
6642
  }
6425
6643
  case "settings": {
@@ -6442,7 +6660,8 @@ Examples:
6442
6660
  const { totalCount: totalEntries } = await writeAllEntries(
6443
6661
  contentDir,
6444
6662
  result.entries,
6445
- result.meta.pulledAt
6663
+ result.meta.pulledAt,
6664
+ result.meta.entries
6446
6665
  );
6447
6666
  await writePages(contentDir, { pages: result.pages, meta: result.meta });
6448
6667
  await writeNavigation(contentDir, { menus: result.navigation, meta: result.meta });
@@ -6489,100 +6708,6 @@ async function loadCliConfig(configPath) {
6489
6708
  throw error;
6490
6709
  }
6491
6710
  }
6492
- async function fileExists(filePath) {
6493
- try {
6494
- await fs5__namespace.access(filePath);
6495
- return true;
6496
- } catch {
6497
- return false;
6498
- }
6499
- }
6500
- async function readJsonFile(filePath) {
6501
- const content = await fs5__namespace.readFile(filePath, "utf-8");
6502
- return JSON.parse(content);
6503
- }
6504
- async function listFiles(dirPath, extension) {
6505
- try {
6506
- const files = await fs5__namespace.readdir(dirPath);
6507
- return files.filter((f) => f.endsWith(extension)).map((f) => path9__namespace.join(dirPath, f));
6508
- } catch {
6509
- return [];
6510
- }
6511
- }
6512
- async function readEntries(contentDir, contentType) {
6513
- const entriesDir = path9__namespace.join(contentDir, "entries");
6514
- const result = /* @__PURE__ */ new Map();
6515
- {
6516
- const files = await listFiles(entriesDir, ".json");
6517
- for (const filePath of files) {
6518
- try {
6519
- const file = await readJsonFile(filePath);
6520
- result.set(file.contentType, file.entries);
6521
- } catch (error) {
6522
- console.warn(`Warning: Could not parse ${filePath}:`, error);
6523
- }
6524
- }
6525
- }
6526
- return result;
6527
- }
6528
- async function readPages(contentDir) {
6529
- const pagesDir = path9__namespace.join(contentDir, "pages");
6530
- const pages = [];
6531
- const files = await listFiles(pagesDir, ".json");
6532
- for (const filePath of files) {
6533
- try {
6534
- const page = await readJsonFile(filePath);
6535
- pages.push(page);
6536
- } catch (error) {
6537
- console.warn(`Warning: Could not parse ${filePath}:`, error);
6538
- }
6539
- }
6540
- return pages;
6541
- }
6542
- async function readNavigation(contentDir) {
6543
- const filePath = path9__namespace.join(contentDir, "navigation.json");
6544
- if (!await fileExists(filePath)) {
6545
- return null;
6546
- }
6547
- return readJsonFile(filePath);
6548
- }
6549
- async function readSettings(contentDir) {
6550
- const filePath = path9__namespace.join(contentDir, "settings.json");
6551
- if (!await fileExists(filePath)) {
6552
- return null;
6553
- }
6554
- return readJsonFile(filePath);
6555
- }
6556
- async function readAllContent(contentDir) {
6557
- const [entries, pages, navigation, settings] = await Promise.all([
6558
- readEntries(contentDir),
6559
- readPages(contentDir),
6560
- readNavigation(contentDir),
6561
- readSettings(contentDir)
6562
- ]);
6563
- return { entries, pages, navigation, settings };
6564
- }
6565
- async function contentDirExists(contentDir) {
6566
- return fileExists(contentDir);
6567
- }
6568
- async function getContentSummary(contentDir) {
6569
- const content = await readAllContent(contentDir);
6570
- const entryTypes = Array.from(content.entries.keys());
6571
- let totalEntries = 0;
6572
- for (const entries of content.entries.values()) {
6573
- totalEntries += entries.length;
6574
- }
6575
- return {
6576
- hasEntries: content.entries.size > 0,
6577
- entryTypes,
6578
- totalEntries,
6579
- hasPages: content.pages.length > 0,
6580
- pageCount: content.pages.length,
6581
- hasNavigation: content.navigation !== null && content.navigation.menus.length > 0,
6582
- menuCount: content.navigation?.menus.length ?? 0,
6583
- hasSettings: content.settings !== null
6584
- };
6585
- }
6586
6711
 
6587
6712
  // src/cli/sync/mapper.ts
6588
6713
  function stripNavigationItemIds(items) {
@@ -7365,7 +7490,48 @@ function reportSyncResults(output, result, hasErrors) {
7365
7490
  }
7366
7491
  output.info(formatSyncResult(result));
7367
7492
  }
7368
- var pushCommand = new commander.Command("push").description("Push content to CMS").argument("[scope]", "What to push: entries, pages, navigation, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--content-dir <dir>", "Content directory (overrides config)").option("--dry-run", "Show what would be pushed without making changes").option("--yes", "Skip confirmation prompt (required for --remote)").option("--force", "Push even if remote content was truncated (may cause incomplete sync)").option("--json-diff [mode]", "Output JSON diff (summary or full)", "summary").addHelpText("after", `
7493
+ function checkForStaleContent(localContent, localMeta, pagesMeta, navigationMeta, remoteContent) {
7494
+ const staleItems = [];
7495
+ for (const [contentType, localEntries] of localContent.entries) {
7496
+ const meta = localMeta.get(contentType);
7497
+ if (!meta) continue;
7498
+ remoteContent.entries[contentType]?.forEach((remoteEntry) => {
7499
+ const localEntry = localEntries.find((e) => e.identifier === remoteEntry.identifier);
7500
+ if (localEntry) {
7501
+ const entryMeta = meta.entries[remoteEntry.identifier];
7502
+ const localBaseTime = entryMeta?.updatedAt;
7503
+ const remoteTime = remoteContent.meta.entries?.[`${contentType}:${remoteEntry.identifier}`]?.updatedAt;
7504
+ if (localBaseTime && remoteTime && new Date(remoteTime) > new Date(localBaseTime)) {
7505
+ staleItems.push(`Entry: ${contentType}/${remoteEntry.identifier} (Remote updated: ${remoteTime})`);
7506
+ }
7507
+ }
7508
+ });
7509
+ }
7510
+ if (pagesMeta) {
7511
+ localContent.pages.forEach((localPage) => {
7512
+ const remotePage = remoteContent.pages.find((p) => p.identifier === localPage.identifier);
7513
+ const localBaseTime = pagesMeta.pages[localPage.identifier]?.updatedAt;
7514
+ if (remotePage && localBaseTime) {
7515
+ if (new Date(remotePage.updatedAt) > new Date(localBaseTime)) {
7516
+ staleItems.push(`Page: ${localPage.identifier} (Remote updated: ${remotePage.updatedAt})`);
7517
+ }
7518
+ }
7519
+ });
7520
+ }
7521
+ if (navigationMeta) {
7522
+ localContent.navigation?.menus.forEach((localMenu) => {
7523
+ const remoteMenu = remoteContent.navigation.find((m) => m.name === localMenu.name);
7524
+ const localBaseTime = navigationMeta.menus[localMenu.name]?.updatedAt;
7525
+ if (remoteMenu && localBaseTime) {
7526
+ if (new Date(remoteMenu.updatedAt) > new Date(localBaseTime)) {
7527
+ staleItems.push(`Navigation: ${localMenu.name} (Remote updated: ${remoteMenu.updatedAt})`);
7528
+ }
7529
+ }
7530
+ });
7531
+ }
7532
+ return staleItems;
7533
+ }
7534
+ var pushCommand = new commander.Command("push").description("Push content to CMS").argument("[scope]", "What to push: entries, pages, navigation, or all (default)").argument("[type]", 'Content type (when scope is "entries")').option("--content-dir <dir>", "Content directory (overrides config)").option("--dry-run", "Show what would be pushed without making changes").option("--yes", "Skip confirmation prompt (required for --remote)").option("--force", "Push even if remote content is newer (skip stale check)").option("--allow-truncated", "Push even if remote content was truncated (may cause incomplete sync)").option("--json-diff [mode]", "Output JSON diff (summary or full)", "summary").addHelpText("after", `
7369
7535
  Examples:
7370
7536
  $ riverbankcms push # Push all content
7371
7537
  $ riverbankcms push --dry-run # Preview changes
@@ -7384,9 +7550,9 @@ Sync Behavior:
7384
7550
  - sync.contentTarget: 'draft' (default) or 'publish'
7385
7551
 
7386
7552
  Safety:
7387
- - Local environment: pushes directly
7553
+ - Stale detection: aborts if remote content is newer than last pull (use --force to override)
7554
+ - Truncation: aborts if remote has >100 entries per type (use --allow-truncated to override)
7388
7555
  - Remote environment (--remote): defaults to dry-run, requires --yes to execute
7389
- - Use --dry-run to preview changes before pushing
7390
7556
  `).action(async (scope, type, options, command) => {
7391
7557
  const { output, isRemote, globalOpts } = getOutputContext(command);
7392
7558
  let dryRun = options.dryRun ?? false;
@@ -7420,24 +7586,42 @@ Safety:
7420
7586
  });
7421
7587
  output.info("Reading local content...");
7422
7588
  const localContent = await readAllContent(contentDir);
7589
+ const pushScope = scope?.toLowerCase() ?? "all";
7590
+ const filteredLocal = filterLocalContent(localContent, pushScope, type);
7423
7591
  output.info("Fetching remote content for comparison...");
7424
7592
  const remoteContent = await client.pull.all();
7425
7593
  if (remoteContent.meta.truncated) {
7426
7594
  output.warn("Warning: Remote content was truncated due to size limits.");
7427
7595
  output.warn(
7428
- remoteContent.meta.truncationMessage ?? "Some content types have more than 500 entries. Results may be incomplete."
7596
+ remoteContent.meta.truncationMessage ?? "Some content types have more than 100 entries. Results may be incomplete."
7429
7597
  );
7430
- if (!options.force) {
7598
+ if (!options.allowTruncated) {
7599
+ output.error(
7600
+ "Push aborted due to truncated remote content.",
7601
+ { suggestion: "Use --allow-truncated to push anyway, or push specific content types: riverbankcms push entries blog-post" }
7602
+ );
7603
+ return;
7604
+ }
7605
+ output.warn("Proceeding with push despite truncation (--allow-truncated flag used)");
7606
+ }
7607
+ if (!options.force) {
7608
+ output.info("Checking for stale content...");
7609
+ const [localMeta, pagesMeta, navigationMeta] = await Promise.all([
7610
+ readAllMeta(contentDir),
7611
+ readPagesMeta(contentDir),
7612
+ readNavigationMeta(contentDir)
7613
+ ]);
7614
+ const staleItems = checkForStaleContent(filteredLocal, localMeta, pagesMeta, navigationMeta, remoteContent);
7615
+ if (staleItems.length > 0) {
7616
+ output.warn("WARNING: The following remote content has changed since you last pulled:");
7617
+ staleItems.forEach((item) => output.warn(` - ${item}`));
7431
7618
  output.error(
7432
- "Use --force to push anyway, or use type-specific pulls for accuracy.",
7433
- { suggestion: "For large sites, push specific content types: riverbankcms push entries blog-post" }
7619
+ "Push aborted to prevent overwriting newer content.",
7620
+ { suggestion: 'Run "riverbankcms pull" to update your local content, or use --force to overwrite.' }
7434
7621
  );
7435
7622
  return;
7436
7623
  }
7437
- output.warn("Proceeding with push despite truncation (--force flag used)");
7438
7624
  }
7439
- const pushScope = scope?.toLowerCase() ?? "all";
7440
- const filteredLocal = filterLocalContent(localContent, pushScope, type);
7441
7625
  output.info("Calculating changes...");
7442
7626
  const diff = calculateDiff(filteredLocal, remoteContent, {
7443
7627
  existingEntries: cliConfig.sync.existingEntries
@@ -7478,7 +7662,7 @@ var formatEntryRow = (entry) => [
7478
7662
  entry.identifier,
7479
7663
  entry.status,
7480
7664
  entry.hasUnpublishedChanges ? "Yes" : "No",
7481
- entry.slug,
7665
+ entry.slug ?? "-",
7482
7666
  formatDateShort(entry.updatedAt)
7483
7667
  ];
7484
7668
  var upsertCommand = new commander.Command("upsert").description("Create or update an entry").argument("<type>", "Content type").argument("<identifier>", "Entry identifier").option("--data <json>", "Entry data as JSON string").option("--file <path>", "Path to JSON file with entry data").option("--slug <slug>", "Entry slug (defaults to identifier)").option("--title <title>", "Entry title").action(
@@ -7850,9 +8034,9 @@ function resolveOutputMode(options) {
7850
8034
  return "terminal";
7851
8035
  }
7852
8036
  async function writePreviewHtmlFile(html) {
7853
- const dir = await fs5__namespace.mkdtemp(path9__namespace.join(os__namespace.tmpdir(), "riverbank-preview-"));
8037
+ const dir = await fs3__namespace.mkdtemp(path9__namespace.join(os__namespace.tmpdir(), "riverbank-preview-"));
7854
8038
  const filePath = path9__namespace.join(dir, "index.html");
7855
- await fs5__namespace.writeFile(filePath, html, "utf-8");
8039
+ await fs3__namespace.writeFile(filePath, html, "utf-8");
7856
8040
  return filePath;
7857
8041
  }
7858
8042
  async function openPreviewFile(filePath) {
@@ -7946,6 +8130,8 @@ function buildSchemaTemplate(state) {
7946
8130
  "",
7947
8131
  "This document captures the content model and SDK configuration for this site.",
7948
8132
  "The generated section is updated by `riverbankcms init-docs`.",
8133
+ "",
8134
+ renderConfigGuideSection(),
7949
8135
  ""
7950
8136
  ].join("\n");
7951
8137
  const generated = buildSchemaGeneratedSection(state);
@@ -8049,6 +8235,132 @@ function renderContentTypesSection(config3) {
8049
8235
  }
8050
8236
  return lines.join("\n");
8051
8237
  }
8238
+ function renderConfigGuideSection() {
8239
+ return [
8240
+ "## riverbank.config.ts Guide",
8241
+ "",
8242
+ "Use `riverbank.config.ts` as the source of truth for the site schema, custom blocks, and CLI behavior.",
8243
+ "This section is static guidance for agents; the generated section below shows the live config.",
8244
+ "",
8245
+ "### Field Overrides (blockFieldOptions)",
8246
+ "",
8247
+ "Use `blockFieldOptions` to override the *options* for existing `select` fields.",
8248
+ "This only affects fields that use the `sdkSelect` UI widget (for example: the embed block layout picker).",
8249
+ "",
8250
+ "```ts",
8251
+ "export default defineConfig({",
8252
+ " siteId: 'your-site-id',",
8253
+ " blockFieldOptions: {",
8254
+ " 'block.embed': {",
8255
+ " layout: {",
8256
+ " options: [",
8257
+ " { value: 'showcase', label: 'Showcase Grid' },",
8258
+ " { value: 'list', label: 'Simple List' },",
8259
+ " ],",
8260
+ " },",
8261
+ " },",
8262
+ " 'custom.featured-posts': {",
8263
+ " style: {",
8264
+ " options: [",
8265
+ " { value: 'grid', label: 'Grid' },",
8266
+ " { value: 'carousel', label: 'Carousel' },",
8267
+ " ],",
8268
+ " },",
8269
+ " },",
8270
+ " },",
8271
+ "});",
8272
+ "```",
8273
+ "",
8274
+ "### Block Field Extensions (blockFieldExtensions)",
8275
+ "",
8276
+ "Use `blockFieldExtensions` to add *new* fields to built-in system blocks.",
8277
+ "Extended fields appear at the end of the block form and are available via `content` in `blockOverrides`.",
8278
+ "",
8279
+ "```ts",
8280
+ "export default defineConfig({",
8281
+ " siteId: 'your-site-id',",
8282
+ " blockFieldExtensions: {",
8283
+ " 'block.hero': {",
8284
+ " fields: [",
8285
+ " { id: 'videoBackground', type: 'media', label: 'Video Background', mediaKinds: ['video'] },",
8286
+ " { id: 'overlayOpacity', type: 'number', label: 'Overlay Opacity', defaultValue: 50 },",
8287
+ " ],",
8288
+ " },",
8289
+ " 'block.bodyText': {",
8290
+ " fields: [",
8291
+ " { id: 'layout', type: 'select', label: 'Layout', defaultValue: 'default',",
8292
+ " options: [",
8293
+ " { value: 'default', label: 'Default' },",
8294
+ " { value: 'wide', label: 'Wide' },",
8295
+ " ],",
8296
+ " },",
8297
+ " ],",
8298
+ " },",
8299
+ " },",
8300
+ "});",
8301
+ "```",
8302
+ "",
8303
+ "Rules:",
8304
+ "- Only system blocks (`block.*`) can be extended.",
8305
+ "- Extended field IDs must not collide with existing block fields.",
8306
+ "- If `required: true`, you must set a `defaultValue` to avoid breaking existing blocks.",
8307
+ "",
8308
+ "### Custom Blocks (customBlocks)",
8309
+ "",
8310
+ "Use `customBlocks` to define new block types with their own fields.",
8311
+ "Custom blocks must be rendered via `blockOverrides` in the SDK site.",
8312
+ "",
8313
+ "```ts",
8314
+ "export default defineConfig({",
8315
+ " siteId: 'your-site-id',",
8316
+ " customBlocks: [",
8317
+ " {",
8318
+ " id: 'custom.team-member',",
8319
+ " title: 'Team Member',",
8320
+ " titleSource: 'name',",
8321
+ " description: 'Profile card with bio and photo',",
8322
+ " category: 'content',",
8323
+ " icon: 'User',",
8324
+ " fields: [",
8325
+ " { id: 'name', type: 'text', label: 'Name', required: true },",
8326
+ " { id: 'role', type: 'text', label: 'Role' },",
8327
+ " { id: 'photo', type: 'media', label: 'Photo', mediaKinds: ['image'] },",
8328
+ " { id: 'bio', type: 'richText', label: 'Bio' },",
8329
+ " ],",
8330
+ " },",
8331
+ " ],",
8332
+ "});",
8333
+ "```",
8334
+ "",
8335
+ "### Field Types (for customBlocks and blockFieldExtensions)",
8336
+ "",
8337
+ "All field definitions use the same `FieldDefinition` format as system blocks.",
8338
+ "Supported `type` values:",
8339
+ "",
8340
+ "- `text`: Single or multiline text",
8341
+ "- `richText`: Rich text editor (markdown or HTML)",
8342
+ "- `media`: Image or video upload",
8343
+ "- `boolean`: Toggle/checkbox",
8344
+ "- `number`: Numeric input",
8345
+ "- `date`: Date picker",
8346
+ "- `time`: Time picker",
8347
+ "- `datetime`: Date + time picker",
8348
+ "- `slug`: Slug with optional source field",
8349
+ "- `url`: URL input (optionally allow relative)",
8350
+ "- `link`: Link picker",
8351
+ "- `select`: Single or multi-select options",
8352
+ "- `reference`: Reference another entry type",
8353
+ "- `repeater`: Repeatable list of fields",
8354
+ "- `group`: Group of fields",
8355
+ "- `modal`: Fields inside a modal dialog",
8356
+ "- `tabGroup`: Group fields by tabs",
8357
+ "- `presetOrCustom`: Preset list with custom fallback",
8358
+ "- `contentTypeSelect`: Content type picker (all/routable/nonRoutable)",
8359
+ "- `entryPicker`: Pick a content entry",
8360
+ "",
8361
+ "Tip: For `repeater`, define either `schema.fields` (monomorphic) or `polymorphic: true` with `itemTypes`."
8362
+ ].join("\n");
8363
+ }
8052
8364
  function renderCustomBlocksSection(customBlocks) {
8053
8365
  const lines = ["## Custom Blocks", ""];
8054
8366
  if (customBlocks.length === 0) {
@@ -8196,24 +8508,24 @@ async function loadConfig(configPath, output) {
8196
8508
  }
8197
8509
  }
8198
8510
  async function ensureDir2(dirPath) {
8199
- await fs5__namespace.mkdir(dirPath, { recursive: true });
8511
+ await fs3__namespace.mkdir(dirPath, { recursive: true });
8200
8512
  }
8201
8513
  async function writeFileIfMissing(filePath, contents) {
8202
8514
  try {
8203
- await fs5__namespace.access(filePath);
8515
+ await fs3__namespace.access(filePath);
8204
8516
  } catch {
8205
- await fs5__namespace.writeFile(filePath, contents, "utf-8");
8517
+ await fs3__namespace.writeFile(filePath, contents, "utf-8");
8206
8518
  }
8207
8519
  }
8208
8520
  async function upsertGeneratedDoc(filePath, template, generatedSection) {
8209
8521
  const markers = getGeneratedMarkers();
8210
8522
  const contents = await readFileOrTemplate(filePath, template);
8211
8523
  const updated = replaceMarkedSection(contents, markers.start, markers.end, generatedSection);
8212
- await fs5__namespace.writeFile(filePath, updated, "utf-8");
8524
+ await fs3__namespace.writeFile(filePath, updated, "utf-8");
8213
8525
  }
8214
8526
  async function readFileOrTemplate(filePath, template) {
8215
8527
  try {
8216
- return await fs5__namespace.readFile(filePath, "utf-8");
8528
+ return await fs3__namespace.readFile(filePath, "utf-8");
8217
8529
  } catch {
8218
8530
  return template;
8219
8531
  }
@@ -8240,7 +8552,7 @@ async function upsertAgentsSection(filePath) {
8240
8552
  const content = await readFileOrTemplate(filePath, "# AGENTS.md\n");
8241
8553
  const section2 = agentsSectionTemplate();
8242
8554
  const updated = replaceMarkedSection(content, AGENTS_START, AGENTS_END, section2);
8243
- await fs5__namespace.writeFile(filePath, updated, "utf-8");
8555
+ await fs3__namespace.writeFile(filePath, updated, "utf-8");
8244
8556
  }
8245
8557
  function cliReferenceTemplate() {
8246
8558
  return [
@@ -8350,6 +8662,51 @@ var initDocsCommand = new commander.Command("init-docs").description("Scaffold a
8350
8662
  await runInitDocs(options, command);
8351
8663
  })
8352
8664
  );
8665
+ var backfillCommand = new commander.Command("backfill").description("Backfill identifiers for pages, blocks, and entries that don't have one").addHelpText("after", `
8666
+ This command generates stable identifiers for content created before the SDK was available.
8667
+ Identifiers are generated from titles (slugified) and are idempotent - running this
8668
+ command multiple times won't overwrite existing identifiers.
8669
+
8670
+ Examples:
8671
+ $ riverbankcms identifiers backfill
8672
+ $ riverbankcms identifiers backfill --remote
8673
+ `).action(
8674
+ withErrorHandling(async (_options, command) => {
8675
+ const { output, client } = createCommandContext(command);
8676
+ output.info("Backfilling identifiers for pages, blocks, and entries...");
8677
+ const result = await client.identifiers.backfill();
8678
+ const totalUpdated = result.pages.pagesUpdated + result.blocks.total + result.entries.entriesUpdated;
8679
+ if (totalUpdated === 0) {
8680
+ output.success("All content already has identifiers - nothing to backfill");
8681
+ return;
8682
+ }
8683
+ output.success("Backfill complete", {
8684
+ pages: {
8685
+ updated: result.pages.pagesUpdated,
8686
+ identifiers: result.pages.identifiersAssigned
8687
+ },
8688
+ blocks: {
8689
+ updated: result.blocks.total,
8690
+ byPage: Object.keys(result.blocks.byPage).length > 0 ? result.blocks.byPage : void 0
8691
+ },
8692
+ entries: {
8693
+ updated: result.entries.entriesUpdated,
8694
+ identifiers: result.entries.identifiersAssigned
8695
+ }
8696
+ });
8697
+ })
8698
+ );
8699
+ var identifiersCommand = new commander.Command("identifiers").description("Manage SDK identifiers").addHelpText("after", `
8700
+ Identifiers are stable, human-readable names used to reference content in the SDK.
8701
+ They are automatically generated when content is created via the SDK, but older
8702
+ content may need backfilling.
8703
+
8704
+ Commands:
8705
+ backfill Generate identifiers for content that doesn't have one
8706
+
8707
+ Examples:
8708
+ $ riverbankcms identifiers backfill
8709
+ `).addCommand(backfillCommand);
8353
8710
 
8354
8711
  // src/cli/index.ts
8355
8712
  dotenv.config({ path: ".env.local" });
@@ -8387,6 +8744,7 @@ program.addCommand(navigationCommand);
8387
8744
  program.addCommand(deleteCommand);
8388
8745
  program.addCommand(previewCommand);
8389
8746
  program.addCommand(initDocsCommand);
8747
+ program.addCommand(identifiersCommand);
8390
8748
  program.parse();
8391
8749
  //# sourceMappingURL=index.js.map
8392
8750
  //# sourceMappingURL=index.js.map