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