@tandem-language-exchange/content-store 1.2.0 → 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 +63 -6
- package/dist/{chunk-XP3USUQC.js → chunk-EQ3DSPTJ.js} +6 -2
- package/dist/{chunk-XP3USUQC.js.map → chunk-EQ3DSPTJ.js.map} +1 -1
- package/dist/{chunk-VRWRAFDK.js → chunk-LOCC2BXB.js} +68 -9
- package/dist/chunk-LOCC2BXB.js.map +1 -0
- package/dist/{chunk-YZSLCPN6.js → chunk-OTZLCMZ6.js} +126 -2
- package/dist/chunk-OTZLCMZ6.js.map +1 -0
- package/dist/{chunk-UCBZUEUP.js → chunk-PQJ2MGH7.js} +28 -68
- package/dist/chunk-PQJ2MGH7.js.map +1 -0
- package/dist/client/cli.js +3 -3
- package/dist/client/fetch-content-bundles.js +3 -3
- 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 +11 -4
- package/dist/client/fetch-translation-bundles.js.map +1 -1
- package/dist/{index-Db97SUTy.d.ts → index-kfqHGgMO.d.ts} +16 -1
- package/dist/index.d.ts +1 -1
- package/dist/node.browser.js +7 -0
- package/dist/node.browser.js.map +1 -1
- package/dist/node.d.ts +9 -3
- package/dist/node.js +190 -10
- package/dist/node.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-UCBZUEUP.js.map +0 -1
- package/dist/chunk-VRWRAFDK.js.map +0 -1
- package/dist/chunk-YZSLCPN6.js.map +0 -1
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
buildCmsObjectKey,
|
|
6
6
|
buildTranslationObjectKey,
|
|
7
7
|
config,
|
|
8
|
+
contentTypeForTranslationKey,
|
|
8
9
|
defaultLocales
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OTZLCMZ6.js";
|
|
10
11
|
|
|
11
12
|
// src/server/config.ts
|
|
12
13
|
import dotenv from "dotenv";
|
|
@@ -321,69 +322,20 @@ function createAdapter(cms) {
|
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
324
|
|
|
324
|
-
// src/shared/utils.ts
|
|
325
|
-
import convert from "xml-js";
|
|
326
|
-
import set from "lodash.set";
|
|
327
|
-
import merge from "lodash.merge";
|
|
328
|
-
var convertXMLToJS = (xml) => {
|
|
329
|
-
const converted = convert.xml2js(xml, {
|
|
330
|
-
ignoreComment: true,
|
|
331
|
-
ignoreDeclaration: true,
|
|
332
|
-
ignoreInstruction: true,
|
|
333
|
-
compact: true
|
|
334
|
-
});
|
|
335
|
-
let mapped = {};
|
|
336
|
-
converted.resources.string.forEach((item) => {
|
|
337
|
-
mapped = {
|
|
338
|
-
...mapped,
|
|
339
|
-
[item._attributes.name]: item._text
|
|
340
|
-
};
|
|
341
|
-
});
|
|
342
|
-
return mapped;
|
|
343
|
-
};
|
|
344
|
-
var parseIOSStrings = (strings) => {
|
|
345
|
-
const parsedObj = {};
|
|
346
|
-
strings.split("\n").filter((line) => line.startsWith('"') && line.endsWith(";")).map((line) => line.trim().slice(0, -1)).forEach((line) => {
|
|
347
|
-
let [key, value] = line.split(" = ");
|
|
348
|
-
if (!key || !value) return;
|
|
349
|
-
key = key.slice(1, -1);
|
|
350
|
-
value = value.slice(1, -1);
|
|
351
|
-
parsedObj[key] = value;
|
|
352
|
-
});
|
|
353
|
-
return parsedObj;
|
|
354
|
-
};
|
|
355
|
-
|
|
356
325
|
// src/server/adapters/lingohub.ts
|
|
357
326
|
var cfg = config2.lingohub;
|
|
358
327
|
var apiUrl = "https://api.lingohub.com/v1/" + cfg.workspace + "/projects/";
|
|
359
|
-
|
|
360
|
-
const urlForResourceLocalised = `${apiUrl}${project}/resources/${resource.fileName}?auth_token=${cfg.authToken}`.replace(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (resource.type === "json") {
|
|
371
|
-
return res.json();
|
|
372
|
-
}
|
|
373
|
-
if (resource.type === "xml") {
|
|
374
|
-
const xml = await res.text();
|
|
375
|
-
return convertXMLToJS(xml);
|
|
376
|
-
}
|
|
377
|
-
if (resource.type === "strings") {
|
|
378
|
-
const strings = await res.text();
|
|
379
|
-
return parseIOSStrings(strings);
|
|
380
|
-
}
|
|
381
|
-
throw new Error("Invalid resource type");
|
|
382
|
-
}).catch((err) => {
|
|
383
|
-
throw err;
|
|
384
|
-
});
|
|
385
|
-
return result;
|
|
386
|
-
};
|
|
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
|
+
}
|
|
387
339
|
|
|
388
340
|
// src/server/sync/engine.ts
|
|
389
341
|
async function syncCmsContent(cms, contentTypes, includeLevels) {
|
|
@@ -444,18 +396,26 @@ async function syncTranslations(projects, locales) {
|
|
|
444
396
|
for (const loc of locales) {
|
|
445
397
|
const locale = resource.localeMapping && resource.localeMapping[loc] ? resource.localeMapping[loc] : loc;
|
|
446
398
|
try {
|
|
447
|
-
const
|
|
448
|
-
const objectKey = buildTranslationObjectKey(
|
|
449
|
-
|
|
450
|
-
|
|
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");
|
|
451
411
|
entries.push({
|
|
452
412
|
project,
|
|
453
413
|
locale,
|
|
454
|
-
itemCount,
|
|
414
|
+
itemCount: byteLength,
|
|
455
415
|
objectKey
|
|
456
416
|
});
|
|
457
417
|
console.log(
|
|
458
|
-
` + ${project} - ${locale}: ${
|
|
418
|
+
` + ${project} - ${locale}: ${byteLength} bytes -> ${objectKey}`
|
|
459
419
|
);
|
|
460
420
|
} catch (err) {
|
|
461
421
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -473,4 +433,4 @@ export {
|
|
|
473
433
|
syncCmsContent,
|
|
474
434
|
syncTranslations
|
|
475
435
|
};
|
|
476
|
-
//# 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
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
fetchCmsBundles,
|
|
5
5
|
fetchTranslationBundles,
|
|
6
6
|
queryCmsBundle
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-LOCC2BXB.js";
|
|
8
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
9
9
|
import {
|
|
10
10
|
ContentStore
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
12
12
|
|
|
13
13
|
// src/client/cli.ts
|
|
14
14
|
import { Command } from "commander";
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
config,
|
|
4
4
|
fetchCmsBundles
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
5
|
+
} from "../chunk-LOCC2BXB.js";
|
|
6
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
7
7
|
import {
|
|
8
8
|
ContentStore
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
10
10
|
|
|
11
11
|
// src/client/fetch-content-bundles.ts
|
|
12
12
|
import { Command } from "commander";
|
|
@@ -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":[]}
|
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
import {
|
|
3
3
|
config,
|
|
4
4
|
fetchTranslationBundles
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
5
|
+
} from "../chunk-LOCC2BXB.js";
|
|
6
|
+
import "../chunk-EQ3DSPTJ.js";
|
|
7
7
|
import {
|
|
8
8
|
ContentStore
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-OTZLCMZ6.js";
|
|
10
10
|
|
|
11
11
|
// src/client/fetch-translation-bundles.ts
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
var program = new Command();
|
|
14
|
-
program.name("fetch-translation-bundles").description(
|
|
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) => {
|
|
15
22
|
const projects = opts.projects.split(",").map((s) => s.trim());
|
|
16
23
|
const locales = opts.locales ? opts.locales.split(",").map((s) => s.trim()) : void 0;
|
|
17
24
|
const store = new ContentStore(config.s3);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/client/fetch-translation-bundles.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { config
|
|
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":[]}
|
|
@@ -11,7 +11,11 @@ declare class ContentStore {
|
|
|
11
11
|
private bucket;
|
|
12
12
|
constructor(cfg: S3Config);
|
|
13
13
|
upload(key: string, data: unknown): Promise<string>;
|
|
14
|
+
/** Raw UTF-8 body (e.g. Lingohub file bytes as text). */
|
|
15
|
+
uploadRaw(key: string, body: string, contentType: string): Promise<string>;
|
|
14
16
|
download(key: string): Promise<unknown>;
|
|
17
|
+
/** Raw UTF-8 body from S3 (no JSON.parse). */
|
|
18
|
+
downloadRaw(key: string): Promise<string>;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/** Retry/backoff for S3 reads (aligned with CMS `RetryConfig` shape). */
|
|
@@ -47,6 +51,8 @@ interface FetchTranslationBundlesOptions {
|
|
|
47
51
|
/** S3 GET retry; defaults via {@link getDefaultS3RetryConfig}. */
|
|
48
52
|
retry?: S3RetryConfig;
|
|
49
53
|
}
|
|
54
|
+
/** One merged JSON file per catalog locale (`en.json`, …). Same options as {@link FetchTranslationBundlesOptions}. */
|
|
55
|
+
type FetchMergedTranslationBundlesOptions = FetchTranslationBundlesOptions;
|
|
50
56
|
interface QueryOptions {
|
|
51
57
|
/**
|
|
52
58
|
* Filter items by matching property values (exact equality, or array for IN).
|
|
@@ -89,6 +95,15 @@ declare function fetchCmsBundles(store: ContentStore, outputDir: string, options
|
|
|
89
95
|
* @returns For each project, a map of S3 object key to absolute file path on disk.
|
|
90
96
|
*/
|
|
91
97
|
declare function fetchTranslationBundles(store: ContentStore, outputDir: string, options: FetchTranslationBundlesOptions): Promise<TranslationBundleInfo>;
|
|
98
|
+
/**
|
|
99
|
+
* Downloads translation bundles from S3, parses each raw Lingohub file, and merges all
|
|
100
|
+
* key/value pairs per **catalog** locale into a single JSON file (e.g. `en.json`).
|
|
101
|
+
* Duplicate keys across resources or projects: **last write wins** (iteration order:
|
|
102
|
+
* projects → resources → locales).
|
|
103
|
+
*
|
|
104
|
+
* @returns Map of locale code to absolute path of the merged `{locale}.json` file.
|
|
105
|
+
*/
|
|
106
|
+
declare function fetchMergedTranslationBundles(store: ContentStore, outputDir: string, options: FetchMergedTranslationBundlesOptions): Promise<Record<string, string>>;
|
|
92
107
|
/**
|
|
93
108
|
* Queries a previously fetched bundle from the local filesystem.
|
|
94
109
|
*/
|
|
@@ -100,4 +115,4 @@ interface SDKConfig {
|
|
|
100
115
|
outputDir: string;
|
|
101
116
|
}
|
|
102
117
|
|
|
103
|
-
export { type BundleItem as B, type CMSProvider as C, type FetchCmsBundlesOptions as F, type QueryOptions as Q, type SDKConfig as S, type TranslationBundleInfo as T, type FetchTranslationBundlesOptions as a, type
|
|
118
|
+
export { type BundleItem as B, type CMSProvider as C, type FetchCmsBundlesOptions as F, type QueryOptions as Q, type SDKConfig as S, type TranslationBundleInfo as T, type FetchTranslationBundlesOptions as a, type FetchMergedTranslationBundlesOptions as b, type CmsBundleInfo as c, ContentStore as d, type S3Config as e, type S3RetryConfig as f, fetchCmsBundles as g, fetchMergedTranslationBundles as h, fetchTranslationBundles as i, getDefaultS3RetryConfig as j, queryCmsBundle as q };
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { B as BundleItem, C as CMSProvider,
|
|
1
|
+
export { B as BundleItem, C as CMSProvider, c as CmsBundleInfo, F as FetchCmsBundlesOptions, b as FetchMergedTranslationBundlesOptions, a as FetchTranslationBundlesOptions, Q as QueryOptions, e as S3Config, f as S3RetryConfig, S as SDKConfig, T as TranslationBundleInfo } from './index-kfqHGgMO.js';
|
package/dist/node.browser.js
CHANGED
|
@@ -45,6 +45,9 @@ var ContentStoreSDK = class {
|
|
|
45
45
|
fetchTranslationBundles() {
|
|
46
46
|
return Promise.reject(new Error(SERVER_ONLY));
|
|
47
47
|
}
|
|
48
|
+
fetchMergedTranslationBundles() {
|
|
49
|
+
return Promise.reject(new Error(SERVER_ONLY));
|
|
50
|
+
}
|
|
48
51
|
};
|
|
49
52
|
var ContentStore = class {
|
|
50
53
|
constructor(_cfg) {
|
|
@@ -60,10 +63,14 @@ async function queryCmsBundle() {
|
|
|
60
63
|
async function fetchTranslationBundles() {
|
|
61
64
|
serverOnly();
|
|
62
65
|
}
|
|
66
|
+
async function fetchMergedTranslationBundles() {
|
|
67
|
+
serverOnly();
|
|
68
|
+
}
|
|
63
69
|
export {
|
|
64
70
|
ContentStore,
|
|
65
71
|
ContentStoreSDK,
|
|
66
72
|
fetchCmsBundles,
|
|
73
|
+
fetchMergedTranslationBundles,
|
|
67
74
|
fetchTranslationBundles,
|
|
68
75
|
queryCmsBundle,
|
|
69
76
|
trimDepth
|
package/dist/node.browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/trimDepth.ts","../src/node.browser.ts"],"sourcesContent":["/**\n * Trims nested object depth.\n *\n * `remaining` represents how many levels the current object is allowed.\n * - Scalar properties are always kept.\n * - Nested objects / arrays-of-objects consume one level. When `remaining`\n * drops to 1 they are replaced with `null` (no budget left for refs).\n */\nexport function trimDepth(value: unknown, remaining: number): unknown {\n if (value === null || value === undefined || typeof value !== 'object') {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => trimDepth(item, remaining));\n }\n\n const obj = value as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n\n for (const [k, v] of Object.entries(obj)) {\n if (v === null || v === undefined || typeof v !== 'object') {\n result[k] = v;\n } else if (remaining <= 1) {\n result[k] = null;\n } else if (Array.isArray(v)) {\n result[k] = v.map((item) => {\n if (item !== null && typeof item === 'object' && !Array.isArray(item)) {\n return trimDepth(item, remaining - 1);\n }\n return item;\n });\n } else {\n result[k] = trimDepth(v, remaining - 1);\n }\n }\n\n return result;\n}\n","/**\n * Browser / client-bundler entry for `@tandem-language-exchange/content-store/node`.\n * Next.js and Turbopack resolve this when the `browser` condition applies, so `fs`\n * is never pulled into client chunks.\n *\n * Server-only APIs throw if called; `trimDepth` is real and safe everywhere.\n */\nexport type {\n SDKConfig,\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n QueryOptions,\n CmsBundleInfo,\n TranslationBundleInfo,\n BundleItem,\n} from './sdk/client-types';\nexport type { CMSProvider, S3Config } from './shared/types';\n\nexport { trimDepth } from './shared/trimDepth';\n\nconst SERVER_ONLY =\n '@tandem-language-exchange/content-store/node uses the filesystem and S3 and only runs on the server. ' +\n 'Import it from getServerSideProps, Route Handlers, or \"use server\" modules — not from _app, layouts, or client components. ' +\n 'You can use `import type { ... } from \"@tandem-language-exchange/content-store\"` in shared code.';\n\nfunction serverOnly(): never {\n throw new Error(SERVER_ONLY);\n}\n\nexport class ContentStoreSDK {\n constructor(_config: unknown) {\n serverOnly();\n }\n\n fetchCmsBundles(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n\n queryCmsBundle(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n\n fetchTranslationBundles(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n}\n\nexport class ContentStore {\n constructor(_cfg: unknown) {\n serverOnly();\n }\n}\n\nexport async function fetchCmsBundles(): Promise<never> {\n serverOnly();\n}\n\nexport async function queryCmsBundle(): Promise<never> {\n serverOnly();\n}\n\nexport async function fetchTranslationBundles(): Promise<never> {\n serverOnly();\n}\n"],"mappings":";AAQO,SAAS,UAAU,OAAgB,WAA4B;AACpE,MAAI,UAAU,QAAQ,UAAU,UAAa,OAAO,UAAU,UAAU;AACtE,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,SAAS,UAAU,MAAM,SAAS,CAAC;AAAA,EACvD;AAEA,QAAM,MAAM;AACZ,QAAM,SAAkC,CAAC;AAEzC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,MAAM,QAAQ,MAAM,UAAa,OAAO,MAAM,UAAU;AAC1D,aAAO,CAAC,IAAI;AAAA,IACd,WAAW,aAAa,GAAG;AACzB,aAAO,CAAC,IAAI;AAAA,IACd,WAAW,MAAM,QAAQ,CAAC,GAAG;AAC3B,aAAO,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS;AAC1B,YAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AACrE,iBAAO,UAAU,MAAM,YAAY,CAAC;AAAA,QACtC;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH,OAAO;AACL,aAAO,CAAC,IAAI,UAAU,GAAG,YAAY,CAAC;AAAA,IACxC;AAAA,EACF;AAEA,SAAO;AACT;;;
|
|
1
|
+
{"version":3,"sources":["../src/shared/trimDepth.ts","../src/node.browser.ts"],"sourcesContent":["/**\n * Trims nested object depth.\n *\n * `remaining` represents how many levels the current object is allowed.\n * - Scalar properties are always kept.\n * - Nested objects / arrays-of-objects consume one level. When `remaining`\n * drops to 1 they are replaced with `null` (no budget left for refs).\n */\nexport function trimDepth(value: unknown, remaining: number): unknown {\n if (value === null || value === undefined || typeof value !== 'object') {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => trimDepth(item, remaining));\n }\n\n const obj = value as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n\n for (const [k, v] of Object.entries(obj)) {\n if (v === null || v === undefined || typeof v !== 'object') {\n result[k] = v;\n } else if (remaining <= 1) {\n result[k] = null;\n } else if (Array.isArray(v)) {\n result[k] = v.map((item) => {\n if (item !== null && typeof item === 'object' && !Array.isArray(item)) {\n return trimDepth(item, remaining - 1);\n }\n return item;\n });\n } else {\n result[k] = trimDepth(v, remaining - 1);\n }\n }\n\n return result;\n}\n","/**\n * Browser / client-bundler entry for `@tandem-language-exchange/content-store/node`.\n * Next.js and Turbopack resolve this when the `browser` condition applies, so `fs`\n * is never pulled into client chunks.\n *\n * Server-only APIs throw if called; `trimDepth` is real and safe everywhere.\n */\nexport type {\n SDKConfig,\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n FetchMergedTranslationBundlesOptions,\n QueryOptions,\n CmsBundleInfo,\n TranslationBundleInfo,\n BundleItem,\n} from './sdk/client-types';\nexport type { CMSProvider, S3Config } from './shared/types';\n\nexport { trimDepth } from './shared/trimDepth';\n\nconst SERVER_ONLY =\n '@tandem-language-exchange/content-store/node uses the filesystem and S3 and only runs on the server. ' +\n 'Import it from getServerSideProps, Route Handlers, or \"use server\" modules — not from _app, layouts, or client components. ' +\n 'You can use `import type { ... } from \"@tandem-language-exchange/content-store\"` in shared code.';\n\nfunction serverOnly(): never {\n throw new Error(SERVER_ONLY);\n}\n\nexport class ContentStoreSDK {\n constructor(_config: unknown) {\n serverOnly();\n }\n\n fetchCmsBundles(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n\n queryCmsBundle(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n\n fetchTranslationBundles(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n\n fetchMergedTranslationBundles(): Promise<never> {\n return Promise.reject(new Error(SERVER_ONLY));\n }\n}\n\nexport class ContentStore {\n constructor(_cfg: unknown) {\n serverOnly();\n }\n}\n\nexport async function fetchCmsBundles(): Promise<never> {\n serverOnly();\n}\n\nexport async function queryCmsBundle(): Promise<never> {\n serverOnly();\n}\n\nexport async function fetchTranslationBundles(): Promise<never> {\n serverOnly();\n}\n\nexport async function fetchMergedTranslationBundles(): Promise<never> {\n serverOnly();\n}\n"],"mappings":";AAQO,SAAS,UAAU,OAAgB,WAA4B;AACpE,MAAI,UAAU,QAAQ,UAAU,UAAa,OAAO,UAAU,UAAU;AACtE,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,SAAS,UAAU,MAAM,SAAS,CAAC;AAAA,EACvD;AAEA,QAAM,MAAM;AACZ,QAAM,SAAkC,CAAC;AAEzC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,GAAG,GAAG;AACxC,QAAI,MAAM,QAAQ,MAAM,UAAa,OAAO,MAAM,UAAU;AAC1D,aAAO,CAAC,IAAI;AAAA,IACd,WAAW,aAAa,GAAG;AACzB,aAAO,CAAC,IAAI;AAAA,IACd,WAAW,MAAM,QAAQ,CAAC,GAAG;AAC3B,aAAO,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS;AAC1B,YAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAAG;AACrE,iBAAO,UAAU,MAAM,YAAY,CAAC;AAAA,QACtC;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH,OAAO;AACL,aAAO,CAAC,IAAI,UAAU,GAAG,YAAY,CAAC;AAAA,IACxC;AAAA,EACF;AAEA,SAAO;AACT;;;ACjBA,IAAM,cACJ;AAIF,SAAS,aAAoB;AAC3B,QAAM,IAAI,MAAM,WAAW;AAC7B;AAEO,IAAM,kBAAN,MAAsB;AAAA,EAC3B,YAAY,SAAkB;AAC5B,eAAW;AAAA,EACb;AAAA,EAEA,kBAAkC;AAChC,WAAO,QAAQ,OAAO,IAAI,MAAM,WAAW,CAAC;AAAA,EAC9C;AAAA,EAEA,iBAAiC;AAC/B,WAAO,QAAQ,OAAO,IAAI,MAAM,WAAW,CAAC;AAAA,EAC9C;AAAA,EAEA,0BAA0C;AACxC,WAAO,QAAQ,OAAO,IAAI,MAAM,WAAW,CAAC;AAAA,EAC9C;AAAA,EAEA,gCAAgD;AAC9C,WAAO,QAAQ,OAAO,IAAI,MAAM,WAAW,CAAC;AAAA,EAC9C;AACF;AAEO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAY,MAAe;AACzB,eAAW;AAAA,EACb;AACF;AAEA,eAAsB,kBAAkC;AACtD,aAAW;AACb;AAEA,eAAsB,iBAAiC;AACrD,aAAW;AACb;AAEA,eAAsB,0BAA0C;AAC9D,aAAW;AACb;AAEA,eAAsB,gCAAgD;AACpE,aAAW;AACb;","names":[]}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SDKConfig, F as FetchCmsBundlesOptions, a as FetchTranslationBundlesOptions, T as TranslationBundleInfo, C as CMSProvider, Q as QueryOptions } from './index-
|
|
2
|
-
export { B as BundleItem,
|
|
1
|
+
import { S as SDKConfig, F as FetchCmsBundlesOptions, a as FetchTranslationBundlesOptions, T as TranslationBundleInfo, b as FetchMergedTranslationBundlesOptions, C as CMSProvider, Q as QueryOptions } from './index-kfqHGgMO.js';
|
|
2
|
+
export { B as BundleItem, c as CmsBundleInfo, d as ContentStore, e as S3Config, f as S3RetryConfig, g as fetchCmsBundles, h as fetchMergedTranslationBundles, i as fetchTranslationBundles, j as getDefaultS3RetryConfig, q as queryCmsBundle } from './index-kfqHGgMO.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Trims nested object depth.
|
|
@@ -29,10 +29,16 @@ declare class ContentStoreSDK {
|
|
|
29
29
|
* @returns Per project, a map of S3 object key to absolute file path.
|
|
30
30
|
*/
|
|
31
31
|
fetchTranslationBundles(options: FetchTranslationBundlesOptions): Promise<TranslationBundleInfo>;
|
|
32
|
+
/**
|
|
33
|
+
* Downloads all translation resources for the given projects/locales, parses them,
|
|
34
|
+
* and writes one merged `{locale}.json` per locale (string key/value map; duplicate keys:
|
|
35
|
+
* last wins).
|
|
36
|
+
*/
|
|
37
|
+
fetchMergedTranslationBundles(options: FetchMergedTranslationBundlesOptions): Promise<Record<string, string>>;
|
|
32
38
|
/**
|
|
33
39
|
* Queries a previously fetched bundle from the local filesystem.
|
|
34
40
|
*/
|
|
35
41
|
queryCmsBundle(cms: CMSProvider, contentType: string, options?: QueryOptions): Promise<unknown[]>;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
export { CMSProvider, ContentStoreSDK, FetchCmsBundlesOptions, FetchTranslationBundlesOptions, QueryOptions, SDKConfig, TranslationBundleInfo, trimDepth };
|
|
44
|
+
export { CMSProvider, ContentStoreSDK, FetchCmsBundlesOptions, FetchMergedTranslationBundlesOptions, FetchTranslationBundlesOptions, QueryOptions, SDKConfig, TranslationBundleInfo, trimDepth };
|