@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,463 @@
|
|
|
1
|
+
import type { Attribute, Locale, Message, Target, Segment, Test } from "@messagevisor/types";
|
|
2
|
+
|
|
3
|
+
import { MessagevisorCLIError, printMessagevisorCLIError } from "../error";
|
|
4
|
+
import { assertProjectSetJsonSelection, getProjectSetExecutions } from "../sets";
|
|
5
|
+
import {
|
|
6
|
+
expandLocaleAssertions,
|
|
7
|
+
expandMessageAssertions,
|
|
8
|
+
expandSegmentAssertions,
|
|
9
|
+
expandTargetAssertions,
|
|
10
|
+
} from "../tester/matrix";
|
|
11
|
+
|
|
12
|
+
type EntityType = "messages" | "locales" | "segments" | "attributes" | "targets" | "tests";
|
|
13
|
+
|
|
14
|
+
type ListableEntity = Message | Locale | Segment | Attribute | Target;
|
|
15
|
+
|
|
16
|
+
interface ParsedEntity<T> {
|
|
17
|
+
key: string;
|
|
18
|
+
entity: T;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface EntitiesWithTests {
|
|
22
|
+
messages: string[];
|
|
23
|
+
locales: string[];
|
|
24
|
+
segments: string[];
|
|
25
|
+
targets: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toArray(value: unknown): string[] {
|
|
29
|
+
if (typeof value === "undefined") {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Array.isArray(value) ? value : [String(value)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toRegex(pattern: string) {
|
|
37
|
+
return new RegExp(pattern, "i");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function matchesPattern(key: string, patterns?: string[]) {
|
|
41
|
+
if (!patterns || patterns.length === 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return patterns.some((pattern) => {
|
|
46
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
47
|
+
return new RegExp(`^${escaped}$`).test(key);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseBooleanOption(name: string, value: unknown): boolean {
|
|
52
|
+
if (value === true || value === "true") {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (value === false || value === "false") {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new MessagevisorCLIError(`Invalid ${name}: expected true or false`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function matchesOptionalBoolean(
|
|
64
|
+
optionValue: unknown,
|
|
65
|
+
actualValue: boolean | undefined,
|
|
66
|
+
optionName: string,
|
|
67
|
+
defaultValue = false,
|
|
68
|
+
) {
|
|
69
|
+
if (typeof optionValue === "undefined") {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (actualValue ?? defaultValue) === parseBooleanOption(optionName, optionValue);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hasFormats(formats: unknown) {
|
|
77
|
+
return typeof formats !== "undefined";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasContext(context: unknown) {
|
|
81
|
+
return typeof context !== "undefined";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasMeta(meta: unknown) {
|
|
85
|
+
return typeof meta !== "undefined";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasOverrides(overrides: unknown) {
|
|
89
|
+
return Array.isArray(overrides) && overrides.length > 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function getEntitiesWithTests(datasource: any): Promise<EntitiesWithTests> {
|
|
93
|
+
const messages = new Set<string>();
|
|
94
|
+
const locales = new Set<string>();
|
|
95
|
+
const segments = new Set<string>();
|
|
96
|
+
const targets = new Set<string>();
|
|
97
|
+
|
|
98
|
+
const testKeys = await datasource.listTests();
|
|
99
|
+
|
|
100
|
+
for (const testKey of testKeys) {
|
|
101
|
+
const test = (await datasource.readTest(testKey)) as Test;
|
|
102
|
+
|
|
103
|
+
if ("message" in test) {
|
|
104
|
+
messages.add(test.message);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ("locale" in test) {
|
|
109
|
+
locales.add(test.locale);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if ("segment" in test) {
|
|
114
|
+
segments.add(test.segment);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if ("target" in test) {
|
|
119
|
+
targets.add(test.target);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
messages: Array.from(messages).sort(),
|
|
125
|
+
locales: Array.from(locales).sort(),
|
|
126
|
+
segments: Array.from(segments).sort(),
|
|
127
|
+
targets: Array.from(targets).sort(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getSelectedEntityType(options: any): EntityType {
|
|
132
|
+
const selected = (
|
|
133
|
+
["messages", "locales", "segments", "attributes", "targets", "tests"] as EntityType[]
|
|
134
|
+
).filter((entityType) => Boolean(options[entityType]));
|
|
135
|
+
|
|
136
|
+
if (selected.length === 0) {
|
|
137
|
+
throw new MessagevisorCLIError(
|
|
138
|
+
"Nothing to list. Pass exactly one of --messages, --locales, --segments, --attributes, --targets, or --tests.",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (selected.length > 1) {
|
|
143
|
+
throw new MessagevisorCLIError(
|
|
144
|
+
"Pass exactly one of --messages, --locales, --segments, --attributes, --targets, or --tests.",
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return selected[0];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function validateFilters(entityType: EntityType, options: any) {
|
|
152
|
+
if ((options.withTests || options.withoutTests) && entityType === "attributes") {
|
|
153
|
+
throw new MessagevisorCLIError(
|
|
154
|
+
"--with-tests and --without-tests are not supported for attributes.",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function listEntityKeys(datasource: any, entityType: EntityType) {
|
|
160
|
+
if (entityType === "messages") return datasource.listMessages();
|
|
161
|
+
if (entityType === "locales") return datasource.listLocales();
|
|
162
|
+
if (entityType === "segments") return datasource.listSegments();
|
|
163
|
+
if (entityType === "attributes") return datasource.listAttributes();
|
|
164
|
+
if (entityType === "tests") return datasource.listTests();
|
|
165
|
+
return datasource.listTargets();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function readEntity(datasource: any, entityType: EntityType, key: string) {
|
|
169
|
+
if (entityType === "messages") return (await datasource.readMessage(key)) as Message;
|
|
170
|
+
if (entityType === "locales") return (await datasource.readLocale(key)) as Locale;
|
|
171
|
+
if (entityType === "segments") return (await datasource.readSegment(key)) as Segment;
|
|
172
|
+
if (entityType === "attributes") return (await datasource.readAttribute(key)) as Attribute;
|
|
173
|
+
if (entityType === "tests") return (await datasource.readTest(key)) as Test;
|
|
174
|
+
return (await datasource.readTarget(key)) as Target;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function getTargetFilteredMessageKeys(datasource: any, targetOptions: unknown) {
|
|
178
|
+
const targetKeys = toArray(targetOptions);
|
|
179
|
+
|
|
180
|
+
if (targetKeys.length === 0) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const allMessageKeys = await datasource.listMessages();
|
|
185
|
+
const selected = new Set<string>();
|
|
186
|
+
|
|
187
|
+
for (const targetKey of targetKeys) {
|
|
188
|
+
const target = (await datasource.readTarget(targetKey)) as Target;
|
|
189
|
+
const includeMessages = target.includeMessages?.length ? target.includeMessages : ["*"];
|
|
190
|
+
const excludeMessages = target.excludeMessages || [];
|
|
191
|
+
|
|
192
|
+
for (const messageKey of allMessageKeys) {
|
|
193
|
+
if (
|
|
194
|
+
matchesPattern(messageKey, includeMessages) &&
|
|
195
|
+
!matchesPattern(messageKey, excludeMessages)
|
|
196
|
+
) {
|
|
197
|
+
selected.add(messageKey);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return selected;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function applySharedFilters(
|
|
206
|
+
item: ParsedEntity<ListableEntity>,
|
|
207
|
+
entityType: EntityType,
|
|
208
|
+
options: any,
|
|
209
|
+
entitiesWithTests?: EntitiesWithTests,
|
|
210
|
+
) {
|
|
211
|
+
if (options.keyPattern && !toRegex(options.keyPattern).test(item.key)) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (options.description) {
|
|
216
|
+
const description = (item.entity as any).description || "";
|
|
217
|
+
|
|
218
|
+
if (!toRegex(options.description).test(description)) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if ("promotable" in item.entity) {
|
|
224
|
+
const promotable = (item.entity as any).promotable as boolean | undefined;
|
|
225
|
+
|
|
226
|
+
if (!matchesOptionalBoolean(options.promotable, promotable, "--promotable")) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (options.withTests || options.withoutTests) {
|
|
232
|
+
const entityKeysWithTests = entitiesWithTests?.[entityType as keyof EntitiesWithTests] || [];
|
|
233
|
+
const hasTests = entityKeysWithTests.includes(item.key);
|
|
234
|
+
|
|
235
|
+
if (options.withTests && !hasTests) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options.withoutTests && hasTests) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function applyEntitySpecificFilters(
|
|
248
|
+
item: ParsedEntity<ListableEntity>,
|
|
249
|
+
entityType: EntityType,
|
|
250
|
+
options: any,
|
|
251
|
+
) {
|
|
252
|
+
if (entityType === "tests") {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (entityType === "messages") {
|
|
257
|
+
const message = item.entity as Message;
|
|
258
|
+
|
|
259
|
+
if (!matchesOptionalBoolean(options.archived, message.archived, "--archived")) return false;
|
|
260
|
+
if (!matchesOptionalBoolean(options.deprecated, message.deprecated, "--deprecated"))
|
|
261
|
+
return false;
|
|
262
|
+
if (options.withOverrides && !hasOverrides(message.overrides)) return false;
|
|
263
|
+
if (options.withoutOverrides && hasOverrides(message.overrides)) return false;
|
|
264
|
+
if (options.withMeta && !hasMeta(message.meta)) return false;
|
|
265
|
+
if (options.withoutMeta && hasMeta(message.meta)) return false;
|
|
266
|
+
if (options.locale && typeof message.translations?.[options.locale] === "undefined")
|
|
267
|
+
return false;
|
|
268
|
+
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (entityType === "locales") {
|
|
273
|
+
const locale = item.entity as Locale;
|
|
274
|
+
|
|
275
|
+
if (options.withFormats && !hasFormats(locale.formats)) return false;
|
|
276
|
+
if (options.withoutFormats && hasFormats(locale.formats)) return false;
|
|
277
|
+
if (options.inheritFormatsFrom && locale.inheritFormatsFrom !== options.inheritFormatsFrom)
|
|
278
|
+
return false;
|
|
279
|
+
if (
|
|
280
|
+
options.inheritTranslationsFrom &&
|
|
281
|
+
locale.inheritTranslationsFrom !== options.inheritTranslationsFrom
|
|
282
|
+
) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (entityType === "segments") {
|
|
290
|
+
return matchesOptionalBoolean(
|
|
291
|
+
options.archived,
|
|
292
|
+
(item.entity as Segment).archived,
|
|
293
|
+
"--archived",
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (entityType === "attributes") {
|
|
298
|
+
const attribute = item.entity as Attribute;
|
|
299
|
+
|
|
300
|
+
if (!matchesOptionalBoolean(options.archived, attribute.archived, "--archived")) return false;
|
|
301
|
+
if (options.type && attribute.type !== options.type) return false;
|
|
302
|
+
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const target = item.entity as Target;
|
|
307
|
+
|
|
308
|
+
if (options.locale && !target.locales?.includes(options.locale)) return false;
|
|
309
|
+
if (options.withContext && !hasContext(target.context)) return false;
|
|
310
|
+
if (options.withoutContext && hasContext(target.context)) return false;
|
|
311
|
+
if (options.withFormats && !hasFormats(target.formats)) return false;
|
|
312
|
+
if (options.withoutFormats && hasFormats(target.formats)) return false;
|
|
313
|
+
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function listEntities(datasource: any, options: any, entityType: EntityType) {
|
|
318
|
+
validateFilters(entityType, options);
|
|
319
|
+
|
|
320
|
+
const entityKeys = await listEntityKeys(datasource, entityType);
|
|
321
|
+
const requiresTestScan = Boolean(options.withTests || options.withoutTests);
|
|
322
|
+
const entitiesWithTests = requiresTestScan ? await getEntitiesWithTests(datasource) : undefined;
|
|
323
|
+
const targetFilteredMessageKeys =
|
|
324
|
+
entityType === "messages"
|
|
325
|
+
? await getTargetFilteredMessageKeys(datasource, options.target)
|
|
326
|
+
: undefined;
|
|
327
|
+
const result: ParsedEntity<any>[] = [];
|
|
328
|
+
|
|
329
|
+
for (const key of entityKeys) {
|
|
330
|
+
if (targetFilteredMessageKeys && !targetFilteredMessageKeys.has(key)) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const entity = await readEntity(datasource, entityType, key);
|
|
335
|
+
const item = {
|
|
336
|
+
key,
|
|
337
|
+
entity,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
if (!applySharedFilters(item, entityType, options, entitiesWithTests)) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!applyEntitySpecificFilters(item, entityType, options)) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (entityType === "tests" && options.applyMatrix) {
|
|
349
|
+
const test = entity as Test;
|
|
350
|
+
if ("message" in test) {
|
|
351
|
+
result.push({
|
|
352
|
+
key,
|
|
353
|
+
entity: { ...test, assertions: expandMessageAssertions(test.assertions || []) },
|
|
354
|
+
});
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if ("segment" in test) {
|
|
359
|
+
result.push({
|
|
360
|
+
key,
|
|
361
|
+
entity: { ...test, assertions: expandSegmentAssertions(test.assertions || []) },
|
|
362
|
+
});
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if ("locale" in test) {
|
|
367
|
+
result.push({
|
|
368
|
+
key,
|
|
369
|
+
entity: { ...test, assertions: expandLocaleAssertions(test.assertions || []) },
|
|
370
|
+
});
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
result.push({
|
|
375
|
+
key,
|
|
376
|
+
entity: { ...test, assertions: expandTargetAssertions(test.assertions || []) },
|
|
377
|
+
});
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
result.push(item);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result.map(({ key, entity }) => ({
|
|
385
|
+
key,
|
|
386
|
+
...entity,
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function ucfirst(str: string) {
|
|
391
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function printEntityList(entityType: EntityType, result: Array<{ key: string }>) {
|
|
395
|
+
if (result.length === 0) {
|
|
396
|
+
console.log(`No ${entityType} found.`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
console.log(`\n${ucfirst(entityType)}:\n`);
|
|
401
|
+
|
|
402
|
+
for (const item of result) {
|
|
403
|
+
console.log(`- ${item.key}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
console.log(`\nFound ${result.length} ${entityType}.`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export const listPlugin = {
|
|
410
|
+
command: "list",
|
|
411
|
+
handler: async ({ datasource, parsed }: any) => {
|
|
412
|
+
try {
|
|
413
|
+
const projectConfig = datasource.getConfig();
|
|
414
|
+
const entityType = getSelectedEntityType(parsed);
|
|
415
|
+
assertProjectSetJsonSelection(projectConfig, parsed.set, parsed.json);
|
|
416
|
+
|
|
417
|
+
if (projectConfig.sets) {
|
|
418
|
+
const executions = await getProjectSetExecutions(projectConfig, datasource, parsed.set);
|
|
419
|
+
|
|
420
|
+
if (parsed.json) {
|
|
421
|
+
const result = await listEntities(executions[0].datasource, parsed, entityType);
|
|
422
|
+
console.log(parsed.pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const execution of executions) {
|
|
427
|
+
console.log(`\nSet "${execution.set}":`);
|
|
428
|
+
printEntityList(entityType, await listEntities(execution.datasource, parsed, entityType));
|
|
429
|
+
console.log("");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const result = await listEntities(datasource, parsed, entityType);
|
|
436
|
+
|
|
437
|
+
if (parsed.json) {
|
|
438
|
+
console.log(parsed.pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
printEntityList(entityType, result);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (printMessagevisorCLIError(error)) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
examples: [
|
|
452
|
+
{ command: "list --messages", description: "list messages" },
|
|
453
|
+
{ command: "list --messages --target=web", description: "list messages covered by a target" },
|
|
454
|
+
{ command: "list --locales", description: "list locales" },
|
|
455
|
+
{ command: "list --segments", description: "list segments" },
|
|
456
|
+
{ command: "list --attributes", description: "list attributes" },
|
|
457
|
+
{ command: "list --targets", description: "list targets" },
|
|
458
|
+
{
|
|
459
|
+
command: "list --tests --applyMatrix --json",
|
|
460
|
+
description: "list tests with matrix-expanded assertions as JSON",
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
};
|
package/src/matrix.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Matrix } from "@messagevisor/types";
|
|
2
|
+
|
|
3
|
+
export type MatrixCombination = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
function generateCombinations(
|
|
6
|
+
keys: string[],
|
|
7
|
+
matrix: Matrix,
|
|
8
|
+
index: number,
|
|
9
|
+
previous: MatrixCombination,
|
|
10
|
+
combinations: MatrixCombination[],
|
|
11
|
+
) {
|
|
12
|
+
const key = keys[index];
|
|
13
|
+
const values = matrix[key] || [];
|
|
14
|
+
|
|
15
|
+
for (const value of values) {
|
|
16
|
+
const combination = { ...previous, [key]: value };
|
|
17
|
+
|
|
18
|
+
if (index === keys.length - 1) {
|
|
19
|
+
combinations.push(combination);
|
|
20
|
+
} else {
|
|
21
|
+
generateCombinations(keys, matrix, index + 1, combination, combinations);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getMatrixCombinations(matrix: Matrix) {
|
|
27
|
+
const keys = Object.keys(matrix || {});
|
|
28
|
+
|
|
29
|
+
if (keys.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const combinations: MatrixCombination[] = [];
|
|
34
|
+
generateCombinations(keys, matrix, 0, {}, combinations);
|
|
35
|
+
|
|
36
|
+
return combinations;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function applyCombinationToValue(value: unknown, combination: MatrixCombination): unknown {
|
|
40
|
+
if (typeof value === "string") {
|
|
41
|
+
const variableKeys = value.match(/\${{(.+?)}}/g);
|
|
42
|
+
|
|
43
|
+
if (!variableKeys) {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (variableKeys.length === 1 && value.startsWith("${{") && value.endsWith("}}")) {
|
|
48
|
+
const key = value.replace("${{", "").replace("}}", "").trim();
|
|
49
|
+
return combination[key];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return value.replace(/\${{(.+?)}}/g, (_, key) => String(combination[key.trim()]));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(value)) {
|
|
56
|
+
return value.map((entry) => applyCombinationToValue(entry, combination));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof value === "object" && value !== null) {
|
|
60
|
+
return Object.fromEntries(
|
|
61
|
+
Object.entries(value).map(([key, entry]) => [
|
|
62
|
+
key,
|
|
63
|
+
applyCombinationToValue(entry, combination),
|
|
64
|
+
]),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return value;
|
|
69
|
+
}
|