@tandem-language-exchange/content-store 1.1.2 → 1.2.1
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/README.md +163 -20
- package/dist/chunk-EQ3DSPTJ.js +86 -0
- package/dist/chunk-EQ3DSPTJ.js.map +1 -0
- package/dist/{chunk-JBFJU4JA.js → chunk-LOCC2BXB.js} +118 -13
- package/dist/chunk-LOCC2BXB.js.map +1 -0
- package/dist/chunk-OTZLCMZ6.js +396 -0
- package/dist/chunk-OTZLCMZ6.js.map +1 -0
- package/dist/{chunk-YOREZCXB.js → chunk-PQJ2MGH7.js} +180 -29
- package/dist/chunk-PQJ2MGH7.js.map +1 -0
- package/dist/client/cli.js +31 -8
- package/dist/client/cli.js.map +1 -1
- package/dist/client/{fetch-bundles.js → fetch-content-bundles.js} +8 -7
- package/dist/client/fetch-content-bundles.js.map +1 -0
- package/dist/client/fetch-merged-translation-bundles.js +40 -0
- package/dist/client/fetch-merged-translation-bundles.js.map +1 -0
- package/dist/client/fetch-translation-bundles.js +43 -0
- package/dist/client/fetch-translation-bundles.js.map +1 -0
- package/dist/index-kfqHGgMO.d.ts +118 -0
- package/dist/index.d.ts +1 -1
- package/dist/node.browser.js +20 -6
- package/dist/node.browser.js.map +1 -1
- package/dist/node.d.ts +18 -5
- package/dist/node.js +527 -41
- package/dist/node.js.map +1 -1
- package/package.json +25 -11
- package/dist/chunk-JBFJU4JA.js.map +0 -1
- package/dist/chunk-UWGOF36L.js +0 -85
- package/dist/chunk-UWGOF36L.js.map +0 -1
- package/dist/chunk-YOREZCXB.js.map +0 -1
- package/dist/client/fetch-bundles.js.map +0 -1
- package/dist/index-DJXkO17k.d.ts +0 -83
|
@@ -1,13 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ContentStore,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
allProjects,
|
|
5
|
+
buildCmsObjectKey,
|
|
6
|
+
buildTranslationObjectKey,
|
|
7
|
+
config,
|
|
8
|
+
contentTypeForTranslationKey,
|
|
9
|
+
defaultLocales
|
|
10
|
+
} from "./chunk-OTZLCMZ6.js";
|
|
6
11
|
|
|
7
12
|
// src/server/config.ts
|
|
8
13
|
import dotenv from "dotenv";
|
|
9
14
|
dotenv.config({ path: ".env.local" });
|
|
10
15
|
dotenv.config();
|
|
16
|
+
function parseScheduledCmsJobConfig() {
|
|
17
|
+
const intervalMinutes = parseInt(process.env.SCHEDULE_INTERVAL_MINUTES ?? "0", 60);
|
|
18
|
+
const enabled = Number.isFinite(intervalMinutes) && intervalMinutes > 0;
|
|
19
|
+
const runOnStart = (process.env.SCHEDULE_RUN_ON_START ?? "true").toLowerCase() !== "false";
|
|
20
|
+
const task = (process.env.SCHEDULE_TASK ?? "sync").trim();
|
|
21
|
+
const syncCms = (process.env.SCHEDULE_SYNC_CMS ?? "").trim();
|
|
22
|
+
const rawTypes = process.env.SCHEDULE_SYNC_TYPES?.trim();
|
|
23
|
+
const syncTypes = rawTypes ? rawTypes.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
24
|
+
return {
|
|
25
|
+
enabled,
|
|
26
|
+
intervalMinutes,
|
|
27
|
+
runOnStart,
|
|
28
|
+
task,
|
|
29
|
+
syncCms: syncCms || void 0,
|
|
30
|
+
syncTypes
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function parseScheduledTranslationJobConfig() {
|
|
34
|
+
const intervalMinutes = parseInt(process.env.SCHEDULE_INTERVAL_MINUTES ?? "0", 60);
|
|
35
|
+
const enabled = Number.isFinite(intervalMinutes) && intervalMinutes > 0;
|
|
36
|
+
const runOnStart = (process.env.SCHEDULE_RUN_ON_START ?? "true").toLowerCase() !== "false";
|
|
37
|
+
const task = (process.env.SCHEDULE_TASK ?? "sync").trim();
|
|
38
|
+
const rawProjects = process.env.SCHEDULE_SYNC_TRANSLATION_PROJECTS?.trim();
|
|
39
|
+
const syncProjects = rawProjects ? rawProjects.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
40
|
+
return {
|
|
41
|
+
enabled,
|
|
42
|
+
intervalMinutes,
|
|
43
|
+
runOnStart,
|
|
44
|
+
task,
|
|
45
|
+
syncProjects
|
|
46
|
+
};
|
|
47
|
+
}
|
|
11
48
|
var config2 = {
|
|
12
49
|
...config,
|
|
13
50
|
contentful: {
|
|
@@ -17,11 +54,14 @@ var config2 = {
|
|
|
17
54
|
batchSize: 1e3,
|
|
18
55
|
maxDepth: 4,
|
|
19
56
|
contentTypes: [
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
57
|
+
"asset",
|
|
58
|
+
"page",
|
|
59
|
+
"longtailPage",
|
|
60
|
+
"customJson",
|
|
61
|
+
"banner",
|
|
62
|
+
"cookieBanner",
|
|
63
|
+
"downloadPage"
|
|
23
64
|
// Add Contentful content type IDs here to limit sync scope.
|
|
24
|
-
// Leave empty to sync all content types in the space.
|
|
25
65
|
]
|
|
26
66
|
},
|
|
27
67
|
sanity: {
|
|
@@ -30,6 +70,10 @@ var config2 = {
|
|
|
30
70
|
token: process.env.SANITY_API_TOKEN ?? "",
|
|
31
71
|
apiVersion: "2024-01-01"
|
|
32
72
|
},
|
|
73
|
+
lingohub: {
|
|
74
|
+
authToken: process.env.LINGOHUB_AUTH_TOKEN ?? "",
|
|
75
|
+
workspace: process.env.LINGOHUB_WORKSPACE ?? ""
|
|
76
|
+
},
|
|
33
77
|
retry: {
|
|
34
78
|
maxRetries: parseInt(process.env.RETRY_MAX_RETRIES ?? "5", 10),
|
|
35
79
|
baseDelayMs: parseInt(process.env.RETRY_BASE_DELAY_MS ?? "1000", 10),
|
|
@@ -38,6 +82,18 @@ var config2 = {
|
|
|
38
82
|
api: {
|
|
39
83
|
port: parseInt(process.env.PORT ?? "3010"),
|
|
40
84
|
apiToken: process.env.CONTENT_STORE_API_TOKEN ?? ""
|
|
85
|
+
},
|
|
86
|
+
scheduledCmsJob: parseScheduledCmsJobConfig(),
|
|
87
|
+
scheduledTranslationJob: parseScheduledTranslationJobConfig(),
|
|
88
|
+
cleanup: {
|
|
89
|
+
enabled: (process.env.CLEANUP_ENABLED ?? "true").toLowerCase() !== "false",
|
|
90
|
+
retentionDays: parseInt(process.env.CLEANUP_RETENTION_DAYS ?? "30", 10)
|
|
91
|
+
},
|
|
92
|
+
slack: {
|
|
93
|
+
enabled: (process.env.SLACK_BOT_TOKEN ?? "").length > 0,
|
|
94
|
+
botToken: process.env.SLACK_BOT_TOKEN ?? "",
|
|
95
|
+
signingSecret: process.env.SLACK_SIGNING_SECRET ?? "",
|
|
96
|
+
appToken: process.env.SLACK_APP_TOKEN ?? ""
|
|
41
97
|
}
|
|
42
98
|
};
|
|
43
99
|
|
|
@@ -134,15 +190,15 @@ var ContentfulAdapter = class {
|
|
|
134
190
|
maxDepth;
|
|
135
191
|
allowedTypes;
|
|
136
192
|
retryConfig;
|
|
137
|
-
constructor(
|
|
193
|
+
constructor(cfg2, retryConfig) {
|
|
138
194
|
this.client = createClient({
|
|
139
|
-
space:
|
|
140
|
-
accessToken:
|
|
141
|
-
host:
|
|
195
|
+
space: cfg2.spaceId,
|
|
196
|
+
accessToken: cfg2.accessToken,
|
|
197
|
+
host: cfg2.host
|
|
142
198
|
});
|
|
143
|
-
this.batchSize =
|
|
144
|
-
this.maxDepth =
|
|
145
|
-
this.allowedTypes =
|
|
199
|
+
this.batchSize = cfg2.batchSize;
|
|
200
|
+
this.maxDepth = cfg2.maxDepth;
|
|
201
|
+
this.allowedTypes = cfg2.contentTypes;
|
|
146
202
|
this.retryConfig = retryConfig;
|
|
147
203
|
}
|
|
148
204
|
async getContentTypes() {
|
|
@@ -160,8 +216,13 @@ var ContentfulAdapter = class {
|
|
|
160
216
|
* Fetches every entry for a content type using batched pagination.
|
|
161
217
|
* Contentful caps `getEntries` at 1 000 items per call, so we page through
|
|
162
218
|
* with `skip` until all items are collected.
|
|
219
|
+
*
|
|
220
|
+
* The reserved content type `"asset"` fetches from `getAssets()` instead.
|
|
163
221
|
*/
|
|
164
222
|
async fetchAll(contentType, includeLevels = 4) {
|
|
223
|
+
if (contentType === "asset") {
|
|
224
|
+
return this.fetchAllAssets();
|
|
225
|
+
}
|
|
165
226
|
const allItems = [];
|
|
166
227
|
let skip = 0;
|
|
167
228
|
let total = 0;
|
|
@@ -188,6 +249,30 @@ var ContentfulAdapter = class {
|
|
|
188
249
|
total
|
|
189
250
|
};
|
|
190
251
|
}
|
|
252
|
+
async fetchAllAssets() {
|
|
253
|
+
const allItems = [];
|
|
254
|
+
let skip = 0;
|
|
255
|
+
let total = 0;
|
|
256
|
+
do {
|
|
257
|
+
const response = await withRetry(
|
|
258
|
+
() => this.client.getAssets({ limit: this.batchSize, skip }),
|
|
259
|
+
this.retryConfig
|
|
260
|
+
);
|
|
261
|
+
total = response.total;
|
|
262
|
+
allItems.push(...response.items);
|
|
263
|
+
skip += response.items.length;
|
|
264
|
+
if (total > this.batchSize) {
|
|
265
|
+
console.log(
|
|
266
|
+
` [contentful] asset: fetched ${allItems.length}/${total}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
} while (skip < total);
|
|
270
|
+
return {
|
|
271
|
+
contentType: "asset",
|
|
272
|
+
items: allItems.map((item) => stripEnvelope(item, this.maxDepth)),
|
|
273
|
+
total
|
|
274
|
+
};
|
|
275
|
+
}
|
|
191
276
|
};
|
|
192
277
|
|
|
193
278
|
// src/server/adapters/sanity.ts
|
|
@@ -196,19 +281,19 @@ var SanityAdapter = class {
|
|
|
196
281
|
name = "sanity";
|
|
197
282
|
client;
|
|
198
283
|
retryConfig;
|
|
199
|
-
constructor(
|
|
284
|
+
constructor(cfg2, retryConfig) {
|
|
200
285
|
this.client = createClient2({
|
|
201
|
-
projectId:
|
|
202
|
-
dataset:
|
|
203
|
-
token:
|
|
204
|
-
apiVersion:
|
|
286
|
+
projectId: cfg2.projectId,
|
|
287
|
+
dataset: cfg2.dataset,
|
|
288
|
+
token: cfg2.token,
|
|
289
|
+
apiVersion: cfg2.apiVersion,
|
|
205
290
|
useCdn: false
|
|
206
291
|
});
|
|
207
292
|
this.retryConfig = retryConfig;
|
|
208
293
|
}
|
|
209
294
|
async getContentTypes() {
|
|
210
295
|
const types = await withRetry(
|
|
211
|
-
() => this.client.fetch(
|
|
296
|
+
() => this.client.fetch("array::unique(*[]._type)"),
|
|
212
297
|
this.retryConfig
|
|
213
298
|
);
|
|
214
299
|
return types.filter(
|
|
@@ -217,7 +302,7 @@ var SanityAdapter = class {
|
|
|
217
302
|
}
|
|
218
303
|
async fetchAll(contentType) {
|
|
219
304
|
const items = await withRetry(
|
|
220
|
-
() => this.client.fetch(
|
|
305
|
+
() => this.client.fetch("*[_type == $type]", { type: contentType }),
|
|
221
306
|
this.retryConfig
|
|
222
307
|
);
|
|
223
308
|
console.log(` [sanity] ${contentType}: fetched ${items.length} items`);
|
|
@@ -237,8 +322,23 @@ function createAdapter(cms) {
|
|
|
237
322
|
}
|
|
238
323
|
}
|
|
239
324
|
|
|
325
|
+
// src/server/adapters/lingohub.ts
|
|
326
|
+
var cfg = config2.lingohub;
|
|
327
|
+
var apiUrl = "https://api.lingohub.com/v1/" + cfg.workspace + "/projects/";
|
|
328
|
+
async function fetchLingohubResourceRaw(project, resource, locale) {
|
|
329
|
+
const urlForResourceLocalised = `${apiUrl}${project}/resources/${resource.fileName}?auth_token=${cfg.authToken}`.replace(
|
|
330
|
+
"[locale]",
|
|
331
|
+
locale
|
|
332
|
+
);
|
|
333
|
+
const res = await fetch(urlForResourceLocalised, { method: "GET" });
|
|
334
|
+
if (!res.ok) {
|
|
335
|
+
throw new Error(`Failed to fetch resource: ${res.status} - ${res.statusText}`);
|
|
336
|
+
}
|
|
337
|
+
return await res.text();
|
|
338
|
+
}
|
|
339
|
+
|
|
240
340
|
// src/server/sync/engine.ts
|
|
241
|
-
async function
|
|
341
|
+
async function syncCmsContent(cms, contentTypes, includeLevels) {
|
|
242
342
|
const adapter = createAdapter(cms);
|
|
243
343
|
const store = new ContentStore(config2.s3);
|
|
244
344
|
const timestamp = Math.floor(Date.now() / 1e3);
|
|
@@ -252,17 +352,15 @@ Starting sync from ${cms} at ${new Date(timestamp * 1e3).toISOString()}`);
|
|
|
252
352
|
for (const contentType of typesToSync) {
|
|
253
353
|
try {
|
|
254
354
|
const result = await adapter.fetchAll(contentType, includeLevels);
|
|
255
|
-
const
|
|
256
|
-
await store.upload(
|
|
257
|
-
const latestKey = await store.copyToLatest(versionedKey, cms, contentType);
|
|
355
|
+
const objectKey = buildCmsObjectKey(cms, contentType);
|
|
356
|
+
await store.upload(objectKey, result.items);
|
|
258
357
|
entries.push({
|
|
259
358
|
contentType,
|
|
260
359
|
itemCount: result.total,
|
|
261
|
-
|
|
262
|
-
latestKey
|
|
360
|
+
objectKey
|
|
263
361
|
});
|
|
264
362
|
console.log(
|
|
265
|
-
` + ${contentType}: ${result.total} items -> ${
|
|
363
|
+
` + ${contentType}: ${result.total} items -> ${objectKey}`
|
|
266
364
|
);
|
|
267
365
|
} catch (err) {
|
|
268
366
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -277,9 +375,62 @@ Sync complete: ${entries.length} succeeded, ${errors.length} failed
|
|
|
277
375
|
);
|
|
278
376
|
return { cms, timestamp, entries, errors };
|
|
279
377
|
}
|
|
378
|
+
async function syncTranslations(projects, locales) {
|
|
379
|
+
const store = new ContentStore(config2.s3);
|
|
380
|
+
const entries = [];
|
|
381
|
+
const errors = [];
|
|
382
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
383
|
+
if (!locales) {
|
|
384
|
+
locales = defaultLocales;
|
|
385
|
+
}
|
|
386
|
+
if (!projects) {
|
|
387
|
+
projects = Object.keys(allProjects);
|
|
388
|
+
}
|
|
389
|
+
for (const project of projects) {
|
|
390
|
+
const resources = allProjects[project];
|
|
391
|
+
if (!resources) {
|
|
392
|
+
console.error(`No resources found for ${project}`);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
for (const resource of resources) {
|
|
396
|
+
for (const loc of locales) {
|
|
397
|
+
const locale = resource.localeMapping && resource.localeMapping[loc] ? resource.localeMapping[loc] : loc;
|
|
398
|
+
try {
|
|
399
|
+
const raw = await fetchLingohubResourceRaw(project, resource, locale);
|
|
400
|
+
const objectKey = buildTranslationObjectKey(
|
|
401
|
+
project,
|
|
402
|
+
resource.fileName,
|
|
403
|
+
locale
|
|
404
|
+
);
|
|
405
|
+
await store.uploadRaw(
|
|
406
|
+
objectKey,
|
|
407
|
+
raw,
|
|
408
|
+
contentTypeForTranslationKey(objectKey)
|
|
409
|
+
);
|
|
410
|
+
const byteLength = Buffer.byteLength(raw, "utf8");
|
|
411
|
+
entries.push({
|
|
412
|
+
project,
|
|
413
|
+
locale,
|
|
414
|
+
itemCount: byteLength,
|
|
415
|
+
objectKey
|
|
416
|
+
});
|
|
417
|
+
console.log(
|
|
418
|
+
` + ${project} - ${locale}: ${byteLength} bytes -> ${objectKey}`
|
|
419
|
+
);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
422
|
+
errors.push({ project, locale, error: message });
|
|
423
|
+
console.error(` x ${project} - ${locale}: ${message}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return { timestamp, entries, errors };
|
|
429
|
+
}
|
|
280
430
|
|
|
281
431
|
export {
|
|
282
432
|
config2 as config,
|
|
283
|
-
|
|
433
|
+
syncCmsContent,
|
|
434
|
+
syncTranslations
|
|
284
435
|
};
|
|
285
|
-
//# sourceMappingURL=chunk-
|
|
436
|
+
//# sourceMappingURL=chunk-PQJ2MGH7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/config.ts","../src/server/adapters/contentful.ts","../src/server/sync/retry.ts","../src/server/adapters/sanity.ts","../src/server/adapters/index.ts","../src/server/adapters/lingohub.ts","../src/server/sync/engine.ts"],"sourcesContent":["import dotenv from 'dotenv';\nimport type { S3Config, CMSProvider } from '../shared/types';\nimport {SharedConfig, config as sharedConfig} from '../shared/config';\n\ndotenv.config({ path: '.env.local' });\ndotenv.config();\n\nexport type { CMSProvider, S3Config };\n\nexport interface ContentfulConfig {\n spaceId: string;\n accessToken: string;\n host: string;\n batchSize: number;\n /** Content types to sync. When empty, all content types in the space are synced. */\n contentTypes: string[];\n /** Max nesting depth when unwrapping resolved entries. Deeper references are dropped. */\n maxDepth: number;\n}\n\nexport interface SanityConfig {\n projectId: string;\n dataset: string;\n token: string;\n apiVersion: string;\n}\n\nexport interface LingohubConfig {\n authToken: string;\n workspace: string;\n}\n\nexport interface RetryConfig {\n maxRetries: number;\n baseDelayMs: number;\n maxDelayMs: number;\n}\n\nexport interface RestApiConfig {\n port: number;\n apiToken: string;\n}\n\n/** Background schedule (same behaviour as `content-store sync` in the CLI). */\nexport interface ScheduledCmsJobConfig {\n enabled: boolean;\n intervalMinutes: number;\n /** When true, run once when the server starts, then on every interval. */\n runOnStart: boolean;\n /** Job kind; only `sync` is implemented. */\n task: string;\n syncCms?: CMSProvider;\n syncTypes?: string[];\n}\n\nexport interface ScheduledTranslationJobConfig {\n enabled: boolean;\n intervalMinutes: number;\n /** When true, run once when the server starts, then on every interval. */\n runOnStart: boolean;\n /** Job kind; only `sync` is implemented. */\n task: string;\n syncProjects?: string[];\n}\n\nfunction parseScheduledCmsJobConfig(): ScheduledCmsJobConfig {\n const intervalMinutes = parseInt(process.env.SCHEDULE_INTERVAL_MINUTES ?? '0', 60);\n const enabled = Number.isFinite(intervalMinutes) && intervalMinutes > 0;\n const runOnStart =\n (process.env.SCHEDULE_RUN_ON_START ?? 'true').toLowerCase() !== 'false';\n const task = (process.env.SCHEDULE_TASK ?? 'sync').trim();\n const syncCms = (process.env.SCHEDULE_SYNC_CMS ?? '').trim() as CMSProvider;\n const rawTypes = process.env.SCHEDULE_SYNC_TYPES?.trim();\n const syncTypes = rawTypes\n ? rawTypes.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined;\n\n return {\n enabled,\n intervalMinutes,\n runOnStart,\n task,\n syncCms: syncCms || undefined,\n syncTypes,\n };\n}\n\nfunction parseScheduledTranslationJobConfig(): ScheduledTranslationJobConfig {\n const intervalMinutes = parseInt(process.env.SCHEDULE_INTERVAL_MINUTES ?? '0', 60);\n const enabled = Number.isFinite(intervalMinutes) && intervalMinutes > 0;\n const runOnStart =\n (process.env.SCHEDULE_RUN_ON_START ?? 'true').toLowerCase() !== 'false';\n const task = (process.env.SCHEDULE_TASK ?? 'sync').trim();\n const rawProjects = process.env.SCHEDULE_SYNC_TRANSLATION_PROJECTS?.trim();\n const syncProjects = rawProjects\n ? rawProjects.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined;\n\n return {\n enabled,\n intervalMinutes,\n runOnStart,\n task,\n syncProjects,\n };\n}\n\nexport interface CleanupConfig {\n enabled: boolean;\n /** Delete versioned S3 objects older than this many days. */\n retentionDays: number;\n}\n\nexport interface SlackConfig {\n enabled: boolean;\n botToken: string;\n signingSecret: string;\n /** Socket Mode app-level token (xapp-…). Required when enabled. */\n appToken: string;\n}\n\nexport interface ServerConfig {\n contentful: ContentfulConfig;\n sanity: SanityConfig;\n lingohub: LingohubConfig;\n retry: RetryConfig;\n api: RestApiConfig;\n scheduledCmsJob: ScheduledCmsJobConfig;\n scheduledTranslationJob: ScheduledTranslationJobConfig;\n cleanup: CleanupConfig;\n slack: SlackConfig;\n}\n\nexport const config: ServerConfig & SharedConfig = {\n ...sharedConfig,\n contentful: {\n spaceId: process.env.CONTENTFUL_SPACE_ID ?? '',\n accessToken: process.env.CONTENTFUL_WEBSITE_TOKEN ?? '',\n host: process.env.CONTENTFUL_HOST ?? 'preview.contentful.com',\n batchSize: 1000,\n maxDepth: 4,\n contentTypes: [\n 'asset','page','longtailPage','customJson','banner','cookieBanner','downloadPage'\n // Add Contentful content type IDs here to limit sync scope.\n ],\n },\n\n sanity: {\n projectId: process.env.SANITY_PROJECT_ID ?? '',\n dataset: process.env.SANITY_DATASET ?? 'main',\n token: process.env.SANITY_API_TOKEN ?? '',\n apiVersion: '2024-01-01',\n },\n\n lingohub: {\n authToken : process.env.LINGOHUB_AUTH_TOKEN ?? '',\n workspace: process.env.LINGOHUB_WORKSPACE ?? '',\n },\n\n retry: {\n maxRetries: parseInt(process.env.RETRY_MAX_RETRIES ?? '5', 10),\n baseDelayMs: parseInt(process.env.RETRY_BASE_DELAY_MS ?? '1000', 10),\n maxDelayMs: parseInt(process.env.RETRY_MAX_DELAY_MS ?? '60000', 10),\n },\n\n api: {\n port: parseInt(process.env.PORT ?? '3010'),\n apiToken: process.env.CONTENT_STORE_API_TOKEN ?? '',\n },\n\n scheduledCmsJob: parseScheduledCmsJobConfig(),\n scheduledTranslationJob: parseScheduledTranslationJobConfig(),\n\n cleanup: {\n enabled: (process.env.CLEANUP_ENABLED ?? 'true').toLowerCase() !== 'false',\n retentionDays: parseInt(process.env.CLEANUP_RETENTION_DAYS ?? '30', 10),\n },\n\n slack: {\n enabled: (process.env.SLACK_BOT_TOKEN ?? '').length > 0,\n botToken: process.env.SLACK_BOT_TOKEN ?? '',\n signingSecret: process.env.SLACK_SIGNING_SECRET ?? '',\n appToken: process.env.SLACK_APP_TOKEN ?? '',\n },\n};\n","import {\n createClient,\n type ContentfulClientApi,\n type ContentTypeCollection,\n type AssetCollection,\n} from 'contentful';\nimport type { ContentfulConfig, RetryConfig } from '../config';\nimport type { CMSAdapter, FetchResult } from './types';\nimport { withRetry } from '../sync/retry';\n\ntype CfCollection = {\n items: CfItem[],\n total: number\n}\n\ntype CfItem = {\n metadata:{\n tags: string[],\n concepts: string[]\n }\n sys:{\n type: string,\n id: string\n space: {\n sys: {\n type: string\n linkType: string\n id: string\n }\n },\n environment: {\n sys: {\n id: string\n type: 'Link',\n linkType: 'Environment'\n }\n },\n contentType: {\n sys: {\n type: 'Link',\n linkType: 'ContentType',\n id: string\n }\n },\n createdBy: {\n sys: {\n type: 'Link',\n linkType: 'User',\n id: string,\n }\n },\n updatedBy: {\n sys: {\n type: 'Link',\n linkType: 'User',\n id: string\n }\n },\n 'revision': number,\n 'createdAt': string,\n 'updatedAt': string,\n 'publishedVersion': string\n },\n fields:{\n [key:string]: unknown\n }\n}\n\n\n/**\n * Recursively unwraps Contentful's { metadata, sys, fields } envelope.\n * `depth` tracks how many entry/asset envelopes deep we are — anything\n * beyond `maxDepth` is dropped to avoid blowing the call stack on\n * circular or extremely deep reference chains.\n *\n * `path` holds objects on the current recursion branch only. That way a\n * shared reference (e.g. the same asset on `image` and `mobileImage`) is\n * unwrapped for each sibling; only true cycles (an object recurring as a\n * descendant of itself) yield `undefined`.\n */\nfunction stripEnvelope(\n value: unknown,\n maxDepth: number,\n depth = 0,\n path = new WeakSet<object>(),\n): unknown {\n if (value === null || typeof value !== 'object') return value;\n\n const obj = value as CfItem;\n\n if (path.has(obj)) return undefined;\n path.add(obj);\n\n try {\n if (Array.isArray(value)) {\n return value.map((item) => stripEnvelope(item, maxDepth, depth, path));\n }\n\n const isEnvelope = 'sys' in obj && 'fields' in obj && typeof obj.fields === 'object';\n\n if (isEnvelope) {\n if (depth >= maxDepth) return undefined;\n\n const fields = obj.fields as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(fields)) {\n result[k] = stripEnvelope(v, maxDepth, depth + 1, path);\n }\n\n const sys = obj.sys;\n if (sys) {\n const existingMeta = result.meta;\n const metaBase =\n typeof existingMeta === 'object' &&\n existingMeta !== null &&\n !Array.isArray(existingMeta)\n ? (existingMeta as Record<string, unknown>)\n : {};\n const _contentType = sys.contentType ? sys.contentType.sys.id : 'Asset'\n result.meta = { ...metaBase, _id: sys.id, _contentType, _updatedAt: sys.updatedAt };\n }\n\n return result;\n }\n\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n result[k] = stripEnvelope(v, maxDepth, depth, path);\n }\n return result;\n } finally {\n path.delete(obj);\n }\n}\n\nexport class ContentfulAdapter implements CMSAdapter {\n readonly name = 'contentful';\n private client: ContentfulClientApi<undefined>;\n private batchSize: number;\n private maxDepth: number;\n private allowedTypes: string[];\n private retryConfig: RetryConfig;\n\n constructor(cfg: ContentfulConfig, retryConfig: RetryConfig) {\n this.client = createClient({\n space: cfg.spaceId,\n accessToken: cfg.accessToken,\n host: cfg.host,\n });\n this.batchSize = cfg.batchSize;\n this.maxDepth = cfg.maxDepth;\n this.allowedTypes = cfg.contentTypes;\n this.retryConfig = retryConfig;\n }\n\n async getContentTypes(): Promise<string[]> {\n const response = await withRetry<ContentTypeCollection>(\n () => this.client.getContentTypes(),\n this.retryConfig,\n );\n\n const allTypes = response.items.map((ct) => ct.sys.id);\n\n if (this.allowedTypes.length > 0) {\n return allTypes.filter((t) => this.allowedTypes.includes(t));\n }\n return allTypes;\n }\n\n /**\n * Fetches every entry for a content type using batched pagination.\n * Contentful caps `getEntries` at 1 000 items per call, so we page through\n * with `skip` until all items are collected.\n *\n * The reserved content type `\"asset\"` fetches from `getAssets()` instead.\n */\n async fetchAll(contentType: string, includeLevels: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 = 4): Promise<FetchResult> {\n if (contentType === 'asset') {\n return this.fetchAllAssets();\n }\n\n const allItems: unknown[] = [];\n let skip = 0;\n let total = 0;\n\n do {\n const payload = {\n content_type: contentType,\n limit: this.batchSize,\n skip,\n include: includeLevels,\n };\n const response = await this.client.getEntries(payload) as unknown as CfCollection;\n total = response.total;\n allItems.push(...response.items);\n skip += response.items.length;\n\n if (total > this.batchSize) {\n console.log(\n ` [contentful] ${contentType}: fetched ${allItems.length}/${total}`,\n );\n }\n } while (skip < total);\n\n return {\n contentType,\n items: allItems.map((item) => stripEnvelope(item, this.maxDepth)),\n total,\n };\n }\n\n private async fetchAllAssets(): Promise<FetchResult> {\n const allItems: unknown[] = [];\n let skip = 0;\n let total = 0;\n\n do {\n const response = await withRetry<AssetCollection>(\n () => this.client.getAssets({ limit: this.batchSize, skip }),\n this.retryConfig,\n );\n total = response.total;\n allItems.push(...response.items);\n skip += response.items.length;\n\n if (total > this.batchSize) {\n console.log(\n ` [contentful] asset: fetched ${allItems.length}/${total}`,\n );\n }\n } while (skip < total);\n\n return {\n contentType: 'asset',\n items: allItems.map((item) => stripEnvelope(item, this.maxDepth)),\n total,\n };\n }\n}\n","import type { RetryConfig } from '../config';\n\n/**\n * Inspects an error to determine if it represents an API rate-limit (HTTP 429).\n * Returns the suggested wait time in ms when available, otherwise `0` to signal\n * that the caller should fall back to computed backoff. Returns `null` when the\n * error is *not* a rate-limit error.\n */\nfunction rateLimitDelayMs(err: unknown): number | null {\n const e = err as Record<string, unknown>;\n\n if (e?.status === 429 || e?.statusCode === 429) {\n const reset = (e?.headers as Record<string, string> | undefined)?.[\n 'x-contentful-ratelimit-reset'\n ];\n return reset ? parseFloat(reset) * 1000 : 0;\n }\n\n const resp = e?.response as Record<string, unknown> | undefined;\n if (resp?.status === 429) {\n const headers = resp?.headers as Record<string, string> | undefined;\n const reset = headers?.['x-contentful-ratelimit-reset'];\n return reset ? parseFloat(reset) * 1000 : 0;\n }\n\n return null;\n}\n\nfunction computeDelay(\n attempt: number,\n baseDelayMs: number,\n maxDelayMs: number,\n): number {\n const exponential = baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.random() * baseDelayMs;\n return Math.min(exponential + jitter, maxDelayMs);\n}\n\n/**\n * Executes `fn` with automatic retry + exponential backoff.\n * Rate-limit (429) responses are handled specially: if the API provides a\n * Retry-After / reset header, that value is respected instead of computed backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n { maxRetries, baseDelayMs, maxDelayMs }: RetryConfig,\n): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (attempt === maxRetries) throw err;\n\n const rlDelay = rateLimitDelayMs(err);\n let delay: number;\n\n if (rlDelay !== null) {\n delay =\n rlDelay > 0 ? rlDelay : computeDelay(attempt, baseDelayMs, maxDelayMs);\n console.warn(\n ` Rate limited (attempt ${attempt + 1}/${maxRetries}). ` +\n `Waiting ${Math.round(delay)}ms…`,\n );\n } else {\n delay = computeDelay(attempt, baseDelayMs, maxDelayMs);\n console.warn(\n ` Request failed (attempt ${attempt + 1}/${maxRetries}): ` +\n `${(err as Error).message}. Retrying in ${Math.round(delay)}ms…`,\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error('withRetry: unreachable');\n}\n","import { createClient, type SanityClient } from '@sanity/client';\nimport type { SanityConfig, RetryConfig } from '../config';\nimport type { CMSAdapter, FetchResult } from './types';\nimport { withRetry } from '../sync/retry';\n\nexport class SanityAdapter implements CMSAdapter {\n readonly name = 'sanity';\n private client: SanityClient;\n private retryConfig: RetryConfig;\n\n constructor(cfg: SanityConfig, retryConfig: RetryConfig) {\n this.client = createClient({\n projectId: cfg.projectId,\n dataset: cfg.dataset,\n token: cfg.token,\n apiVersion: cfg.apiVersion,\n useCdn: false,\n });\n this.retryConfig = retryConfig;\n }\n\n async getContentTypes(): Promise<string[]> {\n const types: string[] = await withRetry(\n () => this.client.fetch('array::unique(*[]._type)'),\n this.retryConfig,\n );\n return types.filter(\n (t) => !t.startsWith('system.') && !t.startsWith('sanity.'),\n );\n }\n\n async fetchAll(contentType: string): Promise<FetchResult> {\n const items: unknown[] = await withRetry(\n () => this.client.fetch('*[_type == $type]', { type: contentType }),\n this.retryConfig,\n );\n\n console.log(` [sanity] ${contentType}: fetched ${items.length} items`);\n return { contentType, items, total: items.length };\n }\n}\n","import { config, type CMSProvider } from '../config';\nimport type { CMSAdapter } from './types';\nimport { ContentfulAdapter } from './contentful';\nimport { SanityAdapter } from './sanity';\n\nexport function createAdapter(cms: CMSProvider): CMSAdapter {\n switch (cms) {\n case 'contentful':\n return new ContentfulAdapter(config.contentful, config.retry);\n case 'sanity':\n return new SanityAdapter(config.sanity, config.retry);\n default:\n throw new Error(`Unknown CMS provider: ${cms as string}`);\n }\n}\n\nexport type { CMSAdapter, FetchResult } from './types';\n","import { config } from '../config';\nimport type { LingohubResource } from '../../shared/lingohub';\n\nconst cfg = config.lingohub;\nconst apiUrl = 'https://api.lingohub.com/v1/' + cfg.workspace + '/projects/';\n\n/**\n * Downloads the raw Lingohub resource body (exact bytes as UTF-8 text).\n * Sync uploads this unmodified to S3; conversion happens at fetch time.\n */\nexport async function fetchLingohubResourceRaw(\n project: string,\n resource: LingohubResource,\n locale: string,\n): Promise<string> {\n const urlForResourceLocalised =\n `${apiUrl}${project}/resources/${resource.fileName}?auth_token=${cfg.authToken}`.replace(\n '[locale]',\n locale,\n );\n const res = await fetch(urlForResourceLocalised, { method: 'GET' });\n if (!res.ok) {\n throw new Error(`Failed to fetch resource: ${res.status} - ${res.statusText}`);\n }\n return await res.text();\n}\n","import type { CMSProvider } from '../config';\nimport { config } from '../config';\nimport { createAdapter } from '../adapters';\nimport {buildCmsObjectKey, buildTranslationObjectKey, ContentStore} from '../../shared/s3';\nimport { allProjects, defaultLocales } from '../../shared/lingohub';\nimport { fetchLingohubResourceRaw } from '../adapters/lingohub';\nimport { contentTypeForTranslationKey } from '../../shared/translationResource';\n\nexport interface CmsSyncResultEntry {\n contentType: string;\n itemCount: number;\n objectKey: string;\n}\n\nexport interface CmsSyncResult {\n cms: CMSProvider;\n timestamp: number;\n entries: CmsSyncResultEntry[];\n errors: Array<{ contentType: string; error: string }>;\n}\n\nexport interface TranslationSyncResultEntry {\n project: string;\n locale: string;\n /** UTF-8 byte size of the raw Lingohub file uploaded to S3. */\n itemCount: number;\n objectKey: string;\n}\n\nexport interface TranslationSyncResult {\n timestamp: number;\n entries: TranslationSyncResultEntry[];\n errors: Array<{ locale: string; project: string, error: string }>;\n}\n\nexport async function runSync(cms: CMSProvider, contentTypes?: string[], includeLevels?: number){\n await syncCmsContent(cms, contentTypes, includeLevels )\n}\n\nexport async function syncCmsContent(\n cms: CMSProvider,\n contentTypes?: string[],\n includeLevels?: number\n): Promise<CmsSyncResult> {\n const adapter = createAdapter(cms);\n const store = new ContentStore(config.s3);\n const timestamp = Math.floor(Date.now() / 1000);\n\n console.log(`\\nStarting sync from ${cms} at ${new Date(timestamp * 1000).toISOString()}`);\n\n const typesToSync =\n contentTypes && contentTypes.length > 0\n ? contentTypes\n : await adapter.getContentTypes();\n\n console.log(`Content types to sync: ${typesToSync.join(', ')}\\n`);\n\n const entries: CmsSyncResultEntry[] = [];\n const errors: Array<{ contentType: string; error: string }> = [];\n\n for (const contentType of typesToSync) {\n try {\n const result = await adapter.fetchAll(contentType, includeLevels);\n const objectKey = buildCmsObjectKey(cms, contentType);\n await store.upload(objectKey, result.items);\n\n entries.push({\n contentType,\n itemCount: result.total,\n objectKey,\n });\n\n console.log(\n ` + ${contentType}: ${result.total} items -> ${objectKey}`,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ contentType, error: message });\n console.error(` x ${contentType}: ${message}`);\n }\n }\n\n console.log(\n `\\nSync complete: ${entries.length} succeeded, ${errors.length} failed\\n`,\n );\n\n return { cms, timestamp, entries, errors };\n}\n\nexport async function syncTranslations(projects?: string[], locales?:string[]):Promise<TranslationSyncResult> {\n\n const store = new ContentStore(config.s3);\n const entries: TranslationSyncResultEntry[] = [];\n const errors: Array<{ project: string; locale: string, error: string }> = [];\n const timestamp = Math.floor(Date.now() / 1000);\n if(!locales){\n locales = defaultLocales;\n }\n if(!projects){\n projects = Object.keys(allProjects);\n }\n\n for(const project of projects) {\n const resources = allProjects[project];\n if(!resources){\n console.error(`No resources found for ${project}`);\n continue;\n }\n for(const resource of resources) {\n for(const loc of locales){\n const locale = (resource.localeMapping && resource.localeMapping[loc]) ? resource.localeMapping[loc] : loc;\n try {\n const raw = await fetchLingohubResourceRaw(project, resource, locale);\n const objectKey = buildTranslationObjectKey(\n project,\n resource.fileName,\n locale,\n );\n await store.uploadRaw(\n objectKey,\n raw,\n contentTypeForTranslationKey(objectKey),\n );\n const byteLength = Buffer.byteLength(raw, 'utf8');\n entries.push({\n project,\n locale,\n itemCount: byteLength,\n objectKey,\n });\n\n console.log(\n ` + ${project} - ${locale}: ${byteLength} bytes -> ${objectKey}`,\n );\n\n // await new Promise(resolve => setTimeout(resolve, 1000)); // Rate limiting\n\n }catch(err){\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ project,locale, error: message });\n console.error(` x ${project} - ${locale}: ${message}`);\n }\n }\n }\n }\n\n return { timestamp, entries, errors };\n}\n"],"mappings":"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","names":["config","result","cfg","createClient","cfg","createClient","config","config","config"]}
|
package/dist/client/cli.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
config,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
fetchCmsBundles,
|
|
5
|
+
fetchTranslationBundles,
|
|
6
|
+
queryCmsBundle
|
|
7
|
+
} from "../chunk-LOCC2BXB.js";
|
|
8
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
7
9
|
import {
|
|
8
10
|
ContentStore
|
|
9
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
10
12
|
|
|
11
13
|
// src/client/cli.ts
|
|
12
14
|
import { Command } from "commander";
|
|
13
15
|
var program = new Command();
|
|
14
16
|
program.name("content-store").description("Sync CMS content to S3").version("1.0.0");
|
|
15
|
-
program.command("fetch").description("Download latest bundles from S3 to the local filesystem").requiredOption("--cms <provider>", "CMS provider: contentful | sanity").requiredOption("--types <types>", "Comma-separated content types to fetch").option("--output <directory>", "Output directory", "./content-cache").action(async (opts) => {
|
|
17
|
+
program.command("fetch-cms").description("Download latest CMS bundles from S3 to the local filesystem").requiredOption("--cms <provider>", "CMS provider: contentful | sanity").requiredOption("--types <types>", "Comma-separated content types to fetch").option("--output <directory>", "Output directory", "./content-cache").action(async (opts) => {
|
|
16
18
|
const cms = opts.cms;
|
|
17
19
|
if (!["contentful", "sanity"].includes(cms)) {
|
|
18
20
|
console.error(`Invalid CMS provider: ${cms}`);
|
|
@@ -21,7 +23,7 @@ program.command("fetch").description("Download latest bundles from S3 to the loc
|
|
|
21
23
|
const contentTypes = opts.types.split(",").map((s) => s.trim());
|
|
22
24
|
const store = new ContentStore(config.s3);
|
|
23
25
|
try {
|
|
24
|
-
const files = await
|
|
26
|
+
const files = await fetchCmsBundles(store, opts.output, {
|
|
25
27
|
cms,
|
|
26
28
|
contentTypes
|
|
27
29
|
});
|
|
@@ -34,7 +36,28 @@ program.command("fetch").description("Download latest bundles from S3 to the loc
|
|
|
34
36
|
process.exit(1);
|
|
35
37
|
}
|
|
36
38
|
});
|
|
37
|
-
program.command("
|
|
39
|
+
program.command("fetch-translations").description("Download latest Translation bundles from S3 to the local filesystem").requiredOption("--projects <projects>", "Comma-separated projects to fetch").option("--locales <locales>", "Comma-separated locales to fetch (omit to fetch all)").option("--output <directory>", "Output directory", "./content-cache").action(async (opts) => {
|
|
40
|
+
const projects = opts.projects.split(",").map((s) => s.trim());
|
|
41
|
+
const locales = opts.locales ? opts.locales.split(",").map((s) => s.trim()) : void 0;
|
|
42
|
+
const store = new ContentStore(config.s3);
|
|
43
|
+
try {
|
|
44
|
+
const files = await fetchTranslationBundles(store, opts.output, {
|
|
45
|
+
projects,
|
|
46
|
+
locales
|
|
47
|
+
});
|
|
48
|
+
console.log("Fetched bundles:");
|
|
49
|
+
for (const [project, pathsByKey] of Object.entries(files)) {
|
|
50
|
+
console.log(` ${project}:`);
|
|
51
|
+
for (const [objectKey, filePath] of Object.entries(pathsByKey)) {
|
|
52
|
+
console.log(` ${objectKey} -> ${filePath}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error("Fetch failed:", err);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
program.command("query-cms").description("Query a previously fetched CMS bundle from the local filesystem").requiredOption("--cms <provider>", "CMS provider: contentful | sanity").requiredOption("--type <type>", "Content type to query").option("--output <directory>", "Directory where bundles are stored", "./content-cache").option("--fields <json>", `Filter by fields (JSON object, e.g. '{"columns":"2"}')`).option("--select <props>", "Comma-separated properties to include in results").option("--limit <n>", "Maximum number of results", parseInt).option("--include <n>", "Depth of nested references to include", parseInt).action(
|
|
38
61
|
async (opts) => {
|
|
39
62
|
const cms = opts.cms;
|
|
40
63
|
if (!["contentful", "sanity"].includes(cms)) {
|
|
@@ -42,7 +65,7 @@ program.command("query").description("Query a previously fetched bundle from the
|
|
|
42
65
|
process.exit(1);
|
|
43
66
|
}
|
|
44
67
|
try {
|
|
45
|
-
const results = await
|
|
68
|
+
const results = await queryCmsBundle(opts.output, cms, opts.type, {
|
|
46
69
|
fields: opts.fields ? JSON.parse(opts.fields) : void 0,
|
|
47
70
|
select: opts.select ? opts.select.split(",").map((s) => s.trim()) : void 0,
|
|
48
71
|
limit: opts.limit,
|
package/dist/client/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { config, type CMSProvider } from './config';\nimport { ContentStore } from '../shared/s3';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/client/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { config, type CMSProvider } from './config';\nimport { ContentStore } from '../shared/s3';\nimport { fetchCmsBundles, fetchTranslationBundles, queryCmsBundle } from '../shared/bundles';\n\nconst program = new Command();\n\nprogram\n .name('content-store')\n .description('Sync CMS content to S3')\n .version('1.0.0');\n\nprogram\n .command('fetch-cms')\n .description('Download latest CMS bundles from S3 to the local filesystem')\n .requiredOption('--cms <provider>', 'CMS provider: contentful | sanity')\n .requiredOption('--types <types>', 'Comma-separated content types to fetch')\n .option('--output <directory>', 'Output directory', './content-cache')\n .action(async (opts: { cms: string; types: string; output: string }) => {\n const cms = opts.cms as CMSProvider;\n\n if (!['contentful', 'sanity'].includes(cms)) {\n console.error(`Invalid CMS provider: ${cms}`);\n process.exit(1);\n }\n\n const contentTypes = opts.types.split(',').map((s) => s.trim());\n const store = new ContentStore(config.s3);\n\n try {\n const files = await fetchCmsBundles(store, opts.output, {\n cms,\n contentTypes,\n });\n\n console.log('Fetched bundles:');\n for (const [type, filePath] of Object.entries(files)) {\n console.log(` ${type} -> ${filePath}`);\n }\n } catch (err) {\n console.error('Fetch failed:', err);\n process.exit(1);\n }\n });\n\nprogram\n .command('fetch-translations')\n .description('Download latest Translation bundles from S3 to the local filesystem')\n .requiredOption('--projects <projects>', 'Comma-separated projects to fetch')\n .option('--locales <locales>', 'Comma-separated locales to fetch (omit to fetch all)')\n .option('--output <directory>', 'Output directory', './content-cache')\n .action(async (opts: { projects: string; locales: string; output: string }) => {\n\n const projects = opts.projects.split(',').map((s) => s.trim());\n const locales = opts.locales ? opts.locales.split(',').map((s) => s.trim()) : undefined;\n const store = new ContentStore(config.s3);\n\n try {\n const files = await fetchTranslationBundles(store, opts.output, {\n projects,\n locales,\n });\n\n console.log('Fetched bundles:');\n for (const [project, pathsByKey] of Object.entries(files)) {\n console.log(` ${project}:`);\n for (const [objectKey, filePath] of Object.entries(pathsByKey)) {\n console.log(` ${objectKey} -> ${filePath}`);\n }\n }\n } catch (err) {\n console.error('Fetch failed:', err);\n process.exit(1);\n }\n });\n\nprogram\n .command('query-cms')\n .description('Query a previously fetched CMS bundle from the local filesystem')\n .requiredOption('--cms <provider>', 'CMS provider: contentful | sanity')\n .requiredOption('--type <type>', 'Content type to query')\n .option('--output <directory>', 'Directory where bundles are stored', './content-cache')\n .option('--fields <json>', 'Filter by fields (JSON object, e.g. \\'{\"columns\":\"2\"}\\')')\n .option('--select <props>', 'Comma-separated properties to include in results')\n .option('--limit <n>', 'Maximum number of results', parseInt)\n .option('--include <n>', 'Depth of nested references to include', parseInt)\n .action(\n async (opts: {\n cms: string;\n type: string;\n output: string;\n fields?: string;\n select?: string;\n limit?: number;\n include?: number;\n }) => {\n const cms = opts.cms as CMSProvider;\n\n if (!['contentful', 'sanity'].includes(cms)) {\n console.error(`Invalid CMS provider: ${cms}`);\n process.exit(1);\n }\n\n try {\n const results = await queryCmsBundle(opts.output, cms, opts.type, {\n fields: opts.fields ? JSON.parse(opts.fields) : undefined,\n select: opts.select\n ? opts.select.split(',').map((s) => s.trim())\n : undefined,\n limit: opts.limit,\n include: opts.include,\n });\n\n console.log(JSON.stringify(results, null, 2));\n } catch (err) {\n console.error('Query failed:', err);\n process.exit(1);\n }\n },\n );\n\nprogram.parse();\n"],"mappings":"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","names":[]}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
config,
|
|
4
|
-
|
|
5
|
-
} from "../chunk-
|
|
4
|
+
fetchCmsBundles
|
|
5
|
+
} from "../chunk-LOCC2BXB.js";
|
|
6
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
6
7
|
import {
|
|
7
8
|
ContentStore
|
|
8
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
9
10
|
|
|
10
|
-
// src/client/fetch-bundles.ts
|
|
11
|
+
// src/client/fetch-content-bundles.ts
|
|
11
12
|
import { Command } from "commander";
|
|
12
13
|
var program = new Command();
|
|
13
|
-
program.name("fetch-content-bundles").description("Download latest bundles from S3 to the local filesystem").requiredOption("--cms <provider>", "CMS provider: contentful | sanity").requiredOption("--types <types>", "Comma-separated content types to fetch").option("--output <directory>", "Output directory", "./content-cache").action(async (opts) => {
|
|
14
|
+
program.name("fetch-content-bundles").description("Download latest CMS bundles from S3 to the local filesystem").requiredOption("--cms <provider>", "CMS provider: contentful | sanity").requiredOption("--types <types>", "Comma-separated content types to fetch").option("--output <directory>", "Output directory", "./content-cache").action(async (opts) => {
|
|
14
15
|
const cms = opts.cms;
|
|
15
16
|
if (!["contentful", "sanity"].includes(cms)) {
|
|
16
17
|
console.error(`Invalid CMS provider: ${cms}`);
|
|
@@ -19,7 +20,7 @@ program.name("fetch-content-bundles").description("Download latest bundles from
|
|
|
19
20
|
const contentTypes = opts.types.split(",").map((s) => s.trim());
|
|
20
21
|
const store = new ContentStore(config.s3);
|
|
21
22
|
try {
|
|
22
|
-
const files = await
|
|
23
|
+
const files = await fetchCmsBundles(store, opts.output, {
|
|
23
24
|
cms,
|
|
24
25
|
contentTypes
|
|
25
26
|
});
|
|
@@ -33,4 +34,4 @@ program.name("fetch-content-bundles").description("Download latest bundles from
|
|
|
33
34
|
}
|
|
34
35
|
});
|
|
35
36
|
program.parse();
|
|
36
|
-
//# sourceMappingURL=fetch-bundles.js.map
|
|
37
|
+
//# sourceMappingURL=fetch-content-bundles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/fetch-content-bundles.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { config, type CMSProvider } from './config';\nimport { ContentStore } from '../shared/s3';\nimport { fetchCmsBundles } from '../shared/bundles';\n\nconst program = new Command();\n\nprogram\n .name('fetch-content-bundles')\n .description('Download latest CMS bundles from S3 to the local filesystem')\n .requiredOption('--cms <provider>', 'CMS provider: contentful | sanity')\n .requiredOption('--types <types>', 'Comma-separated content types to fetch')\n .option('--output <directory>', 'Output directory', './content-cache')\n .action(async (opts: { cms: string; types: string; output: string }) => {\n const cms = opts.cms as CMSProvider;\n\n if (!['contentful', 'sanity'].includes(cms)) {\n console.error(`Invalid CMS provider: ${cms}`);\n process.exit(1);\n }\n\n const contentTypes = opts.types.split(',').map((s) => s.trim());\n const store = new ContentStore(config.s3);\n\n try {\n const files = await fetchCmsBundles(store, opts.output, {\n cms,\n contentTypes,\n });\n\n console.log('Fetched bundles:');\n for (const [type, filePath] of Object.entries(files)) {\n console.log(` ${type} -> ${filePath}`);\n }\n } catch (err) {\n console.error('Fetch failed:', err);\n process.exit(1);\n }\n });\n\nprogram.parse();\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,eAAe;AAKxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,uBAAuB,EAC5B,YAAY,6DAA6D,EACzE,eAAe,oBAAoB,mCAAmC,EACtE,eAAe,mBAAmB,wCAAwC,EAC1E,OAAO,wBAAwB,oBAAoB,iBAAiB,EACpE,OAAO,OAAO,SAAyD;AACtE,QAAM,MAAM,KAAK;AAEjB,MAAI,CAAC,CAAC,cAAc,QAAQ,EAAE,SAAS,GAAG,GAAG;AAC3C,YAAQ,MAAM,yBAAyB,GAAG,EAAE;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,eAAe,KAAK,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAC9D,QAAM,QAAQ,IAAI,aAAa,OAAO,EAAE;AAExC,MAAI;AACF,UAAM,QAAQ,MAAM,gBAAgB,OAAO,KAAK,QAAQ;AAAA,MACtD;AAAA,MACA;AAAA,IACF,CAAC;AAED,YAAQ,IAAI,kBAAkB;AAC9B,eAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK,GAAG;AACpD,cAAQ,IAAI,KAAK,IAAI,OAAO,QAAQ,EAAE;AAAA,IACxC;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,iBAAiB,GAAG;AAClC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QAAQ,MAAM;","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
config,
|
|
4
|
+
fetchMergedTranslationBundles
|
|
5
|
+
} from "../chunk-LOCC2BXB.js";
|
|
6
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
7
|
+
import {
|
|
8
|
+
ContentStore
|
|
9
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
10
|
+
|
|
11
|
+
// src/client/fetch-merged-translation-bundles.ts
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
var program = new Command();
|
|
14
|
+
program.name("fetch-merged-translation-bundles").description(
|
|
15
|
+
"Download translation bundles from S3 and write one merged {locale}.json per locale"
|
|
16
|
+
).requiredOption(
|
|
17
|
+
"--projects <projects>",
|
|
18
|
+
"Comma-separated Lingohub project ids (quote in zsh if names contain parentheses)"
|
|
19
|
+
).option("--locales <locales>", "Comma-separated locales (omit for default set)").option("--output <directory>", "Output directory", "./content-cache").action(
|
|
20
|
+
async (opts) => {
|
|
21
|
+
const projects = opts.projects.split(",").map((s) => s.trim());
|
|
22
|
+
const locales = opts.locales ? opts.locales.split(",").map((s) => s.trim()) : void 0;
|
|
23
|
+
const store = new ContentStore(config.s3);
|
|
24
|
+
try {
|
|
25
|
+
const files = await fetchMergedTranslationBundles(store, opts.output, {
|
|
26
|
+
projects,
|
|
27
|
+
locales
|
|
28
|
+
});
|
|
29
|
+
console.log("Wrote merged translation files:");
|
|
30
|
+
for (const [locale, filePath] of Object.entries(files)) {
|
|
31
|
+
console.log(` ${locale}.json -> ${filePath}`);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error("Fetch failed:", err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
program.parse();
|
|
40
|
+
//# sourceMappingURL=fetch-merged-translation-bundles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/fetch-merged-translation-bundles.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { config } from './config';\nimport { ContentStore } from '../shared/s3';\nimport { fetchMergedTranslationBundles } from '../shared/bundles';\n\nconst program = new Command();\n\nprogram\n .name('fetch-merged-translation-bundles')\n .description(\n 'Download translation bundles from S3 and write one merged {locale}.json per locale',\n )\n .requiredOption(\n '--projects <projects>',\n 'Comma-separated Lingohub project ids (quote in zsh if names contain parentheses)',\n )\n .option('--locales <locales>', 'Comma-separated locales (omit for default set)')\n .option('--output <directory>', 'Output directory', './content-cache')\n .action(\n async (opts: { projects: string; locales?: string; output: string }) => {\n const projects = opts.projects.split(',').map((s) => s.trim());\n const locales = opts.locales\n ? opts.locales.split(',').map((s) => s.trim())\n : undefined;\n const store = new ContentStore(config.s3);\n\n try {\n const files = await fetchMergedTranslationBundles(store, opts.output, {\n projects,\n locales,\n });\n\n console.log('Wrote merged translation files:');\n for (const [locale, filePath] of Object.entries(files)) {\n console.log(` ${locale}.json -> ${filePath}`);\n }\n } catch (err) {\n console.error('Fetch failed:', err);\n process.exit(1);\n }\n },\n );\n\nprogram.parse();\n"],"mappings":";;;;;;;;;;;AAAA,SAAS,eAAe;AAKxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,kCAAkC,EACvC;AAAA,EACC;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,uBAAuB,gDAAgD,EAC9E,OAAO,wBAAwB,oBAAoB,iBAAiB,EACpE;AAAA,EACC,OAAO,SAAiE;AACtE,UAAM,WAAW,KAAK,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAC7D,UAAM,UAAU,KAAK,UACjB,KAAK,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAC3C;AACJ,UAAM,QAAQ,IAAI,aAAa,OAAO,EAAE;AAExC,QAAI;AACF,YAAM,QAAQ,MAAM,8BAA8B,OAAO,KAAK,QAAQ;AAAA,QACpE;AAAA,QACA;AAAA,MACF,CAAC;AAED,cAAQ,IAAI,iCAAiC;AAC7C,iBAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO,QAAQ,KAAK,GAAG;AACtD,gBAAQ,IAAI,KAAK,MAAM,YAAY,QAAQ,EAAE;AAAA,MAC/C;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,iBAAiB,GAAG;AAClC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;AAEF,QAAQ,MAAM;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
config,
|
|
4
|
+
fetchTranslationBundles
|
|
5
|
+
} from "../chunk-LOCC2BXB.js";
|
|
6
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
7
|
+
import {
|
|
8
|
+
ContentStore
|
|
9
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
10
|
+
|
|
11
|
+
// src/client/fetch-translation-bundles.ts
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
var program = new Command();
|
|
14
|
+
program.name("fetch-translation-bundles").description(
|
|
15
|
+
`Download latest translation bundles from S3 to the local filesystem.
|
|
16
|
+
|
|
17
|
+
Shell note: project ids often contain parentheses, e.g. tandem-(website). In zsh/bash, quote the value (--projects='\u2026' or --projects "\u2026") or use a space after --projects so the shell does not treat ( ) as syntax.`
|
|
18
|
+
).requiredOption(
|
|
19
|
+
"--projects <projects>",
|
|
20
|
+
"Comma-separated Lingohub project ids (must match keys in the package registry)"
|
|
21
|
+
).option("--locales <locales>", "Comma-separated locales (omit to fetch default set)").option("--output <directory>", "Output directory", "./content-cache").action(async (opts) => {
|
|
22
|
+
const projects = opts.projects.split(",").map((s) => s.trim());
|
|
23
|
+
const locales = opts.locales ? opts.locales.split(",").map((s) => s.trim()) : void 0;
|
|
24
|
+
const store = new ContentStore(config.s3);
|
|
25
|
+
try {
|
|
26
|
+
const files = await fetchTranslationBundles(store, opts.output, {
|
|
27
|
+
projects,
|
|
28
|
+
locales
|
|
29
|
+
});
|
|
30
|
+
console.log("Fetched bundles:");
|
|
31
|
+
for (const [project, pathsByKey] of Object.entries(files)) {
|
|
32
|
+
console.log(` ${project}:`);
|
|
33
|
+
for (const [objectKey, filePath] of Object.entries(pathsByKey)) {
|
|
34
|
+
console.log(` ${objectKey} -> ${filePath}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error("Fetch failed:", err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
program.parse();
|
|
43
|
+
//# sourceMappingURL=fetch-translation-bundles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/fetch-translation-bundles.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { config } from './config';\nimport { ContentStore } from '../shared/s3';\nimport { fetchTranslationBundles } from '../shared/bundles';\n\nconst program = new Command();\n\nprogram\n .name('fetch-translation-bundles')\n .description(\n 'Download latest translation bundles from S3 to the local filesystem.\\n\\n' +\n 'Shell note: project ids often contain parentheses, e.g. tandem-(website). In zsh/bash, ' +\n 'quote the value (--projects=\\'…\\' or --projects \"…\") or use a space after --projects ' +\n 'so the shell does not treat ( ) as syntax.',\n )\n .requiredOption(\n '--projects <projects>',\n 'Comma-separated Lingohub project ids (must match keys in the package registry)',\n )\n .option('--locales <locales>', 'Comma-separated locales (omit to fetch default set)')\n .option('--output <directory>', 'Output directory', './content-cache')\n .action(async (opts: { projects: string; locales?: string; output: string }) => {\n\n const projects = opts.projects.split(',').map((s) => s.trim());\n const locales = opts.locales ? opts.locales.split(',').map((s) => s.trim()) : undefined;\n const store = new ContentStore(config.s3);\n\n try {\n const files = await fetchTranslationBundles(store, opts.output, {\n projects,\n locales,\n });\n\n console.log('Fetched bundles:');\n for (const [project, pathsByKey] of Object.entries(files)) {\n console.log(` ${project}:`);\n for (const [objectKey, filePath] of Object.entries(pathsByKey)) {\n console.log(` ${objectKey} -> ${filePath}`);\n }\n }\n } catch (err) {\n console.error('Fetch failed:', err);\n process.exit(1);\n }\n });\n\nprogram.parse();"],"mappings":";;;;;;;;;;;AAAA,SAAS,eAAe;AAKxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACK,KAAK,2BAA2B,EAChC;AAAA,EACC;AAAA;AAAA;AAIF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,uBAAuB,qDAAqD,EACnF,OAAO,wBAAwB,oBAAoB,iBAAiB,EACpE,OAAO,OAAO,SAAiE;AAE5E,QAAM,WAAW,KAAK,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAC7D,QAAM,UAAU,KAAK,UAAU,KAAK,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI;AAC9E,QAAM,QAAQ,IAAI,aAAa,OAAO,EAAE;AAExC,MAAI;AACA,UAAM,QAAQ,MAAM,wBAAwB,OAAO,KAAK,QAAQ;AAAA,MAC5D;AAAA,MACA;AAAA,IACJ,CAAC;AAED,YAAQ,IAAI,kBAAkB;AAC9B,eAAW,CAAC,SAAS,UAAU,KAAK,OAAO,QAAQ,KAAK,GAAG;AACvD,cAAQ,IAAI,KAAK,OAAO,GAAG;AAC3B,iBAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC5D,gBAAQ,IAAI,OAAO,SAAS,OAAO,QAAQ,EAAE;AAAA,MACjD;AAAA,IACJ;AAAA,EACJ,SAAS,KAAK;AACV,YAAQ,MAAM,iBAAiB,GAAG;AAClC,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ,CAAC;AAEL,QAAQ,MAAM;","names":[]}
|