@messagevisor/core 0.0.1 → 0.1.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/jest.config.js +8 -0
- package/lib/benchmark/index.d.ts +2 -0
- package/lib/benchmark/index.js +417 -0
- package/lib/benchmark/index.js.map +1 -0
- package/lib/builder/index.d.ts +70 -0
- package/lib/builder/index.js +831 -0
- package/lib/builder/index.js.map +1 -0
- package/lib/cli/index.d.ts +28 -0
- package/lib/cli/index.js +182 -0
- package/lib/cli/index.js.map +1 -0
- package/lib/config/index.d.ts +61 -0
- package/lib/config/index.js +255 -0
- package/lib/config/index.js.map +1 -0
- package/lib/create/index.d.ts +2 -0
- package/lib/create/index.js +405 -0
- package/lib/create/index.js.map +1 -0
- package/lib/datasource/filesystemAdapter.d.ts +44 -0
- package/lib/datasource/filesystemAdapter.js +424 -0
- package/lib/datasource/filesystemAdapter.js.map +1 -0
- package/lib/datasource/index.d.ts +39 -0
- package/lib/datasource/index.js +96 -0
- package/lib/datasource/index.js.map +1 -0
- package/lib/error.d.ts +6 -0
- package/lib/error.js +49 -0
- package/lib/error.js.map +1 -0
- package/lib/evaluate/cli.d.ts +8 -0
- package/lib/evaluate/cli.js +179 -0
- package/lib/evaluate/cli.js.map +1 -0
- package/lib/evaluate/index.d.ts +10 -0
- package/lib/evaluate/index.js +131 -0
- package/lib/evaluate/index.js.map +1 -0
- package/lib/examples/coerceExampleIsoDates.d.ts +12 -0
- package/lib/examples/coerceExampleIsoDates.js +81 -0
- package/lib/examples/coerceExampleIsoDates.js.map +1 -0
- package/lib/examples/index.d.ts +63 -0
- package/lib/examples/index.js +713 -0
- package/lib/examples/index.js.map +1 -0
- package/lib/exporter/index.d.ts +60 -0
- package/lib/exporter/index.js +610 -0
- package/lib/exporter/index.js.map +1 -0
- package/lib/find-duplicates/index.d.ts +41 -0
- package/lib/find-duplicates/index.js +297 -0
- package/lib/find-duplicates/index.js.map +1 -0
- package/lib/generate-code/index.d.ts +11 -0
- package/lib/generate-code/index.js +157 -0
- package/lib/generate-code/index.js.map +1 -0
- package/lib/generate-code/typescript.d.ts +14 -0
- package/lib/generate-code/typescript.js +307 -0
- package/lib/generate-code/typescript.js.map +1 -0
- package/lib/importer/index.d.ts +64 -0
- package/lib/importer/index.js +1092 -0
- package/lib/importer/index.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +35 -0
- package/lib/index.js.map +1 -0
- package/lib/info/index.d.ts +17 -0
- package/lib/info/index.js +132 -0
- package/lib/info/index.js.map +1 -0
- package/lib/init/index.d.ts +30 -0
- package/lib/init/index.js +348 -0
- package/lib/init/index.js.map +1 -0
- package/lib/lint/index.d.ts +1 -0
- package/lib/lint/index.js +6 -0
- package/lib/lint/index.js.map +1 -0
- package/lib/linter/attributeSchema.d.ts +7 -0
- package/lib/linter/attributeSchema.js +36 -0
- package/lib/linter/attributeSchema.js.map +1 -0
- package/lib/linter/checkLocaleCircularDependency.d.ts +7 -0
- package/lib/linter/checkLocaleCircularDependency.js +42 -0
- package/lib/linter/checkLocaleCircularDependency.js.map +1 -0
- package/lib/linter/conditionSchema.d.ts +3 -0
- package/lib/linter/conditionSchema.js +283 -0
- package/lib/linter/conditionSchema.js.map +1 -0
- package/lib/linter/formatSchema.d.ts +325 -0
- package/lib/linter/formatSchema.js +165 -0
- package/lib/linter/formatSchema.js.map +1 -0
- package/lib/linter/icuStyleLint.d.ts +6 -0
- package/lib/linter/icuStyleLint.js +226 -0
- package/lib/linter/icuStyleLint.js.map +1 -0
- package/lib/linter/index.d.ts +34 -0
- package/lib/linter/index.js +557 -0
- package/lib/linter/index.js.map +1 -0
- package/lib/linter/localeSchema.d.ts +672 -0
- package/lib/linter/localeSchema.js +50 -0
- package/lib/linter/localeSchema.js.map +1 -0
- package/lib/linter/messageSchema.d.ts +35 -0
- package/lib/linter/messageSchema.js +115 -0
- package/lib/linter/messageSchema.js.map +1 -0
- package/lib/linter/printError.d.ts +8 -0
- package/lib/linter/printError.js +41 -0
- package/lib/linter/printError.js.map +1 -0
- package/lib/linter/schema.d.ts +33 -0
- package/lib/linter/schema.js +192 -0
- package/lib/linter/schema.js.map +1 -0
- package/lib/linter/segmentSchema.d.ts +8 -0
- package/lib/linter/segmentSchema.js +18 -0
- package/lib/linter/segmentSchema.js.map +1 -0
- package/lib/linter/targetSchema.d.ts +337 -0
- package/lib/linter/targetSchema.js +39 -0
- package/lib/linter/targetSchema.js.map +1 -0
- package/lib/linter/testSchema.d.ts +71 -0
- package/lib/linter/testSchema.js +165 -0
- package/lib/linter/testSchema.js.map +1 -0
- package/lib/linter/zodHelpers.d.ts +2 -0
- package/lib/linter/zodHelpers.js +15 -0
- package/lib/linter/zodHelpers.js.map +1 -0
- package/lib/list/index.d.ts +8 -0
- package/lib/list/index.js +524 -0
- package/lib/list/index.js.map +1 -0
- package/lib/matrix.d.ts +4 -0
- package/lib/matrix.js +66 -0
- package/lib/matrix.js.map +1 -0
- package/lib/promoter/index.d.ts +65 -0
- package/lib/promoter/index.js +1208 -0
- package/lib/promoter/index.js.map +1 -0
- package/lib/prune/index.d.ts +37 -0
- package/lib/prune/index.js +673 -0
- package/lib/prune/index.js.map +1 -0
- package/lib/sets.d.ts +10 -0
- package/lib/sets.js +120 -0
- package/lib/sets.js.map +1 -0
- package/lib/tester/cliFormat.d.ts +8 -0
- package/lib/tester/cliFormat.js +15 -0
- package/lib/tester/cliFormat.js.map +1 -0
- package/lib/tester/index.d.ts +35 -0
- package/lib/tester/index.js +713 -0
- package/lib/tester/index.js.map +1 -0
- package/lib/tester/matrix.d.ts +14 -0
- package/lib/tester/matrix.js +76 -0
- package/lib/tester/matrix.js.map +1 -0
- package/lib/tester/prettyDuration.d.ts +1 -0
- package/lib/tester/prettyDuration.js +30 -0
- package/lib/tester/prettyDuration.js.map +1 -0
- package/lib/tester/printTestResult.d.ts +2 -0
- package/lib/tester/printTestResult.js +32 -0
- package/lib/tester/printTestResult.js.map +1 -0
- package/lib/tester/types.d.ts +29 -0
- package/lib/tester/types.js +3 -0
- package/lib/tester/types.js.map +1 -0
- package/package.json +41 -13
- package/src/benchmark/index.spec.ts +375 -0
- package/src/benchmark/index.ts +433 -0
- package/src/builder/index.spec.ts +822 -0
- package/src/builder/index.ts +920 -0
- package/src/cli/index.spec.ts +54 -0
- package/src/cli/index.ts +150 -0
- package/src/config/index.spec.ts +70 -0
- package/src/config/index.ts +259 -0
- package/src/create/index.spec.ts +272 -0
- package/src/create/index.ts +295 -0
- package/src/datasource/filesystemAdapter.ts +313 -0
- package/src/datasource/index.ts +135 -0
- package/src/error.ts +33 -0
- package/src/evaluate/cli.spec.ts +368 -0
- package/src/evaluate/cli.ts +130 -0
- package/src/evaluate/index.ts +161 -0
- package/src/examples/coerceExampleIsoDates.spec.ts +81 -0
- package/src/examples/coerceExampleIsoDates.ts +98 -0
- package/src/examples/index.spec.ts +453 -0
- package/src/examples/index.ts +854 -0
- package/src/exporter/index.spec.ts +443 -0
- package/src/exporter/index.ts +643 -0
- package/src/find-duplicates/index.spec.ts +289 -0
- package/src/find-duplicates/index.ts +314 -0
- package/src/generate-code/index.ts +92 -0
- package/src/generate-code/typescript.spec.ts +241 -0
- package/src/generate-code/typescript.ts +284 -0
- package/src/importer/index.spec.ts +1101 -0
- package/src/importer/index.ts +1190 -0
- package/src/index.ts +18 -0
- package/src/info/index.ts +67 -0
- package/src/init/index.spec.ts +279 -0
- package/src/init/index.ts +292 -0
- package/src/lint/index.ts +1 -0
- package/src/linter/attributeSchema.ts +38 -0
- package/src/linter/checkLocaleCircularDependency.ts +51 -0
- package/src/linter/conditionSchema.ts +386 -0
- package/src/linter/formatSchema.ts +170 -0
- package/src/linter/icuStyleLint.ts +312 -0
- package/src/linter/index.spec.ts +824 -0
- package/src/linter/index.ts +460 -0
- package/src/linter/localeSchema.ts +70 -0
- package/src/linter/messageSchema.ts +152 -0
- package/src/linter/printError.ts +52 -0
- package/src/linter/schema.ts +230 -0
- package/src/linter/segmentSchema.ts +15 -0
- package/src/linter/targetSchema.ts +50 -0
- package/src/linter/testSchema.spec.ts +405 -0
- package/src/linter/testSchema.ts +239 -0
- package/src/linter/zodHelpers.ts +16 -0
- package/src/list/index.spec.ts +431 -0
- package/src/list/index.ts +463 -0
- package/src/matrix.ts +69 -0
- package/src/promoter/index.spec.ts +584 -0
- package/src/promoter/index.ts +1267 -0
- package/src/prune/index.spec.ts +418 -0
- package/src/prune/index.ts +693 -0
- package/src/sets.ts +74 -0
- package/src/tester/cliFormat.ts +11 -0
- package/src/tester/featurevisorIntegration.spec.ts +101 -0
- package/src/tester/index.spec.ts +577 -0
- package/src/tester/index.ts +679 -0
- package/src/tester/matrix.ts +106 -0
- package/src/tester/prettyDuration.ts +34 -0
- package/src/tester/printTestResult.ts +40 -0
- package/src/tester/types.ts +32 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.typecheck.json +4 -0
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import * as crypto from "crypto";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
DatafileContent,
|
|
7
|
+
Condition,
|
|
8
|
+
Context,
|
|
9
|
+
FormatPresets,
|
|
10
|
+
GroupSegment,
|
|
11
|
+
Locale,
|
|
12
|
+
Message,
|
|
13
|
+
MessageOverride,
|
|
14
|
+
Target,
|
|
15
|
+
Segment,
|
|
16
|
+
SegmentKey,
|
|
17
|
+
} from "@messagevisor/types";
|
|
18
|
+
|
|
19
|
+
import { SCHEMA_VERSION, ProjectConfig, formatDatafilePath } from "../config";
|
|
20
|
+
import { Datasource } from "../datasource";
|
|
21
|
+
import { evaluateCondition } from "../evaluate";
|
|
22
|
+
import { assertProjectSetJsonSelection, getProjectSetExecutions } from "../sets";
|
|
23
|
+
import { CLI_FORMAT_BOLD, CLI_FORMAT_GREEN } from "../tester/cliFormat";
|
|
24
|
+
import { prettyDuration } from "../tester/prettyDuration";
|
|
25
|
+
|
|
26
|
+
interface TargetDatafileOptions {
|
|
27
|
+
stringify: boolean;
|
|
28
|
+
pretty: boolean;
|
|
29
|
+
revisionFromHash: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BuildProjectOptions {
|
|
33
|
+
target?: string;
|
|
34
|
+
locale?: string;
|
|
35
|
+
revision?: string;
|
|
36
|
+
noStateFiles?: boolean;
|
|
37
|
+
json?: boolean;
|
|
38
|
+
pretty?: boolean;
|
|
39
|
+
showSize?: boolean;
|
|
40
|
+
onProgress?: (event: BuildProgressEvent) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BuildProjectSetsOptions extends BuildProjectOptions {
|
|
44
|
+
set?: string;
|
|
45
|
+
onProjectSetsProgress?: (event: BuildProjectSetsProgressEvent) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type BuildProgressEvent =
|
|
49
|
+
| { type: "start"; previousRevision: string; revision: string; targets: string[] }
|
|
50
|
+
| { type: "targetStart"; target: string; locales: string[] }
|
|
51
|
+
| {
|
|
52
|
+
type: "localeBuilt";
|
|
53
|
+
target: string;
|
|
54
|
+
locale: string;
|
|
55
|
+
datafile: DatafileContent;
|
|
56
|
+
filePath?: string;
|
|
57
|
+
sizeInBytes?: number;
|
|
58
|
+
}
|
|
59
|
+
| { type: "complete"; datafiles: DatafileContent[]; duration: number; revision: string };
|
|
60
|
+
|
|
61
|
+
export type BuildProjectSetsProgressEvent =
|
|
62
|
+
| { type: "setsStart"; previousRevision: string; revision: string; sets: string[] }
|
|
63
|
+
| { type: "setStart"; set: string }
|
|
64
|
+
| ({ set: string } & BuildProgressEvent)
|
|
65
|
+
| {
|
|
66
|
+
type: "setsComplete";
|
|
67
|
+
duration: number;
|
|
68
|
+
revision: string;
|
|
69
|
+
sets: string[];
|
|
70
|
+
datafiles: DatafileContent[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
74
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function deepMerge<T>(parent?: T, child?: T): T | undefined {
|
|
78
|
+
if (typeof parent === "undefined") {
|
|
79
|
+
return child;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof child === "undefined") {
|
|
83
|
+
return parent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!isPlainObject(parent) || !isPlainObject(child)) {
|
|
87
|
+
return child;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result: Record<string, unknown> = { ...parent };
|
|
91
|
+
|
|
92
|
+
for (const key of Object.keys(child)) {
|
|
93
|
+
result[key] = deepMerge(result[key], child[key]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result as T;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function mergeFormats(
|
|
100
|
+
parent?: FormatPresets,
|
|
101
|
+
child?: FormatPresets,
|
|
102
|
+
): FormatPresets | undefined {
|
|
103
|
+
return deepMerge(parent, child);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function matchesPattern(key: string, patterns?: string[]) {
|
|
107
|
+
if (!patterns || patterns.length === 0) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return patterns.some((pattern) => {
|
|
112
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
113
|
+
return new RegExp(`^${escaped}$`).test(key);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isAvailable<T extends { archived?: boolean }>(entity: T) {
|
|
118
|
+
return !entity.archived;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function readAll<T>(
|
|
122
|
+
keys: string[],
|
|
123
|
+
read: (key: string) => Promise<T>,
|
|
124
|
+
): Promise<Record<string, T>> {
|
|
125
|
+
const entries = await Promise.all(keys.map(async (key) => [key, await read(key)] as const));
|
|
126
|
+
return Object.fromEntries(entries);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveLocaleChain(
|
|
130
|
+
localeKey: string,
|
|
131
|
+
locales: Record<string, Locale>,
|
|
132
|
+
field: "inheritFormatsFrom" | "inheritTranslationsFrom",
|
|
133
|
+
) {
|
|
134
|
+
const chain: string[] = [];
|
|
135
|
+
const seen = new Set<string>();
|
|
136
|
+
let currentKey: string | undefined = localeKey;
|
|
137
|
+
|
|
138
|
+
while (currentKey && !seen.has(currentKey)) {
|
|
139
|
+
seen.add(currentKey);
|
|
140
|
+
chain.unshift(currentKey);
|
|
141
|
+
currentKey = locales[currentKey]?.[field];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return chain;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function resolveFormats(
|
|
148
|
+
localeKey: string,
|
|
149
|
+
locales: Record<string, Locale>,
|
|
150
|
+
target?: Target,
|
|
151
|
+
): FormatPresets | undefined {
|
|
152
|
+
const chain = resolveLocaleChain(localeKey, locales, "inheritFormatsFrom");
|
|
153
|
+
let formats: FormatPresets | undefined;
|
|
154
|
+
|
|
155
|
+
for (const key of chain) {
|
|
156
|
+
formats = mergeFormats(formats, locales[key]?.formats);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return mergeFormats(formats, target?.formats?.[localeKey]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveLocaleValue<T>(
|
|
163
|
+
values: Record<string, T> | undefined,
|
|
164
|
+
localeKey: string,
|
|
165
|
+
locales: Record<string, Locale>,
|
|
166
|
+
) {
|
|
167
|
+
const chain = resolveLocaleChain(localeKey, locales, "inheritTranslationsFrom");
|
|
168
|
+
const candidates = chain.reverse();
|
|
169
|
+
|
|
170
|
+
for (const candidate of candidates) {
|
|
171
|
+
if (values && typeof values[candidate] !== "undefined") {
|
|
172
|
+
return values[candidate];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function collectSegmentKeys(
|
|
178
|
+
segments: GroupSegment | GroupSegment[] | "*" | undefined,
|
|
179
|
+
result: Set<SegmentKey>,
|
|
180
|
+
) {
|
|
181
|
+
if (!segments || segments === "*") {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (typeof segments === "string") {
|
|
186
|
+
result.add(segments);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (Array.isArray(segments)) {
|
|
191
|
+
for (const segment of segments) {
|
|
192
|
+
collectSegmentKeys(segment, result);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if ("and" in segments) {
|
|
199
|
+
collectSegmentKeys(segments.and, result);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if ("or" in segments) {
|
|
204
|
+
collectSegmentKeys(segments.or, result);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if ("not" in segments) {
|
|
209
|
+
collectSegmentKeys(segments.not, result);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function collectConditionSegmentKeys(
|
|
214
|
+
conditions: Condition | Condition[] | "*" | undefined,
|
|
215
|
+
result: Set<SegmentKey>,
|
|
216
|
+
) {
|
|
217
|
+
if (!conditions || conditions === "*") {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof conditions === "string") {
|
|
222
|
+
result.add(conditions);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (Array.isArray(conditions)) {
|
|
227
|
+
for (const condition of conditions) {
|
|
228
|
+
collectConditionSegmentKeys(condition, result);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if ("attribute" in conditions) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if ("and" in conditions) {
|
|
239
|
+
collectConditionSegmentKeys(conditions.and, result);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if ("or" in conditions) {
|
|
244
|
+
collectConditionSegmentKeys(conditions.or, result);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if ("not" in conditions) {
|
|
249
|
+
collectConditionSegmentKeys(conditions.not, result);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveTargetDatafileOptions(target?: Target): TargetDatafileOptions {
|
|
254
|
+
return {
|
|
255
|
+
stringify: target?.stringify !== false,
|
|
256
|
+
pretty: target?.pretty === true,
|
|
257
|
+
revisionFromHash: target?.revisionFromHash === true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function getDatafileSizeInBytes(datafile: DatafileContent, options: TargetDatafileOptions) {
|
|
262
|
+
return Buffer.byteLength(
|
|
263
|
+
options.pretty ? JSON.stringify(datafile, null, 2) : JSON.stringify(datafile),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function stringifyDatafileExpression<T>(options: TargetDatafileOptions, value: T): T | string {
|
|
268
|
+
if (!options.stringify || typeof value === "undefined" || typeof value === "string") {
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return JSON.stringify(value);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
type TargetedResult<T> = { state: "true" } | { state: "false" } | { state: "partial"; value: T };
|
|
276
|
+
|
|
277
|
+
function hasContextValue(context: Context | undefined, attribute: string) {
|
|
278
|
+
if (!context) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let current: any = context;
|
|
283
|
+
|
|
284
|
+
for (const part of attribute.split(".")) {
|
|
285
|
+
if (
|
|
286
|
+
!current ||
|
|
287
|
+
typeof current !== "object" ||
|
|
288
|
+
!Object.prototype.hasOwnProperty.call(current, part)
|
|
289
|
+
) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
current = current[part];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function simplifyAnd<T>(items: TargetedResult<T>[], create: (items: T[]) => T): TargetedResult<T> {
|
|
300
|
+
if (items.some((item) => item.state === "false")) {
|
|
301
|
+
return { state: "false" };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const partials = items
|
|
305
|
+
.filter((item): item is { state: "partial"; value: T } => item.state === "partial")
|
|
306
|
+
.map((item) => item.value);
|
|
307
|
+
|
|
308
|
+
if (partials.length === 0) {
|
|
309
|
+
return { state: "true" };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (partials.length === 1) {
|
|
313
|
+
return { state: "partial", value: partials[0] };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { state: "partial", value: create(partials) };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function simplifyOr<T>(items: TargetedResult<T>[], create: (items: T[]) => T): TargetedResult<T> {
|
|
320
|
+
if (items.some((item) => item.state === "true")) {
|
|
321
|
+
return { state: "true" };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const partials = items
|
|
325
|
+
.filter((item): item is { state: "partial"; value: T } => item.state === "partial")
|
|
326
|
+
.map((item) => item.value);
|
|
327
|
+
|
|
328
|
+
if (partials.length === 0) {
|
|
329
|
+
return { state: "false" };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (partials.length === 1) {
|
|
333
|
+
return { state: "partial", value: partials[0] };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { state: "partial", value: create(partials) };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function simplifyNot<T>(items: TargetedResult<T>[], create: (items: T[]) => T): TargetedResult<T> {
|
|
340
|
+
if (items.some((item) => item.state === "false")) {
|
|
341
|
+
return { state: "true" };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const partials = items
|
|
345
|
+
.filter((item): item is { state: "partial"; value: T } => item.state === "partial")
|
|
346
|
+
.map((item) => item.value);
|
|
347
|
+
|
|
348
|
+
if (partials.length === 0) {
|
|
349
|
+
return { state: "false" };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { state: "partial", value: create(partials) };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function createTargetSimplifier(segments: Record<string, Segment>, context?: Context) {
|
|
356
|
+
const targetedSegments: Record<string, Segment> = {};
|
|
357
|
+
const segmentConditionResults: Record<string, TargetedResult<Condition | Condition[]>> = {};
|
|
358
|
+
|
|
359
|
+
function simplifyCondition(
|
|
360
|
+
condition: Condition | Condition[],
|
|
361
|
+
): TargetedResult<Condition | Condition[]> {
|
|
362
|
+
if (Array.isArray(condition)) {
|
|
363
|
+
return simplifyAnd(
|
|
364
|
+
condition.map((item) => simplifyCondition(item)),
|
|
365
|
+
(items) => items as Condition[],
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (typeof condition === "string") {
|
|
370
|
+
return simplifyGroupSegment(condition);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if ("and" in condition) {
|
|
374
|
+
return simplifyAnd(
|
|
375
|
+
condition.and.map((item) => simplifyCondition(item)),
|
|
376
|
+
(items) => ({ and: items as Condition[] }),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if ("or" in condition) {
|
|
381
|
+
return simplifyOr(
|
|
382
|
+
condition.or.map((item) => simplifyCondition(item)),
|
|
383
|
+
(items) => ({ or: items as Condition[] }),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if ("not" in condition) {
|
|
388
|
+
return simplifyNot(
|
|
389
|
+
condition.not.map((item) => simplifyCondition(item)),
|
|
390
|
+
(items) => ({ not: items as Condition[] }),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!("attribute" in condition) || !hasContextValue(context, condition.attribute)) {
|
|
395
|
+
return { state: "partial", value: condition };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return evaluateCondition(condition, { context, segments })
|
|
399
|
+
? { state: "true" }
|
|
400
|
+
: { state: "false" };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function simplifySegmentCondition(segmentKey: string): TargetedResult<Condition | Condition[]> {
|
|
404
|
+
if (segmentConditionResults[segmentKey]) {
|
|
405
|
+
return segmentConditionResults[segmentKey];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const segment = segments[segmentKey];
|
|
409
|
+
|
|
410
|
+
if (!segment || segment.archived) {
|
|
411
|
+
segmentConditionResults[segmentKey] = { state: "false" };
|
|
412
|
+
return segmentConditionResults[segmentKey];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const result = simplifyCondition(segment.conditions);
|
|
416
|
+
segmentConditionResults[segmentKey] = result;
|
|
417
|
+
|
|
418
|
+
if (result.state === "partial") {
|
|
419
|
+
const {
|
|
420
|
+
key: _key,
|
|
421
|
+
description: _description,
|
|
422
|
+
promotable: _promotable,
|
|
423
|
+
...segmentForDatafile
|
|
424
|
+
} = segment;
|
|
425
|
+
targetedSegments[segmentKey] = {
|
|
426
|
+
...segmentForDatafile,
|
|
427
|
+
conditions: result.value,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function simplifyGroupSegment(
|
|
435
|
+
groupSegment: GroupSegment | GroupSegment[],
|
|
436
|
+
): TargetedResult<GroupSegment | GroupSegment[]> {
|
|
437
|
+
if (Array.isArray(groupSegment)) {
|
|
438
|
+
return simplifyAnd(
|
|
439
|
+
groupSegment.map((item) => simplifyGroupSegment(item)),
|
|
440
|
+
(items) => items as GroupSegment[],
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (typeof groupSegment === "string") {
|
|
445
|
+
const segmentResult = simplifySegmentCondition(groupSegment);
|
|
446
|
+
|
|
447
|
+
if (segmentResult.state !== "partial") {
|
|
448
|
+
return segmentResult;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return { state: "partial", value: groupSegment };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if ("and" in groupSegment) {
|
|
455
|
+
return simplifyAnd(
|
|
456
|
+
groupSegment.and.map((item) => simplifyGroupSegment(item)),
|
|
457
|
+
(items) => ({ and: items as GroupSegment[] }),
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if ("or" in groupSegment) {
|
|
462
|
+
return simplifyOr(
|
|
463
|
+
groupSegment.or.map((item) => simplifyGroupSegment(item)),
|
|
464
|
+
(items) => ({ or: items as GroupSegment[] }),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return simplifyNot(
|
|
469
|
+
groupSegment.not.map((item) => simplifyGroupSegment(item)),
|
|
470
|
+
(items) => ({ not: items as GroupSegment[] }),
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
targetedSegments,
|
|
476
|
+
simplifyCondition,
|
|
477
|
+
simplifyGroupSegment,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function buildDatafileFromMessageKeys(
|
|
482
|
+
projectConfig: ProjectConfig,
|
|
483
|
+
datasource: Datasource,
|
|
484
|
+
messageKeys: string[],
|
|
485
|
+
targetKey: string | undefined,
|
|
486
|
+
localeKey: string,
|
|
487
|
+
revision: string,
|
|
488
|
+
): Promise<DatafileContent> {
|
|
489
|
+
const localeKeys = await datasource.listLocales();
|
|
490
|
+
|
|
491
|
+
const [locales, messages] = await Promise.all([
|
|
492
|
+
readAll<Locale>(localeKeys, (key) => datasource.readLocale(key)),
|
|
493
|
+
readAll<Message>(messageKeys, (key) => datasource.readMessage(key)),
|
|
494
|
+
]);
|
|
495
|
+
|
|
496
|
+
const target = targetKey ? await datasource.readTarget(targetKey) : undefined;
|
|
497
|
+
const datafileOptions = resolveTargetDatafileOptions(target);
|
|
498
|
+
const includedMessages = target?.includeMessages?.length ? target.includeMessages : ["*"];
|
|
499
|
+
const excludedMessages = target?.excludeMessages || [];
|
|
500
|
+
const datafileMessages: DatafileContent["messages"] = {};
|
|
501
|
+
const translations: DatafileContent["translations"] = {};
|
|
502
|
+
const usedSegmentKeys = new Set<SegmentKey>();
|
|
503
|
+
const segmentKeys = await datasource.listSegments();
|
|
504
|
+
const segments = await readAll<Segment>(segmentKeys, (key) => datasource.readSegment(key));
|
|
505
|
+
const targetSimplifier = createTargetSimplifier(segments, target?.context);
|
|
506
|
+
|
|
507
|
+
for (const key of messageKeys) {
|
|
508
|
+
const message = messages[key];
|
|
509
|
+
|
|
510
|
+
if (!isAvailable(message)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (!matchesPattern(key, includedMessages) || matchesPattern(key, excludedMessages)) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const translation = resolveLocaleValue(message.translations, localeKey, locales);
|
|
519
|
+
|
|
520
|
+
if (typeof translation === "undefined") {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
translations[key] = translation;
|
|
525
|
+
|
|
526
|
+
const overrides = (message.overrides || [])
|
|
527
|
+
.map<MessageOverride | undefined>((override) => {
|
|
528
|
+
const overrideTranslation = resolveLocaleValue(override.translations, localeKey, locales);
|
|
529
|
+
|
|
530
|
+
if (typeof overrideTranslation === "undefined") {
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const targetedOverride: MessageOverride = {
|
|
535
|
+
key: override.key,
|
|
536
|
+
translation: overrideTranslation,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
if (override.conditions) {
|
|
540
|
+
if (override.conditions === "*") {
|
|
541
|
+
targetedOverride.conditions = override.conditions;
|
|
542
|
+
} else {
|
|
543
|
+
const targetedConditions = targetSimplifier.simplifyCondition(override.conditions);
|
|
544
|
+
|
|
545
|
+
if (targetedConditions.state === "false") {
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (targetedConditions.state === "partial") {
|
|
550
|
+
collectConditionSegmentKeys(targetedConditions.value, usedSegmentKeys);
|
|
551
|
+
targetedOverride.conditions = stringifyDatafileExpression(
|
|
552
|
+
datafileOptions,
|
|
553
|
+
targetedConditions.value,
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (override.segments) {
|
|
560
|
+
if (override.segments === "*") {
|
|
561
|
+
targetedOverride.segments = override.segments;
|
|
562
|
+
} else {
|
|
563
|
+
const targetedSegments = targetSimplifier.simplifyGroupSegment(override.segments);
|
|
564
|
+
|
|
565
|
+
if (targetedSegments.state === "false") {
|
|
566
|
+
return undefined;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (targetedSegments.state === "partial") {
|
|
570
|
+
collectSegmentKeys(targetedSegments.value, usedSegmentKeys);
|
|
571
|
+
targetedOverride.segments = stringifyDatafileExpression(
|
|
572
|
+
datafileOptions,
|
|
573
|
+
targetedSegments.value,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return targetedOverride;
|
|
580
|
+
})
|
|
581
|
+
.filter((override): override is MessageOverride => Boolean(override));
|
|
582
|
+
|
|
583
|
+
if (message.deprecated || message.deprecationWarning || message.meta || overrides.length > 0) {
|
|
584
|
+
datafileMessages[key] = {
|
|
585
|
+
deprecated: message.deprecated || undefined,
|
|
586
|
+
deprecationWarning: message.deprecationWarning,
|
|
587
|
+
meta: message.meta,
|
|
588
|
+
overrides: overrides.length > 0 ? overrides : undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const datafileSegments: Record<string, Segment> = {};
|
|
594
|
+
const datafileSegmentKeys = Array.from(usedSegmentKeys).sort();
|
|
595
|
+
|
|
596
|
+
for (const key of datafileSegmentKeys) {
|
|
597
|
+
if (targetSimplifier.targetedSegments[key]) {
|
|
598
|
+
const segment = targetSimplifier.targetedSegments[key];
|
|
599
|
+
|
|
600
|
+
datafileSegments[key] = {
|
|
601
|
+
...segment,
|
|
602
|
+
conditions: stringifyDatafileExpression(datafileOptions, segment.conditions),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
schemaVersion: SCHEMA_VERSION,
|
|
609
|
+
messagevisorVersion: "0.0.1",
|
|
610
|
+
revision,
|
|
611
|
+
target: targetKey || "",
|
|
612
|
+
locale: localeKey,
|
|
613
|
+
direction: locales[localeKey]?.direction,
|
|
614
|
+
formats: resolveFormats(localeKey, locales, target),
|
|
615
|
+
segments: datafileSegments,
|
|
616
|
+
messages: datafileMessages,
|
|
617
|
+
translations,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export async function buildDatafile(
|
|
622
|
+
projectConfig: ProjectConfig,
|
|
623
|
+
datasource: Datasource,
|
|
624
|
+
targetKey: string | undefined,
|
|
625
|
+
localeKey: string,
|
|
626
|
+
revision: string,
|
|
627
|
+
): Promise<DatafileContent> {
|
|
628
|
+
const messageKeys = await datasource.listMessages();
|
|
629
|
+
|
|
630
|
+
return buildDatafileFromMessageKeys(
|
|
631
|
+
projectConfig,
|
|
632
|
+
datasource,
|
|
633
|
+
messageKeys,
|
|
634
|
+
targetKey,
|
|
635
|
+
localeKey,
|
|
636
|
+
revision,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export async function buildMessageDatafile(
|
|
641
|
+
projectConfig: ProjectConfig,
|
|
642
|
+
datasource: Datasource,
|
|
643
|
+
messageKey: string,
|
|
644
|
+
localeKey: string,
|
|
645
|
+
revision: string,
|
|
646
|
+
targetKey?: string,
|
|
647
|
+
): Promise<DatafileContent> {
|
|
648
|
+
const availableMessageKeys = await datasource.listMessages();
|
|
649
|
+
const selectedMessageKeys = availableMessageKeys.includes(messageKey) ? [messageKey] : [];
|
|
650
|
+
|
|
651
|
+
return buildDatafileFromMessageKeys(
|
|
652
|
+
projectConfig,
|
|
653
|
+
datasource,
|
|
654
|
+
selectedMessageKeys,
|
|
655
|
+
targetKey,
|
|
656
|
+
localeKey,
|
|
657
|
+
revision,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export async function buildProject(
|
|
662
|
+
projectConfig: ProjectConfig,
|
|
663
|
+
datasource: Datasource,
|
|
664
|
+
options: BuildProjectOptions = {},
|
|
665
|
+
) {
|
|
666
|
+
const startTime = Date.now();
|
|
667
|
+
const [targetKeys, localeKeys] = await Promise.all([
|
|
668
|
+
datasource.listTargets(),
|
|
669
|
+
datasource.listLocales(),
|
|
670
|
+
]);
|
|
671
|
+
const selectedTargetKeys = options.target ? [options.target] : targetKeys;
|
|
672
|
+
const builtDatafiles: DatafileContent[] = [];
|
|
673
|
+
const previousRevision = await datasource.readRevision();
|
|
674
|
+
let revision = options.revision;
|
|
675
|
+
|
|
676
|
+
if (!revision) {
|
|
677
|
+
const numericRevision = Number(previousRevision);
|
|
678
|
+
revision = Number.isNaN(numericRevision) ? previousRevision : String(numericRevision + 1);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
options.onProgress?.({
|
|
682
|
+
type: "start",
|
|
683
|
+
previousRevision,
|
|
684
|
+
revision,
|
|
685
|
+
targets: selectedTargetKeys,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
for (const targetKey of selectedTargetKeys) {
|
|
689
|
+
const target = await datasource.readTarget(targetKey);
|
|
690
|
+
const selectedLocaleKeys = options.locale
|
|
691
|
+
? [options.locale]
|
|
692
|
+
: target.locales?.length
|
|
693
|
+
? target.locales
|
|
694
|
+
: localeKeys;
|
|
695
|
+
|
|
696
|
+
options.onProgress?.({
|
|
697
|
+
type: "targetStart",
|
|
698
|
+
target: targetKey,
|
|
699
|
+
locales: selectedLocaleKeys,
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
for (const localeKey of selectedLocaleKeys) {
|
|
703
|
+
let datafile = await buildDatafile(projectConfig, datasource, targetKey, localeKey, revision);
|
|
704
|
+
const datafileOptions = resolveTargetDatafileOptions(target);
|
|
705
|
+
|
|
706
|
+
if (datafileOptions.revisionFromHash) {
|
|
707
|
+
const content = JSON.stringify({ ...datafile, revision: "" });
|
|
708
|
+
datafile = {
|
|
709
|
+
...datafile,
|
|
710
|
+
revision: crypto.createHash("sha1").update(content).digest("hex"),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (options.json) {
|
|
715
|
+
const pretty = options.pretty === true || datafileOptions.pretty;
|
|
716
|
+
console.log(pretty ? JSON.stringify(datafile, null, 2) : JSON.stringify(datafile));
|
|
717
|
+
} else {
|
|
718
|
+
await datasource.writeDatafile(datafile, { pretty: datafileOptions.pretty });
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
builtDatafiles.push(datafile);
|
|
722
|
+
|
|
723
|
+
options.onProgress?.({
|
|
724
|
+
type: "localeBuilt",
|
|
725
|
+
target: targetKey,
|
|
726
|
+
locale: localeKey,
|
|
727
|
+
datafile,
|
|
728
|
+
filePath: path.join(
|
|
729
|
+
projectConfig.datafilesDirectoryPath,
|
|
730
|
+
formatDatafilePath(projectConfig, targetKey, localeKey),
|
|
731
|
+
),
|
|
732
|
+
sizeInBytes: options.showSize
|
|
733
|
+
? getDatafileSizeInBytes(datafile, datafileOptions)
|
|
734
|
+
: undefined,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (!options.noStateFiles && !options.revision) {
|
|
740
|
+
await datasource.writeRevision(revision);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
options.onProgress?.({
|
|
744
|
+
type: "complete",
|
|
745
|
+
datafiles: builtDatafiles,
|
|
746
|
+
duration: Date.now() - startTime,
|
|
747
|
+
revision,
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
return builtDatafiles;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export async function buildProjectSets(
|
|
754
|
+
projectConfig: ProjectConfig,
|
|
755
|
+
datasource: Datasource,
|
|
756
|
+
options: BuildProjectSetsOptions = {},
|
|
757
|
+
) {
|
|
758
|
+
const startTime = Date.now();
|
|
759
|
+
const setExecutions = await getProjectSetExecutions(projectConfig, datasource, options.set);
|
|
760
|
+
const setKeys = setExecutions.map((execution) => execution.set);
|
|
761
|
+
const builtDatafiles: DatafileContent[] = [];
|
|
762
|
+
const previousRevision = await datasource.readRevision();
|
|
763
|
+
const numericRevision = Number(previousRevision);
|
|
764
|
+
const revision = Number.isNaN(numericRevision) ? previousRevision : String(numericRevision + 1);
|
|
765
|
+
|
|
766
|
+
if (projectConfig.sets) {
|
|
767
|
+
options.onProjectSetsProgress?.({
|
|
768
|
+
type: "setsStart",
|
|
769
|
+
previousRevision,
|
|
770
|
+
revision,
|
|
771
|
+
sets: setKeys,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
for (const execution of setExecutions) {
|
|
776
|
+
if (projectConfig.sets) {
|
|
777
|
+
options.onProjectSetsProgress?.({ type: "setStart", set: execution.set });
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const datafiles = await buildProject(execution.projectConfig, execution.datasource, {
|
|
781
|
+
...options,
|
|
782
|
+
onProgress: options.onProjectSetsProgress
|
|
783
|
+
? (event) =>
|
|
784
|
+
options.onProjectSetsProgress?.({
|
|
785
|
+
...event,
|
|
786
|
+
set: execution.set,
|
|
787
|
+
} as BuildProjectSetsProgressEvent)
|
|
788
|
+
: options.onProgress,
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
builtDatafiles.push(...datafiles);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (projectConfig.sets && !options.noStateFiles && !options.revision) {
|
|
795
|
+
await datasource.writeRevision(revision);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (projectConfig.sets) {
|
|
799
|
+
options.onProjectSetsProgress?.({
|
|
800
|
+
type: "setsComplete",
|
|
801
|
+
duration: Date.now() - startTime,
|
|
802
|
+
revision,
|
|
803
|
+
sets: setKeys,
|
|
804
|
+
datafiles: builtDatafiles,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return builtDatafiles;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function printBuildProgress(projectConfig: ProjectConfig, event: BuildProgressEvent) {
|
|
812
|
+
if (event.type === "start") {
|
|
813
|
+
console.log("");
|
|
814
|
+
console.log(CLI_FORMAT_BOLD, `Building Messagevisor datafiles`);
|
|
815
|
+
console.log(` Starting revision: ${event.previousRevision}`);
|
|
816
|
+
console.log(` Targets: ${event.targets.join(", ") || "(none)"}`);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (event.type === "targetStart") {
|
|
821
|
+
console.log("");
|
|
822
|
+
console.log(CLI_FORMAT_BOLD, `Target "${event.target}"`);
|
|
823
|
+
console.log(` Locales: ${event.locales.join(", ") || "(none)"}`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (event.type === "localeBuilt") {
|
|
828
|
+
const relativeFilePath = event.filePath
|
|
829
|
+
? path.relative(process.cwd(), event.filePath)
|
|
830
|
+
: formatDatafilePath(projectConfig, event.target, event.locale);
|
|
831
|
+
const messageCount = Object.keys(event.datafile.translations).length;
|
|
832
|
+
const metadataCount = Object.keys(event.datafile.messages).length;
|
|
833
|
+
const segmentCount = Object.keys(event.datafile.segments).length;
|
|
834
|
+
const size =
|
|
835
|
+
typeof event.sizeInBytes === "number" ? `, ${(event.sizeInBytes / 1024).toFixed(2)} kB` : "";
|
|
836
|
+
|
|
837
|
+
console.log(
|
|
838
|
+
` ✔ ${event.locale} -> ${relativeFilePath} (${messageCount} translations, ${metadataCount} metadata entries, ${segmentCount} segments${size})`,
|
|
839
|
+
);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
console.log("");
|
|
844
|
+
console.log(
|
|
845
|
+
CLI_FORMAT_GREEN,
|
|
846
|
+
`Built ${event.datafiles.length} datafile(s) in ${path.relative(process.cwd(), projectConfig.datafilesDirectoryPath) || projectConfig.datafilesDirectoryPath}`,
|
|
847
|
+
);
|
|
848
|
+
console.log(CLI_FORMAT_BOLD, `Revision: ${event.revision}`);
|
|
849
|
+
console.log(CLI_FORMAT_BOLD, `Time: ${prettyDuration(event.duration)}`);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function printProjectSetsBuildProgress(
|
|
853
|
+
projectConfig: ProjectConfig,
|
|
854
|
+
event: BuildProjectSetsProgressEvent,
|
|
855
|
+
) {
|
|
856
|
+
if (event.type === "setsStart") {
|
|
857
|
+
console.log("");
|
|
858
|
+
console.log(CLI_FORMAT_BOLD, `Building Messagevisor sets`);
|
|
859
|
+
console.log(` Starting project revision: ${event.previousRevision}`);
|
|
860
|
+
console.log(` Sets: ${event.sets.join(", ") || "(none)"}`);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (event.type === "setStart") {
|
|
865
|
+
console.log("");
|
|
866
|
+
console.log(CLI_FORMAT_BOLD, `Set "${event.set}"`);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (event.type === "setsComplete") {
|
|
871
|
+
console.log("");
|
|
872
|
+
console.log(
|
|
873
|
+
CLI_FORMAT_GREEN,
|
|
874
|
+
`Built ${event.datafiles.length} datafile(s) across ${event.sets.length} set(s) in ${path.relative(process.cwd(), projectConfig.datafilesDirectoryPath) || projectConfig.datafilesDirectoryPath}`,
|
|
875
|
+
);
|
|
876
|
+
console.log(CLI_FORMAT_BOLD, `Project revision: ${event.revision}`);
|
|
877
|
+
console.log(CLI_FORMAT_BOLD, `Time: ${prettyDuration(event.duration)}`);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
printBuildProgress(
|
|
882
|
+
projectConfig.sets
|
|
883
|
+
? {
|
|
884
|
+
...projectConfig,
|
|
885
|
+
datafilesDirectoryPath: path.join(projectConfig.datafilesDirectoryPath, event.set),
|
|
886
|
+
}
|
|
887
|
+
: projectConfig,
|
|
888
|
+
event,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export const buildPlugin = {
|
|
893
|
+
command: "build",
|
|
894
|
+
handler: async ({ projectConfig, datasource, parsed }: any) => {
|
|
895
|
+
assertProjectSetJsonSelection(projectConfig, parsed.set, parsed.json);
|
|
896
|
+
|
|
897
|
+
const datafiles = await buildProjectSets(projectConfig, datasource, {
|
|
898
|
+
set: parsed.set,
|
|
899
|
+
target: parsed.target,
|
|
900
|
+
locale: parsed.locale,
|
|
901
|
+
revision: parsed.revision,
|
|
902
|
+
noStateFiles: parsed.json || parsed.stateFiles === false,
|
|
903
|
+
json: parsed.json,
|
|
904
|
+
pretty: parsed.pretty,
|
|
905
|
+
showSize: parsed.showSize,
|
|
906
|
+
onProgress: parsed.json ? undefined : (event) => printBuildProgress(projectConfig, event),
|
|
907
|
+
onProjectSetsProgress: parsed.json
|
|
908
|
+
? undefined
|
|
909
|
+
: (event) => printProjectSetsBuildProgress(projectConfig, event),
|
|
910
|
+
});
|
|
911
|
+
},
|
|
912
|
+
examples: [
|
|
913
|
+
{ command: "build", description: "build datafiles" },
|
|
914
|
+
{
|
|
915
|
+
command: "build --target=web --locale=en-US",
|
|
916
|
+
description: "build a single target-specific locale datafile",
|
|
917
|
+
},
|
|
918
|
+
{ command: "build --showSize", description: "show datafile sizes in the build output" },
|
|
919
|
+
],
|
|
920
|
+
};
|