@messagevisor/catalog 0.6.0 → 0.8.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/dist/assets/index-4rkVIXGk.js +73 -0
- package/dist/index.html +1 -1
- package/lib/node/index.d.ts +53 -0
- package/lib/node/index.js +1019 -396
- package/lib/node/index.js.map +1 -1
- package/package.json +2 -2
- package/src/api.spec.ts +46 -1
- package/src/api.ts +29 -1
- package/src/components/lists/EntityList.tsx +207 -63
- package/src/node/index.spec.ts +235 -4
- package/src/node/index.ts +891 -96
- package/dist/assets/index-DJ8oQlZp.js +0 -73
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}`);
|
|
@@ -335,6 +341,8 @@ export interface CatalogExportOptions {
|
|
|
335
341
|
devEditors?: CatalogDevEditor[];
|
|
336
342
|
withTranslationSearch?: boolean;
|
|
337
343
|
withDuplicates?: boolean;
|
|
344
|
+
devSession?: CatalogDevSession;
|
|
345
|
+
preserveAssets?: boolean;
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
export interface CatalogServeOptions {
|
|
@@ -352,6 +360,7 @@ export interface CatalogServerHandle {
|
|
|
352
360
|
interface CatalogBuildContext {
|
|
353
361
|
rootDirectoryPath: string;
|
|
354
362
|
repositoryRootDirectoryPath: string;
|
|
363
|
+
repositorySourceRootDirectoryPath: string;
|
|
355
364
|
outputDirectoryPath: string;
|
|
356
365
|
dataDirectoryPath: string;
|
|
357
366
|
historyIndex: CatalogHistoryIndex;
|
|
@@ -361,6 +370,23 @@ interface CatalogBuildContext {
|
|
|
361
370
|
withTranslationSearch: boolean;
|
|
362
371
|
withDuplicates: boolean;
|
|
363
372
|
progress: CatalogProgressReporter;
|
|
373
|
+
writer: CatalogJsonWriter;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
interface CatalogDevSession {
|
|
377
|
+
outputDirectoryPath: string;
|
|
378
|
+
devEditors: CatalogDevEditor[];
|
|
379
|
+
historyIndex: CatalogHistoryIndex;
|
|
380
|
+
links: ReturnType<typeof getRepoLinks>;
|
|
381
|
+
repositoryRootDirectoryPath: string;
|
|
382
|
+
repositorySourceRootDirectoryPath: string;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
interface CatalogDevRebuildRequest {
|
|
386
|
+
kind: "full" | "set" | "message";
|
|
387
|
+
reason: string;
|
|
388
|
+
set?: string;
|
|
389
|
+
messageKeys?: string[];
|
|
364
390
|
}
|
|
365
391
|
|
|
366
392
|
interface SourceFileInfo {
|
|
@@ -723,9 +749,41 @@ function toLocaleDuplicatesFile(
|
|
|
723
749
|
};
|
|
724
750
|
}
|
|
725
751
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
752
|
+
class CatalogJsonWriter {
|
|
753
|
+
private readonly directories = new Map<string, Promise<void>>();
|
|
754
|
+
|
|
755
|
+
private ensureDirectory(directoryPath: string) {
|
|
756
|
+
let promise = this.directories.get(directoryPath);
|
|
757
|
+
|
|
758
|
+
if (!promise) {
|
|
759
|
+
promise = fs.promises.mkdir(directoryPath, { recursive: true }).then(() => undefined);
|
|
760
|
+
this.directories.set(directoryPath, promise);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return promise;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async write(filePath: string, content: unknown) {
|
|
767
|
+
await this.ensureDirectory(path.dirname(filePath));
|
|
768
|
+
await fs.promises.writeFile(filePath, JSON.stringify(content, null, 2));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async function mapWithConcurrency<T>(
|
|
773
|
+
items: T[],
|
|
774
|
+
concurrency: number,
|
|
775
|
+
callback: (item: T, index: number) => Promise<void>,
|
|
776
|
+
) {
|
|
777
|
+
let nextIndex = 0;
|
|
778
|
+
const workerCount = Math.min(concurrency, items.length);
|
|
779
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
780
|
+
while (nextIndex < items.length) {
|
|
781
|
+
const index = nextIndex++;
|
|
782
|
+
await callback(items[index], index);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
await Promise.all(workers);
|
|
729
787
|
}
|
|
730
788
|
|
|
731
789
|
function getEntityDirectoryPaths(config: any): Record<CatalogEntityType | "test", string> {
|
|
@@ -870,6 +928,18 @@ function addHistoryIndexEntry(
|
|
|
870
928
|
target[key].push(entry);
|
|
871
929
|
}
|
|
872
930
|
|
|
931
|
+
function toEntityHistoryEntry(
|
|
932
|
+
entry: CatalogHistoryEntry,
|
|
933
|
+
entity: CatalogHistoryEntity,
|
|
934
|
+
): CatalogHistoryEntry {
|
|
935
|
+
return {
|
|
936
|
+
commit: entry.commit,
|
|
937
|
+
author: entry.author,
|
|
938
|
+
timestamp: entry.timestamp,
|
|
939
|
+
entities: [entity],
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
873
943
|
function buildCatalogHistoryIndex(entries: CatalogHistoryEntry[]): CatalogHistoryIndex {
|
|
874
944
|
const index = createEmptyHistoryIndex();
|
|
875
945
|
index.entries = entries;
|
|
@@ -883,7 +953,7 @@ function buildCatalogHistoryIndex(entries: CatalogHistoryEntry[]): CatalogHistor
|
|
|
883
953
|
}
|
|
884
954
|
|
|
885
955
|
const entityKey = getHistoryEntityKey(entity.type, entity.key, entity.set);
|
|
886
|
-
addHistoryIndexEntry(index.byEntity, entityKey, entry);
|
|
956
|
+
addHistoryIndexEntry(index.byEntity, entityKey, toEntityHistoryEntry(entry, entity));
|
|
887
957
|
|
|
888
958
|
if (!index.lastModifiedByEntity[entityKey]) {
|
|
889
959
|
index.lastModifiedByEntity[entityKey] = toLastModified(entry);
|
|
@@ -1129,6 +1199,25 @@ function getRepositoryRootDirectoryPath(rootDirectoryPath: string) {
|
|
|
1129
1199
|
}
|
|
1130
1200
|
}
|
|
1131
1201
|
|
|
1202
|
+
function getRepositorySourceRootDirectoryPath(rootDirectoryPath: string) {
|
|
1203
|
+
try {
|
|
1204
|
+
const gitRootDirectoryPath =
|
|
1205
|
+
runGit(rootDirectoryPath, ["rev-parse", "--show-toplevel"]).trim() || rootDirectoryPath;
|
|
1206
|
+
const realRootDirectoryPath = getRealPath(rootDirectoryPath);
|
|
1207
|
+
|
|
1208
|
+
if (realRootDirectoryPath !== rootDirectoryPath) {
|
|
1209
|
+
return path.resolve(
|
|
1210
|
+
rootDirectoryPath,
|
|
1211
|
+
path.relative(realRootDirectoryPath, gitRootDirectoryPath),
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return gitRootDirectoryPath;
|
|
1216
|
+
} catch (_error) {
|
|
1217
|
+
return rootDirectoryPath;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1132
1221
|
function getOwnerAndRepoFromGitRemote(origin: string, host: string) {
|
|
1133
1222
|
const escapedHost = host.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1134
1223
|
const match = origin.match(new RegExp(`${escapedHost}[:/]([^/]+)/(.+?)(?:\\.git)?$`));
|
|
@@ -1204,17 +1293,28 @@ function chunkHistory(history: CatalogHistoryEntry[], pageSize = CATALOG_HISTORY
|
|
|
1204
1293
|
return pages.length > 0 ? pages : [[]];
|
|
1205
1294
|
}
|
|
1206
1295
|
|
|
1207
|
-
async function writeHistoryPages(
|
|
1296
|
+
async function writeHistoryPages(
|
|
1297
|
+
writer: CatalogJsonWriter,
|
|
1298
|
+
directoryPath: string,
|
|
1299
|
+
history: CatalogHistoryEntry[],
|
|
1300
|
+
options: { skipEmpty?: boolean } = {},
|
|
1301
|
+
) {
|
|
1302
|
+
if (options.skipEmpty && history.length === 0) {
|
|
1303
|
+
return 1;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1208
1306
|
const pages = chunkHistory(history);
|
|
1209
1307
|
|
|
1210
1308
|
for (let index = 0; index < pages.length; index++) {
|
|
1211
|
-
await
|
|
1309
|
+
await writer.write(path.join(directoryPath, `page-${index + 1}.json`), {
|
|
1212
1310
|
page: index + 1,
|
|
1213
1311
|
pageSize: CATALOG_HISTORY_PAGE_SIZE,
|
|
1214
1312
|
totalPages: pages.length,
|
|
1215
1313
|
entries: pages[index],
|
|
1216
1314
|
});
|
|
1217
1315
|
}
|
|
1316
|
+
|
|
1317
|
+
return 0;
|
|
1218
1318
|
}
|
|
1219
1319
|
|
|
1220
1320
|
function getHistoryForEntity(
|
|
@@ -1227,11 +1327,12 @@ function getHistoryForEntity(
|
|
|
1227
1327
|
}
|
|
1228
1328
|
|
|
1229
1329
|
function getSourceFileInfo(
|
|
1230
|
-
|
|
1330
|
+
repositorySourceRootDirectoryPath: string,
|
|
1231
1331
|
rootDirectoryPath: string,
|
|
1232
1332
|
projectConfig: any,
|
|
1233
1333
|
type: CatalogEntityType,
|
|
1234
1334
|
key: string,
|
|
1335
|
+
options: { resolveAbsolutePath?: boolean } = {},
|
|
1235
1336
|
): SourceFileInfo {
|
|
1236
1337
|
const directoryByType: Record<CatalogEntityType, string> = {
|
|
1237
1338
|
locale: projectConfig.localesDirectoryPath,
|
|
@@ -1248,10 +1349,10 @@ function getSourceFileInfo(
|
|
|
1248
1349
|
...key.split(projectConfig.namespaceCharacter),
|
|
1249
1350
|
) + extension,
|
|
1250
1351
|
);
|
|
1251
|
-
const absolutePath = getRealPath(filePath);
|
|
1352
|
+
const absolutePath = options.resolveAbsolutePath ? getRealPath(filePath) : filePath;
|
|
1252
1353
|
|
|
1253
1354
|
return {
|
|
1254
|
-
sourcePath: toPosixPath(path.relative(
|
|
1355
|
+
sourcePath: toPosixPath(path.relative(repositorySourceRootDirectoryPath, filePath)),
|
|
1255
1356
|
absolutePath,
|
|
1256
1357
|
};
|
|
1257
1358
|
}
|
|
@@ -1284,13 +1385,13 @@ async function buildSetCatalog(
|
|
|
1284
1385
|
const outputDirectoryPath = path.join(context.dataDirectoryPath, outputRelativeDirectory);
|
|
1285
1386
|
const setStartedAt = context.progress.setStart(set);
|
|
1286
1387
|
const entitiesStartedAt = context.progress.step("Processing entities");
|
|
1287
|
-
const [localeKeys, messageKeys, attributeKeys, segmentKeys, targetKeys] = await Promise.all([
|
|
1388
|
+
const [localeKeys, messageKeys, attributeKeys, segmentKeys, targetKeys] = (await Promise.all([
|
|
1288
1389
|
datasource.listLocales(),
|
|
1289
1390
|
datasource.listMessages(),
|
|
1290
1391
|
datasource.listAttributes(),
|
|
1291
1392
|
datasource.listSegments(),
|
|
1292
1393
|
datasource.listTargets(),
|
|
1293
|
-
]);
|
|
1394
|
+
])) as [string[], string[], string[], string[], string[]];
|
|
1294
1395
|
const [locales, messages, attributes, segments, targets] = await Promise.all([
|
|
1295
1396
|
readAll<Locale>(localeKeys, (key) => datasource.readLocale(key)),
|
|
1296
1397
|
readAll<Message>(messageKeys, (key) => datasource.readMessage(key)),
|
|
@@ -1440,7 +1541,7 @@ async function buildSetCatalog(
|
|
|
1440
1541
|
};
|
|
1441
1542
|
|
|
1442
1543
|
const historyStartedAt = context.progress.step("Writing history pages");
|
|
1443
|
-
await writeHistoryPages(path.join(outputDirectoryPath, "history"), history);
|
|
1544
|
+
await writeHistoryPages(context.writer, path.join(outputDirectoryPath, "history"), history);
|
|
1444
1545
|
context.progress.done(historyStartedAt, `(${pluralize(history.length, "entry", "entries")})`);
|
|
1445
1546
|
|
|
1446
1547
|
const examplesStartedAt = context.progress.step("Evaluating examples");
|
|
@@ -1492,14 +1593,16 @@ async function buildSetCatalog(
|
|
|
1492
1593
|
);
|
|
1493
1594
|
|
|
1494
1595
|
const localesStartedAt = context.progress.step("Writing locales");
|
|
1495
|
-
|
|
1596
|
+
let skippedEmptyHistoryCount = 0;
|
|
1597
|
+
await mapWithConcurrency(localeKeys, 32, async (localeKey) => {
|
|
1496
1598
|
const locale = locales[localeKey];
|
|
1497
1599
|
const sourceFileInfo = getSourceFileInfo(
|
|
1498
|
-
context.
|
|
1600
|
+
context.repositorySourceRootDirectoryPath,
|
|
1499
1601
|
context.rootDirectoryPath,
|
|
1500
1602
|
projectConfig,
|
|
1501
1603
|
"locale",
|
|
1502
1604
|
localeKey,
|
|
1605
|
+
{ resolveAbsolutePath: context.devEditors.length > 0 },
|
|
1503
1606
|
);
|
|
1504
1607
|
const detail = {
|
|
1505
1608
|
type: "locale",
|
|
@@ -1525,26 +1628,28 @@ async function buildSetCatalog(
|
|
|
1525
1628
|
targets: sortStrings(Array.from(localeTargets[localeKey] || [])),
|
|
1526
1629
|
}),
|
|
1527
1630
|
);
|
|
1528
|
-
await
|
|
1631
|
+
await context.writer.write(
|
|
1529
1632
|
path.join(outputDirectoryPath, "entities", "locale", `${encodeKey(localeKey)}.json`),
|
|
1530
1633
|
detail,
|
|
1531
1634
|
);
|
|
1532
|
-
await writeHistoryPages(
|
|
1635
|
+
skippedEmptyHistoryCount += await writeHistoryPages(
|
|
1636
|
+
context.writer,
|
|
1533
1637
|
path.join(outputDirectoryPath, "history", "locale", encodeKey(localeKey)),
|
|
1534
1638
|
getHistoryForEntity(context.historyIndex, "locale", localeKey, set || undefined),
|
|
1639
|
+
{ skipEmpty: true },
|
|
1535
1640
|
);
|
|
1536
|
-
}
|
|
1641
|
+
});
|
|
1537
1642
|
context.progress.done(localesStartedAt, `(${pluralize(localeKeys.length, "locale")})`);
|
|
1538
1643
|
|
|
1539
1644
|
if (context.withDuplicates) {
|
|
1540
1645
|
const duplicatesStartedAt = context.progress.step("Writing duplicate reports");
|
|
1541
1646
|
|
|
1542
|
-
|
|
1543
|
-
await
|
|
1647
|
+
await mapWithConcurrency(localeKeys, 32, async (localeKey) => {
|
|
1648
|
+
await context.writer.write(
|
|
1544
1649
|
path.join(outputDirectoryPath, "duplicates", "locales", `${encodeKey(localeKey)}.json`),
|
|
1545
1650
|
toLocaleDuplicatesFile(localeKey, duplicatesByLocale),
|
|
1546
1651
|
);
|
|
1547
|
-
}
|
|
1652
|
+
});
|
|
1548
1653
|
|
|
1549
1654
|
context.progress.done(duplicatesStartedAt, `(${pluralize(localeKeys.length, "locale")})`);
|
|
1550
1655
|
}
|
|
@@ -1568,7 +1673,9 @@ async function buildSetCatalog(
|
|
|
1568
1673
|
}
|
|
1569
1674
|
|
|
1570
1675
|
const messagesStartedAt = context.progress.step("Writing messages");
|
|
1571
|
-
|
|
1676
|
+
const messageDetailsStartedAt = context.progress.substep("Writing message details");
|
|
1677
|
+
let skippedEmptyMessageHistoryCount = 0;
|
|
1678
|
+
await mapWithConcurrency(messageKeys, 32, async (messageKey) => {
|
|
1572
1679
|
const message = messages[messageKey];
|
|
1573
1680
|
const overrides = (message.overrides || []).map((override: Override) => {
|
|
1574
1681
|
const attributes = new Set<string>();
|
|
@@ -1583,11 +1690,12 @@ async function buildSetCatalog(
|
|
|
1583
1690
|
};
|
|
1584
1691
|
});
|
|
1585
1692
|
const sourceFileInfo = getSourceFileInfo(
|
|
1586
|
-
context.
|
|
1693
|
+
context.repositorySourceRootDirectoryPath,
|
|
1587
1694
|
context.rootDirectoryPath,
|
|
1588
1695
|
projectConfig,
|
|
1589
1696
|
"message",
|
|
1590
1697
|
messageKey,
|
|
1698
|
+
{ resolveAbsolutePath: context.devEditors.length > 0 },
|
|
1591
1699
|
);
|
|
1592
1700
|
const detail = {
|
|
1593
1701
|
type: "message",
|
|
@@ -1629,16 +1737,40 @@ async function buildSetCatalog(
|
|
|
1629
1737
|
...(overrideLocalesList.length > 0 ? { overrideLocales: overrideLocalesList } : {}),
|
|
1630
1738
|
}),
|
|
1631
1739
|
);
|
|
1632
|
-
await
|
|
1740
|
+
await context.writer.write(
|
|
1633
1741
|
path.join(outputDirectoryPath, "entities", "message", `${encodeKey(messageKey)}.json`),
|
|
1634
1742
|
detail,
|
|
1635
1743
|
);
|
|
1636
|
-
|
|
1744
|
+
});
|
|
1745
|
+
context.progress.done(messageDetailsStartedAt, `(${pluralize(messageKeys.length, "message")})`);
|
|
1746
|
+
|
|
1747
|
+
const messageHistoryStartedAt = context.progress.substep("Writing message history pages");
|
|
1748
|
+
await mapWithConcurrency(messageKeys, 32, async (messageKey) => {
|
|
1749
|
+
const skippedHistory = await writeHistoryPages(
|
|
1750
|
+
context.writer,
|
|
1637
1751
|
path.join(outputDirectoryPath, "history", "message", encodeKey(messageKey)),
|
|
1638
1752
|
getHistoryForEntity(context.historyIndex, "message", messageKey, set || undefined),
|
|
1753
|
+
{ skipEmpty: true },
|
|
1639
1754
|
);
|
|
1640
|
-
|
|
1641
|
-
|
|
1755
|
+
skippedEmptyMessageHistoryCount += skippedHistory;
|
|
1756
|
+
skippedEmptyHistoryCount += skippedHistory;
|
|
1757
|
+
});
|
|
1758
|
+
context.progress.done(
|
|
1759
|
+
messageHistoryStartedAt,
|
|
1760
|
+
`(${pluralize(messageKeys.length, "message")}, ${pluralize(
|
|
1761
|
+
skippedEmptyMessageHistoryCount,
|
|
1762
|
+
"empty history",
|
|
1763
|
+
"empty histories",
|
|
1764
|
+
)} skipped)`,
|
|
1765
|
+
);
|
|
1766
|
+
context.progress.done(
|
|
1767
|
+
messagesStartedAt,
|
|
1768
|
+
`(${pluralize(messageKeys.length, "message")}, ${pluralize(
|
|
1769
|
+
skippedEmptyMessageHistoryCount,
|
|
1770
|
+
"empty history",
|
|
1771
|
+
"empty histories",
|
|
1772
|
+
)} skipped)`,
|
|
1773
|
+
);
|
|
1642
1774
|
|
|
1643
1775
|
if (context.withTranslationSearch) {
|
|
1644
1776
|
const translationSearchStartedAt = context.progress.step("Building translation search shards");
|
|
@@ -1666,7 +1798,10 @@ async function buildSetCatalog(
|
|
|
1666
1798
|
for (const [msgKey, valueSet] of Object.entries(messageMap)) {
|
|
1667
1799
|
shardData[msgKey] = Array.from(valueSet);
|
|
1668
1800
|
}
|
|
1669
|
-
await
|
|
1801
|
+
await context.writer.write(
|
|
1802
|
+
path.join(outputDirectoryPath, "translations", `${prefix}.json`),
|
|
1803
|
+
shardData,
|
|
1804
|
+
);
|
|
1670
1805
|
}
|
|
1671
1806
|
context.progress.done(
|
|
1672
1807
|
translationSearchStartedAt,
|
|
@@ -1675,14 +1810,15 @@ async function buildSetCatalog(
|
|
|
1675
1810
|
}
|
|
1676
1811
|
|
|
1677
1812
|
const attributesStartedAt = context.progress.step("Writing attributes");
|
|
1678
|
-
|
|
1813
|
+
await mapWithConcurrency(attributeKeys, 32, async (attributeKey) => {
|
|
1679
1814
|
const attribute = attributes[attributeKey];
|
|
1680
1815
|
const sourceFileInfo = getSourceFileInfo(
|
|
1681
|
-
context.
|
|
1816
|
+
context.repositorySourceRootDirectoryPath,
|
|
1682
1817
|
context.rootDirectoryPath,
|
|
1683
1818
|
projectConfig,
|
|
1684
1819
|
"attribute",
|
|
1685
1820
|
attributeKey,
|
|
1821
|
+
{ resolveAbsolutePath: context.devEditors.length > 0 },
|
|
1686
1822
|
);
|
|
1687
1823
|
const detail = {
|
|
1688
1824
|
type: "attribute",
|
|
@@ -1714,28 +1850,31 @@ async function buildSetCatalog(
|
|
|
1714
1850
|
},
|
|
1715
1851
|
),
|
|
1716
1852
|
);
|
|
1717
|
-
await
|
|
1853
|
+
await context.writer.write(
|
|
1718
1854
|
path.join(outputDirectoryPath, "entities", "attribute", `${encodeKey(attributeKey)}.json`),
|
|
1719
1855
|
detail,
|
|
1720
1856
|
);
|
|
1721
|
-
await writeHistoryPages(
|
|
1857
|
+
skippedEmptyHistoryCount += await writeHistoryPages(
|
|
1858
|
+
context.writer,
|
|
1722
1859
|
path.join(outputDirectoryPath, "history", "attribute", encodeKey(attributeKey)),
|
|
1723
1860
|
getHistoryForEntity(context.historyIndex, "attribute", attributeKey, set || undefined),
|
|
1861
|
+
{ skipEmpty: true },
|
|
1724
1862
|
);
|
|
1725
|
-
}
|
|
1863
|
+
});
|
|
1726
1864
|
context.progress.done(attributesStartedAt, `(${pluralize(attributeKeys.length, "attribute")})`);
|
|
1727
1865
|
|
|
1728
1866
|
const segmentsStartedAt = context.progress.step("Writing segments");
|
|
1729
|
-
|
|
1867
|
+
await mapWithConcurrency(segmentKeys, 32, async (segmentKey) => {
|
|
1730
1868
|
const segment = segments[segmentKey];
|
|
1731
1869
|
const usedAttributes = new Set<string>();
|
|
1732
1870
|
collectAttributeKeysFromConditions(segment.conditions, usedAttributes);
|
|
1733
1871
|
const sourceFileInfo = getSourceFileInfo(
|
|
1734
|
-
context.
|
|
1872
|
+
context.repositorySourceRootDirectoryPath,
|
|
1735
1873
|
context.rootDirectoryPath,
|
|
1736
1874
|
projectConfig,
|
|
1737
1875
|
"segment",
|
|
1738
1876
|
segmentKey,
|
|
1877
|
+
{ resolveAbsolutePath: context.devEditors.length > 0 },
|
|
1739
1878
|
);
|
|
1740
1879
|
const detail = {
|
|
1741
1880
|
type: "segment",
|
|
@@ -1755,19 +1894,21 @@ async function buildSetCatalog(
|
|
|
1755
1894
|
targets: sortStrings(Array.from(segmentTargets[segmentKey] || [])),
|
|
1756
1895
|
}),
|
|
1757
1896
|
);
|
|
1758
|
-
await
|
|
1897
|
+
await context.writer.write(
|
|
1759
1898
|
path.join(outputDirectoryPath, "entities", "segment", `${encodeKey(segmentKey)}.json`),
|
|
1760
1899
|
detail,
|
|
1761
1900
|
);
|
|
1762
|
-
await writeHistoryPages(
|
|
1901
|
+
skippedEmptyHistoryCount += await writeHistoryPages(
|
|
1902
|
+
context.writer,
|
|
1763
1903
|
path.join(outputDirectoryPath, "history", "segment", encodeKey(segmentKey)),
|
|
1764
1904
|
getHistoryForEntity(context.historyIndex, "segment", segmentKey, set || undefined),
|
|
1905
|
+
{ skipEmpty: true },
|
|
1765
1906
|
);
|
|
1766
|
-
}
|
|
1907
|
+
});
|
|
1767
1908
|
context.progress.done(segmentsStartedAt, `(${pluralize(segmentKeys.length, "segment")})`);
|
|
1768
1909
|
|
|
1769
1910
|
const targetsStartedAt = context.progress.step("Writing targets");
|
|
1770
|
-
|
|
1911
|
+
await mapWithConcurrency(targetKeys, 32, async (targetKey) => {
|
|
1771
1912
|
const target = targets[targetKey];
|
|
1772
1913
|
const targetLocaleKeys = target.locales?.length ? target.locales : localeKeys;
|
|
1773
1914
|
const formatsByLocale: Record<string, FormatPresets | undefined> = {};
|
|
@@ -1779,11 +1920,12 @@ async function buildSetCatalog(
|
|
|
1779
1920
|
}
|
|
1780
1921
|
|
|
1781
1922
|
const sourceFileInfo = getSourceFileInfo(
|
|
1782
|
-
context.
|
|
1923
|
+
context.repositorySourceRootDirectoryPath,
|
|
1783
1924
|
context.rootDirectoryPath,
|
|
1784
1925
|
projectConfig,
|
|
1785
1926
|
"target",
|
|
1786
1927
|
targetKey,
|
|
1928
|
+
{ resolveAbsolutePath: context.devEditors.length > 0 },
|
|
1787
1929
|
);
|
|
1788
1930
|
const detail = {
|
|
1789
1931
|
type: "target",
|
|
@@ -1803,15 +1945,17 @@ async function buildSetCatalog(
|
|
|
1803
1945
|
messageCount: targetMessages[targetKey].length,
|
|
1804
1946
|
}),
|
|
1805
1947
|
);
|
|
1806
|
-
await
|
|
1948
|
+
await context.writer.write(
|
|
1807
1949
|
path.join(outputDirectoryPath, "entities", "target", `${encodeKey(targetKey)}.json`),
|
|
1808
1950
|
detail,
|
|
1809
1951
|
);
|
|
1810
|
-
await writeHistoryPages(
|
|
1952
|
+
skippedEmptyHistoryCount += await writeHistoryPages(
|
|
1953
|
+
context.writer,
|
|
1811
1954
|
path.join(outputDirectoryPath, "history", "target", encodeKey(targetKey)),
|
|
1812
1955
|
getHistoryForEntity(context.historyIndex, "target", targetKey, set || undefined),
|
|
1956
|
+
{ skipEmpty: true },
|
|
1813
1957
|
);
|
|
1814
|
-
}
|
|
1958
|
+
});
|
|
1815
1959
|
context.progress.done(targetsStartedAt, `(${pluralize(targetKeys.length, "target")})`);
|
|
1816
1960
|
|
|
1817
1961
|
const indexStartedAt = context.progress.step("Writing catalog index");
|
|
@@ -1819,9 +1963,12 @@ async function buildSetCatalog(
|
|
|
1819
1963
|
index.entities[type].sort((a, b) => a.key.localeCompare(b.key));
|
|
1820
1964
|
}
|
|
1821
1965
|
|
|
1822
|
-
await
|
|
1966
|
+
await context.writer.write(path.join(outputDirectoryPath, "index.json"), index);
|
|
1823
1967
|
context.progress.done(indexStartedAt);
|
|
1824
|
-
context.progress.done(
|
|
1968
|
+
context.progress.done(
|
|
1969
|
+
setStartedAt,
|
|
1970
|
+
`total (${pluralize(skippedEmptyHistoryCount, "empty history", "empty histories")} skipped)`,
|
|
1971
|
+
);
|
|
1825
1972
|
|
|
1826
1973
|
return index;
|
|
1827
1974
|
}
|
|
@@ -1848,6 +1995,455 @@ async function copyCatalogAssets(outputDirectoryPath: string) {
|
|
|
1848
1995
|
await fs.promises.cp(distPath, outputDirectoryPath, { recursive: true });
|
|
1849
1996
|
}
|
|
1850
1997
|
|
|
1998
|
+
async function createCatalogDevSession(
|
|
1999
|
+
rootDirectoryPath: string,
|
|
2000
|
+
projectConfig: any,
|
|
2001
|
+
options: { outDir?: string; devEditors?: CatalogDevEditor[] } = {},
|
|
2002
|
+
): Promise<CatalogDevSession> {
|
|
2003
|
+
const outputDirectoryPath = options.outDir
|
|
2004
|
+
? path.resolve(rootDirectoryPath, options.outDir)
|
|
2005
|
+
: projectConfig.catalogDirectoryPath;
|
|
2006
|
+
|
|
2007
|
+
return {
|
|
2008
|
+
outputDirectoryPath,
|
|
2009
|
+
devEditors: options.devEditors || detectDevEditors(),
|
|
2010
|
+
historyIndex: await getGitHistoryIndex(rootDirectoryPath, projectConfig),
|
|
2011
|
+
links: getRepoLinks(rootDirectoryPath),
|
|
2012
|
+
repositoryRootDirectoryPath: getRepositoryRootDirectoryPath(rootDirectoryPath),
|
|
2013
|
+
repositorySourceRootDirectoryPath: getRepositorySourceRootDirectoryPath(rootDirectoryPath),
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
async function readJsonFile<T>(filePath: string): Promise<T | undefined> {
|
|
2018
|
+
try {
|
|
2019
|
+
return JSON.parse(await fs.promises.readFile(filePath, "utf8")) as T;
|
|
2020
|
+
} catch (_error) {
|
|
2021
|
+
return undefined;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
function getOutputRelativeDirectory(projectConfig: any, set?: string) {
|
|
2026
|
+
return projectConfig.sets ? path.join("sets", set || "") : "root";
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function getDataOutputDirectoryPath(session: CatalogDevSession, projectConfig: any, set?: string) {
|
|
2030
|
+
return path.join(
|
|
2031
|
+
session.outputDirectoryPath,
|
|
2032
|
+
"data",
|
|
2033
|
+
getOutputRelativeDirectory(projectConfig, set),
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function getEntityKeyFromChangedPath(
|
|
2038
|
+
rootDirectoryPath: string,
|
|
2039
|
+
projectConfig: any,
|
|
2040
|
+
changedPath: string,
|
|
2041
|
+
): EntityPathInfo | undefined {
|
|
2042
|
+
const relativePath = path.relative(rootDirectoryPath, changedPath);
|
|
2043
|
+
|
|
2044
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
2045
|
+
return undefined;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
return getEntityInfoFromRelativePath(rootDirectoryPath, projectConfig, relativePath);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
function getChangedPathSummary(rootDirectoryPath: string, changedPaths: string[]) {
|
|
2052
|
+
return changedPaths
|
|
2053
|
+
.slice(0, 3)
|
|
2054
|
+
.map((changedPath) => formatCatalogPath(rootDirectoryPath, changedPath))
|
|
2055
|
+
.join(", ");
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function classifyCatalogDevChanges(
|
|
2059
|
+
rootDirectoryPath: string,
|
|
2060
|
+
projectConfig: any,
|
|
2061
|
+
changedPaths: string[],
|
|
2062
|
+
options: { withTranslationSearch: boolean; withDuplicates: boolean },
|
|
2063
|
+
): CatalogDevRebuildRequest {
|
|
2064
|
+
const reason = getChangedPathSummary(rootDirectoryPath, changedPaths) || "project changes";
|
|
2065
|
+
const infos = changedPaths.map((changedPath) =>
|
|
2066
|
+
getEntityKeyFromChangedPath(rootDirectoryPath, projectConfig, changedPath),
|
|
2067
|
+
);
|
|
2068
|
+
|
|
2069
|
+
if (infos.length === 0 || infos.some((info) => !info)) {
|
|
2070
|
+
return { kind: "full", reason };
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
const sets = new Set(infos.map((info) => info?.set || ""));
|
|
2074
|
+
const types = new Set(infos.map((info) => info?.type));
|
|
2075
|
+
|
|
2076
|
+
if (sets.size > 1) {
|
|
2077
|
+
return { kind: "full", reason };
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
const set = Array.from(sets)[0] || undefined;
|
|
2081
|
+
|
|
2082
|
+
if (
|
|
2083
|
+
types.size === 1 &&
|
|
2084
|
+
types.has("message") &&
|
|
2085
|
+
!options.withTranslationSearch &&
|
|
2086
|
+
!options.withDuplicates
|
|
2087
|
+
) {
|
|
2088
|
+
return {
|
|
2089
|
+
kind: "message",
|
|
2090
|
+
reason,
|
|
2091
|
+
set,
|
|
2092
|
+
messageKeys: sortStrings(infos.map((info) => info?.key || "").filter(Boolean)),
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
if (
|
|
2097
|
+
projectConfig.sets &&
|
|
2098
|
+
set &&
|
|
2099
|
+
types.size > 0 &&
|
|
2100
|
+
!types.has("test") &&
|
|
2101
|
+
!options.withTranslationSearch &&
|
|
2102
|
+
!options.withDuplicates
|
|
2103
|
+
) {
|
|
2104
|
+
return { kind: "set", reason, set };
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
return { kind: "full", reason };
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
async function writeCatalogManifest(
|
|
2111
|
+
writer: CatalogJsonWriter,
|
|
2112
|
+
rootDirectoryPath: string,
|
|
2113
|
+
projectConfig: any,
|
|
2114
|
+
session: CatalogDevSession,
|
|
2115
|
+
options: {
|
|
2116
|
+
browserRouter: boolean;
|
|
2117
|
+
withTranslationSearch: boolean;
|
|
2118
|
+
withDuplicates: boolean;
|
|
2119
|
+
setIndexes: Record<string, CatalogSetIndex>;
|
|
2120
|
+
executions: Array<{ set: string; projectConfig: any; datasource: any }>;
|
|
2121
|
+
},
|
|
2122
|
+
) {
|
|
2123
|
+
const manifest = {
|
|
2124
|
+
schemaVersion: CATALOG_SCHEMA_VERSION,
|
|
2125
|
+
generatedAt: new Date().toISOString(),
|
|
2126
|
+
router: options.browserRouter === false ? "hash" : "browser",
|
|
2127
|
+
sets: projectConfig.sets,
|
|
2128
|
+
setKeys: projectConfig.sets ? options.executions.map((execution) => execution.set) : [],
|
|
2129
|
+
dev: { editors: session.devEditors },
|
|
2130
|
+
features: {
|
|
2131
|
+
translationSearch: options.withTranslationSearch,
|
|
2132
|
+
duplicates: options.withDuplicates,
|
|
2133
|
+
},
|
|
2134
|
+
links: session.links,
|
|
2135
|
+
paths: {
|
|
2136
|
+
projectHistory: "data/project/history/page-1.json",
|
|
2137
|
+
root: projectConfig.sets ? undefined : "data/root/index.json",
|
|
2138
|
+
sets: projectConfig.sets
|
|
2139
|
+
? Object.fromEntries(
|
|
2140
|
+
options.executions.map((execution) => [
|
|
2141
|
+
execution.set,
|
|
2142
|
+
`data/sets/${encodeURIComponent(execution.set)}/index.json`,
|
|
2143
|
+
]),
|
|
2144
|
+
)
|
|
2145
|
+
: undefined,
|
|
2146
|
+
},
|
|
2147
|
+
counts: Object.fromEntries(
|
|
2148
|
+
Object.keys(options.setIndexes).map((key) => [key, options.setIndexes[key].counts]),
|
|
2149
|
+
),
|
|
2150
|
+
};
|
|
2151
|
+
|
|
2152
|
+
await writer.write(path.join(session.outputDirectoryPath, "data", "manifest.json"), manifest);
|
|
2153
|
+
return manifest;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function getMessageRelationshipFingerprint(message: Message) {
|
|
2157
|
+
const attributes = new Set<string>();
|
|
2158
|
+
const segments = new Set<string>();
|
|
2159
|
+
|
|
2160
|
+
for (const override of message.overrides || []) {
|
|
2161
|
+
collectAttributeKeysFromConditions(override.conditions, attributes);
|
|
2162
|
+
collectSegmentKeys(override.segments, segments);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
return {
|
|
2166
|
+
attributes: sortStrings(Array.from(attributes)),
|
|
2167
|
+
segments: sortStrings(Array.from(segments)),
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
function sameStringList(left: string[] = [], right: string[] = []) {
|
|
2172
|
+
if (left.length !== right.length) {
|
|
2173
|
+
return false;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
return left.every((value, index) => value === right[index]);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function summarizeMessage(
|
|
2180
|
+
message: Message,
|
|
2181
|
+
messageKey: string,
|
|
2182
|
+
historyIndex: CatalogHistoryIndex,
|
|
2183
|
+
set: string | undefined,
|
|
2184
|
+
targets: string[],
|
|
2185
|
+
) {
|
|
2186
|
+
const directLocales = Object.keys(message.translations || {});
|
|
2187
|
+
const overrideLocalesSet = new Set<string>();
|
|
2188
|
+
|
|
2189
|
+
for (const override of message.overrides || []) {
|
|
2190
|
+
for (const localeKey of Object.keys(override.translations || {})) {
|
|
2191
|
+
overrideLocalesSet.add(localeKey);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
const overrideLocales = sortStrings(Array.from(overrideLocalesSet));
|
|
2196
|
+
|
|
2197
|
+
return getEntitySummary(message, "message", messageKey, historyIndex, set, {
|
|
2198
|
+
targets,
|
|
2199
|
+
...(directLocales.length > 0 ? { locales: sortStrings(directLocales) } : {}),
|
|
2200
|
+
...(overrideLocales.length > 0 ? { overrideLocales } : {}),
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
async function tryRebuildCatalogMessage(
|
|
2205
|
+
runtime: CatalogRuntime,
|
|
2206
|
+
rootDirectoryPath: string,
|
|
2207
|
+
rootProjectConfig: any,
|
|
2208
|
+
projectConfig: any,
|
|
2209
|
+
datasource: any,
|
|
2210
|
+
session: CatalogDevSession,
|
|
2211
|
+
request: CatalogDevRebuildRequest,
|
|
2212
|
+
) {
|
|
2213
|
+
if (request.kind !== "message" || !request.messageKeys || request.messageKeys.length === 0) {
|
|
2214
|
+
return false;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
const dataDirectoryPath = getDataOutputDirectoryPath(session, rootProjectConfig, request.set);
|
|
2218
|
+
const indexPath = path.join(dataDirectoryPath, "index.json");
|
|
2219
|
+
const index = await readJsonFile<CatalogSetIndex>(indexPath);
|
|
2220
|
+
|
|
2221
|
+
if (!index) {
|
|
2222
|
+
return false;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
const [localeKeys, messageKeys, targetKeys] = (await Promise.all([
|
|
2226
|
+
datasource.listLocales(),
|
|
2227
|
+
datasource.listMessages(),
|
|
2228
|
+
datasource.listTargets(),
|
|
2229
|
+
])) as [string[], string[], string[]];
|
|
2230
|
+
const messageKeySet = new Set(messageKeys);
|
|
2231
|
+
|
|
2232
|
+
if (request.messageKeys.some((messageKey) => !messageKeySet.has(messageKey))) {
|
|
2233
|
+
return false;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
const [locales, targets] = await Promise.all([
|
|
2237
|
+
readAll<Locale>(localeKeys, (key) => datasource.readLocale(key)),
|
|
2238
|
+
readAll<Target>(targetKeys, (key) => datasource.readTarget(key)),
|
|
2239
|
+
]);
|
|
2240
|
+
const localeDirections = getLocaleDirections(locales);
|
|
2241
|
+
const targetMessages = Object.fromEntries(
|
|
2242
|
+
targetKeys.map((targetKey) => [
|
|
2243
|
+
targetKey,
|
|
2244
|
+
getTargetMessageKeys(targets[targetKey], messageKeys),
|
|
2245
|
+
]),
|
|
2246
|
+
) as Record<string, string[]>;
|
|
2247
|
+
const writer = new CatalogJsonWriter();
|
|
2248
|
+
|
|
2249
|
+
for (const messageKey of request.messageKeys) {
|
|
2250
|
+
const oldDetailPath = path.join(
|
|
2251
|
+
dataDirectoryPath,
|
|
2252
|
+
"entities",
|
|
2253
|
+
"message",
|
|
2254
|
+
`${encodeKey(messageKey)}.json`,
|
|
2255
|
+
);
|
|
2256
|
+
const oldDetail = await readJsonFile<any>(oldDetailPath);
|
|
2257
|
+
|
|
2258
|
+
if (!oldDetail) {
|
|
2259
|
+
return false;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
const message = await datasource.readMessage(messageKey);
|
|
2263
|
+
const messageTargets = sortStrings(
|
|
2264
|
+
targetKeys.filter((targetKey) => targetMessages[targetKey].includes(messageKey)),
|
|
2265
|
+
);
|
|
2266
|
+
|
|
2267
|
+
if (!sameStringList(sortStrings(oldDetail.targets || []), messageTargets)) {
|
|
2268
|
+
return false;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
const oldRelationshipFingerprint = getMessageRelationshipFingerprint(oldDetail.entity || {});
|
|
2272
|
+
const nextRelationshipFingerprint = getMessageRelationshipFingerprint(message);
|
|
2273
|
+
|
|
2274
|
+
if (
|
|
2275
|
+
!sameStringList(
|
|
2276
|
+
oldRelationshipFingerprint.attributes,
|
|
2277
|
+
nextRelationshipFingerprint.attributes,
|
|
2278
|
+
) ||
|
|
2279
|
+
!sameStringList(oldRelationshipFingerprint.segments, nextRelationshipFingerprint.segments)
|
|
2280
|
+
) {
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
const examples = await runtime.resolveExamples(projectConfig, datasource, {
|
|
2285
|
+
set: request.set,
|
|
2286
|
+
message: messageKey,
|
|
2287
|
+
onlyMessages: true,
|
|
2288
|
+
});
|
|
2289
|
+
const overrides = (message.overrides || []).map((override: Override) => {
|
|
2290
|
+
const attributes = new Set<string>();
|
|
2291
|
+
const overrideSegments = new Set<string>();
|
|
2292
|
+
collectAttributeKeysFromConditions(override.conditions, attributes);
|
|
2293
|
+
collectSegmentKeys(override.segments, overrideSegments);
|
|
2294
|
+
|
|
2295
|
+
return {
|
|
2296
|
+
...override,
|
|
2297
|
+
usedAttributes: sortStrings(Array.from(attributes)),
|
|
2298
|
+
usedSegments: sortStrings(Array.from(overrideSegments)),
|
|
2299
|
+
};
|
|
2300
|
+
});
|
|
2301
|
+
const sourceFileInfo = getSourceFileInfo(
|
|
2302
|
+
session.repositorySourceRootDirectoryPath,
|
|
2303
|
+
rootDirectoryPath,
|
|
2304
|
+
projectConfig,
|
|
2305
|
+
"message",
|
|
2306
|
+
messageKey,
|
|
2307
|
+
{ resolveAbsolutePath: session.devEditors.length > 0 },
|
|
2308
|
+
);
|
|
2309
|
+
const detail = {
|
|
2310
|
+
type: "message",
|
|
2311
|
+
key: messageKey,
|
|
2312
|
+
entity: { ...message, overrides },
|
|
2313
|
+
sourcePath: sourceFileInfo.sourcePath,
|
|
2314
|
+
editLinks: getEditorLinks(session.devEditors, sourceFileInfo),
|
|
2315
|
+
targets: messageTargets,
|
|
2316
|
+
localeKeys,
|
|
2317
|
+
localeDirections,
|
|
2318
|
+
translations: localeKeys.map((localeKey) =>
|
|
2319
|
+
resolveTranslationRow(message.translations, localeKey, locales),
|
|
2320
|
+
),
|
|
2321
|
+
evaluatedExamples: examples.messages,
|
|
2322
|
+
overrideTranslations: overrides.map((override) => ({
|
|
2323
|
+
key: override.key,
|
|
2324
|
+
rows: localeKeys.map((localeKey) =>
|
|
2325
|
+
resolveTranslationRow(override.translations, localeKey, locales),
|
|
2326
|
+
),
|
|
2327
|
+
})),
|
|
2328
|
+
lastModified: getLastModified(session.historyIndex, "message", messageKey, request.set),
|
|
2329
|
+
};
|
|
2330
|
+
|
|
2331
|
+
await writer.write(oldDetailPath, detail);
|
|
2332
|
+
|
|
2333
|
+
await writeHistoryPages(
|
|
2334
|
+
writer,
|
|
2335
|
+
path.join(dataDirectoryPath, "history", "message", encodeKey(messageKey)),
|
|
2336
|
+
getHistoryForEntity(session.historyIndex, "message", messageKey, request.set),
|
|
2337
|
+
{ skipEmpty: true },
|
|
2338
|
+
);
|
|
2339
|
+
|
|
2340
|
+
const nextSummary = summarizeMessage(
|
|
2341
|
+
message,
|
|
2342
|
+
messageKey,
|
|
2343
|
+
session.historyIndex,
|
|
2344
|
+
request.set,
|
|
2345
|
+
messageTargets,
|
|
2346
|
+
);
|
|
2347
|
+
const existingSummaryIndex = index.entities.message.findIndex(
|
|
2348
|
+
(entry) => entry.key === messageKey,
|
|
2349
|
+
);
|
|
2350
|
+
|
|
2351
|
+
if (existingSummaryIndex === -1) {
|
|
2352
|
+
index.entities.message.push(nextSummary);
|
|
2353
|
+
} else {
|
|
2354
|
+
index.entities.message[existingSummaryIndex] = nextSummary;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
index.entities.message.sort((left, right) => left.key.localeCompare(right.key));
|
|
2359
|
+
index.counts.message = messageKeys.length;
|
|
2360
|
+
await writer.write(indexPath, index);
|
|
2361
|
+
|
|
2362
|
+
return true;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
async function rebuildCatalogSetForDev(
|
|
2366
|
+
runtime: CatalogRuntime,
|
|
2367
|
+
rootDirectoryPath: string,
|
|
2368
|
+
projectConfig: any,
|
|
2369
|
+
datasource: any,
|
|
2370
|
+
session: CatalogDevSession,
|
|
2371
|
+
options: {
|
|
2372
|
+
set?: string;
|
|
2373
|
+
browserRouter: boolean;
|
|
2374
|
+
withTranslationSearch: boolean;
|
|
2375
|
+
withDuplicates: boolean;
|
|
2376
|
+
},
|
|
2377
|
+
) {
|
|
2378
|
+
const writer = new CatalogJsonWriter();
|
|
2379
|
+
const progress = new CatalogProgressReporter(rootDirectoryPath, session.outputDirectoryPath);
|
|
2380
|
+
const executions = await runtime.getProjectSetExecutions(projectConfig, datasource);
|
|
2381
|
+
const setIndexes: Record<string, CatalogSetIndex> = {};
|
|
2382
|
+
const existingIndexes = await Promise.all(
|
|
2383
|
+
executions.map(async (execution) => {
|
|
2384
|
+
const indexPath = path.join(
|
|
2385
|
+
session.outputDirectoryPath,
|
|
2386
|
+
"data",
|
|
2387
|
+
getOutputRelativeDirectory(projectConfig, execution.set),
|
|
2388
|
+
"index.json",
|
|
2389
|
+
);
|
|
2390
|
+
return [execution.set || "root", await readJsonFile<CatalogSetIndex>(indexPath)] as const;
|
|
2391
|
+
}),
|
|
2392
|
+
);
|
|
2393
|
+
|
|
2394
|
+
for (const [key, index] of existingIndexes) {
|
|
2395
|
+
if (index) {
|
|
2396
|
+
setIndexes[key] = index;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
const execution = executions.find((item) => (item.set || undefined) === options.set);
|
|
2401
|
+
|
|
2402
|
+
if (!execution) {
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const outputRelativeDirectory = getOutputRelativeDirectory(projectConfig, execution.set);
|
|
2407
|
+
await fs.promises.rm(path.join(session.outputDirectoryPath, "data", outputRelativeDirectory), {
|
|
2408
|
+
recursive: true,
|
|
2409
|
+
force: true,
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
const context: CatalogBuildContext = {
|
|
2413
|
+
rootDirectoryPath,
|
|
2414
|
+
repositoryRootDirectoryPath: session.repositoryRootDirectoryPath,
|
|
2415
|
+
repositorySourceRootDirectoryPath: session.repositorySourceRootDirectoryPath,
|
|
2416
|
+
outputDirectoryPath: session.outputDirectoryPath,
|
|
2417
|
+
dataDirectoryPath: path.join(session.outputDirectoryPath, "data"),
|
|
2418
|
+
historyIndex: session.historyIndex,
|
|
2419
|
+
runtime,
|
|
2420
|
+
devEditors: session.devEditors,
|
|
2421
|
+
duplicateResultsBySet: {},
|
|
2422
|
+
withTranslationSearch: options.withTranslationSearch,
|
|
2423
|
+
withDuplicates: options.withDuplicates,
|
|
2424
|
+
progress,
|
|
2425
|
+
writer,
|
|
2426
|
+
};
|
|
2427
|
+
|
|
2428
|
+
setIndexes[execution.set || "root"] = await buildSetCatalog(
|
|
2429
|
+
context,
|
|
2430
|
+
execution.set,
|
|
2431
|
+
execution.projectConfig,
|
|
2432
|
+
execution.datasource,
|
|
2433
|
+
outputRelativeDirectory,
|
|
2434
|
+
);
|
|
2435
|
+
|
|
2436
|
+
await writeCatalogManifest(writer, rootDirectoryPath, projectConfig, session, {
|
|
2437
|
+
browserRouter: options.browserRouter,
|
|
2438
|
+
withTranslationSearch: options.withTranslationSearch,
|
|
2439
|
+
withDuplicates: options.withDuplicates,
|
|
2440
|
+
setIndexes,
|
|
2441
|
+
executions,
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2444
|
+
return true;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
1851
2447
|
export async function exportCatalog(
|
|
1852
2448
|
runtime: CatalogRuntime,
|
|
1853
2449
|
rootDirectoryPath: string,
|
|
@@ -1862,6 +2458,7 @@ export async function exportCatalog(
|
|
|
1862
2458
|
const withTranslationSearch = options.withTranslationSearch === true;
|
|
1863
2459
|
const withDuplicates = options.withDuplicates === true;
|
|
1864
2460
|
const progress = new CatalogProgressReporter(rootDirectoryPath, outputDirectoryPath);
|
|
2461
|
+
const writer = new CatalogJsonWriter();
|
|
1865
2462
|
|
|
1866
2463
|
progress.start({
|
|
1867
2464
|
browserRouter: options.browserRouter !== false,
|
|
@@ -1873,7 +2470,11 @@ export async function exportCatalog(
|
|
|
1873
2470
|
});
|
|
1874
2471
|
|
|
1875
2472
|
let stepStartedAt = progress.step("Preparing output directory");
|
|
1876
|
-
|
|
2473
|
+
if (options.preserveAssets) {
|
|
2474
|
+
await fs.promises.rm(dataDirectoryPath, { recursive: true, force: true });
|
|
2475
|
+
} else {
|
|
2476
|
+
await fs.promises.rm(outputDirectoryPath, { recursive: true, force: true });
|
|
2477
|
+
}
|
|
1877
2478
|
await fs.promises.mkdir(dataDirectoryPath, { recursive: true });
|
|
1878
2479
|
progress.done(stepStartedAt);
|
|
1879
2480
|
|
|
@@ -1883,13 +2484,17 @@ export async function exportCatalog(
|
|
|
1883
2484
|
progress.done(stepStartedAt);
|
|
1884
2485
|
}
|
|
1885
2486
|
|
|
1886
|
-
const devEditors = options.dev
|
|
2487
|
+
const devEditors = options.dev
|
|
2488
|
+
? options.devSession?.devEditors || options.devEditors || detectDevEditors()
|
|
2489
|
+
: [];
|
|
1887
2490
|
stepStartedAt = progress.step("Reading Git history");
|
|
1888
|
-
const historyIndex =
|
|
2491
|
+
const historyIndex =
|
|
2492
|
+
options.devSession?.historyIndex ||
|
|
2493
|
+
(await getGitHistoryIndex(rootDirectoryPath, projectConfig));
|
|
1889
2494
|
progress.done(stepStartedAt, `(${pluralize(historyIndex.entries.length, "commit")})`);
|
|
1890
2495
|
|
|
1891
2496
|
stepStartedAt = progress.step("Resolving repository links");
|
|
1892
|
-
const links = getRepoLinks(rootDirectoryPath);
|
|
2497
|
+
const links = options.devSession?.links || getRepoLinks(rootDirectoryPath);
|
|
1893
2498
|
progress.done(stepStartedAt);
|
|
1894
2499
|
|
|
1895
2500
|
let duplicateResultsBySet: Record<string, CatalogDuplicateTranslationsSetResult> = {};
|
|
@@ -1920,7 +2525,12 @@ export async function exportCatalog(
|
|
|
1920
2525
|
|
|
1921
2526
|
const context: CatalogBuildContext = {
|
|
1922
2527
|
rootDirectoryPath,
|
|
1923
|
-
repositoryRootDirectoryPath:
|
|
2528
|
+
repositoryRootDirectoryPath:
|
|
2529
|
+
options.devSession?.repositoryRootDirectoryPath ||
|
|
2530
|
+
getRepositoryRootDirectoryPath(rootDirectoryPath),
|
|
2531
|
+
repositorySourceRootDirectoryPath:
|
|
2532
|
+
options.devSession?.repositorySourceRootDirectoryPath ||
|
|
2533
|
+
getRepositorySourceRootDirectoryPath(rootDirectoryPath),
|
|
1924
2534
|
outputDirectoryPath,
|
|
1925
2535
|
dataDirectoryPath,
|
|
1926
2536
|
historyIndex,
|
|
@@ -1930,6 +2540,7 @@ export async function exportCatalog(
|
|
|
1930
2540
|
withTranslationSearch,
|
|
1931
2541
|
withDuplicates,
|
|
1932
2542
|
progress,
|
|
2543
|
+
writer,
|
|
1933
2544
|
};
|
|
1934
2545
|
stepStartedAt = progress.step("Discovering project sets");
|
|
1935
2546
|
const executions = await runtime.getProjectSetExecutions(projectConfig, datasource);
|
|
@@ -1942,7 +2553,11 @@ export async function exportCatalog(
|
|
|
1942
2553
|
const setIndexes: Record<string, CatalogSetIndex> = {};
|
|
1943
2554
|
|
|
1944
2555
|
stepStartedAt = progress.step("Writing project history");
|
|
1945
|
-
await writeHistoryPages(
|
|
2556
|
+
await writeHistoryPages(
|
|
2557
|
+
writer,
|
|
2558
|
+
path.join(dataDirectoryPath, "project", "history"),
|
|
2559
|
+
historyIndex.entries,
|
|
2560
|
+
);
|
|
1946
2561
|
progress.done(stepStartedAt, `(${pluralize(historyIndex.entries.length, "entry", "entries")})`);
|
|
1947
2562
|
|
|
1948
2563
|
for (const execution of executions) {
|
|
@@ -1984,7 +2599,7 @@ export async function exportCatalog(
|
|
|
1984
2599
|
counts: Object.fromEntries(Object.keys(setIndexes).map((key) => [key, setIndexes[key].counts])),
|
|
1985
2600
|
};
|
|
1986
2601
|
|
|
1987
|
-
await
|
|
2602
|
+
await writer.write(path.join(dataDirectoryPath, "manifest.json"), manifest);
|
|
1988
2603
|
progress.done(stepStartedAt);
|
|
1989
2604
|
|
|
1990
2605
|
progress.complete();
|
|
@@ -2041,11 +2656,34 @@ function injectCatalogLiveReloadClient(html: string) {
|
|
|
2041
2656
|
return `${html}${script}`;
|
|
2042
2657
|
}
|
|
2043
2658
|
|
|
2044
|
-
function
|
|
2659
|
+
function getCatalogInputWatchPaths(rootDirectoryPath: string, projectConfig: any) {
|
|
2660
|
+
const paths = [path.join(rootDirectoryPath, "messagevisor.config.js")];
|
|
2661
|
+
|
|
2662
|
+
if (projectConfig.sets) {
|
|
2663
|
+
paths.push(projectConfig.setsDirectoryPath);
|
|
2664
|
+
return paths;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
paths.push(
|
|
2668
|
+
projectConfig.localesDirectoryPath,
|
|
2669
|
+
projectConfig.messagesDirectoryPath,
|
|
2670
|
+
projectConfig.attributesDirectoryPath,
|
|
2671
|
+
projectConfig.segmentsDirectoryPath,
|
|
2672
|
+
projectConfig.targetsDirectoryPath,
|
|
2673
|
+
projectConfig.testsDirectoryPath,
|
|
2674
|
+
);
|
|
2675
|
+
|
|
2676
|
+
return paths.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
function createCatalogInputWatcher(
|
|
2045
2680
|
rootDirectoryPath: string,
|
|
2681
|
+
projectConfig: any,
|
|
2046
2682
|
ignoredDirectoryPaths: string[],
|
|
2047
|
-
onChange: (
|
|
2683
|
+
onChange: (changedPaths: string[]) => void,
|
|
2048
2684
|
) {
|
|
2685
|
+
const watchPaths = getCatalogInputWatchPaths(rootDirectoryPath, projectConfig);
|
|
2686
|
+
|
|
2049
2687
|
function shouldIgnore(targetPath: string) {
|
|
2050
2688
|
const resolvedTargetPath = path.resolve(targetPath);
|
|
2051
2689
|
|
|
@@ -2059,7 +2697,24 @@ function createProjectWatcher(
|
|
|
2059
2697
|
});
|
|
2060
2698
|
}
|
|
2061
2699
|
|
|
2062
|
-
function
|
|
2700
|
+
function shouldWatch(targetPath: string) {
|
|
2701
|
+
const resolvedTargetPath = path.resolve(targetPath);
|
|
2702
|
+
|
|
2703
|
+
if (shouldIgnore(resolvedTargetPath)) {
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
return watchPaths.some((watchPath) => {
|
|
2708
|
+
const resolvedWatchPath = path.resolve(watchPath);
|
|
2709
|
+
|
|
2710
|
+
return (
|
|
2711
|
+
resolvedTargetPath === resolvedWatchPath ||
|
|
2712
|
+
resolvedTargetPath.startsWith(`${resolvedWatchPath}${path.sep}`)
|
|
2713
|
+
);
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function collectSnapshotEntries(directoryPath: string, snapshotEntries: Map<string, string>) {
|
|
2063
2718
|
if (shouldIgnore(directoryPath)) {
|
|
2064
2719
|
return;
|
|
2065
2720
|
}
|
|
@@ -2090,8 +2745,7 @@ function createProjectWatcher(
|
|
|
2090
2745
|
|
|
2091
2746
|
try {
|
|
2092
2747
|
const stat = fs.statSync(entryPath);
|
|
2093
|
-
|
|
2094
|
-
snapshotEntries.push(`${relativePath}:${stat.size}:${stat.mtimeMs}`);
|
|
2748
|
+
snapshotEntries.set(entryPath, `${stat.size}:${stat.mtimeMs}`);
|
|
2095
2749
|
} catch {
|
|
2096
2750
|
// Ignore transient editor save races.
|
|
2097
2751
|
}
|
|
@@ -2099,26 +2753,108 @@ function createProjectWatcher(
|
|
|
2099
2753
|
}
|
|
2100
2754
|
|
|
2101
2755
|
function createSnapshot() {
|
|
2102
|
-
const snapshotEntries
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2756
|
+
const snapshotEntries = new Map<string, string>();
|
|
2757
|
+
|
|
2758
|
+
for (const watchPath of watchPaths) {
|
|
2759
|
+
if (!fs.existsSync(watchPath)) {
|
|
2760
|
+
continue;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
const stat = fs.statSync(watchPath);
|
|
2764
|
+
|
|
2765
|
+
if (stat.isFile()) {
|
|
2766
|
+
snapshotEntries.set(watchPath, `${stat.size}:${stat.mtimeMs}`);
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
collectSnapshotEntries(watchPath, snapshotEntries);
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
return snapshotEntries;
|
|
2106
2774
|
}
|
|
2107
2775
|
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
const nextSnapshot = createSnapshot();
|
|
2776
|
+
function getSnapshotChanges(previous: Map<string, string>, next: Map<string, string>) {
|
|
2777
|
+
const changedPaths = new Set<string>();
|
|
2111
2778
|
|
|
2112
|
-
|
|
2113
|
-
|
|
2779
|
+
for (const [filePath, signature] of Array.from(next.entries())) {
|
|
2780
|
+
if (previous.get(filePath) !== signature) {
|
|
2781
|
+
changedPaths.add(filePath);
|
|
2782
|
+
}
|
|
2114
2783
|
}
|
|
2115
2784
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2785
|
+
for (const filePath of Array.from(previous.keys())) {
|
|
2786
|
+
if (!next.has(filePath)) {
|
|
2787
|
+
changedPaths.add(filePath);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
return Array.from(changedPaths);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
function createPollingWatcher() {
|
|
2795
|
+
let previousSnapshot = createSnapshot();
|
|
2796
|
+
const interval = setInterval(() => {
|
|
2797
|
+
const nextSnapshot = createSnapshot();
|
|
2798
|
+
const changedPaths = getSnapshotChanges(previousSnapshot, nextSnapshot).filter(shouldWatch);
|
|
2799
|
+
|
|
2800
|
+
previousSnapshot = nextSnapshot;
|
|
2801
|
+
|
|
2802
|
+
if (changedPaths.length === 0) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
onChange(changedPaths);
|
|
2807
|
+
}, 1000);
|
|
2808
|
+
|
|
2809
|
+
return () => {
|
|
2810
|
+
clearInterval(interval);
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
const watchers: fs.FSWatcher[] = [];
|
|
2815
|
+
let nativeWatcherFailed = false;
|
|
2816
|
+
|
|
2817
|
+
for (const watchPath of watchPaths) {
|
|
2818
|
+
if (!fs.existsSync(watchPath)) {
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
try {
|
|
2823
|
+
const stat = fs.statSync(watchPath);
|
|
2824
|
+
const directoryPath = stat.isDirectory() ? watchPath : path.dirname(watchPath);
|
|
2825
|
+
const watcher = fs.watch(
|
|
2826
|
+
directoryPath,
|
|
2827
|
+
{ recursive: stat.isDirectory() },
|
|
2828
|
+
(_eventType, filename) => {
|
|
2829
|
+
const changedPath = filename
|
|
2830
|
+
? path.resolve(directoryPath, filename.toString())
|
|
2831
|
+
: directoryPath;
|
|
2832
|
+
|
|
2833
|
+
if (shouldWatch(changedPath)) {
|
|
2834
|
+
onChange([changedPath]);
|
|
2835
|
+
}
|
|
2836
|
+
},
|
|
2837
|
+
);
|
|
2838
|
+
|
|
2839
|
+
watchers.push(watcher);
|
|
2840
|
+
} catch (_error) {
|
|
2841
|
+
nativeWatcherFailed = true;
|
|
2842
|
+
break;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
if (nativeWatcherFailed || watchers.length === 0) {
|
|
2847
|
+
for (const watcher of watchers) {
|
|
2848
|
+
watcher.close();
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
return createPollingWatcher();
|
|
2852
|
+
}
|
|
2119
2853
|
|
|
2120
2854
|
return () => {
|
|
2121
|
-
|
|
2855
|
+
for (const watcher of watchers) {
|
|
2856
|
+
watcher.close();
|
|
2857
|
+
}
|
|
2122
2858
|
};
|
|
2123
2859
|
}
|
|
2124
2860
|
|
|
@@ -2269,6 +3005,11 @@ function isWithDuplicatesEnabled(parsed: CatalogPluginParsedOptions) {
|
|
|
2269
3005
|
return parsed.withDuplicates === true || parsed["with-duplicates"] === true;
|
|
2270
3006
|
}
|
|
2271
3007
|
|
|
3008
|
+
export const __catalogDevInternals = {
|
|
3009
|
+
classifyCatalogDevChanges,
|
|
3010
|
+
getCatalogInputWatchPaths,
|
|
3011
|
+
};
|
|
3012
|
+
|
|
2272
3013
|
export function createCatalogPlugin(
|
|
2273
3014
|
runtime: CatalogRuntime,
|
|
2274
3015
|
api: ReturnType<typeof createCatalogApi> = createCatalogApi(runtime),
|
|
@@ -2282,11 +3023,18 @@ export function createCatalogPlugin(
|
|
|
2282
3023
|
const withDuplicates = isWithDuplicatesEnabled(parsed);
|
|
2283
3024
|
|
|
2284
3025
|
if (!parsed.subcommand) {
|
|
3026
|
+
const outputDirectoryPath = parsed.outDir
|
|
3027
|
+
? path.resolve(rootDirectoryPath, parsed.outDir)
|
|
3028
|
+
: projectConfig.catalogDirectoryPath;
|
|
3029
|
+
const devSession = await createCatalogDevSession(rootDirectoryPath, projectConfig, {
|
|
3030
|
+
outDir: parsed.outDir,
|
|
3031
|
+
});
|
|
2285
3032
|
await api.exportCatalog(rootDirectoryPath, projectConfig, datasource, {
|
|
2286
3033
|
outDir: parsed.outDir,
|
|
2287
3034
|
copyAssets: !parsed.noAssets,
|
|
2288
3035
|
browserRouter,
|
|
2289
3036
|
dev: true,
|
|
3037
|
+
devSession,
|
|
2290
3038
|
withTranslationSearch,
|
|
2291
3039
|
withDuplicates,
|
|
2292
3040
|
});
|
|
@@ -2297,9 +3045,6 @@ export function createCatalogPlugin(
|
|
|
2297
3045
|
liveReload: true,
|
|
2298
3046
|
});
|
|
2299
3047
|
|
|
2300
|
-
const outputDirectoryPath = parsed.outDir
|
|
2301
|
-
? path.resolve(rootDirectoryPath, parsed.outDir)
|
|
2302
|
-
: projectConfig.catalogDirectoryPath;
|
|
2303
3048
|
const ignoredDirectoryPaths = [
|
|
2304
3049
|
path.join(rootDirectoryPath, ".git"),
|
|
2305
3050
|
path.join(rootDirectoryPath, "node_modules"),
|
|
@@ -2311,29 +3056,77 @@ export function createCatalogPlugin(
|
|
|
2311
3056
|
outputDirectoryPath,
|
|
2312
3057
|
];
|
|
2313
3058
|
let exportInFlight = false;
|
|
2314
|
-
let
|
|
2315
|
-
let
|
|
3059
|
+
let queuedChanges: string[] = [];
|
|
3060
|
+
let pendingChanges: string[] = [];
|
|
2316
3061
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
2317
3062
|
|
|
2318
|
-
const
|
|
3063
|
+
const runRebuildAndReload = async (changedPaths: string[]) => {
|
|
2319
3064
|
if (exportInFlight) {
|
|
2320
|
-
|
|
2321
|
-
queuedReason = queuedReason || reason;
|
|
3065
|
+
queuedChanges.push(...changedPaths);
|
|
2322
3066
|
return;
|
|
2323
3067
|
}
|
|
2324
3068
|
|
|
2325
3069
|
exportInFlight = true;
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
copyAssets: !parsed.noAssets,
|
|
2332
|
-
browserRouter,
|
|
2333
|
-
dev: true,
|
|
3070
|
+
const request = classifyCatalogDevChanges(
|
|
3071
|
+
rootDirectoryPath,
|
|
3072
|
+
projectConfig,
|
|
3073
|
+
changedPaths,
|
|
3074
|
+
{
|
|
2334
3075
|
withTranslationSearch,
|
|
2335
3076
|
withDuplicates,
|
|
2336
|
-
}
|
|
3077
|
+
},
|
|
3078
|
+
);
|
|
3079
|
+
console.log(`\n[catalog] Rebuilding (${request.kind}) because ${request.reason}`);
|
|
3080
|
+
|
|
3081
|
+
try {
|
|
3082
|
+
let handled = false;
|
|
3083
|
+
|
|
3084
|
+
if (request.kind === "message") {
|
|
3085
|
+
const [execution] = await runtime.getProjectSetExecutions(
|
|
3086
|
+
projectConfig,
|
|
3087
|
+
datasource,
|
|
3088
|
+
request.set,
|
|
3089
|
+
);
|
|
3090
|
+
handled = await tryRebuildCatalogMessage(
|
|
3091
|
+
runtime,
|
|
3092
|
+
rootDirectoryPath,
|
|
3093
|
+
projectConfig,
|
|
3094
|
+
execution.projectConfig,
|
|
3095
|
+
execution.datasource,
|
|
3096
|
+
devSession,
|
|
3097
|
+
request,
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
if (!handled && request.kind === "set" && request.set) {
|
|
3102
|
+
handled = await rebuildCatalogSetForDev(
|
|
3103
|
+
runtime,
|
|
3104
|
+
rootDirectoryPath,
|
|
3105
|
+
projectConfig,
|
|
3106
|
+
datasource,
|
|
3107
|
+
devSession,
|
|
3108
|
+
{
|
|
3109
|
+
set: request.set,
|
|
3110
|
+
browserRouter,
|
|
3111
|
+
withTranslationSearch,
|
|
3112
|
+
withDuplicates,
|
|
3113
|
+
},
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
if (!handled) {
|
|
3118
|
+
await api.exportCatalog(rootDirectoryPath, projectConfig, datasource, {
|
|
3119
|
+
outDir: parsed.outDir,
|
|
3120
|
+
copyAssets: false,
|
|
3121
|
+
preserveAssets: true,
|
|
3122
|
+
browserRouter,
|
|
3123
|
+
dev: true,
|
|
3124
|
+
devSession,
|
|
3125
|
+
withTranslationSearch,
|
|
3126
|
+
withDuplicates,
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
|
|
2337
3130
|
server.triggerReload();
|
|
2338
3131
|
} catch (error) {
|
|
2339
3132
|
console.error("[catalog] Export failed during watch mode");
|
|
@@ -2341,28 +3134,30 @@ export function createCatalogPlugin(
|
|
|
2341
3134
|
} finally {
|
|
2342
3135
|
exportInFlight = false;
|
|
2343
3136
|
|
|
2344
|
-
if (
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
void runExportAndReload(nextReason);
|
|
3137
|
+
if (queuedChanges.length > 0) {
|
|
3138
|
+
const nextChanges = queuedChanges;
|
|
3139
|
+
queuedChanges = [];
|
|
3140
|
+
void runRebuildAndReload(nextChanges);
|
|
2349
3141
|
}
|
|
2350
3142
|
}
|
|
2351
3143
|
};
|
|
2352
3144
|
|
|
2353
|
-
const stopWatchingProject =
|
|
3145
|
+
const stopWatchingProject = createCatalogInputWatcher(
|
|
2354
3146
|
rootDirectoryPath,
|
|
3147
|
+
projectConfig,
|
|
2355
3148
|
ignoredDirectoryPaths,
|
|
2356
|
-
(
|
|
2357
|
-
|
|
3149
|
+
(changedPaths) => {
|
|
3150
|
+
pendingChanges.push(...changedPaths);
|
|
2358
3151
|
|
|
2359
3152
|
if (debounceTimer) {
|
|
2360
3153
|
clearTimeout(debounceTimer);
|
|
2361
3154
|
}
|
|
2362
3155
|
debounceTimer = setTimeout(() => {
|
|
3156
|
+
const nextChanges = Array.from(new Set(pendingChanges));
|
|
3157
|
+
pendingChanges = [];
|
|
2363
3158
|
debounceTimer = null;
|
|
2364
|
-
void
|
|
2365
|
-
},
|
|
3159
|
+
void runRebuildAndReload(nextChanges);
|
|
3160
|
+
}, 250);
|
|
2366
3161
|
},
|
|
2367
3162
|
);
|
|
2368
3163
|
|