@messagevisor/catalog 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/assets/index-BoO0zn_O.js +73 -0
- package/dist/assets/index-Cn0qFwkD.css +1 -0
- package/dist/favicon.png +0 -0
- package/dist/index.html +14 -0
- package/dist/logo-text.png +0 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +18 -0
- package/lib/index.js.map +1 -0
- package/lib/node/formatExamplePreview.d.ts +10 -0
- package/lib/node/formatExamplePreview.js +79 -0
- package/lib/node/formatExamplePreview.js.map +1 -0
- package/lib/node/index.d.ts +191 -0
- package/lib/node/index.js +1755 -0
- package/lib/node/index.js.map +1 -0
- package/package.json +59 -13
- package/src/App.tsx +73 -0
- package/src/api.spec.ts +42 -0
- package/src/api.ts +87 -0
- package/src/catalogBrandAssets.ts +8 -0
- package/src/components/details/ConditionTree.tsx +146 -0
- package/src/components/details/FieldGrid.tsx +16 -0
- package/src/components/details/GroupSegmentTree.tsx +73 -0
- package/src/components/details/MarkdownContent.tsx +23 -0
- package/src/components/details/TranslationsTable.tsx +263 -0
- package/src/components/details/UsageLinks.tsx +29 -0
- package/src/components/history/HistoryTimeline.tsx +122 -0
- package/src/components/layout/AppShell.tsx +338 -0
- package/src/components/layout/PageHeader.tsx +13 -0
- package/src/components/layout/Tabs.tsx +35 -0
- package/src/components/lists/EntityList.tsx +451 -0
- package/src/components/ui/Badge.tsx +21 -0
- package/src/components/ui/Button.tsx +12 -0
- package/src/components/ui/Card.tsx +9 -0
- package/src/components/ui/CodeBlock.tsx +7 -0
- package/src/components/ui/EmptyState.tsx +8 -0
- package/src/components/ui/Input.tsx +12 -0
- package/src/components/ui/LabelValueBadge.tsx +55 -0
- package/src/config.ts +2 -0
- package/src/context/CatalogContext.tsx +50 -0
- package/src/entityTypes.ts +49 -0
- package/src/index.ts +1 -0
- package/src/main.tsx +28 -0
- package/src/node/formatExamplePreview.ts +85 -0
- package/src/node/index.spec.ts +935 -0
- package/src/node/index.ts +2151 -0
- package/src/pages/EntityDetailPage.tsx +3345 -0
- package/src/pages/HistoryPage.tsx +26 -0
- package/src/pages/HomePage.tsx +21 -0
- package/src/pages/ListPage.tsx +59 -0
- package/src/styles.css +95 -0
- package/src/theme.ts +36 -0
- package/src/types.ts +127 -0
- package/src/utils/formatCatalogTimestamp.ts +77 -0
- package/src/utils/hashTranslationValue.spec.ts +20 -0
- package/src/utils/hashTranslationValue.ts +22 -0
- package/src/utils/searchQuery.ts +46 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as childProcess from "child_process";
|
|
5
|
+
|
|
6
|
+
import { mergeFormats, resolveFormats } from "../../../core/src/builder";
|
|
7
|
+
import { getProjectConfig } from "../../../core/src/config";
|
|
8
|
+
import { Datasource } from "../../../core/src/datasource";
|
|
9
|
+
import { resolveExamples } from "../../../core/src/examples";
|
|
10
|
+
import { findDuplicateTranslations } from "../../../core/src/find-duplicates";
|
|
11
|
+
import { getProjectSetExecutions } from "../../../core/src/sets";
|
|
12
|
+
import { createCatalogApi, createCatalogPlugin, type CatalogRuntime } from "./index";
|
|
13
|
+
|
|
14
|
+
const catalogApi = createCatalogApi({
|
|
15
|
+
mergeFormats,
|
|
16
|
+
resolveFormats,
|
|
17
|
+
getProjectSetExecutions,
|
|
18
|
+
resolveExamples,
|
|
19
|
+
findDuplicateTranslations,
|
|
20
|
+
});
|
|
21
|
+
const catalogRuntime: CatalogRuntime = {
|
|
22
|
+
mergeFormats,
|
|
23
|
+
resolveFormats,
|
|
24
|
+
getProjectSetExecutions,
|
|
25
|
+
resolveExamples,
|
|
26
|
+
findDuplicateTranslations,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function writeFile(root: string, relativePath: string, content: string) {
|
|
30
|
+
const filePath = path.join(root, relativePath);
|
|
31
|
+
|
|
32
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
33
|
+
await fs.promises.writeFile(filePath, content);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readJson<T>(root: string, relativePath: string): Promise<T> {
|
|
37
|
+
return JSON.parse(await fs.promises.readFile(path.join(root, relativePath), "utf8"));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function createProject() {
|
|
41
|
+
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-catalog-"));
|
|
42
|
+
const interpolationModulePath = path.join(
|
|
43
|
+
path.resolve(__dirname, "../../../.."),
|
|
44
|
+
"packages/module-interpolation/src/index.ts",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await writeFile(
|
|
48
|
+
root,
|
|
49
|
+
"messagevisor.config.js",
|
|
50
|
+
[
|
|
51
|
+
`const { createInterpolationModule } = require(${JSON.stringify(interpolationModulePath)});`,
|
|
52
|
+
"module.exports = {",
|
|
53
|
+
" modules: [createInterpolationModule()],",
|
|
54
|
+
"};",
|
|
55
|
+
"",
|
|
56
|
+
].join("\n"),
|
|
57
|
+
);
|
|
58
|
+
await writeFile(
|
|
59
|
+
root,
|
|
60
|
+
"locales/en.yml",
|
|
61
|
+
[
|
|
62
|
+
"description: English",
|
|
63
|
+
"direction: ltr",
|
|
64
|
+
"formats:",
|
|
65
|
+
" number:",
|
|
66
|
+
" money:",
|
|
67
|
+
" style: currency",
|
|
68
|
+
" currency: USD",
|
|
69
|
+
"examples:",
|
|
70
|
+
" - description: Simple text",
|
|
71
|
+
" rawMessage: Hello, world!",
|
|
72
|
+
" - description: Welcome on pro plan",
|
|
73
|
+
" message: common.welcome",
|
|
74
|
+
" context:",
|
|
75
|
+
" plan: pro",
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
);
|
|
79
|
+
await writeFile(
|
|
80
|
+
root,
|
|
81
|
+
"locales/en-US.yml",
|
|
82
|
+
[
|
|
83
|
+
"description: English US",
|
|
84
|
+
"direction: ltr",
|
|
85
|
+
"promotable: false",
|
|
86
|
+
"inheritFormatsFrom: en",
|
|
87
|
+
"inheritTranslationsFrom: en",
|
|
88
|
+
"mergeExamplesFrom: en",
|
|
89
|
+
"formats:",
|
|
90
|
+
" number:",
|
|
91
|
+
" money:",
|
|
92
|
+
" currencyDisplay: code",
|
|
93
|
+
"examples:",
|
|
94
|
+
" - matrix:",
|
|
95
|
+
" name: [Taylor, Sam]",
|
|
96
|
+
" description: Welcome ${{ name }}",
|
|
97
|
+
" rawMessage: Hello, {name}!",
|
|
98
|
+
" values:",
|
|
99
|
+
" name: ${{ name }}",
|
|
100
|
+
"",
|
|
101
|
+
].join("\n"),
|
|
102
|
+
);
|
|
103
|
+
await writeFile(root, "locales/nl.yml", "description: Dutch\n");
|
|
104
|
+
await writeFile(root, "attributes/plan.yml", "description: Plan\ntype: string\n");
|
|
105
|
+
await writeFile(
|
|
106
|
+
root,
|
|
107
|
+
"segments/pro.yml",
|
|
108
|
+
"description: Pro\nconditions:\n attribute: plan\n operator: equals\n value: pro\n",
|
|
109
|
+
);
|
|
110
|
+
await writeFile(
|
|
111
|
+
root,
|
|
112
|
+
"messages/common/welcome.yml",
|
|
113
|
+
[
|
|
114
|
+
"description: Welcome",
|
|
115
|
+
"promotable: false",
|
|
116
|
+
"examples:",
|
|
117
|
+
" - description: Default welcome",
|
|
118
|
+
" locale: en",
|
|
119
|
+
" - matrix:",
|
|
120
|
+
" locale: [en, en-US]",
|
|
121
|
+
" plan: [free, pro]",
|
|
122
|
+
" description: Welcome for ${{ locale }} plan ${{ plan }}",
|
|
123
|
+
" locale: ${{ locale }}",
|
|
124
|
+
" context:",
|
|
125
|
+
" plan: ${{ plan }}",
|
|
126
|
+
"translations:",
|
|
127
|
+
" en: Welcome",
|
|
128
|
+
"overrides:",
|
|
129
|
+
" - key: pro",
|
|
130
|
+
" segments: pro",
|
|
131
|
+
" translations:",
|
|
132
|
+
" en: Welcome Pro",
|
|
133
|
+
"",
|
|
134
|
+
].join("\n"),
|
|
135
|
+
);
|
|
136
|
+
await writeFile(
|
|
137
|
+
root,
|
|
138
|
+
"messages/common/draft.yml",
|
|
139
|
+
"description: Draft\ntranslations:\n en: Welcome\n",
|
|
140
|
+
);
|
|
141
|
+
await writeFile(
|
|
142
|
+
root,
|
|
143
|
+
"targets/web.yml",
|
|
144
|
+
[
|
|
145
|
+
"includeMessages:",
|
|
146
|
+
" - common*",
|
|
147
|
+
"locales:",
|
|
148
|
+
" - en-US",
|
|
149
|
+
"formats:",
|
|
150
|
+
" en-US:",
|
|
151
|
+
" number:",
|
|
152
|
+
" money:",
|
|
153
|
+
" minimumFractionDigits: 2",
|
|
154
|
+
"",
|
|
155
|
+
].join("\n"),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return root;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function git(root: string, args: string[]) {
|
|
162
|
+
childProcess.execFileSync("git", ["-C", root, ...args], {
|
|
163
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function gitCommit(root: string, message: string) {
|
|
168
|
+
childProcess.execFileSync(
|
|
169
|
+
"git",
|
|
170
|
+
[
|
|
171
|
+
"-C",
|
|
172
|
+
root,
|
|
173
|
+
"-c",
|
|
174
|
+
"user.name=Catalog Tester",
|
|
175
|
+
"-c",
|
|
176
|
+
"user.email=catalog@example.com",
|
|
177
|
+
"commit",
|
|
178
|
+
"-m",
|
|
179
|
+
message,
|
|
180
|
+
],
|
|
181
|
+
{
|
|
182
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
183
|
+
},
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
describe("catalog", function () {
|
|
188
|
+
const roots: string[] = [];
|
|
189
|
+
let consoleLogSpy: jest.SpyInstance;
|
|
190
|
+
|
|
191
|
+
beforeEach(function () {
|
|
192
|
+
consoleLogSpy = jest.spyOn(console, "log").mockImplementation(function () {
|
|
193
|
+
// Keep catalog unit tests focused on generated data.
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
afterEach(async function () {
|
|
198
|
+
consoleLogSpy.mockRestore();
|
|
199
|
+
|
|
200
|
+
for (const root of roots) {
|
|
201
|
+
await fs.promises.rm(root, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
roots.length = 0;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("exports regular project catalog data with relationships, status, and computed formats", async function () {
|
|
207
|
+
const root = await createProject();
|
|
208
|
+
roots.push(root);
|
|
209
|
+
const projectConfig = getProjectConfig(root);
|
|
210
|
+
const datasource = new Datasource(projectConfig, root);
|
|
211
|
+
|
|
212
|
+
const result = await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
213
|
+
outDir: "catalog-out",
|
|
214
|
+
copyAssets: false,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(result.outputDirectoryPath).toBe(path.join(root, "catalog-out"));
|
|
218
|
+
|
|
219
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
220
|
+
const index = await readJson<any>(root, "catalog-out/data/root/index.json");
|
|
221
|
+
const locale = await readJson<any>(root, "catalog-out/data/root/entities/locale/en-US.json");
|
|
222
|
+
const localeDuplicates = await readJson<any>(
|
|
223
|
+
root,
|
|
224
|
+
"catalog-out/data/root/duplicates/locales/en-US.json",
|
|
225
|
+
);
|
|
226
|
+
const emptyLocaleDuplicates = await readJson<any>(
|
|
227
|
+
root,
|
|
228
|
+
"catalog-out/data/root/duplicates/locales/nl.json",
|
|
229
|
+
);
|
|
230
|
+
const message = await readJson<any>(
|
|
231
|
+
root,
|
|
232
|
+
"catalog-out/data/root/entities/message/common.welcome.json",
|
|
233
|
+
);
|
|
234
|
+
const attribute = await readJson<any>(
|
|
235
|
+
root,
|
|
236
|
+
"catalog-out/data/root/entities/attribute/plan.json",
|
|
237
|
+
);
|
|
238
|
+
const segment = await readJson<any>(root, "catalog-out/data/root/entities/segment/pro.json");
|
|
239
|
+
const target = await readJson<any>(root, "catalog-out/data/root/entities/target/web.json");
|
|
240
|
+
const history = await readJson<any>(root, "catalog-out/data/project/history/page-1.json");
|
|
241
|
+
|
|
242
|
+
expect(manifest.sets).toBe(false);
|
|
243
|
+
expect(manifest.router).toBe("browser");
|
|
244
|
+
expect(manifest.dev).toBeUndefined();
|
|
245
|
+
expect(manifest.paths.root).toBe("data/root/index.json");
|
|
246
|
+
expect(index.counts.message).toBe(2);
|
|
247
|
+
expect(
|
|
248
|
+
index.entities.message.find((entry: any) => entry.key === "common.welcome").targets,
|
|
249
|
+
).toEqual(["web"]);
|
|
250
|
+
expect(index.entities.locale.find((entry: any) => entry.key === "en-US").targets).toEqual([
|
|
251
|
+
"web",
|
|
252
|
+
]);
|
|
253
|
+
expect(index.entities.attribute.find((entry: any) => entry.key === "plan").targets).toEqual([
|
|
254
|
+
"web",
|
|
255
|
+
]);
|
|
256
|
+
expect(index.entities.segment.find((entry: any) => entry.key === "pro").targets).toEqual([
|
|
257
|
+
"web",
|
|
258
|
+
]);
|
|
259
|
+
expect(index.entities.target.find((entry: any) => entry.key === "web").messageCount).toBe(2);
|
|
260
|
+
expect(locale.computedFormats.number.money).toEqual({
|
|
261
|
+
style: "currency",
|
|
262
|
+
currency: "USD",
|
|
263
|
+
currencyDisplay: "code",
|
|
264
|
+
});
|
|
265
|
+
expect(locale.entity.examples).toHaveLength(1);
|
|
266
|
+
expect(locale.entity.direction).toBe("ltr");
|
|
267
|
+
expect(locale.entity.promotable).toBe(false);
|
|
268
|
+
expect(locale.formatRows).toEqual(
|
|
269
|
+
expect.arrayContaining([
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
path: "number.money.style",
|
|
272
|
+
value: "currency",
|
|
273
|
+
source: "inherited",
|
|
274
|
+
from: "en",
|
|
275
|
+
examplePreview: expect.any(String),
|
|
276
|
+
}),
|
|
277
|
+
expect.objectContaining({
|
|
278
|
+
path: "number.money.currencyDisplay",
|
|
279
|
+
value: "code",
|
|
280
|
+
source: "direct",
|
|
281
|
+
examplePreview: expect.any(String),
|
|
282
|
+
}),
|
|
283
|
+
]),
|
|
284
|
+
);
|
|
285
|
+
expect(locale.evaluatedExamples).toEqual(
|
|
286
|
+
expect.arrayContaining([
|
|
287
|
+
expect.objectContaining({
|
|
288
|
+
locale: "en-US",
|
|
289
|
+
sourceLocale: "en",
|
|
290
|
+
description: "Simple text",
|
|
291
|
+
rawMessage: "Hello, world!",
|
|
292
|
+
evaluatedTranslation: "Hello, world!",
|
|
293
|
+
}),
|
|
294
|
+
expect.objectContaining({
|
|
295
|
+
locale: "en-US",
|
|
296
|
+
sourceLocale: "en",
|
|
297
|
+
description: "Welcome on pro plan",
|
|
298
|
+
message: "common.welcome",
|
|
299
|
+
originalTranslation: "Welcome",
|
|
300
|
+
evaluatedTranslation: "Welcome Pro",
|
|
301
|
+
}),
|
|
302
|
+
expect.objectContaining({
|
|
303
|
+
locale: "en-US",
|
|
304
|
+
sourceLocale: "en-US",
|
|
305
|
+
description: "Welcome Taylor",
|
|
306
|
+
rawMessage: "Hello, {name}!",
|
|
307
|
+
evaluatedTranslation: "Hello, Taylor!",
|
|
308
|
+
}),
|
|
309
|
+
]),
|
|
310
|
+
);
|
|
311
|
+
expect(locale.targetFormats.web.number.money.minimumFractionDigits).toBe(2);
|
|
312
|
+
expect(localeDuplicates).toEqual({
|
|
313
|
+
locale: "en-US",
|
|
314
|
+
summary: {
|
|
315
|
+
duplicateValues: 1,
|
|
316
|
+
duplicateMessageKeys: 2,
|
|
317
|
+
},
|
|
318
|
+
duplicateValues: [
|
|
319
|
+
{
|
|
320
|
+
value: "Welcome",
|
|
321
|
+
messageKeys: ["common.draft", "common.welcome"],
|
|
322
|
+
sources: [
|
|
323
|
+
{ messageKey: "common.draft", locale: "en" },
|
|
324
|
+
{ messageKey: "common.welcome", locale: "en" },
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
});
|
|
329
|
+
expect(emptyLocaleDuplicates).toEqual({
|
|
330
|
+
locale: "nl",
|
|
331
|
+
summary: {
|
|
332
|
+
duplicateValues: 0,
|
|
333
|
+
duplicateMessageKeys: 0,
|
|
334
|
+
},
|
|
335
|
+
duplicateValues: [],
|
|
336
|
+
});
|
|
337
|
+
expect(target.formatRowsByLocale["en-US"]).toEqual(
|
|
338
|
+
expect.arrayContaining([
|
|
339
|
+
expect.objectContaining({
|
|
340
|
+
path: "number.money.minimumFractionDigits",
|
|
341
|
+
value: 2,
|
|
342
|
+
source: "target",
|
|
343
|
+
from: "target",
|
|
344
|
+
examplePreview: expect.any(String),
|
|
345
|
+
}),
|
|
346
|
+
expect.objectContaining({
|
|
347
|
+
path: "number.money.style",
|
|
348
|
+
value: "currency",
|
|
349
|
+
source: "inherited",
|
|
350
|
+
from: "en",
|
|
351
|
+
examplePreview: expect.any(String),
|
|
352
|
+
}),
|
|
353
|
+
]),
|
|
354
|
+
);
|
|
355
|
+
expect(message.targets).toEqual(["web"]);
|
|
356
|
+
expect(message.editLinks).toBeUndefined();
|
|
357
|
+
expect(message.entity.promotable).toBe(false);
|
|
358
|
+
expect(message.entity.examples).toHaveLength(2);
|
|
359
|
+
expect(message.translations).toEqual(
|
|
360
|
+
expect.arrayContaining([
|
|
361
|
+
{
|
|
362
|
+
locale: "en-US",
|
|
363
|
+
value: "Welcome",
|
|
364
|
+
source: "inherited",
|
|
365
|
+
from: "en",
|
|
366
|
+
},
|
|
367
|
+
]),
|
|
368
|
+
);
|
|
369
|
+
expect(message.localeDirections).toEqual({
|
|
370
|
+
en: "ltr",
|
|
371
|
+
"en-US": "ltr",
|
|
372
|
+
});
|
|
373
|
+
expect(message.evaluatedExamples).toEqual(
|
|
374
|
+
expect.arrayContaining([
|
|
375
|
+
expect.objectContaining({
|
|
376
|
+
locale: "en",
|
|
377
|
+
description: "Default welcome",
|
|
378
|
+
evaluatedTranslation: "Welcome",
|
|
379
|
+
}),
|
|
380
|
+
expect.objectContaining({
|
|
381
|
+
locale: "en-US",
|
|
382
|
+
description: "Welcome for en-US plan pro",
|
|
383
|
+
evaluatedTranslation: "Welcome Pro",
|
|
384
|
+
}),
|
|
385
|
+
]),
|
|
386
|
+
);
|
|
387
|
+
expect(message.entity.overrides[0].usedSegments).toEqual(["pro"]);
|
|
388
|
+
expect(attribute.usage.segments).toEqual(["pro"]);
|
|
389
|
+
expect(segment.usage.messages).toEqual(["common.welcome"]);
|
|
390
|
+
expect(target.messages).toEqual(["common.draft", "common.welcome"]);
|
|
391
|
+
expect(history.entries).toEqual([]);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("streams Git history into project, entity, and last-modified catalog data", async function () {
|
|
395
|
+
const root = await createProject();
|
|
396
|
+
roots.push(root);
|
|
397
|
+
git(root, ["init"]);
|
|
398
|
+
git(root, ["add", "."]);
|
|
399
|
+
gitCommit(root, "initial catalog fixtures");
|
|
400
|
+
|
|
401
|
+
await writeFile(
|
|
402
|
+
root,
|
|
403
|
+
"tests/messages/common/welcome.spec.yml",
|
|
404
|
+
"cases:\n - description: Test only\n",
|
|
405
|
+
);
|
|
406
|
+
git(root, ["add", "."]);
|
|
407
|
+
gitCommit(root, "test-only change");
|
|
408
|
+
|
|
409
|
+
await writeFile(
|
|
410
|
+
root,
|
|
411
|
+
"messages/common/welcome.yml",
|
|
412
|
+
["description: Welcome updated", "translations:", " en: Welcome back", ""].join("\n"),
|
|
413
|
+
);
|
|
414
|
+
await writeFile(
|
|
415
|
+
root,
|
|
416
|
+
"messages/common/with space.yml",
|
|
417
|
+
"description: With space\ntranslations:\n en: Spaced\n",
|
|
418
|
+
);
|
|
419
|
+
git(root, ["add", "."]);
|
|
420
|
+
gitCommit(root, "message updates");
|
|
421
|
+
|
|
422
|
+
const projectConfig = getProjectConfig(root);
|
|
423
|
+
const datasource = new Datasource(projectConfig, root);
|
|
424
|
+
|
|
425
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
426
|
+
outDir: "catalog-out",
|
|
427
|
+
copyAssets: false,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const projectHistory = await readJson<any>(
|
|
431
|
+
root,
|
|
432
|
+
"catalog-out/data/project/history/page-1.json",
|
|
433
|
+
);
|
|
434
|
+
const messageHistory = await readJson<any>(
|
|
435
|
+
root,
|
|
436
|
+
"catalog-out/data/root/history/message/common.welcome/page-1.json",
|
|
437
|
+
);
|
|
438
|
+
const spacedMessageHistory = await readJson<any>(
|
|
439
|
+
root,
|
|
440
|
+
"catalog-out/data/root/history/message/common.with%20space/page-1.json",
|
|
441
|
+
);
|
|
442
|
+
const index = await readJson<any>(root, "catalog-out/data/root/index.json");
|
|
443
|
+
const message = await readJson<any>(
|
|
444
|
+
root,
|
|
445
|
+
"catalog-out/data/root/entities/message/common.welcome.json",
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
expect(projectHistory.entries).toHaveLength(2);
|
|
449
|
+
expect(projectHistory.entries[0].entities).toEqual(
|
|
450
|
+
expect.arrayContaining([
|
|
451
|
+
{ type: "message", key: "common.welcome" },
|
|
452
|
+
{ type: "message", key: "common.with space" },
|
|
453
|
+
]),
|
|
454
|
+
);
|
|
455
|
+
expect(projectHistory.entries).not.toEqual(
|
|
456
|
+
expect.arrayContaining([
|
|
457
|
+
expect.objectContaining({
|
|
458
|
+
entities: expect.arrayContaining([{ type: "test", key: "common.welcome" }]),
|
|
459
|
+
}),
|
|
460
|
+
]),
|
|
461
|
+
);
|
|
462
|
+
expect(messageHistory.entries).toHaveLength(2);
|
|
463
|
+
expect(spacedMessageHistory.entries[0].entities).toEqual(
|
|
464
|
+
expect.arrayContaining([{ type: "message", key: "common.with space" }]),
|
|
465
|
+
);
|
|
466
|
+
expect(message.lastModified).toMatchObject({
|
|
467
|
+
author: "Catalog Tester",
|
|
468
|
+
commit: projectHistory.entries[0].commit,
|
|
469
|
+
});
|
|
470
|
+
expect(
|
|
471
|
+
index.entities.message.find((entry: any) => entry.key === "common.welcome").lastModified,
|
|
472
|
+
).toMatchObject({
|
|
473
|
+
commit: projectHistory.entries[0].commit,
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("exports branch-aware repository links and hash router mode when requested", async function () {
|
|
478
|
+
const root = await createProject();
|
|
479
|
+
roots.push(root);
|
|
480
|
+
git(root, ["init"]);
|
|
481
|
+
git(root, ["checkout", "-b", "catalog-test"]);
|
|
482
|
+
git(root, ["remote", "add", "origin", "git@github.com:messagevisor/messagevisor.git"]);
|
|
483
|
+
const projectConfig = getProjectConfig(root);
|
|
484
|
+
const datasource = new Datasource(projectConfig, root);
|
|
485
|
+
|
|
486
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
487
|
+
outDir: "catalog-out",
|
|
488
|
+
copyAssets: false,
|
|
489
|
+
browserRouter: false,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
493
|
+
|
|
494
|
+
expect(manifest.router).toBe("hash");
|
|
495
|
+
expect(manifest.links).toMatchObject({
|
|
496
|
+
provider: "github",
|
|
497
|
+
repository: "https://github.com/messagevisor/messagevisor",
|
|
498
|
+
source: "https://github.com/messagevisor/messagevisor/blob/catalog-test/{{path}}",
|
|
499
|
+
commit: "https://github.com/messagevisor/messagevisor/commit/{{hash}}",
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("exports GitLab and Bitbucket repository links for known providers", async function () {
|
|
504
|
+
const providers = [
|
|
505
|
+
{
|
|
506
|
+
remote: "git@gitlab.com:messagevisor/messagevisor.git",
|
|
507
|
+
expected: {
|
|
508
|
+
provider: "gitlab",
|
|
509
|
+
repository: "https://gitlab.com/messagevisor/messagevisor",
|
|
510
|
+
source: "https://gitlab.com/messagevisor/messagevisor/-/blob/catalog-test/{{path}}",
|
|
511
|
+
commit: "https://gitlab.com/messagevisor/messagevisor/-/commit/{{hash}}",
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
remote: "git@bitbucket.org:messagevisor/messagevisor.git",
|
|
516
|
+
expected: {
|
|
517
|
+
provider: "bitbucket",
|
|
518
|
+
repository: "https://bitbucket.org/messagevisor/messagevisor",
|
|
519
|
+
source: "https://bitbucket.org/messagevisor/messagevisor/src/catalog-test/{{path}}",
|
|
520
|
+
commit: "https://bitbucket.org/messagevisor/messagevisor/commits/{{hash}}",
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
for (const provider of providers) {
|
|
526
|
+
const root = await createProject();
|
|
527
|
+
roots.push(root);
|
|
528
|
+
git(root, ["init"]);
|
|
529
|
+
git(root, ["checkout", "-b", "catalog-test"]);
|
|
530
|
+
git(root, ["remote", "add", "origin", provider.remote]);
|
|
531
|
+
const projectConfig = getProjectConfig(root);
|
|
532
|
+
const datasource = new Datasource(projectConfig, root);
|
|
533
|
+
|
|
534
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
535
|
+
outDir: "catalog-out",
|
|
536
|
+
copyAssets: false,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
540
|
+
|
|
541
|
+
expect(manifest.links).toMatchObject(provider.expected);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it("omits repository links for unknown Git providers", async function () {
|
|
546
|
+
const root = await createProject();
|
|
547
|
+
roots.push(root);
|
|
548
|
+
git(root, ["init"]);
|
|
549
|
+
git(root, ["checkout", "-b", "catalog-test"]);
|
|
550
|
+
git(root, ["remote", "add", "origin", "git@example.com:messagevisor/messagevisor.git"]);
|
|
551
|
+
const projectConfig = getProjectConfig(root);
|
|
552
|
+
const datasource = new Datasource(projectConfig, root);
|
|
553
|
+
|
|
554
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
555
|
+
outDir: "catalog-out",
|
|
556
|
+
copyAssets: false,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
560
|
+
|
|
561
|
+
expect(manifest.links).toBeUndefined();
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("exports dev-only editor metadata and entity editor links when requested", async function () {
|
|
565
|
+
const root = await createProject();
|
|
566
|
+
roots.push(root);
|
|
567
|
+
const projectConfig = getProjectConfig(root);
|
|
568
|
+
const datasource = new Datasource(projectConfig, root);
|
|
569
|
+
|
|
570
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
571
|
+
outDir: "catalog-out",
|
|
572
|
+
copyAssets: false,
|
|
573
|
+
dev: true,
|
|
574
|
+
devEditors: [{ id: "cursor", label: "Cursor", icon: "cursor" }],
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
578
|
+
const message = await readJson<any>(
|
|
579
|
+
root,
|
|
580
|
+
"catalog-out/data/root/entities/message/common.welcome.json",
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
expect(manifest.links).toBeUndefined();
|
|
584
|
+
expect(manifest.dev).toEqual({
|
|
585
|
+
editors: [{ id: "cursor", label: "Cursor", icon: "cursor" }],
|
|
586
|
+
});
|
|
587
|
+
expect(message.editLinks).toEqual({
|
|
588
|
+
cursor: expect.stringMatching(/^cursor:\/\/file\/.+messages\/common\/welcome\.yml$/),
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("exports repo-relative source paths for nested projects", async function () {
|
|
593
|
+
const repositoryRoot = await fs.promises.mkdtemp(
|
|
594
|
+
path.join(os.tmpdir(), "messagevisor-catalog-repo-"),
|
|
595
|
+
);
|
|
596
|
+
roots.push(repositoryRoot);
|
|
597
|
+
const projectRoot = path.join(repositoryRoot, "projects", "shop");
|
|
598
|
+
|
|
599
|
+
await fs.promises.mkdir(projectRoot, { recursive: true });
|
|
600
|
+
await writeFile(projectRoot, "messagevisor.config.js", "module.exports = {};\n");
|
|
601
|
+
await writeFile(projectRoot, "locales/en.yml", "description: English\n");
|
|
602
|
+
await writeFile(
|
|
603
|
+
projectRoot,
|
|
604
|
+
"messages/common/welcome.yml",
|
|
605
|
+
"description: Welcome\ntranslations:\n en: Welcome\n",
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
git(repositoryRoot, ["init"]);
|
|
609
|
+
git(repositoryRoot, ["checkout", "-b", "nested-catalog-test"]);
|
|
610
|
+
git(repositoryRoot, [
|
|
611
|
+
"remote",
|
|
612
|
+
"add",
|
|
613
|
+
"origin",
|
|
614
|
+
"git@github.com:messagevisor/messagevisor.git",
|
|
615
|
+
]);
|
|
616
|
+
|
|
617
|
+
const projectConfig = getProjectConfig(projectRoot);
|
|
618
|
+
const datasource = new Datasource(projectConfig, projectRoot);
|
|
619
|
+
|
|
620
|
+
await catalogApi.exportCatalog(projectRoot, projectConfig, datasource, {
|
|
621
|
+
outDir: "catalog-out",
|
|
622
|
+
copyAssets: false,
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const message = await readJson<any>(
|
|
626
|
+
projectRoot,
|
|
627
|
+
"catalog-out/data/root/entities/message/common.welcome.json",
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
expect(message.sourcePath).toBe("projects/shop/messages/common/welcome.yml");
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("exports set project catalog data independently for each set", async function () {
|
|
634
|
+
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-catalog-"));
|
|
635
|
+
roots.push(root);
|
|
636
|
+
|
|
637
|
+
await writeFile(root, "messagevisor.config.js", "module.exports = { sets: true };\n");
|
|
638
|
+
|
|
639
|
+
for (const set of ["storefront", "admin"]) {
|
|
640
|
+
await writeFile(root, `sets/${set}/locales/en.yml`, "description: English\n");
|
|
641
|
+
await writeFile(
|
|
642
|
+
root,
|
|
643
|
+
`sets/${set}/messages/common/welcome.yml`,
|
|
644
|
+
`description: Welcome\ntranslations:\n en: ${set}\n`,
|
|
645
|
+
);
|
|
646
|
+
await writeFile(
|
|
647
|
+
root,
|
|
648
|
+
`sets/${set}/messages/common/duplicate.yml`,
|
|
649
|
+
`description: Duplicate\ntranslations:\n en: ${set}\n`,
|
|
650
|
+
);
|
|
651
|
+
await writeFile(
|
|
652
|
+
root,
|
|
653
|
+
`sets/${set}/targets/web.yml`,
|
|
654
|
+
"includeMessages:\n - common*\nlocales:\n - en\n",
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const projectConfig = getProjectConfig(root);
|
|
659
|
+
const datasource = new Datasource(projectConfig, root);
|
|
660
|
+
|
|
661
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
662
|
+
outDir: "catalog-out",
|
|
663
|
+
copyAssets: false,
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
const manifest = await readJson<any>(root, "catalog-out/data/manifest.json");
|
|
667
|
+
const storefront = await readJson<any>(root, "catalog-out/data/sets/storefront/index.json");
|
|
668
|
+
const admin = await readJson<any>(root, "catalog-out/data/sets/admin/index.json");
|
|
669
|
+
|
|
670
|
+
expect(manifest.sets).toBe(true);
|
|
671
|
+
expect(manifest.setKeys).toEqual(["admin", "storefront"]);
|
|
672
|
+
const storefrontDuplicates = await readJson<any>(
|
|
673
|
+
root,
|
|
674
|
+
"catalog-out/data/sets/storefront/duplicates/locales/en.json",
|
|
675
|
+
);
|
|
676
|
+
const adminDuplicates = await readJson<any>(
|
|
677
|
+
root,
|
|
678
|
+
"catalog-out/data/sets/admin/duplicates/locales/en.json",
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
expect(storefront.counts.message).toBe(2);
|
|
682
|
+
expect(admin.counts.message).toBe(2);
|
|
683
|
+
expect(storefrontDuplicates.duplicateValues).toEqual([
|
|
684
|
+
{
|
|
685
|
+
value: "storefront",
|
|
686
|
+
messageKeys: ["common.duplicate", "common.welcome"],
|
|
687
|
+
sources: [
|
|
688
|
+
{ messageKey: "common.duplicate", locale: "en" },
|
|
689
|
+
{ messageKey: "common.welcome", locale: "en" },
|
|
690
|
+
],
|
|
691
|
+
},
|
|
692
|
+
]);
|
|
693
|
+
expect(adminDuplicates.duplicateValues).toEqual([
|
|
694
|
+
{
|
|
695
|
+
value: "admin",
|
|
696
|
+
messageKeys: ["common.duplicate", "common.welcome"],
|
|
697
|
+
sources: [
|
|
698
|
+
{ messageKey: "common.duplicate", locale: "en" },
|
|
699
|
+
{ messageKey: "common.welcome", locale: "en" },
|
|
700
|
+
],
|
|
701
|
+
},
|
|
702
|
+
]);
|
|
703
|
+
await expect(
|
|
704
|
+
readJson<any>(root, "catalog-out/data/sets/storefront/entities/message/common.welcome.json"),
|
|
705
|
+
).resolves.toMatchObject({
|
|
706
|
+
key: "common.welcome",
|
|
707
|
+
entity: { translations: { en: "storefront" } },
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it("groups streamed Git history by set", async function () {
|
|
712
|
+
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-catalog-"));
|
|
713
|
+
roots.push(root);
|
|
714
|
+
|
|
715
|
+
await writeFile(root, "messagevisor.config.js", "module.exports = { sets: true };\n");
|
|
716
|
+
await writeFile(root, "sets/storefront/locales/en.yml", "description: English\n");
|
|
717
|
+
await writeFile(
|
|
718
|
+
root,
|
|
719
|
+
"sets/storefront/messages/common/welcome.yml",
|
|
720
|
+
"description: Storefront\ntranslations:\n en: Storefront\n",
|
|
721
|
+
);
|
|
722
|
+
await writeFile(root, "sets/admin/locales/en.yml", "description: English\n");
|
|
723
|
+
await writeFile(
|
|
724
|
+
root,
|
|
725
|
+
"sets/admin/messages/common/welcome.yml",
|
|
726
|
+
"description: Admin\ntranslations:\n en: Admin\n",
|
|
727
|
+
);
|
|
728
|
+
await writeFile(
|
|
729
|
+
root,
|
|
730
|
+
"sets/admin/tests/messages/common/welcome.spec.yml",
|
|
731
|
+
"cases:\n - description: Test only\n",
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
git(root, ["init"]);
|
|
735
|
+
git(root, ["add", "."]);
|
|
736
|
+
gitCommit(root, "initial sets");
|
|
737
|
+
|
|
738
|
+
const projectConfig = getProjectConfig(root);
|
|
739
|
+
const datasource = new Datasource(projectConfig, root);
|
|
740
|
+
|
|
741
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
742
|
+
outDir: "catalog-out",
|
|
743
|
+
copyAssets: false,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const projectHistory = await readJson<any>(
|
|
747
|
+
root,
|
|
748
|
+
"catalog-out/data/project/history/page-1.json",
|
|
749
|
+
);
|
|
750
|
+
const storefrontHistory = await readJson<any>(
|
|
751
|
+
root,
|
|
752
|
+
"catalog-out/data/sets/storefront/history/page-1.json",
|
|
753
|
+
);
|
|
754
|
+
const adminHistory = await readJson<any>(
|
|
755
|
+
root,
|
|
756
|
+
"catalog-out/data/sets/admin/history/page-1.json",
|
|
757
|
+
);
|
|
758
|
+
const adminMessageHistory = await readJson<any>(
|
|
759
|
+
root,
|
|
760
|
+
"catalog-out/data/sets/admin/history/message/common.welcome/page-1.json",
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
expect(projectHistory.entries).toHaveLength(1);
|
|
764
|
+
expect(storefrontHistory.entries).toHaveLength(1);
|
|
765
|
+
expect(adminHistory.entries).toHaveLength(1);
|
|
766
|
+
expect(adminMessageHistory.entries[0].entities).toEqual(
|
|
767
|
+
expect.arrayContaining([{ type: "message", key: "common.welcome", set: "admin" }]),
|
|
768
|
+
);
|
|
769
|
+
expect(adminHistory.entries[0].entities).not.toEqual(
|
|
770
|
+
expect.arrayContaining([{ type: "test", key: "common.welcome", set: "admin" }]),
|
|
771
|
+
);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it("paginates many streamed Git history entries without loading one raw output blob", async function () {
|
|
775
|
+
const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-catalog-"));
|
|
776
|
+
roots.push(root);
|
|
777
|
+
await writeFile(root, "messagevisor.config.js", "module.exports = {};\n");
|
|
778
|
+
await writeFile(root, "locales/en.yml", "description: English\n");
|
|
779
|
+
await writeFile(
|
|
780
|
+
root,
|
|
781
|
+
"messages/common/welcome.yml",
|
|
782
|
+
"description: Welcome\ntranslations:\n en: Welcome\n",
|
|
783
|
+
);
|
|
784
|
+
await writeFile(
|
|
785
|
+
root,
|
|
786
|
+
"messages/common/with space.yml",
|
|
787
|
+
"description: With space\ntranslations:\n en: Spaced\n",
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
git(root, ["init"]);
|
|
791
|
+
git(root, ["add", "."]);
|
|
792
|
+
gitCommit(root, "initial catalog project");
|
|
793
|
+
|
|
794
|
+
for (let index = 0; index < 60; index++) {
|
|
795
|
+
await writeFile(
|
|
796
|
+
root,
|
|
797
|
+
"messages/common/welcome.yml",
|
|
798
|
+
`description: Welcome ${index}\ntranslations:\n en: Welcome ${index}\n`,
|
|
799
|
+
);
|
|
800
|
+
git(root, ["add", "."]);
|
|
801
|
+
gitCommit(root, `message update ${index}`);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const projectConfig = getProjectConfig(root);
|
|
805
|
+
const datasource = new Datasource(projectConfig, root);
|
|
806
|
+
|
|
807
|
+
await catalogApi.exportCatalog(root, projectConfig, datasource, {
|
|
808
|
+
outDir: "catalog-out",
|
|
809
|
+
copyAssets: false,
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
const projectHistory = await readJson<any>(
|
|
813
|
+
root,
|
|
814
|
+
"catalog-out/data/project/history/page-1.json",
|
|
815
|
+
);
|
|
816
|
+
const messageHistory = await readJson<any>(
|
|
817
|
+
root,
|
|
818
|
+
"catalog-out/data/root/history/message/common.welcome/page-1.json",
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
expect(projectHistory.totalPages).toBe(2);
|
|
822
|
+
expect(projectHistory.entries).toHaveLength(50);
|
|
823
|
+
expect(projectHistory.entries[0].entities).toEqual([
|
|
824
|
+
{ type: "message", key: "common.welcome" },
|
|
825
|
+
]);
|
|
826
|
+
expect(messageHistory.totalPages).toBe(2);
|
|
827
|
+
expect(messageHistory.entries).toHaveLength(50);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it("uses root-relative asset paths for browser-router refresh safety", async function () {
|
|
831
|
+
const viteConfigSource = await fs.promises.readFile(
|
|
832
|
+
path.join(__dirname, "../../vite.config.ts"),
|
|
833
|
+
"utf8",
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
expect(viteConfigSource).toContain('base: "/"');
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
describe("catalog plugin", function () {
|
|
841
|
+
let exportMock: jest.Mock;
|
|
842
|
+
let serveMock: jest.Mock;
|
|
843
|
+
|
|
844
|
+
beforeEach(function () {
|
|
845
|
+
jest.useFakeTimers();
|
|
846
|
+
exportMock = jest.fn().mockResolvedValue({
|
|
847
|
+
outputDirectoryPath: "/tmp/catalog",
|
|
848
|
+
manifest: {},
|
|
849
|
+
});
|
|
850
|
+
serveMock = jest.fn().mockResolvedValue({
|
|
851
|
+
close: jest.fn(),
|
|
852
|
+
triggerReload: jest.fn(),
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
afterEach(function () {
|
|
857
|
+
jest.clearAllTimers();
|
|
858
|
+
jest.useRealTimers();
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
function createPlugin() {
|
|
862
|
+
const plugin = createCatalogPlugin(catalogRuntime, {
|
|
863
|
+
exportCatalog: exportMock,
|
|
864
|
+
serveCatalog: serveMock,
|
|
865
|
+
});
|
|
866
|
+
const rootDirectoryPath = "/tmp/messagevisor-project";
|
|
867
|
+
const projectConfig = {
|
|
868
|
+
catalogDirectoryPath: path.join(rootDirectoryPath, "catalog"),
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
return {
|
|
872
|
+
plugin,
|
|
873
|
+
handler: (parsed: Record<string, unknown>) =>
|
|
874
|
+
plugin.handler({
|
|
875
|
+
rootDirectoryPath,
|
|
876
|
+
projectConfig,
|
|
877
|
+
datasource: {},
|
|
878
|
+
parsed: parsed as any,
|
|
879
|
+
}),
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
it("forwards long and short port options for dev catalog mode", async function () {
|
|
884
|
+
const { handler } = createPlugin();
|
|
885
|
+
|
|
886
|
+
await handler({ _: ["catalog"], port: 3101 });
|
|
887
|
+
expect(serveMock).toHaveBeenLastCalledWith(
|
|
888
|
+
expect.any(String),
|
|
889
|
+
expect.any(Object),
|
|
890
|
+
expect.any(Object),
|
|
891
|
+
expect.objectContaining({ port: 3101, liveReload: true }),
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
await handler({ _: ["catalog"], p: 3102 });
|
|
895
|
+
expect(serveMock).toHaveBeenLastCalledWith(
|
|
896
|
+
expect.any(String),
|
|
897
|
+
expect.any(Object),
|
|
898
|
+
expect.any(Object),
|
|
899
|
+
expect.objectContaining({ port: 3102, liveReload: true }),
|
|
900
|
+
);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
it("forwards long and short port options for serve subcommand", async function () {
|
|
904
|
+
const { handler } = createPlugin();
|
|
905
|
+
|
|
906
|
+
await handler({ _: ["catalog", "serve"], subcommand: "serve", port: 3103 });
|
|
907
|
+
expect(serveMock).toHaveBeenLastCalledWith(
|
|
908
|
+
expect.any(String),
|
|
909
|
+
expect.any(Object),
|
|
910
|
+
expect.any(Object),
|
|
911
|
+
expect.objectContaining({ port: 3103 }),
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
await handler({ _: ["catalog", "serve"], subcommand: "serve", p: 3104 });
|
|
915
|
+
expect(serveMock).toHaveBeenLastCalledWith(
|
|
916
|
+
expect.any(String),
|
|
917
|
+
expect.any(Object),
|
|
918
|
+
expect.any(Object),
|
|
919
|
+
expect.objectContaining({ port: 3104 }),
|
|
920
|
+
);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it("lets serveCatalog apply its default port when no port option is provided", async function () {
|
|
924
|
+
const { handler } = createPlugin();
|
|
925
|
+
|
|
926
|
+
await handler({ _: ["catalog", "serve"], subcommand: "serve" });
|
|
927
|
+
|
|
928
|
+
expect(serveMock).toHaveBeenLastCalledWith(
|
|
929
|
+
expect.any(String),
|
|
930
|
+
expect.any(Object),
|
|
931
|
+
expect.any(Object),
|
|
932
|
+
expect.objectContaining({ port: undefined }),
|
|
933
|
+
);
|
|
934
|
+
});
|
|
935
|
+
});
|