@messagevisor/catalog 0.7.0 → 0.9.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-9TVwIAiT.js +73 -0
- package/dist/assets/index-BgEITLIy.css +1 -0
- package/dist/index.html +2 -2
- package/lib/node/index.d.ts +53 -0
- package/lib/node/index.js +646 -102
- package/lib/node/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/lists/EntityList.tsx +207 -63
- package/src/node/index.spec.ts +104 -1
- package/src/node/index.ts +718 -53
- package/src/pages/EntityDetailPage.tsx +63 -24
- package/dist/assets/index-Cx9rnVIG.js +0 -73
- package/dist/assets/index-DrsX4U8c.css +0 -1
package/src/node/index.ts
CHANGED
|
@@ -341,6 +341,8 @@ export interface CatalogExportOptions {
|
|
|
341
341
|
devEditors?: CatalogDevEditor[];
|
|
342
342
|
withTranslationSearch?: boolean;
|
|
343
343
|
withDuplicates?: boolean;
|
|
344
|
+
devSession?: CatalogDevSession;
|
|
345
|
+
preserveAssets?: boolean;
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
export interface CatalogServeOptions {
|
|
@@ -371,6 +373,22 @@ interface CatalogBuildContext {
|
|
|
371
373
|
writer: CatalogJsonWriter;
|
|
372
374
|
}
|
|
373
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[];
|
|
390
|
+
}
|
|
391
|
+
|
|
374
392
|
interface SourceFileInfo {
|
|
375
393
|
sourcePath: string;
|
|
376
394
|
absolutePath: string;
|
|
@@ -630,7 +648,7 @@ function getLocaleFormatSource(
|
|
|
630
648
|
locales: Record<string, Locale>,
|
|
631
649
|
formatPath: string,
|
|
632
650
|
): Pick<CatalogFormatRow, "source" | "from"> {
|
|
633
|
-
const pathSegments = formatPath
|
|
651
|
+
const pathSegments = getFormatStylePathSegments(formatPath);
|
|
634
652
|
|
|
635
653
|
if (typeof getPathValue(locales[localeKey]?.formats, pathSegments) !== "undefined") {
|
|
636
654
|
return { source: "direct" };
|
|
@@ -650,6 +668,11 @@ function getLocaleFormatSource(
|
|
|
650
668
|
return { source: "missing" };
|
|
651
669
|
}
|
|
652
670
|
|
|
671
|
+
function getFormatStylePathSegments(formatPath: string): string[] {
|
|
672
|
+
const pathSegments = formatPath.split(".").filter(Boolean);
|
|
673
|
+
return pathSegments.length > 2 ? pathSegments.slice(0, 2) : pathSegments;
|
|
674
|
+
}
|
|
675
|
+
|
|
653
676
|
function getFormatRows(
|
|
654
677
|
runtime: CatalogRuntime,
|
|
655
678
|
localeKey: string,
|
|
@@ -661,7 +684,8 @@ function getFormatRows(
|
|
|
661
684
|
const rows = flattenObjectRows(computedFormats).map((row) => {
|
|
662
685
|
if (
|
|
663
686
|
target &&
|
|
664
|
-
typeof getPathValue(target.formats?.[localeKey], row.path
|
|
687
|
+
typeof getPathValue(target.formats?.[localeKey], getFormatStylePathSegments(row.path)) !==
|
|
688
|
+
"undefined"
|
|
665
689
|
) {
|
|
666
690
|
return { ...row, source: "target" as const, from: "target" };
|
|
667
691
|
}
|
|
@@ -1977,6 +2001,455 @@ async function copyCatalogAssets(outputDirectoryPath: string) {
|
|
|
1977
2001
|
await fs.promises.cp(distPath, outputDirectoryPath, { recursive: true });
|
|
1978
2002
|
}
|
|
1979
2003
|
|
|
2004
|
+
async function createCatalogDevSession(
|
|
2005
|
+
rootDirectoryPath: string,
|
|
2006
|
+
projectConfig: any,
|
|
2007
|
+
options: { outDir?: string; devEditors?: CatalogDevEditor[] } = {},
|
|
2008
|
+
): Promise<CatalogDevSession> {
|
|
2009
|
+
const outputDirectoryPath = options.outDir
|
|
2010
|
+
? path.resolve(rootDirectoryPath, options.outDir)
|
|
2011
|
+
: projectConfig.catalogDirectoryPath;
|
|
2012
|
+
|
|
2013
|
+
return {
|
|
2014
|
+
outputDirectoryPath,
|
|
2015
|
+
devEditors: options.devEditors || detectDevEditors(),
|
|
2016
|
+
historyIndex: await getGitHistoryIndex(rootDirectoryPath, projectConfig),
|
|
2017
|
+
links: getRepoLinks(rootDirectoryPath),
|
|
2018
|
+
repositoryRootDirectoryPath: getRepositoryRootDirectoryPath(rootDirectoryPath),
|
|
2019
|
+
repositorySourceRootDirectoryPath: getRepositorySourceRootDirectoryPath(rootDirectoryPath),
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
async function readJsonFile<T>(filePath: string): Promise<T | undefined> {
|
|
2024
|
+
try {
|
|
2025
|
+
return JSON.parse(await fs.promises.readFile(filePath, "utf8")) as T;
|
|
2026
|
+
} catch (_error) {
|
|
2027
|
+
return undefined;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function getOutputRelativeDirectory(projectConfig: any, set?: string) {
|
|
2032
|
+
return projectConfig.sets ? path.join("sets", set || "") : "root";
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function getDataOutputDirectoryPath(session: CatalogDevSession, projectConfig: any, set?: string) {
|
|
2036
|
+
return path.join(
|
|
2037
|
+
session.outputDirectoryPath,
|
|
2038
|
+
"data",
|
|
2039
|
+
getOutputRelativeDirectory(projectConfig, set),
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
function getEntityKeyFromChangedPath(
|
|
2044
|
+
rootDirectoryPath: string,
|
|
2045
|
+
projectConfig: any,
|
|
2046
|
+
changedPath: string,
|
|
2047
|
+
): EntityPathInfo | undefined {
|
|
2048
|
+
const relativePath = path.relative(rootDirectoryPath, changedPath);
|
|
2049
|
+
|
|
2050
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
2051
|
+
return undefined;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
return getEntityInfoFromRelativePath(rootDirectoryPath, projectConfig, relativePath);
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function getChangedPathSummary(rootDirectoryPath: string, changedPaths: string[]) {
|
|
2058
|
+
return changedPaths
|
|
2059
|
+
.slice(0, 3)
|
|
2060
|
+
.map((changedPath) => formatCatalogPath(rootDirectoryPath, changedPath))
|
|
2061
|
+
.join(", ");
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
function classifyCatalogDevChanges(
|
|
2065
|
+
rootDirectoryPath: string,
|
|
2066
|
+
projectConfig: any,
|
|
2067
|
+
changedPaths: string[],
|
|
2068
|
+
options: { withTranslationSearch: boolean; withDuplicates: boolean },
|
|
2069
|
+
): CatalogDevRebuildRequest {
|
|
2070
|
+
const reason = getChangedPathSummary(rootDirectoryPath, changedPaths) || "project changes";
|
|
2071
|
+
const infos = changedPaths.map((changedPath) =>
|
|
2072
|
+
getEntityKeyFromChangedPath(rootDirectoryPath, projectConfig, changedPath),
|
|
2073
|
+
);
|
|
2074
|
+
|
|
2075
|
+
if (infos.length === 0 || infos.some((info) => !info)) {
|
|
2076
|
+
return { kind: "full", reason };
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
const sets = new Set(infos.map((info) => info?.set || ""));
|
|
2080
|
+
const types = new Set(infos.map((info) => info?.type));
|
|
2081
|
+
|
|
2082
|
+
if (sets.size > 1) {
|
|
2083
|
+
return { kind: "full", reason };
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
const set = Array.from(sets)[0] || undefined;
|
|
2087
|
+
|
|
2088
|
+
if (
|
|
2089
|
+
types.size === 1 &&
|
|
2090
|
+
types.has("message") &&
|
|
2091
|
+
!options.withTranslationSearch &&
|
|
2092
|
+
!options.withDuplicates
|
|
2093
|
+
) {
|
|
2094
|
+
return {
|
|
2095
|
+
kind: "message",
|
|
2096
|
+
reason,
|
|
2097
|
+
set,
|
|
2098
|
+
messageKeys: sortStrings(infos.map((info) => info?.key || "").filter(Boolean)),
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
if (
|
|
2103
|
+
projectConfig.sets &&
|
|
2104
|
+
set &&
|
|
2105
|
+
types.size > 0 &&
|
|
2106
|
+
!types.has("test") &&
|
|
2107
|
+
!options.withTranslationSearch &&
|
|
2108
|
+
!options.withDuplicates
|
|
2109
|
+
) {
|
|
2110
|
+
return { kind: "set", reason, set };
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
return { kind: "full", reason };
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
async function writeCatalogManifest(
|
|
2117
|
+
writer: CatalogJsonWriter,
|
|
2118
|
+
rootDirectoryPath: string,
|
|
2119
|
+
projectConfig: any,
|
|
2120
|
+
session: CatalogDevSession,
|
|
2121
|
+
options: {
|
|
2122
|
+
browserRouter: boolean;
|
|
2123
|
+
withTranslationSearch: boolean;
|
|
2124
|
+
withDuplicates: boolean;
|
|
2125
|
+
setIndexes: Record<string, CatalogSetIndex>;
|
|
2126
|
+
executions: Array<{ set: string; projectConfig: any; datasource: any }>;
|
|
2127
|
+
},
|
|
2128
|
+
) {
|
|
2129
|
+
const manifest = {
|
|
2130
|
+
schemaVersion: CATALOG_SCHEMA_VERSION,
|
|
2131
|
+
generatedAt: new Date().toISOString(),
|
|
2132
|
+
router: options.browserRouter === false ? "hash" : "browser",
|
|
2133
|
+
sets: projectConfig.sets,
|
|
2134
|
+
setKeys: projectConfig.sets ? options.executions.map((execution) => execution.set) : [],
|
|
2135
|
+
dev: { editors: session.devEditors },
|
|
2136
|
+
features: {
|
|
2137
|
+
translationSearch: options.withTranslationSearch,
|
|
2138
|
+
duplicates: options.withDuplicates,
|
|
2139
|
+
},
|
|
2140
|
+
links: session.links,
|
|
2141
|
+
paths: {
|
|
2142
|
+
projectHistory: "data/project/history/page-1.json",
|
|
2143
|
+
root: projectConfig.sets ? undefined : "data/root/index.json",
|
|
2144
|
+
sets: projectConfig.sets
|
|
2145
|
+
? Object.fromEntries(
|
|
2146
|
+
options.executions.map((execution) => [
|
|
2147
|
+
execution.set,
|
|
2148
|
+
`data/sets/${encodeURIComponent(execution.set)}/index.json`,
|
|
2149
|
+
]),
|
|
2150
|
+
)
|
|
2151
|
+
: undefined,
|
|
2152
|
+
},
|
|
2153
|
+
counts: Object.fromEntries(
|
|
2154
|
+
Object.keys(options.setIndexes).map((key) => [key, options.setIndexes[key].counts]),
|
|
2155
|
+
),
|
|
2156
|
+
};
|
|
2157
|
+
|
|
2158
|
+
await writer.write(path.join(session.outputDirectoryPath, "data", "manifest.json"), manifest);
|
|
2159
|
+
return manifest;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
function getMessageRelationshipFingerprint(message: Message) {
|
|
2163
|
+
const attributes = new Set<string>();
|
|
2164
|
+
const segments = new Set<string>();
|
|
2165
|
+
|
|
2166
|
+
for (const override of message.overrides || []) {
|
|
2167
|
+
collectAttributeKeysFromConditions(override.conditions, attributes);
|
|
2168
|
+
collectSegmentKeys(override.segments, segments);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
return {
|
|
2172
|
+
attributes: sortStrings(Array.from(attributes)),
|
|
2173
|
+
segments: sortStrings(Array.from(segments)),
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
function sameStringList(left: string[] = [], right: string[] = []) {
|
|
2178
|
+
if (left.length !== right.length) {
|
|
2179
|
+
return false;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
return left.every((value, index) => value === right[index]);
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function summarizeMessage(
|
|
2186
|
+
message: Message,
|
|
2187
|
+
messageKey: string,
|
|
2188
|
+
historyIndex: CatalogHistoryIndex,
|
|
2189
|
+
set: string | undefined,
|
|
2190
|
+
targets: string[],
|
|
2191
|
+
) {
|
|
2192
|
+
const directLocales = Object.keys(message.translations || {});
|
|
2193
|
+
const overrideLocalesSet = new Set<string>();
|
|
2194
|
+
|
|
2195
|
+
for (const override of message.overrides || []) {
|
|
2196
|
+
for (const localeKey of Object.keys(override.translations || {})) {
|
|
2197
|
+
overrideLocalesSet.add(localeKey);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
const overrideLocales = sortStrings(Array.from(overrideLocalesSet));
|
|
2202
|
+
|
|
2203
|
+
return getEntitySummary(message, "message", messageKey, historyIndex, set, {
|
|
2204
|
+
targets,
|
|
2205
|
+
...(directLocales.length > 0 ? { locales: sortStrings(directLocales) } : {}),
|
|
2206
|
+
...(overrideLocales.length > 0 ? { overrideLocales } : {}),
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
async function tryRebuildCatalogMessage(
|
|
2211
|
+
runtime: CatalogRuntime,
|
|
2212
|
+
rootDirectoryPath: string,
|
|
2213
|
+
rootProjectConfig: any,
|
|
2214
|
+
projectConfig: any,
|
|
2215
|
+
datasource: any,
|
|
2216
|
+
session: CatalogDevSession,
|
|
2217
|
+
request: CatalogDevRebuildRequest,
|
|
2218
|
+
) {
|
|
2219
|
+
if (request.kind !== "message" || !request.messageKeys || request.messageKeys.length === 0) {
|
|
2220
|
+
return false;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
const dataDirectoryPath = getDataOutputDirectoryPath(session, rootProjectConfig, request.set);
|
|
2224
|
+
const indexPath = path.join(dataDirectoryPath, "index.json");
|
|
2225
|
+
const index = await readJsonFile<CatalogSetIndex>(indexPath);
|
|
2226
|
+
|
|
2227
|
+
if (!index) {
|
|
2228
|
+
return false;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
const [localeKeys, messageKeys, targetKeys] = (await Promise.all([
|
|
2232
|
+
datasource.listLocales(),
|
|
2233
|
+
datasource.listMessages(),
|
|
2234
|
+
datasource.listTargets(),
|
|
2235
|
+
])) as [string[], string[], string[]];
|
|
2236
|
+
const messageKeySet = new Set(messageKeys);
|
|
2237
|
+
|
|
2238
|
+
if (request.messageKeys.some((messageKey) => !messageKeySet.has(messageKey))) {
|
|
2239
|
+
return false;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
const [locales, targets] = await Promise.all([
|
|
2243
|
+
readAll<Locale>(localeKeys, (key) => datasource.readLocale(key)),
|
|
2244
|
+
readAll<Target>(targetKeys, (key) => datasource.readTarget(key)),
|
|
2245
|
+
]);
|
|
2246
|
+
const localeDirections = getLocaleDirections(locales);
|
|
2247
|
+
const targetMessages = Object.fromEntries(
|
|
2248
|
+
targetKeys.map((targetKey) => [
|
|
2249
|
+
targetKey,
|
|
2250
|
+
getTargetMessageKeys(targets[targetKey], messageKeys),
|
|
2251
|
+
]),
|
|
2252
|
+
) as Record<string, string[]>;
|
|
2253
|
+
const writer = new CatalogJsonWriter();
|
|
2254
|
+
|
|
2255
|
+
for (const messageKey of request.messageKeys) {
|
|
2256
|
+
const oldDetailPath = path.join(
|
|
2257
|
+
dataDirectoryPath,
|
|
2258
|
+
"entities",
|
|
2259
|
+
"message",
|
|
2260
|
+
`${encodeKey(messageKey)}.json`,
|
|
2261
|
+
);
|
|
2262
|
+
const oldDetail = await readJsonFile<any>(oldDetailPath);
|
|
2263
|
+
|
|
2264
|
+
if (!oldDetail) {
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
const message = await datasource.readMessage(messageKey);
|
|
2269
|
+
const messageTargets = sortStrings(
|
|
2270
|
+
targetKeys.filter((targetKey) => targetMessages[targetKey].includes(messageKey)),
|
|
2271
|
+
);
|
|
2272
|
+
|
|
2273
|
+
if (!sameStringList(sortStrings(oldDetail.targets || []), messageTargets)) {
|
|
2274
|
+
return false;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
const oldRelationshipFingerprint = getMessageRelationshipFingerprint(oldDetail.entity || {});
|
|
2278
|
+
const nextRelationshipFingerprint = getMessageRelationshipFingerprint(message);
|
|
2279
|
+
|
|
2280
|
+
if (
|
|
2281
|
+
!sameStringList(
|
|
2282
|
+
oldRelationshipFingerprint.attributes,
|
|
2283
|
+
nextRelationshipFingerprint.attributes,
|
|
2284
|
+
) ||
|
|
2285
|
+
!sameStringList(oldRelationshipFingerprint.segments, nextRelationshipFingerprint.segments)
|
|
2286
|
+
) {
|
|
2287
|
+
return false;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
const examples = await runtime.resolveExamples(projectConfig, datasource, {
|
|
2291
|
+
set: request.set,
|
|
2292
|
+
message: messageKey,
|
|
2293
|
+
onlyMessages: true,
|
|
2294
|
+
});
|
|
2295
|
+
const overrides = (message.overrides || []).map((override: Override) => {
|
|
2296
|
+
const attributes = new Set<string>();
|
|
2297
|
+
const overrideSegments = new Set<string>();
|
|
2298
|
+
collectAttributeKeysFromConditions(override.conditions, attributes);
|
|
2299
|
+
collectSegmentKeys(override.segments, overrideSegments);
|
|
2300
|
+
|
|
2301
|
+
return {
|
|
2302
|
+
...override,
|
|
2303
|
+
usedAttributes: sortStrings(Array.from(attributes)),
|
|
2304
|
+
usedSegments: sortStrings(Array.from(overrideSegments)),
|
|
2305
|
+
};
|
|
2306
|
+
});
|
|
2307
|
+
const sourceFileInfo = getSourceFileInfo(
|
|
2308
|
+
session.repositorySourceRootDirectoryPath,
|
|
2309
|
+
rootDirectoryPath,
|
|
2310
|
+
projectConfig,
|
|
2311
|
+
"message",
|
|
2312
|
+
messageKey,
|
|
2313
|
+
{ resolveAbsolutePath: session.devEditors.length > 0 },
|
|
2314
|
+
);
|
|
2315
|
+
const detail = {
|
|
2316
|
+
type: "message",
|
|
2317
|
+
key: messageKey,
|
|
2318
|
+
entity: { ...message, overrides },
|
|
2319
|
+
sourcePath: sourceFileInfo.sourcePath,
|
|
2320
|
+
editLinks: getEditorLinks(session.devEditors, sourceFileInfo),
|
|
2321
|
+
targets: messageTargets,
|
|
2322
|
+
localeKeys,
|
|
2323
|
+
localeDirections,
|
|
2324
|
+
translations: localeKeys.map((localeKey) =>
|
|
2325
|
+
resolveTranslationRow(message.translations, localeKey, locales),
|
|
2326
|
+
),
|
|
2327
|
+
evaluatedExamples: examples.messages,
|
|
2328
|
+
overrideTranslations: overrides.map((override) => ({
|
|
2329
|
+
key: override.key,
|
|
2330
|
+
rows: localeKeys.map((localeKey) =>
|
|
2331
|
+
resolveTranslationRow(override.translations, localeKey, locales),
|
|
2332
|
+
),
|
|
2333
|
+
})),
|
|
2334
|
+
lastModified: getLastModified(session.historyIndex, "message", messageKey, request.set),
|
|
2335
|
+
};
|
|
2336
|
+
|
|
2337
|
+
await writer.write(oldDetailPath, detail);
|
|
2338
|
+
|
|
2339
|
+
await writeHistoryPages(
|
|
2340
|
+
writer,
|
|
2341
|
+
path.join(dataDirectoryPath, "history", "message", encodeKey(messageKey)),
|
|
2342
|
+
getHistoryForEntity(session.historyIndex, "message", messageKey, request.set),
|
|
2343
|
+
{ skipEmpty: true },
|
|
2344
|
+
);
|
|
2345
|
+
|
|
2346
|
+
const nextSummary = summarizeMessage(
|
|
2347
|
+
message,
|
|
2348
|
+
messageKey,
|
|
2349
|
+
session.historyIndex,
|
|
2350
|
+
request.set,
|
|
2351
|
+
messageTargets,
|
|
2352
|
+
);
|
|
2353
|
+
const existingSummaryIndex = index.entities.message.findIndex(
|
|
2354
|
+
(entry) => entry.key === messageKey,
|
|
2355
|
+
);
|
|
2356
|
+
|
|
2357
|
+
if (existingSummaryIndex === -1) {
|
|
2358
|
+
index.entities.message.push(nextSummary);
|
|
2359
|
+
} else {
|
|
2360
|
+
index.entities.message[existingSummaryIndex] = nextSummary;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
index.entities.message.sort((left, right) => left.key.localeCompare(right.key));
|
|
2365
|
+
index.counts.message = messageKeys.length;
|
|
2366
|
+
await writer.write(indexPath, index);
|
|
2367
|
+
|
|
2368
|
+
return true;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
async function rebuildCatalogSetForDev(
|
|
2372
|
+
runtime: CatalogRuntime,
|
|
2373
|
+
rootDirectoryPath: string,
|
|
2374
|
+
projectConfig: any,
|
|
2375
|
+
datasource: any,
|
|
2376
|
+
session: CatalogDevSession,
|
|
2377
|
+
options: {
|
|
2378
|
+
set?: string;
|
|
2379
|
+
browserRouter: boolean;
|
|
2380
|
+
withTranslationSearch: boolean;
|
|
2381
|
+
withDuplicates: boolean;
|
|
2382
|
+
},
|
|
2383
|
+
) {
|
|
2384
|
+
const writer = new CatalogJsonWriter();
|
|
2385
|
+
const progress = new CatalogProgressReporter(rootDirectoryPath, session.outputDirectoryPath);
|
|
2386
|
+
const executions = await runtime.getProjectSetExecutions(projectConfig, datasource);
|
|
2387
|
+
const setIndexes: Record<string, CatalogSetIndex> = {};
|
|
2388
|
+
const existingIndexes = await Promise.all(
|
|
2389
|
+
executions.map(async (execution) => {
|
|
2390
|
+
const indexPath = path.join(
|
|
2391
|
+
session.outputDirectoryPath,
|
|
2392
|
+
"data",
|
|
2393
|
+
getOutputRelativeDirectory(projectConfig, execution.set),
|
|
2394
|
+
"index.json",
|
|
2395
|
+
);
|
|
2396
|
+
return [execution.set || "root", await readJsonFile<CatalogSetIndex>(indexPath)] as const;
|
|
2397
|
+
}),
|
|
2398
|
+
);
|
|
2399
|
+
|
|
2400
|
+
for (const [key, index] of existingIndexes) {
|
|
2401
|
+
if (index) {
|
|
2402
|
+
setIndexes[key] = index;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const execution = executions.find((item) => (item.set || undefined) === options.set);
|
|
2407
|
+
|
|
2408
|
+
if (!execution) {
|
|
2409
|
+
return false;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
const outputRelativeDirectory = getOutputRelativeDirectory(projectConfig, execution.set);
|
|
2413
|
+
await fs.promises.rm(path.join(session.outputDirectoryPath, "data", outputRelativeDirectory), {
|
|
2414
|
+
recursive: true,
|
|
2415
|
+
force: true,
|
|
2416
|
+
});
|
|
2417
|
+
|
|
2418
|
+
const context: CatalogBuildContext = {
|
|
2419
|
+
rootDirectoryPath,
|
|
2420
|
+
repositoryRootDirectoryPath: session.repositoryRootDirectoryPath,
|
|
2421
|
+
repositorySourceRootDirectoryPath: session.repositorySourceRootDirectoryPath,
|
|
2422
|
+
outputDirectoryPath: session.outputDirectoryPath,
|
|
2423
|
+
dataDirectoryPath: path.join(session.outputDirectoryPath, "data"),
|
|
2424
|
+
historyIndex: session.historyIndex,
|
|
2425
|
+
runtime,
|
|
2426
|
+
devEditors: session.devEditors,
|
|
2427
|
+
duplicateResultsBySet: {},
|
|
2428
|
+
withTranslationSearch: options.withTranslationSearch,
|
|
2429
|
+
withDuplicates: options.withDuplicates,
|
|
2430
|
+
progress,
|
|
2431
|
+
writer,
|
|
2432
|
+
};
|
|
2433
|
+
|
|
2434
|
+
setIndexes[execution.set || "root"] = await buildSetCatalog(
|
|
2435
|
+
context,
|
|
2436
|
+
execution.set,
|
|
2437
|
+
execution.projectConfig,
|
|
2438
|
+
execution.datasource,
|
|
2439
|
+
outputRelativeDirectory,
|
|
2440
|
+
);
|
|
2441
|
+
|
|
2442
|
+
await writeCatalogManifest(writer, rootDirectoryPath, projectConfig, session, {
|
|
2443
|
+
browserRouter: options.browserRouter,
|
|
2444
|
+
withTranslationSearch: options.withTranslationSearch,
|
|
2445
|
+
withDuplicates: options.withDuplicates,
|
|
2446
|
+
setIndexes,
|
|
2447
|
+
executions,
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
return true;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
1980
2453
|
export async function exportCatalog(
|
|
1981
2454
|
runtime: CatalogRuntime,
|
|
1982
2455
|
rootDirectoryPath: string,
|
|
@@ -2003,7 +2476,11 @@ export async function exportCatalog(
|
|
|
2003
2476
|
});
|
|
2004
2477
|
|
|
2005
2478
|
let stepStartedAt = progress.step("Preparing output directory");
|
|
2006
|
-
|
|
2479
|
+
if (options.preserveAssets) {
|
|
2480
|
+
await fs.promises.rm(dataDirectoryPath, { recursive: true, force: true });
|
|
2481
|
+
} else {
|
|
2482
|
+
await fs.promises.rm(outputDirectoryPath, { recursive: true, force: true });
|
|
2483
|
+
}
|
|
2007
2484
|
await fs.promises.mkdir(dataDirectoryPath, { recursive: true });
|
|
2008
2485
|
progress.done(stepStartedAt);
|
|
2009
2486
|
|
|
@@ -2013,13 +2490,17 @@ export async function exportCatalog(
|
|
|
2013
2490
|
progress.done(stepStartedAt);
|
|
2014
2491
|
}
|
|
2015
2492
|
|
|
2016
|
-
const devEditors = options.dev
|
|
2493
|
+
const devEditors = options.dev
|
|
2494
|
+
? options.devSession?.devEditors || options.devEditors || detectDevEditors()
|
|
2495
|
+
: [];
|
|
2017
2496
|
stepStartedAt = progress.step("Reading Git history");
|
|
2018
|
-
const historyIndex =
|
|
2497
|
+
const historyIndex =
|
|
2498
|
+
options.devSession?.historyIndex ||
|
|
2499
|
+
(await getGitHistoryIndex(rootDirectoryPath, projectConfig));
|
|
2019
2500
|
progress.done(stepStartedAt, `(${pluralize(historyIndex.entries.length, "commit")})`);
|
|
2020
2501
|
|
|
2021
2502
|
stepStartedAt = progress.step("Resolving repository links");
|
|
2022
|
-
const links = getRepoLinks(rootDirectoryPath);
|
|
2503
|
+
const links = options.devSession?.links || getRepoLinks(rootDirectoryPath);
|
|
2023
2504
|
progress.done(stepStartedAt);
|
|
2024
2505
|
|
|
2025
2506
|
let duplicateResultsBySet: Record<string, CatalogDuplicateTranslationsSetResult> = {};
|
|
@@ -2050,8 +2531,12 @@ export async function exportCatalog(
|
|
|
2050
2531
|
|
|
2051
2532
|
const context: CatalogBuildContext = {
|
|
2052
2533
|
rootDirectoryPath,
|
|
2053
|
-
repositoryRootDirectoryPath:
|
|
2054
|
-
|
|
2534
|
+
repositoryRootDirectoryPath:
|
|
2535
|
+
options.devSession?.repositoryRootDirectoryPath ||
|
|
2536
|
+
getRepositoryRootDirectoryPath(rootDirectoryPath),
|
|
2537
|
+
repositorySourceRootDirectoryPath:
|
|
2538
|
+
options.devSession?.repositorySourceRootDirectoryPath ||
|
|
2539
|
+
getRepositorySourceRootDirectoryPath(rootDirectoryPath),
|
|
2055
2540
|
outputDirectoryPath,
|
|
2056
2541
|
dataDirectoryPath,
|
|
2057
2542
|
historyIndex,
|
|
@@ -2177,11 +2662,34 @@ function injectCatalogLiveReloadClient(html: string) {
|
|
|
2177
2662
|
return `${html}${script}`;
|
|
2178
2663
|
}
|
|
2179
2664
|
|
|
2180
|
-
function
|
|
2665
|
+
function getCatalogInputWatchPaths(rootDirectoryPath: string, projectConfig: any) {
|
|
2666
|
+
const paths = [path.join(rootDirectoryPath, "messagevisor.config.js")];
|
|
2667
|
+
|
|
2668
|
+
if (projectConfig.sets) {
|
|
2669
|
+
paths.push(projectConfig.setsDirectoryPath);
|
|
2670
|
+
return paths;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
paths.push(
|
|
2674
|
+
projectConfig.localesDirectoryPath,
|
|
2675
|
+
projectConfig.messagesDirectoryPath,
|
|
2676
|
+
projectConfig.attributesDirectoryPath,
|
|
2677
|
+
projectConfig.segmentsDirectoryPath,
|
|
2678
|
+
projectConfig.targetsDirectoryPath,
|
|
2679
|
+
projectConfig.testsDirectoryPath,
|
|
2680
|
+
);
|
|
2681
|
+
|
|
2682
|
+
return paths.filter((entry): entry is string => typeof entry === "string" && entry.length > 0);
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
function createCatalogInputWatcher(
|
|
2181
2686
|
rootDirectoryPath: string,
|
|
2687
|
+
projectConfig: any,
|
|
2182
2688
|
ignoredDirectoryPaths: string[],
|
|
2183
|
-
onChange: (
|
|
2689
|
+
onChange: (changedPaths: string[]) => void,
|
|
2184
2690
|
) {
|
|
2691
|
+
const watchPaths = getCatalogInputWatchPaths(rootDirectoryPath, projectConfig);
|
|
2692
|
+
|
|
2185
2693
|
function shouldIgnore(targetPath: string) {
|
|
2186
2694
|
const resolvedTargetPath = path.resolve(targetPath);
|
|
2187
2695
|
|
|
@@ -2195,7 +2703,24 @@ function createProjectWatcher(
|
|
|
2195
2703
|
});
|
|
2196
2704
|
}
|
|
2197
2705
|
|
|
2198
|
-
function
|
|
2706
|
+
function shouldWatch(targetPath: string) {
|
|
2707
|
+
const resolvedTargetPath = path.resolve(targetPath);
|
|
2708
|
+
|
|
2709
|
+
if (shouldIgnore(resolvedTargetPath)) {
|
|
2710
|
+
return false;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
return watchPaths.some((watchPath) => {
|
|
2714
|
+
const resolvedWatchPath = path.resolve(watchPath);
|
|
2715
|
+
|
|
2716
|
+
return (
|
|
2717
|
+
resolvedTargetPath === resolvedWatchPath ||
|
|
2718
|
+
resolvedTargetPath.startsWith(`${resolvedWatchPath}${path.sep}`)
|
|
2719
|
+
);
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
function collectSnapshotEntries(directoryPath: string, snapshotEntries: Map<string, string>) {
|
|
2199
2724
|
if (shouldIgnore(directoryPath)) {
|
|
2200
2725
|
return;
|
|
2201
2726
|
}
|
|
@@ -2226,8 +2751,7 @@ function createProjectWatcher(
|
|
|
2226
2751
|
|
|
2227
2752
|
try {
|
|
2228
2753
|
const stat = fs.statSync(entryPath);
|
|
2229
|
-
|
|
2230
|
-
snapshotEntries.push(`${relativePath}:${stat.size}:${stat.mtimeMs}`);
|
|
2754
|
+
snapshotEntries.set(entryPath, `${stat.size}:${stat.mtimeMs}`);
|
|
2231
2755
|
} catch {
|
|
2232
2756
|
// Ignore transient editor save races.
|
|
2233
2757
|
}
|
|
@@ -2235,26 +2759,108 @@ function createProjectWatcher(
|
|
|
2235
2759
|
}
|
|
2236
2760
|
|
|
2237
2761
|
function createSnapshot() {
|
|
2238
|
-
const snapshotEntries
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2762
|
+
const snapshotEntries = new Map<string, string>();
|
|
2763
|
+
|
|
2764
|
+
for (const watchPath of watchPaths) {
|
|
2765
|
+
if (!fs.existsSync(watchPath)) {
|
|
2766
|
+
continue;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
const stat = fs.statSync(watchPath);
|
|
2770
|
+
|
|
2771
|
+
if (stat.isFile()) {
|
|
2772
|
+
snapshotEntries.set(watchPath, `${stat.size}:${stat.mtimeMs}`);
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
collectSnapshotEntries(watchPath, snapshotEntries);
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
return snapshotEntries;
|
|
2242
2780
|
}
|
|
2243
2781
|
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const nextSnapshot = createSnapshot();
|
|
2782
|
+
function getSnapshotChanges(previous: Map<string, string>, next: Map<string, string>) {
|
|
2783
|
+
const changedPaths = new Set<string>();
|
|
2247
2784
|
|
|
2248
|
-
|
|
2249
|
-
|
|
2785
|
+
for (const [filePath, signature] of Array.from(next.entries())) {
|
|
2786
|
+
if (previous.get(filePath) !== signature) {
|
|
2787
|
+
changedPaths.add(filePath);
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
for (const filePath of Array.from(previous.keys())) {
|
|
2792
|
+
if (!next.has(filePath)) {
|
|
2793
|
+
changedPaths.add(filePath);
|
|
2794
|
+
}
|
|
2250
2795
|
}
|
|
2251
2796
|
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2797
|
+
return Array.from(changedPaths);
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
function createPollingWatcher() {
|
|
2801
|
+
let previousSnapshot = createSnapshot();
|
|
2802
|
+
const interval = setInterval(() => {
|
|
2803
|
+
const nextSnapshot = createSnapshot();
|
|
2804
|
+
const changedPaths = getSnapshotChanges(previousSnapshot, nextSnapshot).filter(shouldWatch);
|
|
2805
|
+
|
|
2806
|
+
previousSnapshot = nextSnapshot;
|
|
2807
|
+
|
|
2808
|
+
if (changedPaths.length === 0) {
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
onChange(changedPaths);
|
|
2813
|
+
}, 1000);
|
|
2814
|
+
|
|
2815
|
+
return () => {
|
|
2816
|
+
clearInterval(interval);
|
|
2817
|
+
};
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
const watchers: fs.FSWatcher[] = [];
|
|
2821
|
+
let nativeWatcherFailed = false;
|
|
2822
|
+
|
|
2823
|
+
for (const watchPath of watchPaths) {
|
|
2824
|
+
if (!fs.existsSync(watchPath)) {
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
try {
|
|
2829
|
+
const stat = fs.statSync(watchPath);
|
|
2830
|
+
const directoryPath = stat.isDirectory() ? watchPath : path.dirname(watchPath);
|
|
2831
|
+
const watcher = fs.watch(
|
|
2832
|
+
directoryPath,
|
|
2833
|
+
{ recursive: stat.isDirectory() },
|
|
2834
|
+
(_eventType, filename) => {
|
|
2835
|
+
const changedPath = filename
|
|
2836
|
+
? path.resolve(directoryPath, filename.toString())
|
|
2837
|
+
: directoryPath;
|
|
2838
|
+
|
|
2839
|
+
if (shouldWatch(changedPath)) {
|
|
2840
|
+
onChange([changedPath]);
|
|
2841
|
+
}
|
|
2842
|
+
},
|
|
2843
|
+
);
|
|
2844
|
+
|
|
2845
|
+
watchers.push(watcher);
|
|
2846
|
+
} catch (_error) {
|
|
2847
|
+
nativeWatcherFailed = true;
|
|
2848
|
+
break;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
if (nativeWatcherFailed || watchers.length === 0) {
|
|
2853
|
+
for (const watcher of watchers) {
|
|
2854
|
+
watcher.close();
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
return createPollingWatcher();
|
|
2858
|
+
}
|
|
2255
2859
|
|
|
2256
2860
|
return () => {
|
|
2257
|
-
|
|
2861
|
+
for (const watcher of watchers) {
|
|
2862
|
+
watcher.close();
|
|
2863
|
+
}
|
|
2258
2864
|
};
|
|
2259
2865
|
}
|
|
2260
2866
|
|
|
@@ -2405,6 +3011,11 @@ function isWithDuplicatesEnabled(parsed: CatalogPluginParsedOptions) {
|
|
|
2405
3011
|
return parsed.withDuplicates === true || parsed["with-duplicates"] === true;
|
|
2406
3012
|
}
|
|
2407
3013
|
|
|
3014
|
+
export const __catalogDevInternals = {
|
|
3015
|
+
classifyCatalogDevChanges,
|
|
3016
|
+
getCatalogInputWatchPaths,
|
|
3017
|
+
};
|
|
3018
|
+
|
|
2408
3019
|
export function createCatalogPlugin(
|
|
2409
3020
|
runtime: CatalogRuntime,
|
|
2410
3021
|
api: ReturnType<typeof createCatalogApi> = createCatalogApi(runtime),
|
|
@@ -2418,11 +3029,18 @@ export function createCatalogPlugin(
|
|
|
2418
3029
|
const withDuplicates = isWithDuplicatesEnabled(parsed);
|
|
2419
3030
|
|
|
2420
3031
|
if (!parsed.subcommand) {
|
|
3032
|
+
const outputDirectoryPath = parsed.outDir
|
|
3033
|
+
? path.resolve(rootDirectoryPath, parsed.outDir)
|
|
3034
|
+
: projectConfig.catalogDirectoryPath;
|
|
3035
|
+
const devSession = await createCatalogDevSession(rootDirectoryPath, projectConfig, {
|
|
3036
|
+
outDir: parsed.outDir,
|
|
3037
|
+
});
|
|
2421
3038
|
await api.exportCatalog(rootDirectoryPath, projectConfig, datasource, {
|
|
2422
3039
|
outDir: parsed.outDir,
|
|
2423
3040
|
copyAssets: !parsed.noAssets,
|
|
2424
3041
|
browserRouter,
|
|
2425
3042
|
dev: true,
|
|
3043
|
+
devSession,
|
|
2426
3044
|
withTranslationSearch,
|
|
2427
3045
|
withDuplicates,
|
|
2428
3046
|
});
|
|
@@ -2433,9 +3051,6 @@ export function createCatalogPlugin(
|
|
|
2433
3051
|
liveReload: true,
|
|
2434
3052
|
});
|
|
2435
3053
|
|
|
2436
|
-
const outputDirectoryPath = parsed.outDir
|
|
2437
|
-
? path.resolve(rootDirectoryPath, parsed.outDir)
|
|
2438
|
-
: projectConfig.catalogDirectoryPath;
|
|
2439
3054
|
const ignoredDirectoryPaths = [
|
|
2440
3055
|
path.join(rootDirectoryPath, ".git"),
|
|
2441
3056
|
path.join(rootDirectoryPath, "node_modules"),
|
|
@@ -2447,29 +3062,77 @@ export function createCatalogPlugin(
|
|
|
2447
3062
|
outputDirectoryPath,
|
|
2448
3063
|
];
|
|
2449
3064
|
let exportInFlight = false;
|
|
2450
|
-
let
|
|
2451
|
-
let
|
|
3065
|
+
let queuedChanges: string[] = [];
|
|
3066
|
+
let pendingChanges: string[] = [];
|
|
2452
3067
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
2453
3068
|
|
|
2454
|
-
const
|
|
3069
|
+
const runRebuildAndReload = async (changedPaths: string[]) => {
|
|
2455
3070
|
if (exportInFlight) {
|
|
2456
|
-
|
|
2457
|
-
queuedReason = queuedReason || reason;
|
|
3071
|
+
queuedChanges.push(...changedPaths);
|
|
2458
3072
|
return;
|
|
2459
3073
|
}
|
|
2460
3074
|
|
|
2461
3075
|
exportInFlight = true;
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
copyAssets: !parsed.noAssets,
|
|
2468
|
-
browserRouter,
|
|
2469
|
-
dev: true,
|
|
3076
|
+
const request = classifyCatalogDevChanges(
|
|
3077
|
+
rootDirectoryPath,
|
|
3078
|
+
projectConfig,
|
|
3079
|
+
changedPaths,
|
|
3080
|
+
{
|
|
2470
3081
|
withTranslationSearch,
|
|
2471
3082
|
withDuplicates,
|
|
2472
|
-
}
|
|
3083
|
+
},
|
|
3084
|
+
);
|
|
3085
|
+
console.log(`\n[catalog] Rebuilding (${request.kind}) because ${request.reason}`);
|
|
3086
|
+
|
|
3087
|
+
try {
|
|
3088
|
+
let handled = false;
|
|
3089
|
+
|
|
3090
|
+
if (request.kind === "message") {
|
|
3091
|
+
const [execution] = await runtime.getProjectSetExecutions(
|
|
3092
|
+
projectConfig,
|
|
3093
|
+
datasource,
|
|
3094
|
+
request.set,
|
|
3095
|
+
);
|
|
3096
|
+
handled = await tryRebuildCatalogMessage(
|
|
3097
|
+
runtime,
|
|
3098
|
+
rootDirectoryPath,
|
|
3099
|
+
projectConfig,
|
|
3100
|
+
execution.projectConfig,
|
|
3101
|
+
execution.datasource,
|
|
3102
|
+
devSession,
|
|
3103
|
+
request,
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
if (!handled && request.kind === "set" && request.set) {
|
|
3108
|
+
handled = await rebuildCatalogSetForDev(
|
|
3109
|
+
runtime,
|
|
3110
|
+
rootDirectoryPath,
|
|
3111
|
+
projectConfig,
|
|
3112
|
+
datasource,
|
|
3113
|
+
devSession,
|
|
3114
|
+
{
|
|
3115
|
+
set: request.set,
|
|
3116
|
+
browserRouter,
|
|
3117
|
+
withTranslationSearch,
|
|
3118
|
+
withDuplicates,
|
|
3119
|
+
},
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
if (!handled) {
|
|
3124
|
+
await api.exportCatalog(rootDirectoryPath, projectConfig, datasource, {
|
|
3125
|
+
outDir: parsed.outDir,
|
|
3126
|
+
copyAssets: false,
|
|
3127
|
+
preserveAssets: true,
|
|
3128
|
+
browserRouter,
|
|
3129
|
+
dev: true,
|
|
3130
|
+
devSession,
|
|
3131
|
+
withTranslationSearch,
|
|
3132
|
+
withDuplicates,
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
|
|
2473
3136
|
server.triggerReload();
|
|
2474
3137
|
} catch (error) {
|
|
2475
3138
|
console.error("[catalog] Export failed during watch mode");
|
|
@@ -2477,28 +3140,30 @@ export function createCatalogPlugin(
|
|
|
2477
3140
|
} finally {
|
|
2478
3141
|
exportInFlight = false;
|
|
2479
3142
|
|
|
2480
|
-
if (
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
void runExportAndReload(nextReason);
|
|
3143
|
+
if (queuedChanges.length > 0) {
|
|
3144
|
+
const nextChanges = queuedChanges;
|
|
3145
|
+
queuedChanges = [];
|
|
3146
|
+
void runRebuildAndReload(nextChanges);
|
|
2485
3147
|
}
|
|
2486
3148
|
}
|
|
2487
3149
|
};
|
|
2488
3150
|
|
|
2489
|
-
const stopWatchingProject =
|
|
3151
|
+
const stopWatchingProject = createCatalogInputWatcher(
|
|
2490
3152
|
rootDirectoryPath,
|
|
3153
|
+
projectConfig,
|
|
2491
3154
|
ignoredDirectoryPaths,
|
|
2492
|
-
(
|
|
2493
|
-
|
|
3155
|
+
(changedPaths) => {
|
|
3156
|
+
pendingChanges.push(...changedPaths);
|
|
2494
3157
|
|
|
2495
3158
|
if (debounceTimer) {
|
|
2496
3159
|
clearTimeout(debounceTimer);
|
|
2497
3160
|
}
|
|
2498
3161
|
debounceTimer = setTimeout(() => {
|
|
3162
|
+
const nextChanges = Array.from(new Set(pendingChanges));
|
|
3163
|
+
pendingChanges = [];
|
|
2499
3164
|
debounceTimer = null;
|
|
2500
|
-
void
|
|
2501
|
-
},
|
|
3165
|
+
void runRebuildAndReload(nextChanges);
|
|
3166
|
+
}, 250);
|
|
2502
3167
|
},
|
|
2503
3168
|
);
|
|
2504
3169
|
|