@messagevisor/catalog 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/node/index.ts CHANGED
@@ -104,6 +104,12 @@ class CatalogProgressReporter {
104
104
  return Date.now();
105
105
  }
106
106
 
107
+ substep(label: string, detail?: string) {
108
+ const suffix = detail ? `: ${colorize(detail, 2)}` : "";
109
+ console.log(` ${colorize("•", 36)} ${label}${suffix}`);
110
+ return Date.now();
111
+ }
112
+
107
113
  done(startedAt: number, detail?: string) {
108
114
  const suffix = detail ? ` ${detail}` : "";
109
115
  console.log(CLI_FORMAT_DIM, ` done in ${prettyDuration(Date.now() - startedAt)}${suffix}`);
@@ -352,6 +358,7 @@ export interface CatalogServerHandle {
352
358
  interface CatalogBuildContext {
353
359
  rootDirectoryPath: string;
354
360
  repositoryRootDirectoryPath: string;
361
+ repositorySourceRootDirectoryPath: string;
355
362
  outputDirectoryPath: string;
356
363
  dataDirectoryPath: string;
357
364
  historyIndex: CatalogHistoryIndex;
@@ -361,6 +368,7 @@ interface CatalogBuildContext {
361
368
  withTranslationSearch: boolean;
362
369
  withDuplicates: boolean;
363
370
  progress: CatalogProgressReporter;
371
+ writer: CatalogJsonWriter;
364
372
  }
365
373
 
366
374
  interface SourceFileInfo {
@@ -723,9 +731,41 @@ function toLocaleDuplicatesFile(
723
731
  };
724
732
  }
725
733
 
726
- async function writeJson(filePath: string, content: unknown) {
727
- await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
728
- await fs.promises.writeFile(filePath, JSON.stringify(content, null, 2));
734
+ class CatalogJsonWriter {
735
+ private readonly directories = new Map<string, Promise<void>>();
736
+
737
+ private ensureDirectory(directoryPath: string) {
738
+ let promise = this.directories.get(directoryPath);
739
+
740
+ if (!promise) {
741
+ promise = fs.promises.mkdir(directoryPath, { recursive: true }).then(() => undefined);
742
+ this.directories.set(directoryPath, promise);
743
+ }
744
+
745
+ return promise;
746
+ }
747
+
748
+ async write(filePath: string, content: unknown) {
749
+ await this.ensureDirectory(path.dirname(filePath));
750
+ await fs.promises.writeFile(filePath, JSON.stringify(content, null, 2));
751
+ }
752
+ }
753
+
754
+ async function mapWithConcurrency<T>(
755
+ items: T[],
756
+ concurrency: number,
757
+ callback: (item: T, index: number) => Promise<void>,
758
+ ) {
759
+ let nextIndex = 0;
760
+ const workerCount = Math.min(concurrency, items.length);
761
+ const workers = Array.from({ length: workerCount }, async () => {
762
+ while (nextIndex < items.length) {
763
+ const index = nextIndex++;
764
+ await callback(items[index], index);
765
+ }
766
+ });
767
+
768
+ await Promise.all(workers);
729
769
  }
730
770
 
731
771
  function getEntityDirectoryPaths(config: any): Record<CatalogEntityType | "test", string> {
@@ -870,6 +910,18 @@ function addHistoryIndexEntry(
870
910
  target[key].push(entry);
871
911
  }
872
912
 
913
+ function toEntityHistoryEntry(
914
+ entry: CatalogHistoryEntry,
915
+ entity: CatalogHistoryEntity,
916
+ ): CatalogHistoryEntry {
917
+ return {
918
+ commit: entry.commit,
919
+ author: entry.author,
920
+ timestamp: entry.timestamp,
921
+ entities: [entity],
922
+ };
923
+ }
924
+
873
925
  function buildCatalogHistoryIndex(entries: CatalogHistoryEntry[]): CatalogHistoryIndex {
874
926
  const index = createEmptyHistoryIndex();
875
927
  index.entries = entries;
@@ -883,7 +935,7 @@ function buildCatalogHistoryIndex(entries: CatalogHistoryEntry[]): CatalogHistor
883
935
  }
884
936
 
885
937
  const entityKey = getHistoryEntityKey(entity.type, entity.key, entity.set);
886
- addHistoryIndexEntry(index.byEntity, entityKey, entry);
938
+ addHistoryIndexEntry(index.byEntity, entityKey, toEntityHistoryEntry(entry, entity));
887
939
 
888
940
  if (!index.lastModifiedByEntity[entityKey]) {
889
941
  index.lastModifiedByEntity[entityKey] = toLastModified(entry);
@@ -1129,6 +1181,25 @@ function getRepositoryRootDirectoryPath(rootDirectoryPath: string) {
1129
1181
  }
1130
1182
  }
1131
1183
 
1184
+ function getRepositorySourceRootDirectoryPath(rootDirectoryPath: string) {
1185
+ try {
1186
+ const gitRootDirectoryPath =
1187
+ runGit(rootDirectoryPath, ["rev-parse", "--show-toplevel"]).trim() || rootDirectoryPath;
1188
+ const realRootDirectoryPath = getRealPath(rootDirectoryPath);
1189
+
1190
+ if (realRootDirectoryPath !== rootDirectoryPath) {
1191
+ return path.resolve(
1192
+ rootDirectoryPath,
1193
+ path.relative(realRootDirectoryPath, gitRootDirectoryPath),
1194
+ );
1195
+ }
1196
+
1197
+ return gitRootDirectoryPath;
1198
+ } catch (_error) {
1199
+ return rootDirectoryPath;
1200
+ }
1201
+ }
1202
+
1132
1203
  function getOwnerAndRepoFromGitRemote(origin: string, host: string) {
1133
1204
  const escapedHost = host.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1134
1205
  const match = origin.match(new RegExp(`${escapedHost}[:/]([^/]+)/(.+?)(?:\\.git)?$`));
@@ -1204,17 +1275,28 @@ function chunkHistory(history: CatalogHistoryEntry[], pageSize = CATALOG_HISTORY
1204
1275
  return pages.length > 0 ? pages : [[]];
1205
1276
  }
1206
1277
 
1207
- async function writeHistoryPages(directoryPath: string, history: CatalogHistoryEntry[]) {
1278
+ async function writeHistoryPages(
1279
+ writer: CatalogJsonWriter,
1280
+ directoryPath: string,
1281
+ history: CatalogHistoryEntry[],
1282
+ options: { skipEmpty?: boolean } = {},
1283
+ ) {
1284
+ if (options.skipEmpty && history.length === 0) {
1285
+ return 1;
1286
+ }
1287
+
1208
1288
  const pages = chunkHistory(history);
1209
1289
 
1210
1290
  for (let index = 0; index < pages.length; index++) {
1211
- await writeJson(path.join(directoryPath, `page-${index + 1}.json`), {
1291
+ await writer.write(path.join(directoryPath, `page-${index + 1}.json`), {
1212
1292
  page: index + 1,
1213
1293
  pageSize: CATALOG_HISTORY_PAGE_SIZE,
1214
1294
  totalPages: pages.length,
1215
1295
  entries: pages[index],
1216
1296
  });
1217
1297
  }
1298
+
1299
+ return 0;
1218
1300
  }
1219
1301
 
1220
1302
  function getHistoryForEntity(
@@ -1227,11 +1309,12 @@ function getHistoryForEntity(
1227
1309
  }
1228
1310
 
1229
1311
  function getSourceFileInfo(
1230
- repositoryRootDirectoryPath: string,
1312
+ repositorySourceRootDirectoryPath: string,
1231
1313
  rootDirectoryPath: string,
1232
1314
  projectConfig: any,
1233
1315
  type: CatalogEntityType,
1234
1316
  key: string,
1317
+ options: { resolveAbsolutePath?: boolean } = {},
1235
1318
  ): SourceFileInfo {
1236
1319
  const directoryByType: Record<CatalogEntityType, string> = {
1237
1320
  locale: projectConfig.localesDirectoryPath,
@@ -1248,10 +1331,10 @@ function getSourceFileInfo(
1248
1331
  ...key.split(projectConfig.namespaceCharacter),
1249
1332
  ) + extension,
1250
1333
  );
1251
- const absolutePath = getRealPath(filePath);
1334
+ const absolutePath = options.resolveAbsolutePath ? getRealPath(filePath) : filePath;
1252
1335
 
1253
1336
  return {
1254
- sourcePath: toPosixPath(path.relative(repositoryRootDirectoryPath, absolutePath)),
1337
+ sourcePath: toPosixPath(path.relative(repositorySourceRootDirectoryPath, filePath)),
1255
1338
  absolutePath,
1256
1339
  };
1257
1340
  }
@@ -1284,13 +1367,13 @@ async function buildSetCatalog(
1284
1367
  const outputDirectoryPath = path.join(context.dataDirectoryPath, outputRelativeDirectory);
1285
1368
  const setStartedAt = context.progress.setStart(set);
1286
1369
  const entitiesStartedAt = context.progress.step("Processing entities");
1287
- const [localeKeys, messageKeys, attributeKeys, segmentKeys, targetKeys] = await Promise.all([
1370
+ const [localeKeys, messageKeys, attributeKeys, segmentKeys, targetKeys] = (await Promise.all([
1288
1371
  datasource.listLocales(),
1289
1372
  datasource.listMessages(),
1290
1373
  datasource.listAttributes(),
1291
1374
  datasource.listSegments(),
1292
1375
  datasource.listTargets(),
1293
- ]);
1376
+ ])) as [string[], string[], string[], string[], string[]];
1294
1377
  const [locales, messages, attributes, segments, targets] = await Promise.all([
1295
1378
  readAll<Locale>(localeKeys, (key) => datasource.readLocale(key)),
1296
1379
  readAll<Message>(messageKeys, (key) => datasource.readMessage(key)),
@@ -1440,7 +1523,7 @@ async function buildSetCatalog(
1440
1523
  };
1441
1524
 
1442
1525
  const historyStartedAt = context.progress.step("Writing history pages");
1443
- await writeHistoryPages(path.join(outputDirectoryPath, "history"), history);
1526
+ await writeHistoryPages(context.writer, path.join(outputDirectoryPath, "history"), history);
1444
1527
  context.progress.done(historyStartedAt, `(${pluralize(history.length, "entry", "entries")})`);
1445
1528
 
1446
1529
  const examplesStartedAt = context.progress.step("Evaluating examples");
@@ -1492,14 +1575,16 @@ async function buildSetCatalog(
1492
1575
  );
1493
1576
 
1494
1577
  const localesStartedAt = context.progress.step("Writing locales");
1495
- for (const localeKey of localeKeys) {
1578
+ let skippedEmptyHistoryCount = 0;
1579
+ await mapWithConcurrency(localeKeys, 32, async (localeKey) => {
1496
1580
  const locale = locales[localeKey];
1497
1581
  const sourceFileInfo = getSourceFileInfo(
1498
- context.repositoryRootDirectoryPath,
1582
+ context.repositorySourceRootDirectoryPath,
1499
1583
  context.rootDirectoryPath,
1500
1584
  projectConfig,
1501
1585
  "locale",
1502
1586
  localeKey,
1587
+ { resolveAbsolutePath: context.devEditors.length > 0 },
1503
1588
  );
1504
1589
  const detail = {
1505
1590
  type: "locale",
@@ -1525,26 +1610,28 @@ async function buildSetCatalog(
1525
1610
  targets: sortStrings(Array.from(localeTargets[localeKey] || [])),
1526
1611
  }),
1527
1612
  );
1528
- await writeJson(
1613
+ await context.writer.write(
1529
1614
  path.join(outputDirectoryPath, "entities", "locale", `${encodeKey(localeKey)}.json`),
1530
1615
  detail,
1531
1616
  );
1532
- await writeHistoryPages(
1617
+ skippedEmptyHistoryCount += await writeHistoryPages(
1618
+ context.writer,
1533
1619
  path.join(outputDirectoryPath, "history", "locale", encodeKey(localeKey)),
1534
1620
  getHistoryForEntity(context.historyIndex, "locale", localeKey, set || undefined),
1621
+ { skipEmpty: true },
1535
1622
  );
1536
- }
1623
+ });
1537
1624
  context.progress.done(localesStartedAt, `(${pluralize(localeKeys.length, "locale")})`);
1538
1625
 
1539
1626
  if (context.withDuplicates) {
1540
1627
  const duplicatesStartedAt = context.progress.step("Writing duplicate reports");
1541
1628
 
1542
- for (const localeKey of localeKeys) {
1543
- await writeJson(
1629
+ await mapWithConcurrency(localeKeys, 32, async (localeKey) => {
1630
+ await context.writer.write(
1544
1631
  path.join(outputDirectoryPath, "duplicates", "locales", `${encodeKey(localeKey)}.json`),
1545
1632
  toLocaleDuplicatesFile(localeKey, duplicatesByLocale),
1546
1633
  );
1547
- }
1634
+ });
1548
1635
 
1549
1636
  context.progress.done(duplicatesStartedAt, `(${pluralize(localeKeys.length, "locale")})`);
1550
1637
  }
@@ -1568,7 +1655,9 @@ async function buildSetCatalog(
1568
1655
  }
1569
1656
 
1570
1657
  const messagesStartedAt = context.progress.step("Writing messages");
1571
- for (const messageKey of messageKeys) {
1658
+ const messageDetailsStartedAt = context.progress.substep("Writing message details");
1659
+ let skippedEmptyMessageHistoryCount = 0;
1660
+ await mapWithConcurrency(messageKeys, 32, async (messageKey) => {
1572
1661
  const message = messages[messageKey];
1573
1662
  const overrides = (message.overrides || []).map((override: Override) => {
1574
1663
  const attributes = new Set<string>();
@@ -1583,11 +1672,12 @@ async function buildSetCatalog(
1583
1672
  };
1584
1673
  });
1585
1674
  const sourceFileInfo = getSourceFileInfo(
1586
- context.repositoryRootDirectoryPath,
1675
+ context.repositorySourceRootDirectoryPath,
1587
1676
  context.rootDirectoryPath,
1588
1677
  projectConfig,
1589
1678
  "message",
1590
1679
  messageKey,
1680
+ { resolveAbsolutePath: context.devEditors.length > 0 },
1591
1681
  );
1592
1682
  const detail = {
1593
1683
  type: "message",
@@ -1629,16 +1719,40 @@ async function buildSetCatalog(
1629
1719
  ...(overrideLocalesList.length > 0 ? { overrideLocales: overrideLocalesList } : {}),
1630
1720
  }),
1631
1721
  );
1632
- await writeJson(
1722
+ await context.writer.write(
1633
1723
  path.join(outputDirectoryPath, "entities", "message", `${encodeKey(messageKey)}.json`),
1634
1724
  detail,
1635
1725
  );
1636
- await writeHistoryPages(
1726
+ });
1727
+ context.progress.done(messageDetailsStartedAt, `(${pluralize(messageKeys.length, "message")})`);
1728
+
1729
+ const messageHistoryStartedAt = context.progress.substep("Writing message history pages");
1730
+ await mapWithConcurrency(messageKeys, 32, async (messageKey) => {
1731
+ const skippedHistory = await writeHistoryPages(
1732
+ context.writer,
1637
1733
  path.join(outputDirectoryPath, "history", "message", encodeKey(messageKey)),
1638
1734
  getHistoryForEntity(context.historyIndex, "message", messageKey, set || undefined),
1735
+ { skipEmpty: true },
1639
1736
  );
1640
- }
1641
- context.progress.done(messagesStartedAt, `(${pluralize(messageKeys.length, "message")})`);
1737
+ skippedEmptyMessageHistoryCount += skippedHistory;
1738
+ skippedEmptyHistoryCount += skippedHistory;
1739
+ });
1740
+ context.progress.done(
1741
+ messageHistoryStartedAt,
1742
+ `(${pluralize(messageKeys.length, "message")}, ${pluralize(
1743
+ skippedEmptyMessageHistoryCount,
1744
+ "empty history",
1745
+ "empty histories",
1746
+ )} skipped)`,
1747
+ );
1748
+ context.progress.done(
1749
+ messagesStartedAt,
1750
+ `(${pluralize(messageKeys.length, "message")}, ${pluralize(
1751
+ skippedEmptyMessageHistoryCount,
1752
+ "empty history",
1753
+ "empty histories",
1754
+ )} skipped)`,
1755
+ );
1642
1756
 
1643
1757
  if (context.withTranslationSearch) {
1644
1758
  const translationSearchStartedAt = context.progress.step("Building translation search shards");
@@ -1666,7 +1780,10 @@ async function buildSetCatalog(
1666
1780
  for (const [msgKey, valueSet] of Object.entries(messageMap)) {
1667
1781
  shardData[msgKey] = Array.from(valueSet);
1668
1782
  }
1669
- await writeJson(path.join(outputDirectoryPath, "translations", `${prefix}.json`), shardData);
1783
+ await context.writer.write(
1784
+ path.join(outputDirectoryPath, "translations", `${prefix}.json`),
1785
+ shardData,
1786
+ );
1670
1787
  }
1671
1788
  context.progress.done(
1672
1789
  translationSearchStartedAt,
@@ -1675,14 +1792,15 @@ async function buildSetCatalog(
1675
1792
  }
1676
1793
 
1677
1794
  const attributesStartedAt = context.progress.step("Writing attributes");
1678
- for (const attributeKey of attributeKeys) {
1795
+ await mapWithConcurrency(attributeKeys, 32, async (attributeKey) => {
1679
1796
  const attribute = attributes[attributeKey];
1680
1797
  const sourceFileInfo = getSourceFileInfo(
1681
- context.repositoryRootDirectoryPath,
1798
+ context.repositorySourceRootDirectoryPath,
1682
1799
  context.rootDirectoryPath,
1683
1800
  projectConfig,
1684
1801
  "attribute",
1685
1802
  attributeKey,
1803
+ { resolveAbsolutePath: context.devEditors.length > 0 },
1686
1804
  );
1687
1805
  const detail = {
1688
1806
  type: "attribute",
@@ -1714,28 +1832,31 @@ async function buildSetCatalog(
1714
1832
  },
1715
1833
  ),
1716
1834
  );
1717
- await writeJson(
1835
+ await context.writer.write(
1718
1836
  path.join(outputDirectoryPath, "entities", "attribute", `${encodeKey(attributeKey)}.json`),
1719
1837
  detail,
1720
1838
  );
1721
- await writeHistoryPages(
1839
+ skippedEmptyHistoryCount += await writeHistoryPages(
1840
+ context.writer,
1722
1841
  path.join(outputDirectoryPath, "history", "attribute", encodeKey(attributeKey)),
1723
1842
  getHistoryForEntity(context.historyIndex, "attribute", attributeKey, set || undefined),
1843
+ { skipEmpty: true },
1724
1844
  );
1725
- }
1845
+ });
1726
1846
  context.progress.done(attributesStartedAt, `(${pluralize(attributeKeys.length, "attribute")})`);
1727
1847
 
1728
1848
  const segmentsStartedAt = context.progress.step("Writing segments");
1729
- for (const segmentKey of segmentKeys) {
1849
+ await mapWithConcurrency(segmentKeys, 32, async (segmentKey) => {
1730
1850
  const segment = segments[segmentKey];
1731
1851
  const usedAttributes = new Set<string>();
1732
1852
  collectAttributeKeysFromConditions(segment.conditions, usedAttributes);
1733
1853
  const sourceFileInfo = getSourceFileInfo(
1734
- context.repositoryRootDirectoryPath,
1854
+ context.repositorySourceRootDirectoryPath,
1735
1855
  context.rootDirectoryPath,
1736
1856
  projectConfig,
1737
1857
  "segment",
1738
1858
  segmentKey,
1859
+ { resolveAbsolutePath: context.devEditors.length > 0 },
1739
1860
  );
1740
1861
  const detail = {
1741
1862
  type: "segment",
@@ -1755,19 +1876,21 @@ async function buildSetCatalog(
1755
1876
  targets: sortStrings(Array.from(segmentTargets[segmentKey] || [])),
1756
1877
  }),
1757
1878
  );
1758
- await writeJson(
1879
+ await context.writer.write(
1759
1880
  path.join(outputDirectoryPath, "entities", "segment", `${encodeKey(segmentKey)}.json`),
1760
1881
  detail,
1761
1882
  );
1762
- await writeHistoryPages(
1883
+ skippedEmptyHistoryCount += await writeHistoryPages(
1884
+ context.writer,
1763
1885
  path.join(outputDirectoryPath, "history", "segment", encodeKey(segmentKey)),
1764
1886
  getHistoryForEntity(context.historyIndex, "segment", segmentKey, set || undefined),
1887
+ { skipEmpty: true },
1765
1888
  );
1766
- }
1889
+ });
1767
1890
  context.progress.done(segmentsStartedAt, `(${pluralize(segmentKeys.length, "segment")})`);
1768
1891
 
1769
1892
  const targetsStartedAt = context.progress.step("Writing targets");
1770
- for (const targetKey of targetKeys) {
1893
+ await mapWithConcurrency(targetKeys, 32, async (targetKey) => {
1771
1894
  const target = targets[targetKey];
1772
1895
  const targetLocaleKeys = target.locales?.length ? target.locales : localeKeys;
1773
1896
  const formatsByLocale: Record<string, FormatPresets | undefined> = {};
@@ -1779,11 +1902,12 @@ async function buildSetCatalog(
1779
1902
  }
1780
1903
 
1781
1904
  const sourceFileInfo = getSourceFileInfo(
1782
- context.repositoryRootDirectoryPath,
1905
+ context.repositorySourceRootDirectoryPath,
1783
1906
  context.rootDirectoryPath,
1784
1907
  projectConfig,
1785
1908
  "target",
1786
1909
  targetKey,
1910
+ { resolveAbsolutePath: context.devEditors.length > 0 },
1787
1911
  );
1788
1912
  const detail = {
1789
1913
  type: "target",
@@ -1803,15 +1927,17 @@ async function buildSetCatalog(
1803
1927
  messageCount: targetMessages[targetKey].length,
1804
1928
  }),
1805
1929
  );
1806
- await writeJson(
1930
+ await context.writer.write(
1807
1931
  path.join(outputDirectoryPath, "entities", "target", `${encodeKey(targetKey)}.json`),
1808
1932
  detail,
1809
1933
  );
1810
- await writeHistoryPages(
1934
+ skippedEmptyHistoryCount += await writeHistoryPages(
1935
+ context.writer,
1811
1936
  path.join(outputDirectoryPath, "history", "target", encodeKey(targetKey)),
1812
1937
  getHistoryForEntity(context.historyIndex, "target", targetKey, set || undefined),
1938
+ { skipEmpty: true },
1813
1939
  );
1814
- }
1940
+ });
1815
1941
  context.progress.done(targetsStartedAt, `(${pluralize(targetKeys.length, "target")})`);
1816
1942
 
1817
1943
  const indexStartedAt = context.progress.step("Writing catalog index");
@@ -1819,9 +1945,12 @@ async function buildSetCatalog(
1819
1945
  index.entities[type].sort((a, b) => a.key.localeCompare(b.key));
1820
1946
  }
1821
1947
 
1822
- await writeJson(path.join(outputDirectoryPath, "index.json"), index);
1948
+ await context.writer.write(path.join(outputDirectoryPath, "index.json"), index);
1823
1949
  context.progress.done(indexStartedAt);
1824
- context.progress.done(setStartedAt, "total");
1950
+ context.progress.done(
1951
+ setStartedAt,
1952
+ `total (${pluralize(skippedEmptyHistoryCount, "empty history", "empty histories")} skipped)`,
1953
+ );
1825
1954
 
1826
1955
  return index;
1827
1956
  }
@@ -1862,6 +1991,7 @@ export async function exportCatalog(
1862
1991
  const withTranslationSearch = options.withTranslationSearch === true;
1863
1992
  const withDuplicates = options.withDuplicates === true;
1864
1993
  const progress = new CatalogProgressReporter(rootDirectoryPath, outputDirectoryPath);
1994
+ const writer = new CatalogJsonWriter();
1865
1995
 
1866
1996
  progress.start({
1867
1997
  browserRouter: options.browserRouter !== false,
@@ -1921,6 +2051,7 @@ export async function exportCatalog(
1921
2051
  const context: CatalogBuildContext = {
1922
2052
  rootDirectoryPath,
1923
2053
  repositoryRootDirectoryPath: getRepositoryRootDirectoryPath(rootDirectoryPath),
2054
+ repositorySourceRootDirectoryPath: getRepositorySourceRootDirectoryPath(rootDirectoryPath),
1924
2055
  outputDirectoryPath,
1925
2056
  dataDirectoryPath,
1926
2057
  historyIndex,
@@ -1930,6 +2061,7 @@ export async function exportCatalog(
1930
2061
  withTranslationSearch,
1931
2062
  withDuplicates,
1932
2063
  progress,
2064
+ writer,
1933
2065
  };
1934
2066
  stepStartedAt = progress.step("Discovering project sets");
1935
2067
  const executions = await runtime.getProjectSetExecutions(projectConfig, datasource);
@@ -1942,7 +2074,11 @@ export async function exportCatalog(
1942
2074
  const setIndexes: Record<string, CatalogSetIndex> = {};
1943
2075
 
1944
2076
  stepStartedAt = progress.step("Writing project history");
1945
- await writeHistoryPages(path.join(dataDirectoryPath, "project", "history"), historyIndex.entries);
2077
+ await writeHistoryPages(
2078
+ writer,
2079
+ path.join(dataDirectoryPath, "project", "history"),
2080
+ historyIndex.entries,
2081
+ );
1946
2082
  progress.done(stepStartedAt, `(${pluralize(historyIndex.entries.length, "entry", "entries")})`);
1947
2083
 
1948
2084
  for (const execution of executions) {
@@ -1984,7 +2120,7 @@ export async function exportCatalog(
1984
2120
  counts: Object.fromEntries(Object.keys(setIndexes).map((key) => [key, setIndexes[key].counts])),
1985
2121
  };
1986
2122
 
1987
- await writeJson(path.join(dataDirectoryPath, "manifest.json"), manifest);
2123
+ await writer.write(path.join(dataDirectoryPath, "manifest.json"), manifest);
1988
2124
  progress.done(stepStartedAt);
1989
2125
 
1990
2126
  progress.complete();