@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.
@@ -1,13 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ContentStore,
4
- config
5
- } from "./chunk-UWGOF36L.js";
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
- "gridLayout",
21
- "iconWithText",
22
- "page"
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(cfg, retryConfig) {
193
+ constructor(cfg2, retryConfig) {
138
194
  this.client = createClient({
139
- space: cfg.spaceId,
140
- accessToken: cfg.accessToken,
141
- host: cfg.host
195
+ space: cfg2.spaceId,
196
+ accessToken: cfg2.accessToken,
197
+ host: cfg2.host
142
198
  });
143
- this.batchSize = cfg.batchSize;
144
- this.maxDepth = cfg.maxDepth;
145
- this.allowedTypes = cfg.contentTypes;
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(cfg, retryConfig) {
284
+ constructor(cfg2, retryConfig) {
200
285
  this.client = createClient2({
201
- projectId: cfg.projectId,
202
- dataset: cfg.dataset,
203
- token: cfg.token,
204
- apiVersion: cfg.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(`array::unique(*[]._type)`),
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(`*[_type == $type]`, { type: contentType }),
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 runSync(cms, contentTypes, includeLevels) {
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 versionedKey = store.buildVersionedKey(cms, contentType, timestamp);
256
- await store.upload(versionedKey, result.items);
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
- versionedKey,
262
- latestKey
360
+ objectKey
263
361
  });
264
362
  console.log(
265
- ` + ${contentType}: ${result.total} items -> ${versionedKey}`
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
- runSync
433
+ syncCmsContent,
434
+ syncTranslations
284
435
  };
285
- //# sourceMappingURL=chunk-YOREZCXB.js.map
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":";;;;;;;;;;;;AAAA,OAAO,YAAY;AAInB,OAAO,OAAO,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,OAAO;AA4Dd,SAAS,6BAAoD;AAC3D,QAAM,kBAAkB,SAAS,QAAQ,IAAI,6BAA6B,KAAK,EAAE;AACjF,QAAM,UAAU,OAAO,SAAS,eAAe,KAAK,kBAAkB;AACtE,QAAM,cACH,QAAQ,IAAI,yBAAyB,QAAQ,YAAY,MAAM;AAClE,QAAM,QAAQ,QAAQ,IAAI,iBAAiB,QAAQ,KAAK;AACxD,QAAM,WAAW,QAAQ,IAAI,qBAAqB,IAAI,KAAK;AAC3D,QAAM,WAAW,QAAQ,IAAI,qBAAqB,KAAK;AACvD,QAAM,YAAY,WACd,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IACvD;AAEJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,WAAW;AAAA,IACpB;AAAA,EACF;AACF;AAEA,SAAS,qCAAoE;AAC3E,QAAM,kBAAkB,SAAS,QAAQ,IAAI,6BAA6B,KAAK,EAAE;AACjF,QAAM,UAAU,OAAO,SAAS,eAAe,KAAK,kBAAkB;AACtE,QAAM,cACD,QAAQ,IAAI,yBAAyB,QAAQ,YAAY,MAAM;AACpE,QAAM,QAAQ,QAAQ,IAAI,iBAAiB,QAAQ,KAAK;AACxD,QAAM,cAAc,QAAQ,IAAI,oCAAoC,KAAK;AACzE,QAAM,eAAe,cACf,YAAY,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAC1D;AAEN,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA4BO,IAAMA,UAAsC;AAAA,EACjD,GAAG;AAAA,EACH,YAAY;AAAA,IACV,SAAS,QAAQ,IAAI,uBAAuB;AAAA,IAC5C,aAAa,QAAQ,IAAI,4BAA4B;AAAA,IACrD,MAAM,QAAQ,IAAI,mBAAmB;AAAA,IACrC,WAAW;AAAA,IACX,UAAU;AAAA,IACV,cAAc;AAAA,MACZ;AAAA,MAAQ;AAAA,MAAO;AAAA,MAAe;AAAA,MAAa;AAAA,MAAS;AAAA,MAAe;AAAA;AAAA,IAErE;AAAA,EACF;AAAA,EAEA,QAAQ;AAAA,IACN,WAAW,QAAQ,IAAI,qBAAqB;AAAA,IAC5C,SAAS,QAAQ,IAAI,kBAAkB;AAAA,IACvC,OAAO,QAAQ,IAAI,oBAAoB;AAAA,IACvC,YAAY;AAAA,EACd;AAAA,EAEA,UAAU;AAAA,IACR,WAAY,QAAQ,IAAI,uBAAuB;AAAA,IAC/C,WAAW,QAAQ,IAAI,sBAAsB;AAAA,EAC/C;AAAA,EAEA,OAAO;AAAA,IACL,YAAY,SAAS,QAAQ,IAAI,qBAAqB,KAAK,EAAE;AAAA,IAC7D,aAAa,SAAS,QAAQ,IAAI,uBAAuB,QAAQ,EAAE;AAAA,IACnE,YAAY,SAAS,QAAQ,IAAI,sBAAsB,SAAS,EAAE;AAAA,EACpE;AAAA,EAEA,KAAK;AAAA,IACH,MAAM,SAAS,QAAQ,IAAI,QAAQ,MAAM;AAAA,IACzC,UAAU,QAAQ,IAAI,2BAA2B;AAAA,EACnD;AAAA,EAEA,iBAAiB,2BAA2B;AAAA,EAC5C,yBAAyB,mCAAmC;AAAA,EAE5D,SAAS;AAAA,IACP,UAAU,QAAQ,IAAI,mBAAmB,QAAQ,YAAY,MAAM;AAAA,IACnE,eAAe,SAAS,QAAQ,IAAI,0BAA0B,MAAM,EAAE;AAAA,EACxE;AAAA,EAEA,OAAO;AAAA,IACL,UAAU,QAAQ,IAAI,mBAAmB,IAAI,SAAS;AAAA,IACtD,UAAU,QAAQ,IAAI,mBAAmB;AAAA,IACzC,eAAe,QAAQ,IAAI,wBAAwB;AAAA,IACnD,UAAU,QAAQ,IAAI,mBAAmB;AAAA,EAC3C;AACF;;;ACxLA;AAAA,EACE;AAAA,OAIK;;;ACGP,SAAS,iBAAiB,KAA6B;AACrD,QAAM,IAAI;AAEV,MAAI,GAAG,WAAW,OAAO,GAAG,eAAe,KAAK;AAC9C,UAAM,QAAS,GAAG,UAChB,8BACF;AACA,WAAO,QAAQ,WAAW,KAAK,IAAI,MAAO;AAAA,EAC5C;AAEA,QAAM,OAAO,GAAG;AAChB,MAAI,MAAM,WAAW,KAAK;AACxB,UAAM,UAAU,MAAM;AACtB,UAAM,QAAQ,UAAU,8BAA8B;AACtD,WAAO,QAAQ,WAAW,KAAK,IAAI,MAAO;AAAA,EAC5C;AAEA,SAAO;AACT;AAEA,SAAS,aACP,SACA,aACA,YACQ;AACR,QAAM,cAAc,cAAc,KAAK,IAAI,GAAG,OAAO;AACrD,QAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,SAAO,KAAK,IAAI,cAAc,QAAQ,UAAU;AAClD;AAOA,eAAsB,UACpB,IACA,EAAE,YAAY,aAAa,WAAW,GAC1B;AACZ,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,KAAK;AACZ,UAAI,YAAY,WAAY,OAAM;AAElC,YAAM,UAAU,iBAAiB,GAAG;AACpC,UAAI;AAEJ,UAAI,YAAY,MAAM;AACpB,gBACE,UAAU,IAAI,UAAU,aAAa,SAAS,aAAa,UAAU;AACvE,gBAAQ;AAAA,UACN,2BAA2B,UAAU,CAAC,IAAI,UAAU,cACvC,KAAK,MAAM,KAAK,CAAC;AAAA,QAChC;AAAA,MACF,OAAO;AACL,gBAAQ,aAAa,SAAS,aAAa,UAAU;AACrD,gBAAQ;AAAA,UACN,6BAA6B,UAAU,CAAC,IAAI,UAAU,MAChD,IAAc,OAAO,iBAAiB,KAAK,MAAM,KAAK,CAAC;AAAA,QAC/D;AAAA,MACF;AAEA,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,wBAAwB;AAC1C;;;ADIA,SAAS,cACP,OACA,UACA,QAAQ,GACR,OAAO,oBAAI,QAAgB,GAClB;AACT,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AAExD,QAAM,MAAM;AAEZ,MAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,OAAK,IAAI,GAAG;AAEZ,MAAI;AACF,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,aAAO,MAAM,IAAI,CAAC,SAAS,cAAc,MAAM,UAAU,OAAO,IAAI,CAAC;AAAA,IACvE;AAEA,UAAM,aAAa,SAAS,OAAO,YAAY,OAAO,OAAO,IAAI,WAAW;AAE5E,QAAI,YAAY;AACd,UAAI,SAAS,SAAU,QAAO;AAE9B,YAAM,SAAS,IAAI;AACnB,YAAMC,UAAkC,CAAC;AACzC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,QAAAA,QAAO,CAAC,IAAI,cAAc,GAAG,UAAU,QAAQ,GAAG,IAAI;AAAA,MACxD;AAEA,YAAM,MAAM,IAAI;AAChB,UAAI,KAAK;AACP,cAAM,eAAeA,QAAO;AAC5B,cAAM,WACJ,OAAO,iBAAiB,YACxB,iBAAiB,QACjB,CAAC,MAAM,QAAQ,YAAY,IACtB,eACD,CAAC;AACP,cAAM,eAAe,IAAI,cAAc,IAAI,YAAY,IAAI,KAAK;AAChE,QAAAA,QAAO,OAAO,EAAE,GAAG,UAAU,KAAK,IAAI,IAAI,cAAc,YAAY,IAAI,UAAU;AAAA,MACpF;AAEA,aAAOA;AAAA,IACT;AAEA,UAAM,SAAkC,CAAC;AACzC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,aAAO,CAAC,IAAI,cAAc,GAAG,UAAU,OAAO,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT,UAAE;AACA,SAAK,OAAO,GAAG;AAAA,EACjB;AACF;AAEO,IAAM,oBAAN,MAA8C;AAAA,EAC1C,OAAO;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAYC,MAAuB,aAA0B;AAC3D,SAAK,SAAS,aAAa;AAAA,MACzB,OAAOA,KAAI;AAAA,MACX,aAAaA,KAAI;AAAA,MACjB,MAAMA,KAAI;AAAA,IACZ,CAAC;AACD,SAAK,YAAYA,KAAI;AACrB,SAAK,WAAWA,KAAI;AACpB,SAAK,eAAeA,KAAI;AACxB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,kBAAqC;AACzC,UAAM,WAAW,MAAM;AAAA,MACrB,MAAM,KAAK,OAAO,gBAAgB;AAAA,MAClC,KAAK;AAAA,IACP;AAEA,UAAM,WAAW,SAAS,MAAM,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE;AAErD,QAAI,KAAK,aAAa,SAAS,GAAG;AAChC,aAAO,SAAS,OAAO,CAAC,MAAM,KAAK,aAAa,SAAS,CAAC,CAAC;AAAA,IAC7D;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,SAAS,aAAqB,gBAA4D,GAAyB;AACvH,QAAI,gBAAgB,SAAS;AAC3B,aAAO,KAAK,eAAe;AAAA,IAC7B;AAEA,UAAM,WAAsB,CAAC;AAC7B,QAAI,OAAO;AACX,QAAI,QAAQ;AAEZ,OAAG;AACD,YAAM,UAAU;AAAA,QACd,cAAc;AAAA,QACd,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,SAAS;AAAA,MACX;AACA,YAAM,WAAW,MAAM,KAAK,OAAO,WAAW,OAAO;AACrD,cAAQ,SAAS;AACjB,eAAS,KAAK,GAAG,SAAS,KAAK;AAC/B,cAAQ,SAAS,MAAM;AAEvB,UAAI,QAAQ,KAAK,WAAW;AAC1B,gBAAQ;AAAA,UACN,kBAAkB,WAAW,aAAa,SAAS,MAAM,IAAI,KAAK;AAAA,QACpE;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AAEhB,WAAO;AAAA,MACL;AAAA,MACA,OAAO,SAAS,IAAI,CAAC,SAAS,cAAc,MAAM,KAAK,QAAQ,CAAC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,iBAAuC;AACnD,UAAM,WAAsB,CAAC;AAC7B,QAAI,OAAO;AACX,QAAI,QAAQ;AAEZ,OAAG;AACD,YAAM,WAAW,MAAM;AAAA,QACrB,MAAM,KAAK,OAAO,UAAU,EAAE,OAAO,KAAK,WAAW,KAAK,CAAC;AAAA,QAC3D,KAAK;AAAA,MACP;AACA,cAAQ,SAAS;AACjB,eAAS,KAAK,GAAG,SAAS,KAAK;AAC/B,cAAQ,SAAS,MAAM;AAEvB,UAAI,QAAQ,KAAK,WAAW;AAC1B,gBAAQ;AAAA,UACN,iCAAiC,SAAS,MAAM,IAAI,KAAK;AAAA,QAC3D;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AAEhB,WAAO;AAAA,MACL,aAAa;AAAA,MACb,OAAO,SAAS,IAAI,CAAC,SAAS,cAAc,MAAM,KAAK,QAAQ,CAAC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACF;;;AE9OA,SAAS,gBAAAC,qBAAuC;AAKzC,IAAM,gBAAN,MAA0C;AAAA,EACtC,OAAO;AAAA,EACR;AAAA,EACA;AAAA,EAER,YAAYC,MAAmB,aAA0B;AACvD,SAAK,SAASC,cAAa;AAAA,MACzB,WAAWD,KAAI;AAAA,MACf,SAASA,KAAI;AAAA,MACb,OAAOA,KAAI;AAAA,MACX,YAAYA,KAAI;AAAA,MAChB,QAAQ;AAAA,IACV,CAAC;AACD,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,kBAAqC;AACzC,UAAM,QAAkB,MAAM;AAAA,MAC5B,MAAM,KAAK,OAAO,MAAM,0BAA0B;AAAA,MAClD,KAAK;AAAA,IACP;AACA,WAAO,MAAM;AAAA,MACX,CAAC,MAAM,CAAC,EAAE,WAAW,SAAS,KAAK,CAAC,EAAE,WAAW,SAAS;AAAA,IAC5D;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,aAA2C;AACxD,UAAM,QAAmB,MAAM;AAAA,MAC7B,MAAM,KAAK,OAAO,MAAM,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAAA,MAClE,KAAK;AAAA,IACP;AAEA,YAAQ,IAAI,cAAc,WAAW,aAAa,MAAM,MAAM,QAAQ;AACtE,WAAO,EAAE,aAAa,OAAO,OAAO,MAAM,OAAO;AAAA,EACnD;AACF;;;ACnCO,SAAS,cAAc,KAA8B;AAC1D,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,aAAO,IAAI,kBAAkBE,QAAO,YAAYA,QAAO,KAAK;AAAA,IAC9D,KAAK;AACH,aAAO,IAAI,cAAcA,QAAO,QAAQA,QAAO,KAAK;AAAA,IACtD;AACE,YAAM,IAAI,MAAM,yBAAyB,GAAa,EAAE;AAAA,EAC5D;AACF;;;ACXA,IAAM,MAAMC,QAAO;AACnB,IAAM,SAAS,iCAAiC,IAAI,YAAY;AAMhE,eAAsB,yBACpB,SACA,UACA,QACiB;AACjB,QAAM,0BACJ,GAAG,MAAM,GAAG,OAAO,cAAc,SAAS,QAAQ,eAAe,IAAI,SAAS,GAAG;AAAA,IAC/E;AAAA,IACA;AAAA,EACF;AACF,QAAM,MAAM,MAAM,MAAM,yBAAyB,EAAE,QAAQ,MAAM,CAAC;AAClE,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,6BAA6B,IAAI,MAAM,MAAM,IAAI,UAAU,EAAE;AAAA,EAC/E;AACA,SAAO,MAAM,IAAI,KAAK;AACxB;;;ACcA,eAAsB,eACpB,KACA,cACA,eACwB;AACxB,QAAM,UAAU,cAAc,GAAG;AACjC,QAAM,QAAQ,IAAI,aAAaC,QAAO,EAAE;AACxC,QAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAE9C,UAAQ,IAAI;AAAA,qBAAwB,GAAG,OAAO,IAAI,KAAK,YAAY,GAAI,EAAE,YAAY,CAAC,EAAE;AAExF,QAAM,cACJ,gBAAgB,aAAa,SAAS,IAClC,eACA,MAAM,QAAQ,gBAAgB;AAEpC,UAAQ,IAAI,0BAA0B,YAAY,KAAK,IAAI,CAAC;AAAA,CAAI;AAEhE,QAAM,UAAgC,CAAC;AACvC,QAAM,SAAwD,CAAC;AAE/D,aAAW,eAAe,aAAa;AACrC,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,SAAS,aAAa,aAAa;AAChE,YAAM,YAAY,kBAAkB,KAAK,WAAW;AACpD,YAAM,MAAM,OAAO,WAAW,OAAO,KAAK;AAE1C,cAAQ,KAAK;AAAA,QACX;AAAA,QACA,WAAW,OAAO;AAAA,QAClB;AAAA,MACF,CAAC;AAED,cAAQ;AAAA,QACN,OAAO,WAAW,KAAK,OAAO,KAAK,aAAa,SAAS;AAAA,MAC3D;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,KAAK,EAAE,aAAa,OAAO,QAAQ,CAAC;AAC3C,cAAQ,MAAM,OAAO,WAAW,KAAK,OAAO,EAAE;AAAA,IAChD;AAAA,EACF;AAEA,UAAQ;AAAA,IACN;AAAA,iBAAoB,QAAQ,MAAM,eAAe,OAAO,MAAM;AAAA;AAAA,EAChE;AAEA,SAAO,EAAE,KAAK,WAAW,SAAS,OAAO;AAC3C;AAEA,eAAsB,iBAAiB,UAAqB,SAAkD;AAE5G,QAAM,QAAQ,IAAI,aAAaA,QAAO,EAAE;AACxC,QAAM,UAAwC,CAAC;AAC/C,QAAM,SAAoE,CAAC;AAC3E,QAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC9C,MAAG,CAAC,SAAQ;AACV,cAAU;AAAA,EACZ;AACA,MAAG,CAAC,UAAS;AACX,eAAW,OAAO,KAAK,WAAW;AAAA,EACpC;AAEA,aAAU,WAAW,UAAU;AAC7B,UAAM,YAAY,YAAY,OAAO;AACrC,QAAG,CAAC,WAAU;AACZ,cAAQ,MAAM,0BAA0B,OAAO,EAAE;AACjD;AAAA,IACF;AACA,eAAU,YAAY,WAAW;AAC/B,iBAAU,OAAO,SAAQ;AACvB,cAAM,SAAU,SAAS,iBAAiB,SAAS,cAAc,GAAG,IAAK,SAAS,cAAc,GAAG,IAAI;AACvG,YAAI;AACF,gBAAM,MAAM,MAAM,yBAAyB,SAAS,UAAU,MAAM;AACpE,gBAAM,YAAY;AAAA,YAChB;AAAA,YACA,SAAS;AAAA,YACT;AAAA,UACF;AACA,gBAAM,MAAM;AAAA,YACV;AAAA,YACA;AAAA,YACA,6BAA6B,SAAS;AAAA,UACxC;AACA,gBAAM,aAAa,OAAO,WAAW,KAAK,MAAM;AAChD,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA;AAAA,YACA,WAAW;AAAA,YACX;AAAA,UACF,CAAC;AAED,kBAAQ;AAAA,YACN,OAAO,OAAO,MAAM,MAAM,KAAK,UAAU,aAAa,SAAS;AAAA,UACjE;AAAA,QAIF,SAAO,KAAI;AACT,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,iBAAO,KAAK,EAAE,SAAQ,QAAQ,OAAO,QAAQ,CAAC;AAC9C,kBAAQ,MAAM,OAAO,OAAO,MAAM,MAAM,KAAK,OAAO,EAAE;AAAA,QACxD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,SAAS,OAAO;AACtC;","names":["config","result","cfg","createClient","cfg","createClient","config","config","config"]}
@@ -1,18 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  config,
4
- fetchBundles,
5
- queryBundle
6
- } from "../chunk-JBFJU4JA.js";
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-UWGOF36L.js";
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 fetchBundles(store, opts.output, {
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("query").description("Query a previously fetched 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(
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 queryBundle(opts.output, cms, opts.type, {
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,
@@ -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 { fetchBundles, queryBundle } 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')\n .description('Download latest 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 fetchBundles(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('query')\n .description('Query a previously fetched 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 queryBundle(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":";;;;;;;;;;;AAAA,SAAS,eAAe;AAKxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,eAAe,EACpB,YAAY,wBAAwB,EACpC,QAAQ,OAAO;AAElB,QACK,QAAQ,OAAO,EACf,YAAY,yDAAyD,EACrE,eAAe,oBAAoB,mCAAmC,EACtE,eAAe,mBAAmB,wCAAwC,EAC1E,OAAO,wBAAwB,oBAAoB,iBAAiB,EACpE,OAAO,OAAO,SAAyD;AACxE,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,aAAa,OAAO,KAAK,QAAQ;AAAA,MACnD;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,QACG,QAAQ,OAAO,EACf,YAAY,6DAA6D,EACzE,eAAe,oBAAoB,mCAAmC,EACtE,eAAe,iBAAiB,uBAAuB,EACvD,OAAO,wBAAwB,sCAAsC,iBAAiB,EACtF,OAAO,mBAAmB,wDAA0D,EACpF,OAAO,oBAAoB,kDAAkD,EAC7E,OAAO,eAAe,6BAA6B,QAAQ,EAC3D,OAAO,iBAAiB,yCAAyC,QAAQ,EACzE;AAAA,EACC,OAAO,SAQD;AACJ,UAAM,MAAM,KAAK;AAEjB,QAAI,CAAC,CAAC,cAAc,QAAQ,EAAE,SAAS,GAAG,GAAG;AAC3C,cAAQ,MAAM,yBAAyB,GAAG,EAAE;AAC5C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI;AACF,YAAM,UAAU,MAAM,YAAY,KAAK,QAAQ,KAAK,KAAK,MAAM;AAAA,QAC7D,QAAQ,KAAK,SAAS,KAAK,MAAM,KAAK,MAAM,IAAI;AAAA,QAChD,QAAQ,KAAK,SACT,KAAK,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAC1C;AAAA,QACJ,OAAO,KAAK;AAAA,QACZ,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,cAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,IAC9C,SAAS,KAAK;AACZ,cAAQ,MAAM,iBAAiB,GAAG;AAClC,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;AAEF,QAAQ,MAAM;","names":[]}
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
- fetchBundles
5
- } from "../chunk-JBFJU4JA.js";
4
+ fetchCmsBundles
5
+ } from "../chunk-LOCC2BXB.js";
6
+ import "../chunk-EQ3DSPTJ.js";
6
7
  import {
7
8
  ContentStore
8
- } from "../chunk-UWGOF36L.js";
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 fetchBundles(store, opts.output, {
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":[]}