@tandem-language-exchange/content-store 1.3.0 → 1.3.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 +9 -9
- package/dist/{chunk-HD7E5M5O.js → chunk-7I67676Y.js} +2 -2
- package/dist/{chunk-ZX5KC4F5.js → chunk-U73PO7OV.js} +9 -12
- package/dist/chunk-U73PO7OV.js.map +1 -0
- package/dist/node.d.ts +11 -4
- package/dist/node.js +18 -14
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZX5KC4F5.js.map +0 -1
- /package/dist/{chunk-HD7E5M5O.js.map → chunk-7I67676Y.js.map} +0 -0
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ On **staging** (pages rebuild on request), content-store can tell your app to pu
|
|
|
113
113
|
|
|
114
114
|
```http
|
|
115
115
|
POST /api/internal/content-refresh
|
|
116
|
-
Authorization:
|
|
116
|
+
Authorization: Basic <base64(username:password)>
|
|
117
117
|
Content-Type: application/json
|
|
118
118
|
|
|
119
119
|
{"scope":"cms","cms":"contentful","content_types":["page","banner"]}
|
|
@@ -157,7 +157,7 @@ import { contentRefreshDefaults, contentStoreSdk } from '../../lib/content-store
|
|
|
157
157
|
|
|
158
158
|
const handler = createNextPagesApiContentRefreshHandler({
|
|
159
159
|
sdk: contentStoreSdk,
|
|
160
|
-
|
|
160
|
+
basicAuthBase64: process.env.CONTENT_REFRESH_BASIC_AUTH!,
|
|
161
161
|
defaults: contentRefreshDefaults,
|
|
162
162
|
});
|
|
163
163
|
|
|
@@ -180,24 +180,24 @@ import { contentRefreshDefaults, contentStoreSdk } from '@/lib/content-store';
|
|
|
180
180
|
export async function POST(request: Request) {
|
|
181
181
|
return handleNextAppRouterContentRefresh(request, {
|
|
182
182
|
sdk: contentStoreSdk,
|
|
183
|
-
|
|
183
|
+
basicAuthBase64: process.env.CONTENT_REFRESH_BASIC_AUTH!,
|
|
184
184
|
defaults: contentRefreshDefaults,
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
Point content-store staging at the deployed URL, e.g.
|
|
190
|
-
`
|
|
190
|
+
`CONTENT_REFRESH_WEB_SITE_URL=https://staging.example.com/api/internal/content-refresh`
|
|
191
191
|
|
|
192
192
|
After refresh, load bundles from `outputDir` in `getStaticProps`, `getServerSideProps`, or Server Components on the next request.
|
|
193
193
|
|
|
194
194
|
### Configure content-store (staging instance)
|
|
195
195
|
|
|
196
|
-
| Variable
|
|
197
|
-
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
196
|
+
| Variable | Description |
|
|
197
|
+
|--------------------------------| --- |
|
|
198
|
+
| `CONTENT_REFRESH_WEB_SITE_URL` | Full URL to web-site refresh endpoint |
|
|
199
|
+
| `CONTENT_REFRESH_WEB_APP_URL` | Full URL to web-app refresh endpoint |
|
|
200
|
+
| `CONTENT_REFRESH_BASIC_AUTH` | Base64 of staging site basic-auth `username:password` (no `Basic ` prefix). Generate with `encodeBasicAuthCredentials` from the package or `printf '%s' 'user:pass' \| base64` |
|
|
201
201
|
|
|
202
202
|
After `POST /syncCmsContent` completes successfully on a **beta/staging** instance, content-store notifies every configured URL automatically.
|
|
203
203
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
syncCmsContent,
|
|
11
11
|
syncTranslations,
|
|
12
12
|
triggerPipelineBuild
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-U73PO7OV.js";
|
|
14
14
|
import {
|
|
15
15
|
allProjects
|
|
16
16
|
} from "./chunk-RZLDRXNQ.js";
|
|
@@ -230,4 +230,4 @@ export {
|
|
|
230
230
|
notifySlack,
|
|
231
231
|
startSlackBot
|
|
232
232
|
};
|
|
233
|
-
//# sourceMappingURL=chunk-
|
|
233
|
+
//# sourceMappingURL=chunk-7I67676Y.js.map
|
|
@@ -191,12 +191,12 @@ function pipelineRefForAzure(pipelineEnvironment) {
|
|
|
191
191
|
|
|
192
192
|
// src/shared/content-refresh.ts
|
|
193
193
|
import { timingSafeEqual } from "crypto";
|
|
194
|
-
async function postContentRefresh(target, url,
|
|
194
|
+
async function postContentRefresh(target, url, basicAuth, request) {
|
|
195
195
|
try {
|
|
196
196
|
const response = await fetch(url, {
|
|
197
197
|
method: "POST",
|
|
198
198
|
headers: {
|
|
199
|
-
Authorization: `
|
|
199
|
+
Authorization: `Basic ${basicAuth}`,
|
|
200
200
|
"Content-Type": "application/json",
|
|
201
201
|
Accept: "application/json"
|
|
202
202
|
},
|
|
@@ -233,7 +233,7 @@ function isContentRefreshEnabledForInstance(instanceEnvironment) {
|
|
|
233
233
|
return normalizeToAzureEnvironment(instanceEnvironment) === "staging";
|
|
234
234
|
}
|
|
235
235
|
async function notifyContentRefreshTargets(notifyConfig, request, projects) {
|
|
236
|
-
if (!notifyConfig.enabled || !notifyConfig.
|
|
236
|
+
if (!notifyConfig.enabled || !notifyConfig.basicAuth) {
|
|
237
237
|
return [];
|
|
238
238
|
}
|
|
239
239
|
const keys = projects ?? AZURE_DEVOPS_PROJECT_KEYS.filter(
|
|
@@ -248,7 +248,7 @@ async function notifyContentRefreshTargets(notifyConfig, request, projects) {
|
|
|
248
248
|
const result = await postContentRefresh(
|
|
249
249
|
project,
|
|
250
250
|
url,
|
|
251
|
-
notifyConfig.
|
|
251
|
+
notifyConfig.basicAuth,
|
|
252
252
|
request
|
|
253
253
|
);
|
|
254
254
|
results.push(result);
|
|
@@ -451,9 +451,9 @@ function parseAzureDevOpsConfig(instanceEnvironment) {
|
|
|
451
451
|
}
|
|
452
452
|
function readContentRefreshUrl(project) {
|
|
453
453
|
const slug = projectEnvSlug(project);
|
|
454
|
-
return process.env[`
|
|
454
|
+
return process.env[`CONTENT_REFRESH_${slug}_URL`]?.trim() || void 0;
|
|
455
455
|
}
|
|
456
|
-
function parseContentRefreshConfig(instanceEnvironment
|
|
456
|
+
function parseContentRefreshConfig(instanceEnvironment) {
|
|
457
457
|
const targets = Object.fromEntries(
|
|
458
458
|
AZURE_DEVOPS_PROJECT_KEYS.map((project) => [
|
|
459
459
|
project,
|
|
@@ -462,7 +462,7 @@ function parseContentRefreshConfig(instanceEnvironment, contentStoreApiToken) {
|
|
|
462
462
|
);
|
|
463
463
|
return {
|
|
464
464
|
enabled: isContentRefreshEnabledForInstance(instanceEnvironment),
|
|
465
|
-
|
|
465
|
+
basicAuth: process.env.CONTENT_REFRESH_BASIC_AUTH?.trim() ?? "",
|
|
466
466
|
targets
|
|
467
467
|
};
|
|
468
468
|
}
|
|
@@ -507,10 +507,7 @@ var config2 = {
|
|
|
507
507
|
scheduledCmsJob: parseScheduledCmsJobConfig(),
|
|
508
508
|
scheduledTranslationJob: parseScheduledTranslationJobConfig(),
|
|
509
509
|
azure: parseAzureDevOpsConfig(config.environment),
|
|
510
|
-
contentRefresh: parseContentRefreshConfig(
|
|
511
|
-
config.environment,
|
|
512
|
-
process.env.CONTENT_STORE_API_TOKEN ?? ""
|
|
513
|
-
),
|
|
510
|
+
contentRefresh: parseContentRefreshConfig(config.environment),
|
|
514
511
|
slack: {
|
|
515
512
|
enabled: (process.env.SLACK_BOT_TOKEN ?? "").length > 0,
|
|
516
513
|
botToken: process.env.SLACK_BOT_TOKEN ?? "",
|
|
@@ -898,4 +895,4 @@ export {
|
|
|
898
895
|
syncCmsContent,
|
|
899
896
|
syncTranslations
|
|
900
897
|
};
|
|
901
|
-
//# sourceMappingURL=chunk-
|
|
898
|
+
//# sourceMappingURL=chunk-U73PO7OV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/adapters/azure/types.ts","../src/server/adapters/azure/client.ts","../src/server/adapters/azure/pipelines.ts","../src/server/adapters/azure/environment.ts","../src/shared/content-refresh.ts","../src/server/content-refresh-notify.ts","../src/server/config.ts","../src/server/restrictedCron.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 type { AzurePipelineEnvironment } from './environment';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/** Azure DevOps project name (e.g. `web-site`, `web-app`). */\nexport type AzureDevOpsProjectKey = 'web-site' | 'web-app' | 'web-invites';\n\nexport const AZURE_DEVOPS_PROJECT_KEYS: AzureDevOpsProjectKey[] = [\n 'web-site',\n 'web-app',\n 'web-invites',\n];\n\nexport interface AzurePipelineDefinition {\n id: number;\n refName: string;\n}\n\n/** Pipeline for this content-store instance (one id/ref per ADO project). */\nexport interface AzureProjectConfig {\n pipeline: AzurePipelineDefinition;\n}\n\n/** Credentials and per-project pipeline map for this deployment. */\nexport interface AzureDevOpsConfig {\n organization: string;\n /** Personal access token with pipeline run permissions. */\n pat: string;\n apiVersion: string;\n /** Default when Slack command omits the project argument. */\n defaultProject: AzureDevOpsProjectKey;\n /** Raw `ENVIRONMENT` on this instance (e.g. beta, live). */\n instanceEnvironment: string;\n /** Normalized for Azure (staging | production). */\n pipelineEnvironment: AzurePipelineEnvironment;\n projects: Record<AzureDevOpsProjectKey, AzureProjectConfig>;\n}\n\n/** Connection settings for a single ADO project (used by the HTTP client). */\nexport interface AzureDevOpsClientConfig {\n organization: string;\n project: string;\n pat: string;\n apiVersion: string;\n}\n\nexport interface AzureRequestOptions {\n method?: HttpMethod;\n /**\n * Path under the project, e.g. `/_apis/pipelines/281/runs`.\n * If it starts with `http://` or `https://`, it is used as the full URL.\n */\n path: string;\n query?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n /** Overrides the default api-version query parameter. */\n apiVersion?: string;\n headers?: Record<string, string>;\n}\n\nexport interface AzureApiErrorBody {\n message?: string;\n typeKey?: string;\n}\n\n/** Subset of the Azure Pipelines run object returned by the Runs API. */\nexport interface PipelineRunResponse {\n id: number;\n name?: string;\n state?: string;\n url?: string;\n createdDate?: string;\n _links?: {\n web?: { href?: string };\n pipeline?: { href?: string };\n };\n}\n\nexport interface TriggerPipelineRunResult {\n project: AzureDevOpsProjectKey;\n instanceEnvironment: string;\n pipelineEnvironment: AzurePipelineEnvironment;\n pipelineId: number;\n refName: string;\n run: PipelineRunResponse;\n}\n","import type {\n AzureApiErrorBody,\n AzureDevOpsClientConfig,\n AzureRequestOptions,\n HttpMethod,\n} from './types';\n\nexport class AzureDevOpsClient {\n private readonly baseUrl: string;\n\n constructor(private readonly config: AzureDevOpsClientConfig) {\n const org = config.organization.replace(/^\\/+|\\/+$/g, '');\n const project = encodeURIComponent(config.project);\n this.baseUrl = `https://dev.azure.com/${org}/${project}`;\n }\n\n /**\n * Call any Azure DevOps REST endpoint under the configured organization and project.\n */\n async request<T>(options: AzureRequestOptions): Promise<T> {\n const {\n method = 'GET',\n path,\n query = {},\n body,\n apiVersion = this.config.apiVersion,\n headers: extraHeaders = {},\n } = options;\n\n if (!this.config.pat) {\n throw new Error(\n 'Azure DevOps is not configured (set AZURE_DEVOPS_ACCESS_TOKEN)',\n );\n }\n\n const url = this.buildUrl(path, { ...query, 'api-version': apiVersion });\n const headers: Record<string, string> = {\n Authorization: this.basicAuthHeader(),\n Accept: 'application/json',\n ...extraHeaders,\n };\n\n const init: RequestInit = { method, headers };\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, init);\n const text = await response.text();\n let parsed: unknown;\n if (text) {\n try {\n parsed = JSON.parse(text) as unknown;\n } catch {\n parsed = text;\n }\n }\n\n if (!response.ok) {\n const errBody = parsed as AzureApiErrorBody | undefined;\n const detail =\n errBody?.message ??\n (typeof parsed === 'string' ? parsed : JSON.stringify(parsed));\n throw new Error(\n `Azure DevOps ${method} ${path} failed (${response.status}): ${detail}`,\n );\n }\n\n return parsed as T;\n }\n\n private buildUrl(\n path: string,\n query: Record<string, string | number | boolean | undefined>,\n ): string {\n const base = /^https?:\\/\\//i.test(path) ? path : `${this.baseUrl}${path}`;\n const url = new URL(base);\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n private basicAuthHeader(): string {\n const encoded = Buffer.from(`:${this.config.pat}`).toString('base64');\n return `Basic ${encoded}`;\n }\n}\n\nexport function createAzureDevOpsClient(\n config: AzureDevOpsClientConfig,\n): AzureDevOpsClient {\n return new AzureDevOpsClient(config);\n}\n\nexport type { HttpMethod };\n","import { createAzureDevOpsClient } from './client';\nimport type {\n AzureDevOpsConfig,\n AzureDevOpsProjectKey,\n PipelineRunResponse,\n TriggerPipelineRunResult,\n} from './types';\nimport { AZURE_DEVOPS_PROJECT_KEYS } from './types';\n\nexport function isAzureDevOpsProjectKey(value: string): value is AzureDevOpsProjectKey {\n return (AZURE_DEVOPS_PROJECT_KEYS as string[]).includes(value);\n}\n\nfunction resolveProjectConfig(\n azureConfig: AzureDevOpsConfig,\n project: AzureDevOpsProjectKey,\n) {\n const projectConfig = azureConfig.projects[project];\n if (!projectConfig) {\n throw new Error(`Unknown Azure DevOps project \"${project}\"`);\n }\n return projectConfig;\n}\n\n/**\n * Queue a pipeline run for the given ADO project using this instance's configured pipeline id/ref.\n */\nexport async function triggerPipelineBuild(\n azureConfig: AzureDevOpsConfig,\n project: AzureDevOpsProjectKey,\n variables: Record<string, string> = {},\n): Promise<TriggerPipelineRunResult> {\n const { pipeline } = resolveProjectConfig(azureConfig, project);\n if (!pipeline?.id) {\n throw new Error(\n `No pipeline id configured for project \"${project}\" ` +\n `(instance ENVIRONMENT=${azureConfig.instanceEnvironment}, ` +\n `Azure=${azureConfig.pipelineEnvironment})`,\n );\n }\n\n const client = createAzureDevOpsClient({\n organization: azureConfig.organization,\n project,\n pat: azureConfig.pat,\n apiVersion: azureConfig.apiVersion,\n });\n\n const run = await client.request<PipelineRunResponse>({\n method: 'POST',\n path: `/_apis/pipelines/${pipeline.id}/runs`,\n body: {\n resources: {\n repositories: {\n self: {\n refName: pipeline.refName,\n },\n },\n },\n variables,\n },\n });\n\n return {\n project,\n instanceEnvironment: azureConfig.instanceEnvironment,\n pipelineEnvironment: azureConfig.pipelineEnvironment,\n pipelineId: pipeline.id,\n refName: pipeline.refName,\n run,\n };\n}\n\nexport function formatPipelineRunSummary(result: TriggerPipelineRunResult): string {\n const {\n run,\n project,\n instanceEnvironment,\n pipelineEnvironment,\n pipelineId,\n refName,\n } = result;\n const webUrl = run._links?.web?.href ?? run.url;\n const envLabel =\n instanceEnvironment === pipelineEnvironment\n ? `\\`${pipelineEnvironment}\\``\n : `\\`${instanceEnvironment}\\` → Azure \\`${pipelineEnvironment}\\``;\n const lines = [\n `Environment ${envLabel} — project \\`${project}\\`, pipeline id \\`${pipelineId}\\`, ref \\`${refName}\\``,\n `Run #${run.id}${run.state ? ` — state: \\`${run.state}\\`` : ''}`,\n ];\n if (webUrl) {\n lines.push(`<${webUrl}|Open run in Azure DevOps>`);\n }\n return lines.join('\\n');\n}\n","/**\n * Azure Pipelines only use staging / production naming.\n * Map AWS / shared `ENVIRONMENT` values (beta, live, …) at the adapter boundary.\n */\nexport type AzurePipelineEnvironment = 'staging' | 'production';\n\nconst PRODUCTION_ALIASES = new Set(['production', 'live', 'prod']);\nconst STAGING_ALIASES = new Set(['staging', 'beta', 'development', 'dev', 'local']);\n\n/**\n * Normalize any instance `ENVIRONMENT` value to Azure's staging | production.\n * Unknown values default to staging and log a warning (avoids queuing production by mistake).\n */\nexport function normalizeToAzureEnvironment(\n instanceEnvironment: string,\n): AzurePipelineEnvironment {\n const key = instanceEnvironment.trim().toLowerCase();\n if (!key) {\n console.warn(\n '[azure] Empty ENVIRONMENT; defaulting Azure pipeline environment to staging',\n );\n return 'staging';\n }\n if (PRODUCTION_ALIASES.has(key)) {\n return 'production';\n }\n if (STAGING_ALIASES.has(key)) {\n return 'staging';\n }\n console.warn(\n `[azure] Unrecognized ENVIRONMENT=\"${instanceEnvironment}\"; defaulting Azure pipeline environment to staging`,\n );\n return 'staging';\n}\n\n/** Git ref queued for the pipeline run (always derived from the Azure environment). */\nexport function pipelineRefForAzure(\n pipelineEnvironment: AzurePipelineEnvironment,\n): string {\n return `refs/heads/${pipelineEnvironment}`;\n}\n","import { timingSafeEqual } from 'node:crypto';\nimport type { CMSProvider } from './types';\nimport type {\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n TranslationBundleInfo,\n} from './bundles';\n\nexport type ContentRefreshScope = 'cms' | 'translations' | 'all';\n\nexport interface ContentRefreshRequest {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n content_types?: string[];\n projects?: string[];\n locales?: string[];\n}\n\n/** Defaults applied when the request omits fields (set by the host application). */\nexport interface ContentRefreshDefaults {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n contentTypes?: string[];\n translationProjects?: string[];\n locales?: string[];\n}\n\nexport interface ContentRefreshError {\n step: string;\n message: string;\n}\n\nexport interface ContentRefreshResult {\n ok: boolean;\n scope: ContentRefreshScope;\n cmsFiles?: Record<string, string>;\n translationFiles?: TranslationBundleInfo;\n errors: ContentRefreshError[];\n durationMs: number;\n}\n\nexport interface ContentRefreshFetchers {\n fetchCmsBundles: (\n options: FetchCmsBundlesOptions,\n ) => Promise<Record<string, string>>;\n fetchTranslationBundles: (\n options: FetchTranslationBundlesOptions,\n ) => Promise<TranslationBundleInfo>;\n}\n\nexport function resolveContentRefreshScope(\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults,\n): ContentRefreshScope {\n return request.scope ?? defaults.scope ?? 'cms';\n}\n\n/** Base64 of `username:password` (value only — prefix with `Basic ` in the header). */\nexport function encodeBasicAuthCredentials(\n username: string,\n password: string,\n): string {\n return Buffer.from(`${username}:${password}`, 'utf8').toString('base64');\n}\n\n/**\n * Validates `Authorization: Basic <base64>` against the expected credentials\n * (base64 of `user:pass`, same as staging site basic auth).\n */\nexport function assertContentRefreshBasicAuth(\n authorizationHeader: string | undefined,\n expectedCredentialsBase64: string,\n): void {\n if (!expectedCredentialsBase64) {\n throw new ContentRefreshAuthError(\n 'Content refresh basic auth is not configured (set CONTENT_REFRESH_BASIC_AUTH)',\n 500,\n );\n }\n if (!authorizationHeader?.startsWith('Basic ')) {\n throw new ContentRefreshAuthError(\n 'Missing or malformed Authorization header (expected Basic)',\n 401,\n );\n }\n const provided = authorizationHeader.slice(6).trim();\n const bufA = new TextEncoder().encode(provided);\n const bufB = new TextEncoder().encode(expectedCredentialsBase64);\n if (bufA.byteLength !== bufB.byteLength || !timingSafeEqual(bufA, bufB)) {\n throw new ContentRefreshAuthError('Invalid basic auth credentials', 403);\n }\n}\n\nexport class ContentRefreshAuthError extends Error {\n constructor(\n message: string,\n readonly statusCode: number,\n ) {\n super(message);\n this.name = 'ContentRefreshAuthError';\n }\n}\n\n/**\n * Pull the latest bundles from S3 into the host app's configured output directory.\n */\nexport async function executeContentRefresh(\n fetchers: ContentRefreshFetchers,\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults = {},\n): Promise<ContentRefreshResult> {\n const started = Date.now();\n const scope = resolveContentRefreshScope(request, defaults);\n const errors: ContentRefreshError[] = [];\n let cmsFiles: Record<string, string> | undefined;\n let translationFiles: TranslationBundleInfo | undefined;\n\n if (scope === 'cms' || scope === 'all') {\n const cms = request.cms ?? defaults.cms ?? 'contentful';\n const contentTypes = request.content_types ?? defaults.contentTypes;\n if (!contentTypes?.length) {\n errors.push({\n step: 'cms',\n message: 'content_types (or handler defaults.contentTypes) is required for CMS refresh',\n });\n } else {\n try {\n cmsFiles = await fetchers.fetchCmsBundles({ cms, contentTypes });\n } catch (err) {\n errors.push({\n step: 'cms',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n if (scope === 'translations' || scope === 'all') {\n const projects = request.projects ?? defaults.translationProjects;\n if (!projects?.length) {\n errors.push({\n step: 'translations',\n message:\n 'projects (or handler defaults.translationProjects) is required for translation refresh',\n });\n } else {\n const locales = request.locales ?? defaults.locales;\n try {\n const projectMap = Object.fromEntries(\n projects.map((p) => [p, [] as string[]]),\n );\n translationFiles = await fetchers.fetchTranslationBundles({\n projects: projectMap,\n locales,\n });\n } catch (err) {\n errors.push({\n step: 'translations',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return {\n ok: errors.length === 0,\n scope,\n cmsFiles,\n translationFiles,\n errors,\n durationMs: Date.now() - started,\n };\n}\n\nexport interface PostContentRefreshResponse {\n target: string;\n ok: boolean;\n status: number;\n body?: unknown;\n error?: string;\n}\n\n/**\n * Ask a remote application (web-site, web-app, …) to refresh its local content cache.\n */\nexport async function postContentRefresh(\n target: string,\n url: string,\n basicAuth: string,\n request: ContentRefreshRequest,\n): Promise<PostContentRefreshResponse> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${basicAuth}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(request),\n });\n\n const text = await response.text();\n let body: unknown;\n if (text) {\n try {\n body = JSON.parse(text) as unknown;\n } catch {\n body = text;\n }\n }\n\n return {\n target,\n ok: response.ok,\n status: response.status,\n body,\n error: response.ok\n ? undefined\n : typeof body === 'object' && body !== null && 'error' in body\n ? String((body as { error: unknown }).error)\n : `HTTP ${response.status}`,\n };\n } catch (err) {\n return {\n target,\n ok: false,\n status: 0,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { AzureDevOpsProjectKey } from './adapters/azure';\nimport { AZURE_DEVOPS_PROJECT_KEYS } from './adapters/azure';\nimport { normalizeToAzureEnvironment } from './adapters/azure';\nimport {\n postContentRefresh,\n type ContentRefreshRequest,\n type PostContentRefreshResponse,\n} from '../shared/content-refresh';\n\nexport interface ContentRefreshTargetConfig {\n url?: string;\n}\n\nexport interface ContentRefreshNotifyConfig {\n /** When false, never call client refresh URLs (e.g. production uses pipeline builds). */\n enabled: boolean;\n /** Base64 of `username:password` (no `Basic ` prefix). */\n basicAuth: string;\n targets: Record<AzureDevOpsProjectKey, ContentRefreshTargetConfig>;\n}\n\nexport function isContentRefreshEnabledForInstance(\n instanceEnvironment: string,\n): boolean {\n return normalizeToAzureEnvironment(instanceEnvironment) === 'staging';\n}\n\nexport async function notifyContentRefreshTargets(\n notifyConfig: ContentRefreshNotifyConfig,\n request: ContentRefreshRequest,\n projects?: AzureDevOpsProjectKey[],\n): Promise<PostContentRefreshResponse[]> {\n if (!notifyConfig.enabled || !notifyConfig.basicAuth) {\n return [];\n }\n\n const keys =\n projects ??\n (AZURE_DEVOPS_PROJECT_KEYS.filter(\n (p) => notifyConfig.targets[p]?.url,\n ) as AzureDevOpsProjectKey[]);\n\n const results: PostContentRefreshResponse[] = [];\n\n for (const project of keys) {\n const url = notifyConfig.targets[project]?.url;\n if (!url) {\n continue;\n }\n const result = await postContentRefresh(\n project,\n url,\n notifyConfig.basicAuth,\n request,\n );\n results.push(result);\n if (result.ok) {\n console.log(\n `[content-refresh] Notified ${project} (${result.status})`,\n );\n } else {\n console.error(\n `[content-refresh] Failed to notify ${project}: ${result.error ?? result.status}`,\n );\n }\n }\n\n return results;\n}\n","import dotenv from 'dotenv';\nimport type { S3Config, CMSProvider } from '../shared/types';\nimport { SharedConfig, config as sharedConfig } from '../shared/config';\nimport type { AzureDevOpsConfig, AzureDevOpsProjectKey } from './adapters/azure';\nimport {\n AZURE_DEVOPS_PROJECT_KEYS,\n normalizeToAzureEnvironment,\n pipelineRefForAzure,\n} from './adapters/azure';\nimport type { AzurePipelineEnvironment } from './adapters/azure';\nimport type { ContentRefreshNotifyConfig } from './content-refresh-notify';\nimport { isContentRefreshEnabledForInstance } from './content-refresh-notify';\nimport { validateScheduleCronExpression } from './restrictedCron';\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 /** Two-field cron: minute then hour only. Example: `30 3` or a minute step with star in the hour field. Empty when disabled. */\n cronExpression: string;\n /** When true, run once when the server starts, then on the cron wall clock. */\n runOnStart: boolean;\n syncCms?: CMSProvider;\n syncTypes?: string[];\n}\n\nexport interface ScheduledTranslationJobConfig {\n enabled: boolean;\n cronExpression: string;\n runOnStart: boolean;\n syncProjects?: string[];\n}\n\nfunction readScheduleCronEnv(): {\n scheduleCron: string;\n cmsCronRaw: string;\n translationCronRaw: string;\n} {\n return {\n scheduleCron: (process.env.SCHEDULE_CRON ?? '').trim(),\n cmsCronRaw: (process.env.SCHEDULE_CMS_CRON ?? '').trim(),\n translationCronRaw: (process.env.SCHEDULE_TRANSLATION_CRON ?? '').trim(),\n };\n}\n\nfunction resolveScheduleCron(\n jobSpecific: string,\n globalCron: string,\n jobLabel: string,\n): string | null {\n const raw = jobSpecific || globalCron;\n if (!raw) return null;\n try {\n return validateScheduleCronExpression(raw);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(`[config] Invalid ${jobLabel} schedule cron: ${msg}`);\n return null;\n }\n}\n\nfunction parseScheduledCmsJobConfig(): ScheduledCmsJobConfig {\n const { scheduleCron, cmsCronRaw } = readScheduleCronEnv();\n const runOnStart =\n (process.env.SCHEDULE_RUN_ON_START ?? 'true').toLowerCase() !== 'false';\n const syncCms = (process.env.SCHEDULE_SYNC_CMS ?? '').trim() as CMSProvider;\n const rawTypes = process.env.SCHEDULE_SYNC_CONTENT_TYPES?.trim();\n const syncTypes = rawTypes\n ? rawTypes.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined;\n\n const resolved = resolveScheduleCron(cmsCronRaw, scheduleCron, 'CMS');\n const cmsOk =\n !!syncCms && (syncCms === 'contentful' || syncCms === 'sanity');\n if (resolved !== null && !cmsOk) {\n console.warn(\n '[config] CMS schedule cron is set but SCHEDULE_SYNC_CMS is missing or not \"contentful\"|\"sanity\"; scheduled CMS sync is disabled.',\n );\n }\n const enabled = resolved !== null && cmsOk;\n\n return {\n enabled,\n cronExpression: resolved ?? '',\n runOnStart,\n syncCms: syncCms || undefined,\n syncTypes,\n };\n}\n\nfunction parseScheduledTranslationJobConfig(): ScheduledTranslationJobConfig {\n const { scheduleCron, translationCronRaw } = readScheduleCronEnv();\n const runOnStart =\n (process.env.SCHEDULE_RUN_ON_START ?? 'true').toLowerCase() !== 'false';\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 const resolved = resolveScheduleCron(\n translationCronRaw,\n scheduleCron,\n 'translation',\n );\n const enabled = resolved !== null;\n\n return {\n enabled,\n cronExpression: resolved ?? '',\n runOnStart,\n syncProjects,\n };\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 /** Channel ID or name for async notifications (e.g. sync results). */\n notifyChannel: string;\n /** Slash command name for CMS sync (default `/sync-content`). */\n cmdSyncContent: string;\n /** Slash command name for translation sync (default `/sync-translations`). */\n cmdSyncTranslations: string;\n /** Slash command to queue an Azure Pipelines run (default `/trigger-pipeline`). */\n cmdTriggerBuild: string;\n}\n\nfunction projectEnvSlug(project: AzureDevOpsProjectKey): string {\n return project.toUpperCase().replace(/-/g, '_');\n}\n\nfunction readProjectPipeline(\n project: AzureDevOpsProjectKey,\n pipelineEnvironment: AzurePipelineEnvironment,\n): { id: number; refName: string } {\n const slug = projectEnvSlug(project);\n const id = parseInt(process.env[`AZURE_${slug}_PIPELINE_ID`]?.trim() ?? '0', 10);\n return { id, refName: pipelineRefForAzure(pipelineEnvironment) };\n}\n\nfunction parseAzureDevOpsConfig(\n instanceEnvironment: string,\n): AzureDevOpsConfig & { enabled: boolean } {\n const pipelineEnvironment = normalizeToAzureEnvironment(instanceEnvironment);\n const pat = process.env.AZURE_DEVOPS_ACCESS_TOKEN?.trim() ?? '';\n\n const defaultProjectRaw = (\n process.env.AZURE_DEVOPS_DEFAULT_PROJECT ?? 'web-site'\n ).trim() as AzureDevOpsProjectKey;\n\n const defaultProject = (AZURE_DEVOPS_PROJECT_KEYS as string[]).includes(\n defaultProjectRaw,\n )\n ? defaultProjectRaw\n : 'web-site';\n\n const projects = Object.fromEntries(\n AZURE_DEVOPS_PROJECT_KEYS.map((project) => [\n project,\n { pipeline: readProjectPipeline(project, pipelineEnvironment) },\n ]),\n ) as AzureDevOpsConfig['projects'];\n\n return {\n enabled: pat.length > 0,\n organization: process.env.AZURE_DEVOPS_ORGANIZATION?.trim() || 'tripod-technology',\n pat,\n apiVersion: process.env.AZURE_DEVOPS_API_VERSION?.trim() || '7.1',\n defaultProject,\n instanceEnvironment,\n pipelineEnvironment,\n projects,\n };\n}\n\nfunction readContentRefreshUrl(project: AzureDevOpsProjectKey): string | undefined {\n const slug = projectEnvSlug(project);\n return process.env[`CONTENT_REFRESH_${slug}_URL`]?.trim() || undefined;\n}\n\nfunction parseContentRefreshConfig(\n instanceEnvironment: string,\n): ContentRefreshNotifyConfig {\n const targets = Object.fromEntries(\n AZURE_DEVOPS_PROJECT_KEYS.map((project) => [\n project,\n { url: readContentRefreshUrl(project) },\n ]),\n ) as ContentRefreshNotifyConfig['targets'];\n\n return {\n enabled: isContentRefreshEnabledForInstance(instanceEnvironment),\n basicAuth: process.env.CONTENT_REFRESH_BASIC_AUTH?.trim() ?? '',\n targets,\n };\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 azure: AzureDevOpsConfig & { enabled: boolean };\n contentRefresh: ContentRefreshNotifyConfig;\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 ?? '3030', 10),\n apiToken: process.env.CONTENT_STORE_API_TOKEN ?? '',\n },\n\n scheduledCmsJob: parseScheduledCmsJobConfig(),\n scheduledTranslationJob: parseScheduledTranslationJobConfig(),\n\n azure: parseAzureDevOpsConfig(sharedConfig.environment),\n\n contentRefresh: parseContentRefreshConfig(sharedConfig.environment),\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 notifyChannel: process.env.SLACK_NOTIFY_CHANNEL ?? '',\n cmdSyncContent: process.env.SLACK_CMD_SYNC_CONTENT ?? '/sync-content',\n cmdSyncTranslations: process.env.SLACK_CMD_SYNC_TRANSLATIONS ?? '/sync-translations',\n cmdTriggerBuild:\n process.env.SLACK_CMD_TRIGGER_BUILD ?? '/trigger-build',\n },\n};\n","/**\n * Schedule expressions: exactly **two** whitespace-separated fields: minute, then hour\n * (same meaning as the first two fields of standard cron). No day-of-month, month, or\n * day-of-week — those are not accepted.\n *\n * Supported per field: `*`, `n`, `a-b`, steps (asterisk + slash + step), and comma lists.\n * Step must be >= 1.\n */\n\nconst MAX_SEARCH_MINUTES = 366 * 24 * 60;\n\nfunction tokenize(expr: string): string[] {\n return expr\n .trim()\n .split(/\\s+/)\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\n/**\n * Validates that `expr` is exactly two cron fields (minute hour) and returns them\n * as a single normalized string `\"<minute> <hour>\"` for storage and logging.\n */\nexport function validateScheduleCronExpression(expr: string): string {\n const parts = tokenize(expr);\n if (parts.length !== 2) {\n throw new Error(\n `Schedule cron must be exactly two fields (minute hour), whitespace-separated; got ${parts.length} field(s): \"${expr}\"`,\n );\n }\n const minuteSpec = parts[0]!;\n const hourSpec = parts[1]!;\n parseField(minuteSpec, 0, 59, 'minute');\n parseField(hourSpec, 0, 23, 'hour');\n return `${minuteSpec} ${hourSpec}`;\n}\n\nfunction parseField(\n spec: string,\n lo: number,\n hi: number,\n fieldName: string,\n): (n: number) => boolean {\n const subs = spec.split(',').map((s) => s.trim()).filter(Boolean);\n if (subs.length === 0) {\n throw new Error(`Empty ${fieldName} field in cron`);\n }\n const preds = subs.map((sub) => parseSubfield(sub, lo, hi, fieldName));\n return (n: number) => preds.some((p) => p(n));\n}\n\nfunction parseSubfield(\n sub: string,\n lo: number,\n hi: number,\n fieldName: string,\n): (n: number) => boolean {\n if (sub === '*') {\n return () => true;\n }\n if (sub.startsWith('*/')) {\n const step = parseInt(sub.slice(2), 10);\n if (!Number.isFinite(step) || step < 1) {\n throw new Error(`Invalid step in ${fieldName} field: \"${sub}\"`);\n }\n return (n: number) => n >= lo && n <= hi && n % step === 0;\n }\n if (sub.includes('-')) {\n const [a, b] = sub.split('-').map((x) => parseInt(x.trim(), 10));\n if (!Number.isFinite(a) || !Number.isFinite(b)) {\n throw new Error(`Invalid range in ${fieldName} field: \"${sub}\"`);\n }\n if (a < lo || b > hi || a > b) {\n throw new Error(`Range out of bounds in ${fieldName} field: \"${sub}\"`);\n }\n return (n: number) => n >= a && n <= b;\n }\n const v = parseInt(sub, 10);\n if (!Number.isFinite(v) || v < lo || v > hi) {\n throw new Error(`Invalid value in ${fieldName} field: \"${sub}\"`);\n }\n return (n: number) => n === v;\n}\n\nfunction floorToMinuteStart(d: Date): Date {\n const t = new Date(d.getTime());\n t.setSeconds(0, 0);\n return t;\n}\n\nfunction addOneMinute(d: Date): Date {\n const t = new Date(d.getTime());\n t.setMinutes(t.getMinutes() + 1, 0, 0);\n return t;\n}\n\nfunction matchesAtMinuteStart(\n minutePred: (m: number) => boolean,\n hourPred: (h: number) => boolean,\n d: Date,\n): boolean {\n return minutePred(d.getMinutes()) && hourPred(d.getHours());\n}\n\n/**\n * Earliest minute boundary strictly after `after` that satisfies the two-field expression.\n */\nexport function nextCronFireAfter(expr: string, after: Date): Date {\n const parts = tokenize(expr);\n if (parts.length !== 2) {\n throw new Error(\n `nextCronFireAfter expects exactly two fields (minute hour); got ${parts.length}: \"${expr}\"`,\n );\n }\n const minutePred = parseField(parts[0]!, 0, 59, 'minute');\n const hourPred = parseField(parts[1]!, 0, 23, 'hour');\n\n let d = floorToMinuteStart(after);\n if (d.getTime() <= after.getTime()) {\n d = addOneMinute(d);\n }\n\n for (let i = 0; i < MAX_SEARCH_MINUTES; i++) {\n if (matchesAtMinuteStart(minutePred, hourPred, d)) {\n return d;\n }\n d = addOneMinute(d);\n }\n\n throw new Error(`No cron match within ${MAX_SEARCH_MINUTES} minutes for \"${expr}\"`);\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 \\`${resource.fileName}\\` (${locale}): ${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';\nimport { withRetry } from './retry';\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 resource: 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 function summariseCmsEntries(result: CmsSyncResult): string[] {\n return result.entries.map(\n (e) => `• \\`${e.contentType}\\` — ${e.itemCount} items`,\n );\n}\n\nexport function summariseCmsErrors(result: CmsSyncResult): string[] {\n return result.errors.map(\n (e) => `• \\`${e.contentType}\\` — ${e.error}`,\n );\n}\n\n/** Group translation sync entries into one summary line per project/resource. */\nexport function summariseTranslationEntries(entries: TranslationSyncResultEntry[]): string[] {\n const grouped = new Map<string, { locales: number; bytes: number }>();\n for (const e of entries) {\n const key = `${e.project} / ${e.resource}`;\n const existing = grouped.get(key);\n if (existing) {\n existing.locales += 1;\n existing.bytes += e.itemCount;\n } else {\n grouped.set(key, { locales: 1, bytes: e.itemCount });\n }\n }\n return [...grouped.entries()].map(\n ([key, { locales, bytes }]) => `• \\`${key}\\` / [${locales} locale${locales === 1 ? '' : 's'}] — ${bytes} bytes`,\n );\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 withRetry(\n () => fetchLingohubResourceRaw(project, resource, locale),\n config.retry,\n );\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 resource: resource.resource,\n locale,\n itemCount: byteLength,\n objectKey,\n });\n\n console.log(\n ` + ${project} - ${locale}: ${byteLength} bytes -> ${objectKey}`,\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","config","result","cfg","createClient","cfg","createClient","config","config","config"]}
|
package/dist/node.d.ts
CHANGED
|
@@ -43,7 +43,13 @@ interface ContentRefreshFetchers {
|
|
|
43
43
|
fetchCmsBundles: (options: FetchCmsBundlesOptions) => Promise<Record<string, string>>;
|
|
44
44
|
fetchTranslationBundles: (options: FetchTranslationBundlesOptions) => Promise<TranslationBundleInfo>;
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
/** Base64 of `username:password` (value only — prefix with `Basic ` in the header). */
|
|
47
|
+
declare function encodeBasicAuthCredentials(username: string, password: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Validates `Authorization: Basic <base64>` against the expected credentials
|
|
50
|
+
* (base64 of `user:pass`, same as staging site basic auth).
|
|
51
|
+
*/
|
|
52
|
+
declare function assertContentRefreshBasicAuth(authorizationHeader: string | undefined, expectedCredentialsBase64: string): void;
|
|
47
53
|
declare class ContentRefreshAuthError extends Error {
|
|
48
54
|
readonly statusCode: number;
|
|
49
55
|
constructor(message: string, statusCode: number);
|
|
@@ -62,7 +68,7 @@ interface PostContentRefreshResponse {
|
|
|
62
68
|
/**
|
|
63
69
|
* Ask a remote application (web-site, web-app, …) to refresh its local content cache.
|
|
64
70
|
*/
|
|
65
|
-
declare function postContentRefresh(target: string, url: string,
|
|
71
|
+
declare function postContentRefresh(target: string, url: string, basicAuth: string, request: ContentRefreshRequest): Promise<PostContentRefreshResponse>;
|
|
66
72
|
|
|
67
73
|
declare class ContentStoreSDK {
|
|
68
74
|
private store;
|
|
@@ -114,7 +120,8 @@ interface NextPagesContentRefreshResponse {
|
|
|
114
120
|
}
|
|
115
121
|
interface NextContentRefreshHandlerConfig {
|
|
116
122
|
sdk: ContentStoreSDK;
|
|
117
|
-
|
|
123
|
+
/** Base64 of `username:password` (same value as `CONTENT_REFRESH_BASIC_AUTH`). */
|
|
124
|
+
basicAuth: string;
|
|
118
125
|
defaults?: ContentRefreshDefaults;
|
|
119
126
|
}
|
|
120
127
|
/** @deprecated Use {@link NextContentRefreshHandlerConfig}. */
|
|
@@ -128,4 +135,4 @@ declare function createNextPagesApiContentRefreshHandler(config: NextContentRefr
|
|
|
128
135
|
*/
|
|
129
136
|
declare function handleNextAppRouterContentRefresh(request: Request, config: NextContentRefreshHandlerConfig): Promise<Response>;
|
|
130
137
|
|
|
131
|
-
export { CMSProvider, ContentRefreshAuthError, type ContentRefreshDefaults, type ContentRefreshRequest, type ContentRefreshResult, type ContentRefreshScope, ContentStoreSDK, FetchCmsBundlesOptions, FetchMergedTranslationBundlesOptions, FetchTranslationBundlesOptions, type NextContentRefreshHandlerConfig, type NextPagesContentRefreshHandlerConfig, type NextPagesContentRefreshRequest, type NextPagesContentRefreshResponse, type PostContentRefreshResponse, QueryOptions, SDKConfig, TranslationBundleInfo,
|
|
138
|
+
export { CMSProvider, ContentRefreshAuthError, type ContentRefreshDefaults, type ContentRefreshRequest, type ContentRefreshResult, type ContentRefreshScope, ContentStoreSDK, FetchCmsBundlesOptions, FetchMergedTranslationBundlesOptions, FetchTranslationBundlesOptions, type NextContentRefreshHandlerConfig, type NextPagesContentRefreshHandlerConfig, type NextPagesContentRefreshRequest, type NextPagesContentRefreshResponse, type PostContentRefreshResponse, QueryOptions, SDKConfig, TranslationBundleInfo, assertContentRefreshBasicAuth, createNextPagesApiContentRefreshHandler, encodeBasicAuthCredentials, executeContentRefresh, handleNextAppRouterContentRefresh, postContentRefresh, trimDepth };
|
package/dist/node.js
CHANGED
|
@@ -747,24 +747,27 @@ import { timingSafeEqual } from "crypto";
|
|
|
747
747
|
function resolveContentRefreshScope(request, defaults) {
|
|
748
748
|
return request.scope ?? defaults.scope ?? "cms";
|
|
749
749
|
}
|
|
750
|
-
function
|
|
751
|
-
|
|
750
|
+
function encodeBasicAuthCredentials(username, password) {
|
|
751
|
+
return Buffer.from(`${username}:${password}`, "utf8").toString("base64");
|
|
752
|
+
}
|
|
753
|
+
function assertContentRefreshBasicAuth(authorizationHeader, expectedCredentialsBase64) {
|
|
754
|
+
if (!expectedCredentialsBase64) {
|
|
752
755
|
throw new ContentRefreshAuthError(
|
|
753
|
-
"Content refresh
|
|
756
|
+
"Content refresh basic auth is not configured (set CONTENT_REFRESH_BASIC_AUTH)",
|
|
754
757
|
500
|
|
755
758
|
);
|
|
756
759
|
}
|
|
757
|
-
if (!authorizationHeader?.startsWith("
|
|
760
|
+
if (!authorizationHeader?.startsWith("Basic ")) {
|
|
758
761
|
throw new ContentRefreshAuthError(
|
|
759
|
-
"Missing or malformed Authorization header",
|
|
762
|
+
"Missing or malformed Authorization header (expected Basic)",
|
|
760
763
|
401
|
|
761
764
|
);
|
|
762
765
|
}
|
|
763
|
-
const
|
|
764
|
-
const bufA = new TextEncoder().encode(
|
|
765
|
-
const bufB = new TextEncoder().encode(
|
|
766
|
+
const provided = authorizationHeader.slice(6).trim();
|
|
767
|
+
const bufA = new TextEncoder().encode(provided);
|
|
768
|
+
const bufB = new TextEncoder().encode(expectedCredentialsBase64);
|
|
766
769
|
if (bufA.byteLength !== bufB.byteLength || !timingSafeEqual(bufA, bufB)) {
|
|
767
|
-
throw new ContentRefreshAuthError("Invalid
|
|
770
|
+
throw new ContentRefreshAuthError("Invalid basic auth credentials", 403);
|
|
768
771
|
}
|
|
769
772
|
}
|
|
770
773
|
var ContentRefreshAuthError = class extends Error {
|
|
@@ -833,12 +836,12 @@ async function executeContentRefresh(fetchers, request, defaults = {}) {
|
|
|
833
836
|
durationMs: Date.now() - started
|
|
834
837
|
};
|
|
835
838
|
}
|
|
836
|
-
async function postContentRefresh(target, url,
|
|
839
|
+
async function postContentRefresh(target, url, basicAuth, request) {
|
|
837
840
|
try {
|
|
838
841
|
const response = await fetch(url, {
|
|
839
842
|
method: "POST",
|
|
840
843
|
headers: {
|
|
841
|
-
Authorization: `
|
|
844
|
+
Authorization: `Basic ${basicAuth}`,
|
|
842
845
|
"Content-Type": "application/json",
|
|
843
846
|
Accept: "application/json"
|
|
844
847
|
},
|
|
@@ -936,9 +939,9 @@ function readAuthorizationHeader(value) {
|
|
|
936
939
|
return value;
|
|
937
940
|
}
|
|
938
941
|
async function runContentRefresh(config, authorization, body) {
|
|
939
|
-
const { sdk,
|
|
942
|
+
const { sdk, basicAuth, defaults = {} } = config;
|
|
940
943
|
try {
|
|
941
|
-
|
|
944
|
+
assertContentRefreshBasicAuth(authorization, basicAuth);
|
|
942
945
|
} catch (err) {
|
|
943
946
|
if (err instanceof ContentRefreshAuthError) {
|
|
944
947
|
return { status: err.statusCode, body: { error: err.message } };
|
|
@@ -991,8 +994,9 @@ export {
|
|
|
991
994
|
ContentRefreshAuthError,
|
|
992
995
|
ContentStore,
|
|
993
996
|
ContentStoreSDK,
|
|
994
|
-
|
|
997
|
+
assertContentRefreshBasicAuth,
|
|
995
998
|
createNextPagesApiContentRefreshHandler,
|
|
999
|
+
encodeBasicAuthCredentials,
|
|
996
1000
|
executeContentRefresh,
|
|
997
1001
|
fetchCmsBundles,
|
|
998
1002
|
fetchMergedTranslationBundles,
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/s3.ts","../src/shared/bundles.ts","../src/shared/s3-retry.ts","../src/shared/trimDepth.ts","../src/shared/lingohub.ts","../src/shared/translationResource.ts","../src/shared/utils.ts","../src/shared/content-refresh.ts","../src/sdk/client.ts","../src/sdk/next-content-refresh.ts"],"sourcesContent":["import {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n} from '@aws-sdk/client-s3';\nimport type { S3Config } from './types';\n\nexport class ContentStore {\n private client: S3Client;\n private bucket: string;\n\n constructor(cfg: S3Config) {\n this.client = new S3Client({\n region: cfg.region,\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n });\n this.bucket = cfg.bucket;\n }\n\n async upload(key: string, data: unknown): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: JSON.stringify(data, null, 2),\n ContentType: 'application/json',\n }),\n );\n return key;\n }\n\n /** Raw UTF-8 body (e.g. Lingohub file bytes as text). */\n async uploadRaw(\n key: string,\n body: string,\n contentType: string,\n ): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n return key;\n }\n\n async download(key: string): Promise<unknown> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n const body = await response.Body?.transformToString();\n if (!body) throw new Error(`Empty response for key: ${key}`);\n return JSON.parse(body);\n }\n\n /** Raw UTF-8 body from S3 (no JSON.parse). */\n async downloadRaw(key: string): Promise<string> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n const body = await response.Body?.transformToString();\n if (!body) throw new Error(`Empty response for key: ${key}`);\n return body;\n }\n}\n\n/** {cms}-{contentType}.json (always points at the latest version) */\nexport const buildCmsObjectKey = (cms: string, contentType: string): string => {\n return `${cms}-${contentType}.json`;\n}\n\nexport const buildTranslationObjectKey = (project:string, fileName: string, locale: string): string => {\n return `lingohub-${project}.${fileName.replaceAll('[locale]', locale)}`;\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CMSProvider } from './types';\nimport { buildCmsObjectKey, buildTranslationObjectKey, ContentStore } from './s3';\nimport {\n downloadRawWithRetry,\n downloadWithRetry,\n getDefaultS3RetryConfig,\n type S3RetryConfig,\n} from './s3-retry';\nimport { trimDepth } from './trimDepth';\nimport {\n allProjects,\n defaultLocales,\n type LingohubResource,\n} from './lingohub';\nimport {\n parseTranslationResourceRaw,\n toFlatStringMap,\n translationJsonOutputPath,\n} from './translationResource';\n\nexport { trimDepth } from './trimDepth';\nexport type { S3RetryConfig } from './s3-retry';\nexport { getDefaultS3RetryConfig } from './s3-retry';\n\n/**\n * Expand dotted keys into a nested object structure.\n * e.g. `{ \"General.tryAgain\": \"Try Again\" }` → `{ \"General\": { \"tryAgain\": \"Try Again\" } }`\n */\nexport function nestKeys(flat: unknown): unknown {\n if (typeof flat !== 'object' || flat === null || Array.isArray(flat)) return flat;\n const src = flat as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(src)) {\n const parts = key.split('.');\n let cur = result;\n for (let i = 0; i < parts.length - 1; i++) {\n const seg = parts[i]!;\n if (!(seg in cur) || typeof cur[seg] !== 'object' || cur[seg] === null) {\n cur[seg] = {};\n }\n cur = cur[seg] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]!] = value;\n }\n return result;\n}\n\nexport interface CmsBundleInfo {\n [key: string]: string;\n}\n\nexport interface TranslationBundleInfo {\n [key: string] : Record<string, string>;\n}\n\nexport interface BundleItem {\n [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n}\n\nexport interface FetchCmsBundlesOptions {\n cms: CMSProvider;\n contentTypes: string[];\n /** S3 GET retry; defaults via {@link getDefaultS3RetryConfig}. */\n retry?: S3RetryConfig;\n}\n\n/**\n * Config-file-friendly shape used by `fetchTranslationBundles` and the CLI.\n *\n * ```json\n * {\n * \"projects\": {\n * \"tandem\": [],\n * \"tandem-(website)\": [\"main\", \"ai\"]\n * },\n * \"locales\": [\"en\", \"de\", \"it\"]\n * }\n * ```\n *\n * `projects` maps each Lingohub project id to an array of resource keys.\n * An empty array means **all** resources for that project.\n * `locales` is optional — omit (or pass `[]`) to fetch the default locale set.\n */\nexport interface TranslationFilterConfig {\n projects: Record<string, string[]>;\n locales?: string[];\n /** Output key structure. `\"flat\"` (default) keeps dotted keys as-is; `\"nested\"` expands dots into nested objects. */\n structure?: 'flat' | 'nested';\n}\n\nexport interface FetchTranslationBundlesOptions extends TranslationFilterConfig {\n /** S3 GET retry; defaults via {@link getDefaultS3RetryConfig}. */\n retry?: S3RetryConfig;\n}\n\n/** One merged JSON file per catalog locale (`en.json`, …). Same options as {@link FetchTranslationBundlesOptions}. */\nexport type FetchMergedTranslationBundlesOptions = FetchTranslationBundlesOptions;\n\nexport interface QueryOptions {\n /**\n * Filter items by matching property values (exact equality, or array for IN).\n * Keys may use dot notation for nested paths, e.g. `{ 'meta._id': 'abc' }` matches\n * `item.meta._id`.\n *\n * Contentful-style operators on the path (before `[`):\n * - `{ 'field[exists]': true }` — field is present and non-empty (not null, undefined,\n * `''`, `[]`, or `{}`).\n * - `{ 'field[exists]': false }` — field is missing or empty (same emptiness rules).\n * Nested paths work, e.g. `{ 'blocks.hero[exists]': false }`.\n */\n fields?: Record<string, unknown>;\n /**\n * Properties to include in each result object. Omit to return all properties.\n * Keys may use dot notation; nested segments become nested objects in the result,\n * e.g. `['slug', 'meta._updatedAt']` → `{ slug, meta: { _updatedAt } }`.\n */\n select?: string[];\n /** Maximum number of items to return. */\n limit?: number;\n /**\n * How many levels deep to return.\n * - 1 = the item's own scalar properties only; nested objects/refs are nulled.\n * - 2 = the item including its direct references; refs inside those are nulled.\n * - 3 = three levels deep, and so on.\n * - Omit to return the full depth.\n */\n include?: number;\n}\n\n/** Read a value at a dotted path; returns whether every segment existed. */\nfunction getAtPath(\n obj: unknown,\n dottedPath: string,\n): { found: true; value: unknown } | { found: false } {\n const parts = dottedPath.split('.').filter((p) => p.length > 0);\n if (parts.length === 0) return { found: false };\n\n let cur: unknown = obj;\n for (const p of parts) {\n if (cur === null || typeof cur !== 'object' || Array.isArray(cur)) {\n return { found: false };\n }\n const rec = cur as Record<string, unknown>;\n if (!(p in rec)) return { found: false };\n cur = rec[p];\n }\n return { found: true, value: cur };\n}\n\nconst FIELD_OP_KEY = /^(.+)\\[([^\\]]+)]$/;\n\nfunction parseFieldKey(key: string): { path: string; operator?: string } {\n const m = FIELD_OP_KEY.exec(key);\n if (!m) return { path: key };\n return { path: m[1]!, operator: m[2]! };\n}\n\n/** True when a bundle value counts as “no content” (aligned with typical CMS “empty”). */\nfunction isEmptyValue(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (value === '') return true;\n if (Array.isArray(value) && value.length === 0) {\n return true;\n }\n return typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0;\n\n}\n\nfunction matchesFieldFilter(\n item: BundleItem,\n key: string,\n expected: unknown,\n): boolean {\n const { path, operator } = parseFieldKey(key);\n const at = getAtPath(item, path);\n\n if (operator === 'exists') {\n if (expected !== true && expected !== false) return false;\n const empty = !at.found || isEmptyValue(at.value);\n return !expected ? empty : !empty;\n }\n\n const actual = at.found ? at.value : undefined;\n if (Array.isArray(expected)) return expected.includes(actual);\n return actual === expected;\n}\n\n/** Set `value` on `target` at a dotted path, creating plain objects as needed. */\nfunction setNestedAt(\n target: Record<string, unknown>,\n dottedPath: string,\n value: unknown,\n): void {\n const parts = dottedPath.split('.').filter((p) => p.length > 0);\n if (parts.length === 0) return;\n\n if (parts.length === 1) {\n target[parts[0]!] = value;\n return;\n }\n\n const head = parts[0]!;\n const rest = parts.slice(1).join('.');\n let nested = target[head];\n if (\n nested === null ||\n typeof nested !== 'object' ||\n Array.isArray(nested)\n ) {\n nested = {};\n target[head] = nested;\n }\n setNestedAt(nested as Record<string, unknown>, rest, value);\n}\n\n/**\n * Downloads the latest CMS bundles from S3 and writes them as JSON files to `outputDir`.\n *\n * @returns A map of contentType to absolute file path.\n */\nexport async function fetchCmsBundles(\n store: ContentStore,\n outputDir: string,\n options: FetchCmsBundlesOptions,\n): Promise<Record<string, string>> {\n const { cms, contentTypes } = options;\n const retry = options.retry ?? getDefaultS3RetryConfig();\n await fs.mkdir(outputDir, { recursive: true });\n\n const result: CmsBundleInfo = {};\n\n await Promise.all(\n contentTypes.map(async (contentType) => {\n const key = buildCmsObjectKey(cms, contentType);\n const data = await downloadWithRetry(store, key, retry);\n const filePath = path.resolve(outputDir, key);\n await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');\n result[contentType] = filePath;\n }),\n );\n\n return result;\n}\n\n/**\n * Downloads the latest translation bundles from S3 and writes them as JSON files to `outputDir`.\n *\n * @returns For each project, a map of S3 object key to absolute file path on disk.\n */\n/** Filter a project's full resource list by the resource keys specified in the config. */\nfunction filterResources(\n projectResources: LingohubResource[],\n resourceFilter: string[],\n): LingohubResource[] {\n if (resourceFilter.length === 0) return projectResources;\n return projectResources.filter((r) => resourceFilter.includes(r.resource));\n}\n\nexport async function fetchTranslationBundles(\n store: ContentStore,\n outputDir: string,\n options: FetchTranslationBundlesOptions,\n): Promise<TranslationBundleInfo> {\n const { projects, locales, structure } = options;\n const nested = structure === 'nested';\n const retry = options.retry ?? getDefaultS3RetryConfig();\n await fs.mkdir(outputDir, { recursive: true });\n\n const result: TranslationBundleInfo = {};\n const localesToSync = locales && locales.length ? locales : defaultLocales;\n\n await Promise.all(\n Object.entries(projects).map(async ([project, resourceFilter]) => {\n const allResources = allProjects[project];\n if (!allResources?.length) return;\n\n const resources = filterResources(allResources, resourceFilter);\n if (!resources.length) return;\n\n const resourceTasks: { objectKey: string; resource: LingohubResource }[] =\n [];\n for (const resource of resources) {\n for (const loc of localesToSync) {\n const locale =\n resource.localeMapping && resource.localeMapping[loc]\n ? resource.localeMapping[loc]\n : loc;\n const objectKey = buildTranslationObjectKey(\n project,\n resource.fileName,\n locale,\n );\n resourceTasks.push({ objectKey, resource });\n }\n }\n\n await Promise.all(\n resourceTasks.map(async ({ objectKey, resource }) => {\n const raw = await downloadRawWithRetry(store, objectKey, retry);\n const parsed = parseTranslationResourceRaw(raw, resource);\n const output = nested ? nestKeys(parsed) : parsed;\n const filePath = translationJsonOutputPath(outputDir, objectKey);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(\n filePath,\n JSON.stringify(output, null, 2),\n 'utf-8',\n );\n if (!result[project]) {\n result[project] = {};\n }\n result[project][objectKey] = filePath;\n }),\n );\n }),\n );\n\n return result;\n}\n\n/**\n * Downloads translation bundles from S3, parses each raw Lingohub file, and merges all\n * key/value pairs per **catalog** locale into a single JSON file (e.g. `en.json`).\n * Duplicate keys across resources or projects: **last write wins** (iteration order:\n * projects → resources → locales).\n *\n * @returns Map of locale code to absolute path of the merged `{locale}.json` file.\n */\nexport async function fetchMergedTranslationBundles(\n store: ContentStore,\n outputDir: string,\n options: FetchMergedTranslationBundlesOptions,\n): Promise<Record<string, string>> {\n const { projects, locales, structure } = options;\n const nested = structure === 'nested';\n const retry = options.retry ?? getDefaultS3RetryConfig();\n await fs.mkdir(outputDir, { recursive: true });\n\n const localesToSync = locales && locales.length ? locales : defaultLocales;\n\n type Task = {\n catalogLocale: string;\n objectKey: string;\n resource: LingohubResource;\n };\n const tasks: Task[] = [];\n\n for (const [project, resourceFilter] of Object.entries(projects)) {\n const allResources = allProjects[project];\n if (!allResources?.length) continue;\n\n const resources = filterResources(allResources, resourceFilter);\n if (!resources.length) continue;\n\n for (const resource of resources) {\n for (const loc of localesToSync) {\n const mappedLocale =\n resource.localeMapping && resource.localeMapping[loc]\n ? resource.localeMapping[loc]\n : loc;\n const objectKey = buildTranslationObjectKey(\n project,\n resource.fileName,\n mappedLocale,\n );\n tasks.push({ catalogLocale: loc, objectKey, resource });\n }\n }\n }\n\n const results = await Promise.all(\n tasks.map(async ({ catalogLocale, objectKey, resource }) => {\n const raw = await downloadRawWithRetry(store, objectKey, retry);\n const parsed = parseTranslationResourceRaw(raw, resource);\n const stringMap = toFlatStringMap(parsed);\n return { catalogLocale, stringMap };\n }),\n );\n\n const merged: Record<string, Record<string, string>> = {};\n for (const loc of localesToSync) {\n merged[loc] = {};\n }\n for (const { catalogLocale, stringMap } of results) {\n Object.assign(merged[catalogLocale]!, stringMap);\n }\n\n const out: Record<string, string> = {};\n for (const loc of localesToSync) {\n const filePath = path.resolve(outputDir, `${loc}.json`);\n const output = nested ? nestKeys(merged[loc]) : merged[loc];\n await fs.writeFile(\n filePath,\n JSON.stringify(output, null, 2),\n 'utf-8',\n );\n out[loc] = filePath;\n }\n\n return out;\n}\n\n/**\n * Queries a previously fetched bundle from the local filesystem.\n */\nexport async function queryCmsBundle(\n outputDir: string,\n cms: CMSProvider,\n contentType: string,\n options: QueryOptions = {},\n): Promise<unknown[]> {\n const filePath = path.resolve(\n outputDir,\n `${cms}-${contentType}.json`,\n );\n const raw = await fs.readFile(filePath, 'utf-8');\n let items:BundleItem[] = JSON.parse(raw) as Record<string, unknown>[];\n\n if (options.fields) {\n const filters = options.fields;\n items = items.filter((item) =>\n Object.entries(filters).every(([key, expected]) =>\n matchesFieldFilter(item, key, expected),\n ),\n );\n }\n\n if (options.limit !== undefined && options.limit > 0) {\n items = items.slice(0, options.limit);\n }\n\n if (options.include !== undefined) {\n items = items.map(\n (item) => trimDepth(item, options.include!) as Record<string, unknown>,\n );\n }\n\n if (options.select?.length) {\n const keys = options.select;\n items = items.map((item) => {\n const picked: Record<string, unknown> = {};\n for (const k of keys) {\n const at = getAtPath(item, k);\n if (at.found) setNestedAt(picked, k, at.value);\n }\n return picked;\n });\n }\n\n return items;\n}\n","import type { ContentStore } from './s3';\n\n/** Retry/backoff for S3 reads (aligned with CMS `RetryConfig` shape). */\nexport interface S3RetryConfig {\n maxRetries: number;\n baseDelayMs: number;\n maxDelayMs: number;\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 * Default S3 download retry policy from env.\n * `S3_RETRY_*` overrides `RETRY_*` when set.\n */\nexport function getDefaultS3RetryConfig(): S3RetryConfig {\n return {\n maxRetries: parseInt(\n process.env.S3_RETRY_MAX_RETRIES ??\n process.env.RETRY_MAX_RETRIES ??\n '5',\n 10,\n ),\n baseDelayMs: parseInt(\n process.env.S3_RETRY_BASE_DELAY_MS ??\n process.env.RETRY_BASE_DELAY_MS ??\n '1000',\n 10,\n ),\n maxDelayMs: parseInt(\n process.env.S3_RETRY_MAX_DELAY_MS ??\n process.env.RETRY_MAX_DELAY_MS ??\n '60000',\n 10,\n ),\n };\n}\n\n/**\n * True when a failed S3 GET may succeed after a short wait (503 Slow Down,\n * transient network, throttling). Never true for definitive client errors (404, etc.).\n */\nexport function isRetryableS3DownloadError(err: unknown): boolean {\n if (err === null || err === undefined) return false;\n\n if (typeof err === 'object') {\n const e = err as {\n name?: string;\n Code?: string;\n $metadata?: { httpStatusCode?: number };\n };\n const status = e.$metadata?.httpStatusCode;\n if (status === 404 || status === 403 || status === 401 || status === 400) {\n return false;\n }\n if (\n status !== undefined &&\n (status === 408 ||\n status === 429 ||\n status === 500 ||\n status === 502 ||\n status === 503 ||\n status === 504)\n ) {\n return true;\n }\n\n const name = e.name ?? '';\n const code = e.Code ?? '';\n if (\n /SlowDown|Throttl|Timeout|TooManyRequests|ServiceUnavailable|InternalError/i.test(\n name,\n ) ||\n /SlowDown|Throttl/i.test(code)\n ) {\n return true;\n }\n }\n\n if (err instanceof Error) {\n const m = err.message;\n if (\n /ECONNRESET|ETIMEDOUT|EPIPE|ECONNREFUSED|socket hang up|getaddrinfo/i.test(\n m,\n )\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Runs `fn` with retries when `isRetryableS3DownloadError` applies; otherwise throws immediately.\n */\nexport async function withS3Retry<T>(\n fn: () => Promise<T>,\n { maxRetries, baseDelayMs, maxDelayMs }: S3RetryConfig,\n): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (!isRetryableS3DownloadError(err)) {\n throw err;\n }\n if (attempt === maxRetries) {\n throw err;\n }\n\n const delay = computeDelay(attempt, baseDelayMs, maxDelayMs);\n console.warn(\n ` S3 request failed (attempt ${attempt + 1}/${maxRetries + 1}): ${\n err instanceof Error ? err.message : String(err)\n }. Retrying in ${Math.round(delay)}ms…`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error('withS3Retry: unreachable');\n}\n\nexport async function downloadWithRetry(\n store: ContentStore,\n key: string,\n cfg: S3RetryConfig,\n): Promise<unknown> {\n return withS3Retry(() => store.download(key), cfg);\n}\n\nexport async function downloadRawWithRetry(\n store: ContentStore,\n key: string,\n cfg: S3RetryConfig,\n): Promise<string> {\n return withS3Retry(() => store.downloadRaw(key), cfg);\n}\n","/**\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","export const defaultLocales = ['en', 'fr', 'de', 'es', 'it', 'pt-br', 'ru', 'zh-hans', 'zh-hant', 'ko', 'ja'];\n\nconst localeMapping = {\n ios: {\n 'pt-br': 'pt',\n 'zh-hans': 'zh-Hans',\n 'zh-hant': 'zh-Hant',\n },\n android: {\n 'pt-br': 'pt',\n 'zh-hans': 'zh-Hans-CN',\n 'zh-hant': 'zh-TW',\n },\n};\n\nexport type LingohubResource = {\n resource: string,\n fileName: string;\n type: 'json'|'strings'|'xml';\n localeMapping?: Record<string, string>;\n}\n\nexport const allProjects: Record<string, LingohubResource[]> = {\n 'tandem-(new-website)': [\n {\n resource: 'main',\n fileName: 'Website.[locale].json',\n type: 'json'\n }\n ],\n 'tandem-(website)': [\n {\n resource: 'main',\n fileName: '[locale].json',\n type: 'json'\n },\n {\n resource: 'ai',\n fileName: 'AI.[locale].json',\n type: 'json'\n },\n {\n resource: 'languages',\n fileName: 'languages.[locale].json',\n type: 'json'\n }\n ],\n 'tandem': [\n {\n resource: 'infoplist',\n fileName: 'InfoPlist.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'localizable',\n fileName: 'Localizable.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'ipad',\n fileName: 'Main_iPad.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'main',\n fileName: 'Main.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n }\n ],\n 'tandem-(android)': [\n {\n resource: 'accessibility',\n fileName: 'accessibility_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'call',\n fileName: 'call_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'cert',\n fileName: 'cert_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'chat',\n fileName: 'chat_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'checklist',\n fileName: 'checklist_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'clubs',\n fileName: 'clubs_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'common',\n fileName: 'common_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'community',\n fileName: 'community_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'correction',\n fileName: 'correction_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'country_names',\n fileName: 'country_names.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'emoji',\n fileName: 'emoji_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'errors',\n fileName: 'errors.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'expressions',\n fileName: 'expressions_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'gif',\n fileName: 'gif_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'guidelines',\n fileName: 'guidelines_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'lanuguages',\n fileName: 'languages_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'localizable',\n fileName: 'localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'localizable2',\n fileName: 'localizable2.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'login',\n fileName: 'login_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'myprofile',\n fileName: 'myprofile_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'onb',\n fileName: 'onb_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'parties',\n fileName: 'parties_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'pro',\n fileName: 'pro_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'pro_screen',\n fileName: 'pro_screen_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'push_notification',\n fileName: 'push_notification_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'reporting',\n fileName: 'reporting_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'translation',\n fileName: 'translation_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n }\n\n ],\n 'tandem-(web-invites)': [\n {\n resource: 'main',\n fileName: '[locale].json',\n type: 'json'\n }\n ]\n};\n","import path from 'node:path';\nimport { convertXMLToJS, parseIOSStrings, transformObjectToFlat } from './utils';\nimport type { LingohubResource } from './lingohub';\n\n/** Content-Type for raw Lingohub bodies stored in S3. */\nexport function contentTypeForTranslationKey(objectKey: string): string {\n if (objectKey.endsWith('.json')) return 'application/json; charset=utf-8';\n if (objectKey.endsWith('.xml')) return 'application/xml; charset=utf-8';\n if (objectKey.endsWith('.strings')) return 'text/plain; charset=utf-8';\n return 'application/octet-stream';\n}\n\n/**\n * Parses a raw Lingohub file body from S3 into structured data.\n */\nexport function parseTranslationResourceRaw(\n raw: string,\n resource: LingohubResource,\n): unknown {\n if (resource.type === 'json') {\n return JSON.parse(raw) as unknown;\n }\n\n if (resource.type === 'strings') {\n return parseIOSStrings(raw);\n }\n\n if (resource.type === 'xml') {\n return convertXMLToJS(raw);\n }\n\n throw new Error(`Unsupported resource type: ${resource.type}`);\n}\n\n/**\n * Normalizes parsed translation data to a flat string map for merging (duplicate keys: last wins).\n */\nexport function toFlatStringMap(parsed: unknown): Record<string, string> {\n if (parsed === null || parsed === undefined) return {};\n if (typeof parsed === 'string') return { value: parsed };\n if (typeof parsed !== 'object') return { value: String(parsed) };\n if (Array.isArray(parsed)) {\n const out: Record<string, string> = {};\n parsed.forEach((v, i) => {\n out[String(i)] =\n typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v);\n });\n return out;\n }\n\n const flat = transformObjectToFlat(parsed as Record<string, unknown>);\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(flat)) {\n if (v === null || v === undefined) {\n out[k] = '';\n } else if (typeof v === 'object') {\n out[k] = JSON.stringify(v);\n } else {\n out[k] = String(v);\n }\n }\n return out;\n}\n\n/** Where to write normalized JSON for one translation object key (avoids `.json.json`). */\nexport function translationJsonOutputPath(\n outputDir: string,\n objectKey: string,\n): string {\n if (objectKey.endsWith('.json')) {\n return path.resolve(outputDir, objectKey);\n }\n return path.resolve(outputDir, `${objectKey}.json`);\n}\n","import convert from 'xml-js';\nimport set from 'lodash.set';\nimport merge from 'lodash.merge';\n\nexport const transformObjectToNested = (data: Record<string, unknown>): Record<string, unknown> => {\n const result: Record<string, unknown> = {};\n\n Object.entries(data).forEach(([key, value]) => {\n const tempObject = {};\n set(tempObject, key, value);\n merge(result, tempObject);\n });\n\n return result;\n};\n\nexport const transformObjectToFlat = (data: Record<string, any>): Record<string, any> => { // eslint-disable-line @typescript-eslint/no-explicit-any\n const result: Record<string, unknown> = {};\n\n const flatten = (obj: Record<string, any>, path: string[] = []) => { // eslint-disable-line @typescript-eslint/no-explicit-any\n Object.entries(obj).forEach(([key, value]) => {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n flatten(value, path.concat(key));\n } else {\n result[path.concat(key).join('.')] = value;\n }\n });\n };\n\n flatten(data);\n\n return result;\n}\n\nexport const convertXMLToJS = (xml: string): Record<string, string> => {\n const converted = convert.xml2js(xml, {\n ignoreComment: true,\n ignoreDeclaration: true,\n ignoreInstruction: true,\n compact: true,\n }) as {\n resources: {\n string: {\n _attributes: { name: string },\n _text: 'User does not exist'\n }[];\n }\n };\n\n let mapped = {};\n const strings = converted.resources.string;\n const items = Array.isArray(strings) ? strings : [strings];\n items.forEach((item) => {\n if (!item?._attributes?.name) return;\n mapped = {\n ...mapped,\n [item._attributes.name]: item._text,\n };\n });\n\n return mapped;\n};\n\nexport const parseIOSStrings = (strings: string): Record<string, string> => {\n const parsedObj: Record<string, string> = {};\n strings\n .split('\\n')\n .filter((line) => line.startsWith('\"') && line.endsWith(';'))\n .map((line) => line.trim().slice(0, -1))\n .forEach((line) => {\n const eqIdx = line.indexOf(' = ');\n if (eqIdx === -1) return;\n const key = line.slice(1, eqIdx - 1);\n const value = line.slice(eqIdx + 3 + 1, -1);\n if (!key) return;\n parsedObj[key] = value;\n });\n\n return parsedObj;\n};","import { timingSafeEqual } from 'node:crypto';\nimport type { CMSProvider } from './types';\nimport type {\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n TranslationBundleInfo,\n} from './bundles';\n\nexport type ContentRefreshScope = 'cms' | 'translations' | 'all';\n\nexport interface ContentRefreshRequest {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n content_types?: string[];\n projects?: string[];\n locales?: string[];\n}\n\n/** Defaults applied when the request omits fields (set by the host application). */\nexport interface ContentRefreshDefaults {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n contentTypes?: string[];\n translationProjects?: string[];\n locales?: string[];\n}\n\nexport interface ContentRefreshError {\n step: string;\n message: string;\n}\n\nexport interface ContentRefreshResult {\n ok: boolean;\n scope: ContentRefreshScope;\n cmsFiles?: Record<string, string>;\n translationFiles?: TranslationBundleInfo;\n errors: ContentRefreshError[];\n durationMs: number;\n}\n\nexport interface ContentRefreshFetchers {\n fetchCmsBundles: (\n options: FetchCmsBundlesOptions,\n ) => Promise<Record<string, string>>;\n fetchTranslationBundles: (\n options: FetchTranslationBundlesOptions,\n ) => Promise<TranslationBundleInfo>;\n}\n\nexport function resolveContentRefreshScope(\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults,\n): ContentRefreshScope {\n return request.scope ?? defaults.scope ?? 'cms';\n}\n\nexport function assertContentRefreshBearer(\n authorizationHeader: string | undefined,\n expectedToken: string,\n): void {\n if (!expectedToken) {\n throw new ContentRefreshAuthError(\n 'Content refresh API token is not configured on the server',\n 500,\n );\n }\n if (!authorizationHeader?.startsWith('Bearer ')) {\n throw new ContentRefreshAuthError(\n 'Missing or malformed Authorization header',\n 401,\n );\n }\n const token = authorizationHeader.slice(7);\n const bufA = new TextEncoder().encode(token);\n const bufB = new TextEncoder().encode(expectedToken);\n if (bufA.byteLength !== bufB.byteLength || !timingSafeEqual(bufA, bufB)) {\n throw new ContentRefreshAuthError('Invalid bearer token', 403);\n }\n}\n\nexport class ContentRefreshAuthError extends Error {\n constructor(\n message: string,\n readonly statusCode: number,\n ) {\n super(message);\n this.name = 'ContentRefreshAuthError';\n }\n}\n\n/**\n * Pull the latest bundles from S3 into the host app's configured output directory.\n */\nexport async function executeContentRefresh(\n fetchers: ContentRefreshFetchers,\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults = {},\n): Promise<ContentRefreshResult> {\n const started = Date.now();\n const scope = resolveContentRefreshScope(request, defaults);\n const errors: ContentRefreshError[] = [];\n let cmsFiles: Record<string, string> | undefined;\n let translationFiles: TranslationBundleInfo | undefined;\n\n if (scope === 'cms' || scope === 'all') {\n const cms = request.cms ?? defaults.cms ?? 'contentful';\n const contentTypes = request.content_types ?? defaults.contentTypes;\n if (!contentTypes?.length) {\n errors.push({\n step: 'cms',\n message: 'content_types (or handler defaults.contentTypes) is required for CMS refresh',\n });\n } else {\n try {\n cmsFiles = await fetchers.fetchCmsBundles({ cms, contentTypes });\n } catch (err) {\n errors.push({\n step: 'cms',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n if (scope === 'translations' || scope === 'all') {\n const projects = request.projects ?? defaults.translationProjects;\n if (!projects?.length) {\n errors.push({\n step: 'translations',\n message:\n 'projects (or handler defaults.translationProjects) is required for translation refresh',\n });\n } else {\n const locales = request.locales ?? defaults.locales;\n try {\n const projectMap = Object.fromEntries(\n projects.map((p) => [p, [] as string[]]),\n );\n translationFiles = await fetchers.fetchTranslationBundles({\n projects: projectMap,\n locales,\n });\n } catch (err) {\n errors.push({\n step: 'translations',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return {\n ok: errors.length === 0,\n scope,\n cmsFiles,\n translationFiles,\n errors,\n durationMs: Date.now() - started,\n };\n}\n\nexport interface PostContentRefreshResponse {\n target: string;\n ok: boolean;\n status: number;\n body?: unknown;\n error?: string;\n}\n\n/**\n * Ask a remote application (web-site, web-app, …) to refresh its local content cache.\n */\nexport async function postContentRefresh(\n target: string,\n url: string,\n apiToken: string,\n request: ContentRefreshRequest,\n): Promise<PostContentRefreshResponse> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(request),\n });\n\n const text = await response.text();\n let body: unknown;\n if (text) {\n try {\n body = JSON.parse(text) as unknown;\n } catch {\n body = text;\n }\n }\n\n return {\n target,\n ok: response.ok,\n status: response.status,\n body,\n error: response.ok\n ? undefined\n : typeof body === 'object' && body !== null && 'error' in body\n ? String((body as { error: unknown }).error)\n : `HTTP ${response.status}`,\n };\n } catch (err) {\n return {\n target,\n ok: false,\n status: 0,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { CMSProvider } from '../shared/types';\nimport { ContentStore } from '../shared/s3';\nimport {\n fetchCmsBundles,\n fetchMergedTranslationBundles,\n fetchTranslationBundles,\n queryCmsBundle,\n type FetchCmsBundlesOptions,\n type FetchMergedTranslationBundlesOptions,\n type FetchTranslationBundlesOptions,\n type QueryOptions,\n type TranslationBundleInfo,\n} from '../shared/bundles';\nimport {\n executeContentRefresh,\n type ContentRefreshDefaults,\n type ContentRefreshRequest,\n type ContentRefreshResult,\n} from '../shared/content-refresh';\nimport type { SDKConfig } from './client-types';\n\nexport type {\n ContentRefreshDefaults,\n ContentRefreshRequest,\n ContentRefreshResult,\n ContentRefreshScope,\n} from '../shared/content-refresh';\nexport type {\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n FetchMergedTranslationBundlesOptions,\n TranslationFilterConfig,\n QueryOptions,\n CmsBundleInfo,\n TranslationBundleInfo,\n BundleItem,\n SDKConfig,\n S3RetryConfig,\n} from './client-types';\nexport { getDefaultS3RetryConfig } from './client-types';\n\nexport class ContentStoreSDK {\n private store: ContentStore;\n private outputDir: string;\n\n constructor(config: SDKConfig) {\n this.store = new ContentStore(config.s3);\n this.outputDir = config.outputDir;\n }\n\n /**\n * Downloads the latest bundles from S3 and writes them as JSON files\n * to `outputDir`.\n *\n * @returns A map of contentType to absolute file path.\n */\n async fetchCmsBundles(\n options: FetchCmsBundlesOptions,\n ): Promise<Record<string, string>> {\n return fetchCmsBundles(this.store, this.outputDir, options);\n }\n\n /**\n * Downloads translation bundles from S3 and writes them as JSON files\n * to `outputDir`.\n *\n * @returns Per project, a map of S3 object key to absolute file path.\n */\n async fetchTranslationBundles(\n options: FetchTranslationBundlesOptions,\n ): Promise<TranslationBundleInfo> {\n return fetchTranslationBundles(this.store, this.outputDir, options);\n }\n\n /**\n * Downloads all translation resources for the given projects/locales, parses them,\n * and writes one merged `{locale}.json` per locale (string key/value map; duplicate keys:\n * last wins).\n */\n async fetchMergedTranslationBundles(\n options: FetchMergedTranslationBundlesOptions,\n ): Promise<Record<string, string>> {\n return fetchMergedTranslationBundles(this.store, this.outputDir, options);\n }\n\n /**\n * Queries a previously fetched bundle from the local filesystem.\n */\n async queryCmsBundle(\n cms: CMSProvider,\n contentType: string,\n options: QueryOptions = {},\n ): Promise<unknown[]> {\n return queryCmsBundle(this.outputDir, cms, contentType, options);\n }\n\n /**\n * Download the latest bundles from S3 (same as the hosted refresh HTTP endpoint).\n */\n async refreshContent(\n request: ContentRefreshRequest = {},\n defaults: ContentRefreshDefaults = {},\n ): Promise<ContentRefreshResult> {\n return executeContentRefresh(\n {\n fetchCmsBundles: (opts) => this.fetchCmsBundles(opts),\n fetchTranslationBundles: (opts) => this.fetchTranslationBundles(opts),\n },\n request,\n defaults,\n );\n }\n}\n","import type { ContentStoreSDK } from './client';\nimport {\n assertContentRefreshBearer,\n ContentRefreshAuthError,\n executeContentRefresh,\n type ContentRefreshDefaults,\n type ContentRefreshRequest,\n type ContentRefreshResult,\n} from '../shared/content-refresh';\n\n/** Minimal request shape for Next.js Pages API routes (`pages/api/*`). */\nexport interface NextPagesContentRefreshRequest {\n method?: string;\n headers: { authorization?: string | string[] };\n body?: ContentRefreshRequest;\n}\n\n/** Minimal response shape for Next.js Pages API routes. */\nexport interface NextPagesContentRefreshResponse {\n status(code: number): { json(body: unknown): void };\n}\n\nexport interface NextContentRefreshHandlerConfig {\n sdk: ContentStoreSDK;\n apiToken: string;\n defaults?: ContentRefreshDefaults;\n}\n\n/** @deprecated Use {@link NextContentRefreshHandlerConfig}. */\nexport type NextPagesContentRefreshHandlerConfig = NextContentRefreshHandlerConfig;\n\nfunction readAuthorizationHeader(\n value: string | string[] | null | undefined,\n): string | undefined {\n if (value == null) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value[0];\n }\n return value;\n}\n\nasync function runContentRefresh(\n config: NextContentRefreshHandlerConfig,\n authorization: string | undefined,\n body: ContentRefreshRequest,\n): Promise<{ status: number; body: ContentRefreshResult | { error: string } }> {\n const { sdk, apiToken, defaults = {} } = config;\n\n try {\n assertContentRefreshBearer(authorization, apiToken);\n } catch (err) {\n if (err instanceof ContentRefreshAuthError) {\n return { status: err.statusCode, body: { error: err.message } };\n }\n throw err;\n }\n\n const result = await executeContentRefresh(\n {\n fetchCmsBundles: (opts) => sdk.fetchCmsBundles(opts),\n fetchTranslationBundles: (opts) => sdk.fetchTranslationBundles(opts),\n },\n body,\n defaults,\n );\n\n const status = result.ok ? 200 : result.errors.length > 0 ? 207 : 500;\n return { status, body: result };\n}\n\n/**\n * Handler for a Next.js Pages API route (e.g. pages/api/internal/content-refresh.ts).\n */\nexport function createNextPagesApiContentRefreshHandler(\n config: NextContentRefreshHandlerConfig,\n): (\n req: NextPagesContentRefreshRequest,\n res: NextPagesContentRefreshResponse,\n) => Promise<void> {\n return async (req, res): Promise<void> => {\n if (req.method !== 'POST') {\n res.status(405).json({ error: 'Method not allowed' });\n return;\n }\n\n const { status, body } = await runContentRefresh(\n config,\n readAuthorizationHeader(req.headers.authorization),\n req.body ?? {},\n );\n res.status(status).json(body);\n };\n}\n\n/**\n * Handler for a Next.js App Router route (e.g. app/api/internal/content-refresh/route.ts).\n */\nexport async function handleNextAppRouterContentRefresh(\n request: Request,\n config: NextContentRefreshHandlerConfig,\n): Promise<Response> {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 });\n }\n\n let body: ContentRefreshRequest = {};\n try {\n body = (await request.json()) as ContentRefreshRequest;\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 });\n }\n\n const { status, body: payload } = await runContentRefresh(\n config,\n request.headers.get('authorization') ?? undefined,\n body,\n );\n\n return Response.json(payload, { status });\n}\n"],"mappings":"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","names":["path","path","out","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/s3.ts","../src/shared/bundles.ts","../src/shared/s3-retry.ts","../src/shared/trimDepth.ts","../src/shared/lingohub.ts","../src/shared/translationResource.ts","../src/shared/utils.ts","../src/shared/content-refresh.ts","../src/sdk/client.ts","../src/sdk/next-content-refresh.ts"],"sourcesContent":["import {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n} from '@aws-sdk/client-s3';\nimport type { S3Config } from './types';\n\nexport class ContentStore {\n private client: S3Client;\n private bucket: string;\n\n constructor(cfg: S3Config) {\n this.client = new S3Client({\n region: cfg.region,\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n });\n this.bucket = cfg.bucket;\n }\n\n async upload(key: string, data: unknown): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: JSON.stringify(data, null, 2),\n ContentType: 'application/json',\n }),\n );\n return key;\n }\n\n /** Raw UTF-8 body (e.g. Lingohub file bytes as text). */\n async uploadRaw(\n key: string,\n body: string,\n contentType: string,\n ): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n return key;\n }\n\n async download(key: string): Promise<unknown> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n const body = await response.Body?.transformToString();\n if (!body) throw new Error(`Empty response for key: ${key}`);\n return JSON.parse(body);\n }\n\n /** Raw UTF-8 body from S3 (no JSON.parse). */\n async downloadRaw(key: string): Promise<string> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n const body = await response.Body?.transformToString();\n if (!body) throw new Error(`Empty response for key: ${key}`);\n return body;\n }\n}\n\n/** {cms}-{contentType}.json (always points at the latest version) */\nexport const buildCmsObjectKey = (cms: string, contentType: string): string => {\n return `${cms}-${contentType}.json`;\n}\n\nexport const buildTranslationObjectKey = (project:string, fileName: string, locale: string): string => {\n return `lingohub-${project}.${fileName.replaceAll('[locale]', locale)}`;\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { CMSProvider } from './types';\nimport { buildCmsObjectKey, buildTranslationObjectKey, ContentStore } from './s3';\nimport {\n downloadRawWithRetry,\n downloadWithRetry,\n getDefaultS3RetryConfig,\n type S3RetryConfig,\n} from './s3-retry';\nimport { trimDepth } from './trimDepth';\nimport {\n allProjects,\n defaultLocales,\n type LingohubResource,\n} from './lingohub';\nimport {\n parseTranslationResourceRaw,\n toFlatStringMap,\n translationJsonOutputPath,\n} from './translationResource';\n\nexport { trimDepth } from './trimDepth';\nexport type { S3RetryConfig } from './s3-retry';\nexport { getDefaultS3RetryConfig } from './s3-retry';\n\n/**\n * Expand dotted keys into a nested object structure.\n * e.g. `{ \"General.tryAgain\": \"Try Again\" }` → `{ \"General\": { \"tryAgain\": \"Try Again\" } }`\n */\nexport function nestKeys(flat: unknown): unknown {\n if (typeof flat !== 'object' || flat === null || Array.isArray(flat)) return flat;\n const src = flat as Record<string, unknown>;\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(src)) {\n const parts = key.split('.');\n let cur = result;\n for (let i = 0; i < parts.length - 1; i++) {\n const seg = parts[i]!;\n if (!(seg in cur) || typeof cur[seg] !== 'object' || cur[seg] === null) {\n cur[seg] = {};\n }\n cur = cur[seg] as Record<string, unknown>;\n }\n cur[parts[parts.length - 1]!] = value;\n }\n return result;\n}\n\nexport interface CmsBundleInfo {\n [key: string]: string;\n}\n\nexport interface TranslationBundleInfo {\n [key: string] : Record<string, string>;\n}\n\nexport interface BundleItem {\n [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any\n}\n\nexport interface FetchCmsBundlesOptions {\n cms: CMSProvider;\n contentTypes: string[];\n /** S3 GET retry; defaults via {@link getDefaultS3RetryConfig}. */\n retry?: S3RetryConfig;\n}\n\n/**\n * Config-file-friendly shape used by `fetchTranslationBundles` and the CLI.\n *\n * ```json\n * {\n * \"projects\": {\n * \"tandem\": [],\n * \"tandem-(website)\": [\"main\", \"ai\"]\n * },\n * \"locales\": [\"en\", \"de\", \"it\"]\n * }\n * ```\n *\n * `projects` maps each Lingohub project id to an array of resource keys.\n * An empty array means **all** resources for that project.\n * `locales` is optional — omit (or pass `[]`) to fetch the default locale set.\n */\nexport interface TranslationFilterConfig {\n projects: Record<string, string[]>;\n locales?: string[];\n /** Output key structure. `\"flat\"` (default) keeps dotted keys as-is; `\"nested\"` expands dots into nested objects. */\n structure?: 'flat' | 'nested';\n}\n\nexport interface FetchTranslationBundlesOptions extends TranslationFilterConfig {\n /** S3 GET retry; defaults via {@link getDefaultS3RetryConfig}. */\n retry?: S3RetryConfig;\n}\n\n/** One merged JSON file per catalog locale (`en.json`, …). Same options as {@link FetchTranslationBundlesOptions}. */\nexport type FetchMergedTranslationBundlesOptions = FetchTranslationBundlesOptions;\n\nexport interface QueryOptions {\n /**\n * Filter items by matching property values (exact equality, or array for IN).\n * Keys may use dot notation for nested paths, e.g. `{ 'meta._id': 'abc' }` matches\n * `item.meta._id`.\n *\n * Contentful-style operators on the path (before `[`):\n * - `{ 'field[exists]': true }` — field is present and non-empty (not null, undefined,\n * `''`, `[]`, or `{}`).\n * - `{ 'field[exists]': false }` — field is missing or empty (same emptiness rules).\n * Nested paths work, e.g. `{ 'blocks.hero[exists]': false }`.\n */\n fields?: Record<string, unknown>;\n /**\n * Properties to include in each result object. Omit to return all properties.\n * Keys may use dot notation; nested segments become nested objects in the result,\n * e.g. `['slug', 'meta._updatedAt']` → `{ slug, meta: { _updatedAt } }`.\n */\n select?: string[];\n /** Maximum number of items to return. */\n limit?: number;\n /**\n * How many levels deep to return.\n * - 1 = the item's own scalar properties only; nested objects/refs are nulled.\n * - 2 = the item including its direct references; refs inside those are nulled.\n * - 3 = three levels deep, and so on.\n * - Omit to return the full depth.\n */\n include?: number;\n}\n\n/** Read a value at a dotted path; returns whether every segment existed. */\nfunction getAtPath(\n obj: unknown,\n dottedPath: string,\n): { found: true; value: unknown } | { found: false } {\n const parts = dottedPath.split('.').filter((p) => p.length > 0);\n if (parts.length === 0) return { found: false };\n\n let cur: unknown = obj;\n for (const p of parts) {\n if (cur === null || typeof cur !== 'object' || Array.isArray(cur)) {\n return { found: false };\n }\n const rec = cur as Record<string, unknown>;\n if (!(p in rec)) return { found: false };\n cur = rec[p];\n }\n return { found: true, value: cur };\n}\n\nconst FIELD_OP_KEY = /^(.+)\\[([^\\]]+)]$/;\n\nfunction parseFieldKey(key: string): { path: string; operator?: string } {\n const m = FIELD_OP_KEY.exec(key);\n if (!m) return { path: key };\n return { path: m[1]!, operator: m[2]! };\n}\n\n/** True when a bundle value counts as “no content” (aligned with typical CMS “empty”). */\nfunction isEmptyValue(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (value === '') return true;\n if (Array.isArray(value) && value.length === 0) {\n return true;\n }\n return typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 0;\n\n}\n\nfunction matchesFieldFilter(\n item: BundleItem,\n key: string,\n expected: unknown,\n): boolean {\n const { path, operator } = parseFieldKey(key);\n const at = getAtPath(item, path);\n\n if (operator === 'exists') {\n if (expected !== true && expected !== false) return false;\n const empty = !at.found || isEmptyValue(at.value);\n return !expected ? empty : !empty;\n }\n\n const actual = at.found ? at.value : undefined;\n if (Array.isArray(expected)) return expected.includes(actual);\n return actual === expected;\n}\n\n/** Set `value` on `target` at a dotted path, creating plain objects as needed. */\nfunction setNestedAt(\n target: Record<string, unknown>,\n dottedPath: string,\n value: unknown,\n): void {\n const parts = dottedPath.split('.').filter((p) => p.length > 0);\n if (parts.length === 0) return;\n\n if (parts.length === 1) {\n target[parts[0]!] = value;\n return;\n }\n\n const head = parts[0]!;\n const rest = parts.slice(1).join('.');\n let nested = target[head];\n if (\n nested === null ||\n typeof nested !== 'object' ||\n Array.isArray(nested)\n ) {\n nested = {};\n target[head] = nested;\n }\n setNestedAt(nested as Record<string, unknown>, rest, value);\n}\n\n/**\n * Downloads the latest CMS bundles from S3 and writes them as JSON files to `outputDir`.\n *\n * @returns A map of contentType to absolute file path.\n */\nexport async function fetchCmsBundles(\n store: ContentStore,\n outputDir: string,\n options: FetchCmsBundlesOptions,\n): Promise<Record<string, string>> {\n const { cms, contentTypes } = options;\n const retry = options.retry ?? getDefaultS3RetryConfig();\n await fs.mkdir(outputDir, { recursive: true });\n\n const result: CmsBundleInfo = {};\n\n await Promise.all(\n contentTypes.map(async (contentType) => {\n const key = buildCmsObjectKey(cms, contentType);\n const data = await downloadWithRetry(store, key, retry);\n const filePath = path.resolve(outputDir, key);\n await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');\n result[contentType] = filePath;\n }),\n );\n\n return result;\n}\n\n/**\n * Downloads the latest translation bundles from S3 and writes them as JSON files to `outputDir`.\n *\n * @returns For each project, a map of S3 object key to absolute file path on disk.\n */\n/** Filter a project's full resource list by the resource keys specified in the config. */\nfunction filterResources(\n projectResources: LingohubResource[],\n resourceFilter: string[],\n): LingohubResource[] {\n if (resourceFilter.length === 0) return projectResources;\n return projectResources.filter((r) => resourceFilter.includes(r.resource));\n}\n\nexport async function fetchTranslationBundles(\n store: ContentStore,\n outputDir: string,\n options: FetchTranslationBundlesOptions,\n): Promise<TranslationBundleInfo> {\n const { projects, locales, structure } = options;\n const nested = structure === 'nested';\n const retry = options.retry ?? getDefaultS3RetryConfig();\n await fs.mkdir(outputDir, { recursive: true });\n\n const result: TranslationBundleInfo = {};\n const localesToSync = locales && locales.length ? locales : defaultLocales;\n\n await Promise.all(\n Object.entries(projects).map(async ([project, resourceFilter]) => {\n const allResources = allProjects[project];\n if (!allResources?.length) return;\n\n const resources = filterResources(allResources, resourceFilter);\n if (!resources.length) return;\n\n const resourceTasks: { objectKey: string; resource: LingohubResource }[] =\n [];\n for (const resource of resources) {\n for (const loc of localesToSync) {\n const locale =\n resource.localeMapping && resource.localeMapping[loc]\n ? resource.localeMapping[loc]\n : loc;\n const objectKey = buildTranslationObjectKey(\n project,\n resource.fileName,\n locale,\n );\n resourceTasks.push({ objectKey, resource });\n }\n }\n\n await Promise.all(\n resourceTasks.map(async ({ objectKey, resource }) => {\n const raw = await downloadRawWithRetry(store, objectKey, retry);\n const parsed = parseTranslationResourceRaw(raw, resource);\n const output = nested ? nestKeys(parsed) : parsed;\n const filePath = translationJsonOutputPath(outputDir, objectKey);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n await fs.writeFile(\n filePath,\n JSON.stringify(output, null, 2),\n 'utf-8',\n );\n if (!result[project]) {\n result[project] = {};\n }\n result[project][objectKey] = filePath;\n }),\n );\n }),\n );\n\n return result;\n}\n\n/**\n * Downloads translation bundles from S3, parses each raw Lingohub file, and merges all\n * key/value pairs per **catalog** locale into a single JSON file (e.g. `en.json`).\n * Duplicate keys across resources or projects: **last write wins** (iteration order:\n * projects → resources → locales).\n *\n * @returns Map of locale code to absolute path of the merged `{locale}.json` file.\n */\nexport async function fetchMergedTranslationBundles(\n store: ContentStore,\n outputDir: string,\n options: FetchMergedTranslationBundlesOptions,\n): Promise<Record<string, string>> {\n const { projects, locales, structure } = options;\n const nested = structure === 'nested';\n const retry = options.retry ?? getDefaultS3RetryConfig();\n await fs.mkdir(outputDir, { recursive: true });\n\n const localesToSync = locales && locales.length ? locales : defaultLocales;\n\n type Task = {\n catalogLocale: string;\n objectKey: string;\n resource: LingohubResource;\n };\n const tasks: Task[] = [];\n\n for (const [project, resourceFilter] of Object.entries(projects)) {\n const allResources = allProjects[project];\n if (!allResources?.length) continue;\n\n const resources = filterResources(allResources, resourceFilter);\n if (!resources.length) continue;\n\n for (const resource of resources) {\n for (const loc of localesToSync) {\n const mappedLocale =\n resource.localeMapping && resource.localeMapping[loc]\n ? resource.localeMapping[loc]\n : loc;\n const objectKey = buildTranslationObjectKey(\n project,\n resource.fileName,\n mappedLocale,\n );\n tasks.push({ catalogLocale: loc, objectKey, resource });\n }\n }\n }\n\n const results = await Promise.all(\n tasks.map(async ({ catalogLocale, objectKey, resource }) => {\n const raw = await downloadRawWithRetry(store, objectKey, retry);\n const parsed = parseTranslationResourceRaw(raw, resource);\n const stringMap = toFlatStringMap(parsed);\n return { catalogLocale, stringMap };\n }),\n );\n\n const merged: Record<string, Record<string, string>> = {};\n for (const loc of localesToSync) {\n merged[loc] = {};\n }\n for (const { catalogLocale, stringMap } of results) {\n Object.assign(merged[catalogLocale]!, stringMap);\n }\n\n const out: Record<string, string> = {};\n for (const loc of localesToSync) {\n const filePath = path.resolve(outputDir, `${loc}.json`);\n const output = nested ? nestKeys(merged[loc]) : merged[loc];\n await fs.writeFile(\n filePath,\n JSON.stringify(output, null, 2),\n 'utf-8',\n );\n out[loc] = filePath;\n }\n\n return out;\n}\n\n/**\n * Queries a previously fetched bundle from the local filesystem.\n */\nexport async function queryCmsBundle(\n outputDir: string,\n cms: CMSProvider,\n contentType: string,\n options: QueryOptions = {},\n): Promise<unknown[]> {\n const filePath = path.resolve(\n outputDir,\n `${cms}-${contentType}.json`,\n );\n const raw = await fs.readFile(filePath, 'utf-8');\n let items:BundleItem[] = JSON.parse(raw) as Record<string, unknown>[];\n\n if (options.fields) {\n const filters = options.fields;\n items = items.filter((item) =>\n Object.entries(filters).every(([key, expected]) =>\n matchesFieldFilter(item, key, expected),\n ),\n );\n }\n\n if (options.limit !== undefined && options.limit > 0) {\n items = items.slice(0, options.limit);\n }\n\n if (options.include !== undefined) {\n items = items.map(\n (item) => trimDepth(item, options.include!) as Record<string, unknown>,\n );\n }\n\n if (options.select?.length) {\n const keys = options.select;\n items = items.map((item) => {\n const picked: Record<string, unknown> = {};\n for (const k of keys) {\n const at = getAtPath(item, k);\n if (at.found) setNestedAt(picked, k, at.value);\n }\n return picked;\n });\n }\n\n return items;\n}\n","import type { ContentStore } from './s3';\n\n/** Retry/backoff for S3 reads (aligned with CMS `RetryConfig` shape). */\nexport interface S3RetryConfig {\n maxRetries: number;\n baseDelayMs: number;\n maxDelayMs: number;\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 * Default S3 download retry policy from env.\n * `S3_RETRY_*` overrides `RETRY_*` when set.\n */\nexport function getDefaultS3RetryConfig(): S3RetryConfig {\n return {\n maxRetries: parseInt(\n process.env.S3_RETRY_MAX_RETRIES ??\n process.env.RETRY_MAX_RETRIES ??\n '5',\n 10,\n ),\n baseDelayMs: parseInt(\n process.env.S3_RETRY_BASE_DELAY_MS ??\n process.env.RETRY_BASE_DELAY_MS ??\n '1000',\n 10,\n ),\n maxDelayMs: parseInt(\n process.env.S3_RETRY_MAX_DELAY_MS ??\n process.env.RETRY_MAX_DELAY_MS ??\n '60000',\n 10,\n ),\n };\n}\n\n/**\n * True when a failed S3 GET may succeed after a short wait (503 Slow Down,\n * transient network, throttling). Never true for definitive client errors (404, etc.).\n */\nexport function isRetryableS3DownloadError(err: unknown): boolean {\n if (err === null || err === undefined) return false;\n\n if (typeof err === 'object') {\n const e = err as {\n name?: string;\n Code?: string;\n $metadata?: { httpStatusCode?: number };\n };\n const status = e.$metadata?.httpStatusCode;\n if (status === 404 || status === 403 || status === 401 || status === 400) {\n return false;\n }\n if (\n status !== undefined &&\n (status === 408 ||\n status === 429 ||\n status === 500 ||\n status === 502 ||\n status === 503 ||\n status === 504)\n ) {\n return true;\n }\n\n const name = e.name ?? '';\n const code = e.Code ?? '';\n if (\n /SlowDown|Throttl|Timeout|TooManyRequests|ServiceUnavailable|InternalError/i.test(\n name,\n ) ||\n /SlowDown|Throttl/i.test(code)\n ) {\n return true;\n }\n }\n\n if (err instanceof Error) {\n const m = err.message;\n if (\n /ECONNRESET|ETIMEDOUT|EPIPE|ECONNREFUSED|socket hang up|getaddrinfo/i.test(\n m,\n )\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Runs `fn` with retries when `isRetryableS3DownloadError` applies; otherwise throws immediately.\n */\nexport async function withS3Retry<T>(\n fn: () => Promise<T>,\n { maxRetries, baseDelayMs, maxDelayMs }: S3RetryConfig,\n): Promise<T> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (!isRetryableS3DownloadError(err)) {\n throw err;\n }\n if (attempt === maxRetries) {\n throw err;\n }\n\n const delay = computeDelay(attempt, baseDelayMs, maxDelayMs);\n console.warn(\n ` S3 request failed (attempt ${attempt + 1}/${maxRetries + 1}): ${\n err instanceof Error ? err.message : String(err)\n }. Retrying in ${Math.round(delay)}ms…`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error('withS3Retry: unreachable');\n}\n\nexport async function downloadWithRetry(\n store: ContentStore,\n key: string,\n cfg: S3RetryConfig,\n): Promise<unknown> {\n return withS3Retry(() => store.download(key), cfg);\n}\n\nexport async function downloadRawWithRetry(\n store: ContentStore,\n key: string,\n cfg: S3RetryConfig,\n): Promise<string> {\n return withS3Retry(() => store.downloadRaw(key), cfg);\n}\n","/**\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","export const defaultLocales = ['en', 'fr', 'de', 'es', 'it', 'pt-br', 'ru', 'zh-hans', 'zh-hant', 'ko', 'ja'];\n\nconst localeMapping = {\n ios: {\n 'pt-br': 'pt',\n 'zh-hans': 'zh-Hans',\n 'zh-hant': 'zh-Hant',\n },\n android: {\n 'pt-br': 'pt',\n 'zh-hans': 'zh-Hans-CN',\n 'zh-hant': 'zh-TW',\n },\n};\n\nexport type LingohubResource = {\n resource: string,\n fileName: string;\n type: 'json'|'strings'|'xml';\n localeMapping?: Record<string, string>;\n}\n\nexport const allProjects: Record<string, LingohubResource[]> = {\n 'tandem-(new-website)': [\n {\n resource: 'main',\n fileName: 'Website.[locale].json',\n type: 'json'\n }\n ],\n 'tandem-(website)': [\n {\n resource: 'main',\n fileName: '[locale].json',\n type: 'json'\n },\n {\n resource: 'ai',\n fileName: 'AI.[locale].json',\n type: 'json'\n },\n {\n resource: 'languages',\n fileName: 'languages.[locale].json',\n type: 'json'\n }\n ],\n 'tandem': [\n {\n resource: 'infoplist',\n fileName: 'InfoPlist.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'localizable',\n fileName: 'Localizable.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'ipad',\n fileName: 'Main_iPad.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'main',\n fileName: 'Main.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n }\n ],\n 'tandem-(android)': [\n {\n resource: 'accessibility',\n fileName: 'accessibility_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'call',\n fileName: 'call_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'cert',\n fileName: 'cert_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'chat',\n fileName: 'chat_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'checklist',\n fileName: 'checklist_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'clubs',\n fileName: 'clubs_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'common',\n fileName: 'common_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'community',\n fileName: 'community_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'correction',\n fileName: 'correction_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'country_names',\n fileName: 'country_names.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'emoji',\n fileName: 'emoji_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'errors',\n fileName: 'errors.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'expressions',\n fileName: 'expressions_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'gif',\n fileName: 'gif_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'guidelines',\n fileName: 'guidelines_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'lanuguages',\n fileName: 'languages_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'localizable',\n fileName: 'localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'localizable2',\n fileName: 'localizable2.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'login',\n fileName: 'login_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'myprofile',\n fileName: 'myprofile_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'onb',\n fileName: 'onb_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'parties',\n fileName: 'parties_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'pro',\n fileName: 'pro_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'pro_screen',\n fileName: 'pro_screen_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'push_notification',\n fileName: 'push_notification_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'reporting',\n fileName: 'reporting_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'translation',\n fileName: 'translation_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n }\n\n ],\n 'tandem-(web-invites)': [\n {\n resource: 'main',\n fileName: '[locale].json',\n type: 'json'\n }\n ]\n};\n","import path from 'node:path';\nimport { convertXMLToJS, parseIOSStrings, transformObjectToFlat } from './utils';\nimport type { LingohubResource } from './lingohub';\n\n/** Content-Type for raw Lingohub bodies stored in S3. */\nexport function contentTypeForTranslationKey(objectKey: string): string {\n if (objectKey.endsWith('.json')) return 'application/json; charset=utf-8';\n if (objectKey.endsWith('.xml')) return 'application/xml; charset=utf-8';\n if (objectKey.endsWith('.strings')) return 'text/plain; charset=utf-8';\n return 'application/octet-stream';\n}\n\n/**\n * Parses a raw Lingohub file body from S3 into structured data.\n */\nexport function parseTranslationResourceRaw(\n raw: string,\n resource: LingohubResource,\n): unknown {\n if (resource.type === 'json') {\n return JSON.parse(raw) as unknown;\n }\n\n if (resource.type === 'strings') {\n return parseIOSStrings(raw);\n }\n\n if (resource.type === 'xml') {\n return convertXMLToJS(raw);\n }\n\n throw new Error(`Unsupported resource type: ${resource.type}`);\n}\n\n/**\n * Normalizes parsed translation data to a flat string map for merging (duplicate keys: last wins).\n */\nexport function toFlatStringMap(parsed: unknown): Record<string, string> {\n if (parsed === null || parsed === undefined) return {};\n if (typeof parsed === 'string') return { value: parsed };\n if (typeof parsed !== 'object') return { value: String(parsed) };\n if (Array.isArray(parsed)) {\n const out: Record<string, string> = {};\n parsed.forEach((v, i) => {\n out[String(i)] =\n typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v);\n });\n return out;\n }\n\n const flat = transformObjectToFlat(parsed as Record<string, unknown>);\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(flat)) {\n if (v === null || v === undefined) {\n out[k] = '';\n } else if (typeof v === 'object') {\n out[k] = JSON.stringify(v);\n } else {\n out[k] = String(v);\n }\n }\n return out;\n}\n\n/** Where to write normalized JSON for one translation object key (avoids `.json.json`). */\nexport function translationJsonOutputPath(\n outputDir: string,\n objectKey: string,\n): string {\n if (objectKey.endsWith('.json')) {\n return path.resolve(outputDir, objectKey);\n }\n return path.resolve(outputDir, `${objectKey}.json`);\n}\n","import convert from 'xml-js';\nimport set from 'lodash.set';\nimport merge from 'lodash.merge';\n\nexport const transformObjectToNested = (data: Record<string, unknown>): Record<string, unknown> => {\n const result: Record<string, unknown> = {};\n\n Object.entries(data).forEach(([key, value]) => {\n const tempObject = {};\n set(tempObject, key, value);\n merge(result, tempObject);\n });\n\n return result;\n};\n\nexport const transformObjectToFlat = (data: Record<string, any>): Record<string, any> => { // eslint-disable-line @typescript-eslint/no-explicit-any\n const result: Record<string, unknown> = {};\n\n const flatten = (obj: Record<string, any>, path: string[] = []) => { // eslint-disable-line @typescript-eslint/no-explicit-any\n Object.entries(obj).forEach(([key, value]) => {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n flatten(value, path.concat(key));\n } else {\n result[path.concat(key).join('.')] = value;\n }\n });\n };\n\n flatten(data);\n\n return result;\n}\n\nexport const convertXMLToJS = (xml: string): Record<string, string> => {\n const converted = convert.xml2js(xml, {\n ignoreComment: true,\n ignoreDeclaration: true,\n ignoreInstruction: true,\n compact: true,\n }) as {\n resources: {\n string: {\n _attributes: { name: string },\n _text: 'User does not exist'\n }[];\n }\n };\n\n let mapped = {};\n const strings = converted.resources.string;\n const items = Array.isArray(strings) ? strings : [strings];\n items.forEach((item) => {\n if (!item?._attributes?.name) return;\n mapped = {\n ...mapped,\n [item._attributes.name]: item._text,\n };\n });\n\n return mapped;\n};\n\nexport const parseIOSStrings = (strings: string): Record<string, string> => {\n const parsedObj: Record<string, string> = {};\n strings\n .split('\\n')\n .filter((line) => line.startsWith('\"') && line.endsWith(';'))\n .map((line) => line.trim().slice(0, -1))\n .forEach((line) => {\n const eqIdx = line.indexOf(' = ');\n if (eqIdx === -1) return;\n const key = line.slice(1, eqIdx - 1);\n const value = line.slice(eqIdx + 3 + 1, -1);\n if (!key) return;\n parsedObj[key] = value;\n });\n\n return parsedObj;\n};","import { timingSafeEqual } from 'node:crypto';\nimport type { CMSProvider } from './types';\nimport type {\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n TranslationBundleInfo,\n} from './bundles';\n\nexport type ContentRefreshScope = 'cms' | 'translations' | 'all';\n\nexport interface ContentRefreshRequest {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n content_types?: string[];\n projects?: string[];\n locales?: string[];\n}\n\n/** Defaults applied when the request omits fields (set by the host application). */\nexport interface ContentRefreshDefaults {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n contentTypes?: string[];\n translationProjects?: string[];\n locales?: string[];\n}\n\nexport interface ContentRefreshError {\n step: string;\n message: string;\n}\n\nexport interface ContentRefreshResult {\n ok: boolean;\n scope: ContentRefreshScope;\n cmsFiles?: Record<string, string>;\n translationFiles?: TranslationBundleInfo;\n errors: ContentRefreshError[];\n durationMs: number;\n}\n\nexport interface ContentRefreshFetchers {\n fetchCmsBundles: (\n options: FetchCmsBundlesOptions,\n ) => Promise<Record<string, string>>;\n fetchTranslationBundles: (\n options: FetchTranslationBundlesOptions,\n ) => Promise<TranslationBundleInfo>;\n}\n\nexport function resolveContentRefreshScope(\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults,\n): ContentRefreshScope {\n return request.scope ?? defaults.scope ?? 'cms';\n}\n\n/** Base64 of `username:password` (value only — prefix with `Basic ` in the header). */\nexport function encodeBasicAuthCredentials(\n username: string,\n password: string,\n): string {\n return Buffer.from(`${username}:${password}`, 'utf8').toString('base64');\n}\n\n/**\n * Validates `Authorization: Basic <base64>` against the expected credentials\n * (base64 of `user:pass`, same as staging site basic auth).\n */\nexport function assertContentRefreshBasicAuth(\n authorizationHeader: string | undefined,\n expectedCredentialsBase64: string,\n): void {\n if (!expectedCredentialsBase64) {\n throw new ContentRefreshAuthError(\n 'Content refresh basic auth is not configured (set CONTENT_REFRESH_BASIC_AUTH)',\n 500,\n );\n }\n if (!authorizationHeader?.startsWith('Basic ')) {\n throw new ContentRefreshAuthError(\n 'Missing or malformed Authorization header (expected Basic)',\n 401,\n );\n }\n const provided = authorizationHeader.slice(6).trim();\n const bufA = new TextEncoder().encode(provided);\n const bufB = new TextEncoder().encode(expectedCredentialsBase64);\n if (bufA.byteLength !== bufB.byteLength || !timingSafeEqual(bufA, bufB)) {\n throw new ContentRefreshAuthError('Invalid basic auth credentials', 403);\n }\n}\n\nexport class ContentRefreshAuthError extends Error {\n constructor(\n message: string,\n readonly statusCode: number,\n ) {\n super(message);\n this.name = 'ContentRefreshAuthError';\n }\n}\n\n/**\n * Pull the latest bundles from S3 into the host app's configured output directory.\n */\nexport async function executeContentRefresh(\n fetchers: ContentRefreshFetchers,\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults = {},\n): Promise<ContentRefreshResult> {\n const started = Date.now();\n const scope = resolveContentRefreshScope(request, defaults);\n const errors: ContentRefreshError[] = [];\n let cmsFiles: Record<string, string> | undefined;\n let translationFiles: TranslationBundleInfo | undefined;\n\n if (scope === 'cms' || scope === 'all') {\n const cms = request.cms ?? defaults.cms ?? 'contentful';\n const contentTypes = request.content_types ?? defaults.contentTypes;\n if (!contentTypes?.length) {\n errors.push({\n step: 'cms',\n message: 'content_types (or handler defaults.contentTypes) is required for CMS refresh',\n });\n } else {\n try {\n cmsFiles = await fetchers.fetchCmsBundles({ cms, contentTypes });\n } catch (err) {\n errors.push({\n step: 'cms',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n if (scope === 'translations' || scope === 'all') {\n const projects = request.projects ?? defaults.translationProjects;\n if (!projects?.length) {\n errors.push({\n step: 'translations',\n message:\n 'projects (or handler defaults.translationProjects) is required for translation refresh',\n });\n } else {\n const locales = request.locales ?? defaults.locales;\n try {\n const projectMap = Object.fromEntries(\n projects.map((p) => [p, [] as string[]]),\n );\n translationFiles = await fetchers.fetchTranslationBundles({\n projects: projectMap,\n locales,\n });\n } catch (err) {\n errors.push({\n step: 'translations',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return {\n ok: errors.length === 0,\n scope,\n cmsFiles,\n translationFiles,\n errors,\n durationMs: Date.now() - started,\n };\n}\n\nexport interface PostContentRefreshResponse {\n target: string;\n ok: boolean;\n status: number;\n body?: unknown;\n error?: string;\n}\n\n/**\n * Ask a remote application (web-site, web-app, …) to refresh its local content cache.\n */\nexport async function postContentRefresh(\n target: string,\n url: string,\n basicAuth: string,\n request: ContentRefreshRequest,\n): Promise<PostContentRefreshResponse> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${basicAuth}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(request),\n });\n\n const text = await response.text();\n let body: unknown;\n if (text) {\n try {\n body = JSON.parse(text) as unknown;\n } catch {\n body = text;\n }\n }\n\n return {\n target,\n ok: response.ok,\n status: response.status,\n body,\n error: response.ok\n ? undefined\n : typeof body === 'object' && body !== null && 'error' in body\n ? String((body as { error: unknown }).error)\n : `HTTP ${response.status}`,\n };\n } catch (err) {\n return {\n target,\n ok: false,\n status: 0,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { CMSProvider } from '../shared/types';\nimport { ContentStore } from '../shared/s3';\nimport {\n fetchCmsBundles,\n fetchMergedTranslationBundles,\n fetchTranslationBundles,\n queryCmsBundle,\n type FetchCmsBundlesOptions,\n type FetchMergedTranslationBundlesOptions,\n type FetchTranslationBundlesOptions,\n type QueryOptions,\n type TranslationBundleInfo,\n} from '../shared/bundles';\nimport {\n executeContentRefresh,\n type ContentRefreshDefaults,\n type ContentRefreshRequest,\n type ContentRefreshResult,\n} from '../shared/content-refresh';\nimport type { SDKConfig } from './client-types';\n\nexport type {\n ContentRefreshDefaults,\n ContentRefreshRequest,\n ContentRefreshResult,\n ContentRefreshScope,\n} from '../shared/content-refresh';\nexport type {\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n FetchMergedTranslationBundlesOptions,\n TranslationFilterConfig,\n QueryOptions,\n CmsBundleInfo,\n TranslationBundleInfo,\n BundleItem,\n SDKConfig,\n S3RetryConfig,\n} from './client-types';\nexport { getDefaultS3RetryConfig } from './client-types';\n\nexport class ContentStoreSDK {\n private store: ContentStore;\n private outputDir: string;\n\n constructor(config: SDKConfig) {\n this.store = new ContentStore(config.s3);\n this.outputDir = config.outputDir;\n }\n\n /**\n * Downloads the latest bundles from S3 and writes them as JSON files\n * to `outputDir`.\n *\n * @returns A map of contentType to absolute file path.\n */\n async fetchCmsBundles(\n options: FetchCmsBundlesOptions,\n ): Promise<Record<string, string>> {\n return fetchCmsBundles(this.store, this.outputDir, options);\n }\n\n /**\n * Downloads translation bundles from S3 and writes them as JSON files\n * to `outputDir`.\n *\n * @returns Per project, a map of S3 object key to absolute file path.\n */\n async fetchTranslationBundles(\n options: FetchTranslationBundlesOptions,\n ): Promise<TranslationBundleInfo> {\n return fetchTranslationBundles(this.store, this.outputDir, options);\n }\n\n /**\n * Downloads all translation resources for the given projects/locales, parses them,\n * and writes one merged `{locale}.json` per locale (string key/value map; duplicate keys:\n * last wins).\n */\n async fetchMergedTranslationBundles(\n options: FetchMergedTranslationBundlesOptions,\n ): Promise<Record<string, string>> {\n return fetchMergedTranslationBundles(this.store, this.outputDir, options);\n }\n\n /**\n * Queries a previously fetched bundle from the local filesystem.\n */\n async queryCmsBundle(\n cms: CMSProvider,\n contentType: string,\n options: QueryOptions = {},\n ): Promise<unknown[]> {\n return queryCmsBundle(this.outputDir, cms, contentType, options);\n }\n\n /**\n * Download the latest bundles from S3 (same as the hosted refresh HTTP endpoint).\n */\n async refreshContent(\n request: ContentRefreshRequest = {},\n defaults: ContentRefreshDefaults = {},\n ): Promise<ContentRefreshResult> {\n return executeContentRefresh(\n {\n fetchCmsBundles: (opts) => this.fetchCmsBundles(opts),\n fetchTranslationBundles: (opts) => this.fetchTranslationBundles(opts),\n },\n request,\n defaults,\n );\n }\n}\n","import type { ContentStoreSDK } from './client';\nimport {\n assertContentRefreshBasicAuth,\n ContentRefreshAuthError,\n executeContentRefresh,\n type ContentRefreshDefaults,\n type ContentRefreshRequest,\n type ContentRefreshResult,\n} from '../shared/content-refresh';\n\n/** Minimal request shape for Next.js Pages API routes (`pages/api/*`). */\nexport interface NextPagesContentRefreshRequest {\n method?: string;\n headers: { authorization?: string | string[] };\n body?: ContentRefreshRequest;\n}\n\n/** Minimal response shape for Next.js Pages API routes. */\nexport interface NextPagesContentRefreshResponse {\n status(code: number): { json(body: unknown): void };\n}\n\nexport interface NextContentRefreshHandlerConfig {\n sdk: ContentStoreSDK;\n /** Base64 of `username:password` (same value as `CONTENT_REFRESH_BASIC_AUTH`). */\n basicAuth: string;\n defaults?: ContentRefreshDefaults;\n}\n\n/** @deprecated Use {@link NextContentRefreshHandlerConfig}. */\nexport type NextPagesContentRefreshHandlerConfig = NextContentRefreshHandlerConfig;\n\nfunction readAuthorizationHeader(\n value: string | string[] | null | undefined,\n): string | undefined {\n if (value == null) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value[0];\n }\n return value;\n}\n\nasync function runContentRefresh(\n config: NextContentRefreshHandlerConfig,\n authorization: string | undefined,\n body: ContentRefreshRequest,\n): Promise<{ status: number; body: ContentRefreshResult | { error: string } }> {\n const { sdk, basicAuth, defaults = {} } = config;\n\n try {\n assertContentRefreshBasicAuth(authorization, basicAuth);\n } catch (err) {\n if (err instanceof ContentRefreshAuthError) {\n return { status: err.statusCode, body: { error: err.message } };\n }\n throw err;\n }\n\n const result = await executeContentRefresh(\n {\n fetchCmsBundles: (opts) => sdk.fetchCmsBundles(opts),\n fetchTranslationBundles: (opts) => sdk.fetchTranslationBundles(opts),\n },\n body,\n defaults,\n );\n\n const status = result.ok ? 200 : result.errors.length > 0 ? 207 : 500;\n return { status, body: result };\n}\n\n/**\n * Handler for a Next.js Pages API route (e.g. pages/api/internal/content-refresh.ts).\n */\nexport function createNextPagesApiContentRefreshHandler(\n config: NextContentRefreshHandlerConfig,\n): (\n req: NextPagesContentRefreshRequest,\n res: NextPagesContentRefreshResponse,\n) => Promise<void> {\n return async (req, res): Promise<void> => {\n if (req.method !== 'POST') {\n res.status(405).json({ error: 'Method not allowed' });\n return;\n }\n\n const { status, body } = await runContentRefresh(\n config,\n readAuthorizationHeader(req.headers.authorization),\n req.body ?? {},\n );\n res.status(status).json(body);\n };\n}\n\n/**\n * Handler for a Next.js App Router route (e.g. app/api/internal/content-refresh/route.ts).\n */\nexport async function handleNextAppRouterContentRefresh(\n request: Request,\n config: NextContentRefreshHandlerConfig,\n): Promise<Response> {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 });\n }\n\n let body: ContentRefreshRequest = {};\n try {\n body = (await request.json()) as ContentRefreshRequest;\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 });\n }\n\n const { status, body: payload } = await runContentRefresh(\n config,\n request.headers.get('authorization') ?? undefined,\n body,\n );\n\n return Response.json(payload, { status });\n}\n"],"mappings":"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","names":["path","path","out","path"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/adapters/azure/types.ts","../src/server/adapters/azure/client.ts","../src/server/adapters/azure/pipelines.ts","../src/server/adapters/azure/environment.ts","../src/shared/content-refresh.ts","../src/server/content-refresh-notify.ts","../src/server/config.ts","../src/server/restrictedCron.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 type { AzurePipelineEnvironment } from './environment';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/** Azure DevOps project name (e.g. `web-site`, `web-app`). */\nexport type AzureDevOpsProjectKey = 'web-site' | 'web-app' | 'web-invites';\n\nexport const AZURE_DEVOPS_PROJECT_KEYS: AzureDevOpsProjectKey[] = [\n 'web-site',\n 'web-app',\n 'web-invites',\n];\n\nexport interface AzurePipelineDefinition {\n id: number;\n refName: string;\n}\n\n/** Pipeline for this content-store instance (one id/ref per ADO project). */\nexport interface AzureProjectConfig {\n pipeline: AzurePipelineDefinition;\n}\n\n/** Credentials and per-project pipeline map for this deployment. */\nexport interface AzureDevOpsConfig {\n organization: string;\n /** Personal access token with pipeline run permissions. */\n pat: string;\n apiVersion: string;\n /** Default when Slack command omits the project argument. */\n defaultProject: AzureDevOpsProjectKey;\n /** Raw `ENVIRONMENT` on this instance (e.g. beta, live). */\n instanceEnvironment: string;\n /** Normalized for Azure (staging | production). */\n pipelineEnvironment: AzurePipelineEnvironment;\n projects: Record<AzureDevOpsProjectKey, AzureProjectConfig>;\n}\n\n/** Connection settings for a single ADO project (used by the HTTP client). */\nexport interface AzureDevOpsClientConfig {\n organization: string;\n project: string;\n pat: string;\n apiVersion: string;\n}\n\nexport interface AzureRequestOptions {\n method?: HttpMethod;\n /**\n * Path under the project, e.g. `/_apis/pipelines/281/runs`.\n * If it starts with `http://` or `https://`, it is used as the full URL.\n */\n path: string;\n query?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n /** Overrides the default api-version query parameter. */\n apiVersion?: string;\n headers?: Record<string, string>;\n}\n\nexport interface AzureApiErrorBody {\n message?: string;\n typeKey?: string;\n}\n\n/** Subset of the Azure Pipelines run object returned by the Runs API. */\nexport interface PipelineRunResponse {\n id: number;\n name?: string;\n state?: string;\n url?: string;\n createdDate?: string;\n _links?: {\n web?: { href?: string };\n pipeline?: { href?: string };\n };\n}\n\nexport interface TriggerPipelineRunResult {\n project: AzureDevOpsProjectKey;\n instanceEnvironment: string;\n pipelineEnvironment: AzurePipelineEnvironment;\n pipelineId: number;\n refName: string;\n run: PipelineRunResponse;\n}\n","import type {\n AzureApiErrorBody,\n AzureDevOpsClientConfig,\n AzureRequestOptions,\n HttpMethod,\n} from './types';\n\nexport class AzureDevOpsClient {\n private readonly baseUrl: string;\n\n constructor(private readonly config: AzureDevOpsClientConfig) {\n const org = config.organization.replace(/^\\/+|\\/+$/g, '');\n const project = encodeURIComponent(config.project);\n this.baseUrl = `https://dev.azure.com/${org}/${project}`;\n }\n\n /**\n * Call any Azure DevOps REST endpoint under the configured organization and project.\n */\n async request<T>(options: AzureRequestOptions): Promise<T> {\n const {\n method = 'GET',\n path,\n query = {},\n body,\n apiVersion = this.config.apiVersion,\n headers: extraHeaders = {},\n } = options;\n\n if (!this.config.pat) {\n throw new Error(\n 'Azure DevOps is not configured (set AZURE_DEVOPS_ACCESS_TOKEN)',\n );\n }\n\n const url = this.buildUrl(path, { ...query, 'api-version': apiVersion });\n const headers: Record<string, string> = {\n Authorization: this.basicAuthHeader(),\n Accept: 'application/json',\n ...extraHeaders,\n };\n\n const init: RequestInit = { method, headers };\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(body);\n }\n\n const response = await fetch(url, init);\n const text = await response.text();\n let parsed: unknown;\n if (text) {\n try {\n parsed = JSON.parse(text) as unknown;\n } catch {\n parsed = text;\n }\n }\n\n if (!response.ok) {\n const errBody = parsed as AzureApiErrorBody | undefined;\n const detail =\n errBody?.message ??\n (typeof parsed === 'string' ? parsed : JSON.stringify(parsed));\n throw new Error(\n `Azure DevOps ${method} ${path} failed (${response.status}): ${detail}`,\n );\n }\n\n return parsed as T;\n }\n\n private buildUrl(\n path: string,\n query: Record<string, string | number | boolean | undefined>,\n ): string {\n const base = /^https?:\\/\\//i.test(path) ? path : `${this.baseUrl}${path}`;\n const url = new URL(base);\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n private basicAuthHeader(): string {\n const encoded = Buffer.from(`:${this.config.pat}`).toString('base64');\n return `Basic ${encoded}`;\n }\n}\n\nexport function createAzureDevOpsClient(\n config: AzureDevOpsClientConfig,\n): AzureDevOpsClient {\n return new AzureDevOpsClient(config);\n}\n\nexport type { HttpMethod };\n","import { createAzureDevOpsClient } from './client';\nimport type {\n AzureDevOpsConfig,\n AzureDevOpsProjectKey,\n PipelineRunResponse,\n TriggerPipelineRunResult,\n} from './types';\nimport { AZURE_DEVOPS_PROJECT_KEYS } from './types';\n\nexport function isAzureDevOpsProjectKey(value: string): value is AzureDevOpsProjectKey {\n return (AZURE_DEVOPS_PROJECT_KEYS as string[]).includes(value);\n}\n\nfunction resolveProjectConfig(\n azureConfig: AzureDevOpsConfig,\n project: AzureDevOpsProjectKey,\n) {\n const projectConfig = azureConfig.projects[project];\n if (!projectConfig) {\n throw new Error(`Unknown Azure DevOps project \"${project}\"`);\n }\n return projectConfig;\n}\n\n/**\n * Queue a pipeline run for the given ADO project using this instance's configured pipeline id/ref.\n */\nexport async function triggerPipelineBuild(\n azureConfig: AzureDevOpsConfig,\n project: AzureDevOpsProjectKey,\n variables: Record<string, string> = {},\n): Promise<TriggerPipelineRunResult> {\n const { pipeline } = resolveProjectConfig(azureConfig, project);\n if (!pipeline?.id) {\n throw new Error(\n `No pipeline id configured for project \"${project}\" ` +\n `(instance ENVIRONMENT=${azureConfig.instanceEnvironment}, ` +\n `Azure=${azureConfig.pipelineEnvironment})`,\n );\n }\n\n const client = createAzureDevOpsClient({\n organization: azureConfig.organization,\n project,\n pat: azureConfig.pat,\n apiVersion: azureConfig.apiVersion,\n });\n\n const run = await client.request<PipelineRunResponse>({\n method: 'POST',\n path: `/_apis/pipelines/${pipeline.id}/runs`,\n body: {\n resources: {\n repositories: {\n self: {\n refName: pipeline.refName,\n },\n },\n },\n variables,\n },\n });\n\n return {\n project,\n instanceEnvironment: azureConfig.instanceEnvironment,\n pipelineEnvironment: azureConfig.pipelineEnvironment,\n pipelineId: pipeline.id,\n refName: pipeline.refName,\n run,\n };\n}\n\nexport function formatPipelineRunSummary(result: TriggerPipelineRunResult): string {\n const {\n run,\n project,\n instanceEnvironment,\n pipelineEnvironment,\n pipelineId,\n refName,\n } = result;\n const webUrl = run._links?.web?.href ?? run.url;\n const envLabel =\n instanceEnvironment === pipelineEnvironment\n ? `\\`${pipelineEnvironment}\\``\n : `\\`${instanceEnvironment}\\` → Azure \\`${pipelineEnvironment}\\``;\n const lines = [\n `Environment ${envLabel} — project \\`${project}\\`, pipeline id \\`${pipelineId}\\`, ref \\`${refName}\\``,\n `Run #${run.id}${run.state ? ` — state: \\`${run.state}\\`` : ''}`,\n ];\n if (webUrl) {\n lines.push(`<${webUrl}|Open run in Azure DevOps>`);\n }\n return lines.join('\\n');\n}\n","/**\n * Azure Pipelines only use staging / production naming.\n * Map AWS / shared `ENVIRONMENT` values (beta, live, …) at the adapter boundary.\n */\nexport type AzurePipelineEnvironment = 'staging' | 'production';\n\nconst PRODUCTION_ALIASES = new Set(['production', 'live', 'prod']);\nconst STAGING_ALIASES = new Set(['staging', 'beta', 'development', 'dev', 'local']);\n\n/**\n * Normalize any instance `ENVIRONMENT` value to Azure's staging | production.\n * Unknown values default to staging and log a warning (avoids queuing production by mistake).\n */\nexport function normalizeToAzureEnvironment(\n instanceEnvironment: string,\n): AzurePipelineEnvironment {\n const key = instanceEnvironment.trim().toLowerCase();\n if (!key) {\n console.warn(\n '[azure] Empty ENVIRONMENT; defaulting Azure pipeline environment to staging',\n );\n return 'staging';\n }\n if (PRODUCTION_ALIASES.has(key)) {\n return 'production';\n }\n if (STAGING_ALIASES.has(key)) {\n return 'staging';\n }\n console.warn(\n `[azure] Unrecognized ENVIRONMENT=\"${instanceEnvironment}\"; defaulting Azure pipeline environment to staging`,\n );\n return 'staging';\n}\n\n/** Git ref queued for the pipeline run (always derived from the Azure environment). */\nexport function pipelineRefForAzure(\n pipelineEnvironment: AzurePipelineEnvironment,\n): string {\n return `refs/heads/${pipelineEnvironment}`;\n}\n","import { timingSafeEqual } from 'node:crypto';\nimport type { CMSProvider } from './types';\nimport type {\n FetchCmsBundlesOptions,\n FetchTranslationBundlesOptions,\n TranslationBundleInfo,\n} from './bundles';\n\nexport type ContentRefreshScope = 'cms' | 'translations' | 'all';\n\nexport interface ContentRefreshRequest {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n content_types?: string[];\n projects?: string[];\n locales?: string[];\n}\n\n/** Defaults applied when the request omits fields (set by the host application). */\nexport interface ContentRefreshDefaults {\n scope?: ContentRefreshScope;\n cms?: CMSProvider;\n contentTypes?: string[];\n translationProjects?: string[];\n locales?: string[];\n}\n\nexport interface ContentRefreshError {\n step: string;\n message: string;\n}\n\nexport interface ContentRefreshResult {\n ok: boolean;\n scope: ContentRefreshScope;\n cmsFiles?: Record<string, string>;\n translationFiles?: TranslationBundleInfo;\n errors: ContentRefreshError[];\n durationMs: number;\n}\n\nexport interface ContentRefreshFetchers {\n fetchCmsBundles: (\n options: FetchCmsBundlesOptions,\n ) => Promise<Record<string, string>>;\n fetchTranslationBundles: (\n options: FetchTranslationBundlesOptions,\n ) => Promise<TranslationBundleInfo>;\n}\n\nexport function resolveContentRefreshScope(\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults,\n): ContentRefreshScope {\n return request.scope ?? defaults.scope ?? 'cms';\n}\n\nexport function assertContentRefreshBearer(\n authorizationHeader: string | undefined,\n expectedToken: string,\n): void {\n if (!expectedToken) {\n throw new ContentRefreshAuthError(\n 'Content refresh API token is not configured on the server',\n 500,\n );\n }\n if (!authorizationHeader?.startsWith('Bearer ')) {\n throw new ContentRefreshAuthError(\n 'Missing or malformed Authorization header',\n 401,\n );\n }\n const token = authorizationHeader.slice(7);\n const bufA = new TextEncoder().encode(token);\n const bufB = new TextEncoder().encode(expectedToken);\n if (bufA.byteLength !== bufB.byteLength || !timingSafeEqual(bufA, bufB)) {\n throw new ContentRefreshAuthError('Invalid bearer token', 403);\n }\n}\n\nexport class ContentRefreshAuthError extends Error {\n constructor(\n message: string,\n readonly statusCode: number,\n ) {\n super(message);\n this.name = 'ContentRefreshAuthError';\n }\n}\n\n/**\n * Pull the latest bundles from S3 into the host app's configured output directory.\n */\nexport async function executeContentRefresh(\n fetchers: ContentRefreshFetchers,\n request: ContentRefreshRequest,\n defaults: ContentRefreshDefaults = {},\n): Promise<ContentRefreshResult> {\n const started = Date.now();\n const scope = resolveContentRefreshScope(request, defaults);\n const errors: ContentRefreshError[] = [];\n let cmsFiles: Record<string, string> | undefined;\n let translationFiles: TranslationBundleInfo | undefined;\n\n if (scope === 'cms' || scope === 'all') {\n const cms = request.cms ?? defaults.cms ?? 'contentful';\n const contentTypes = request.content_types ?? defaults.contentTypes;\n if (!contentTypes?.length) {\n errors.push({\n step: 'cms',\n message: 'content_types (or handler defaults.contentTypes) is required for CMS refresh',\n });\n } else {\n try {\n cmsFiles = await fetchers.fetchCmsBundles({ cms, contentTypes });\n } catch (err) {\n errors.push({\n step: 'cms',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n if (scope === 'translations' || scope === 'all') {\n const projects = request.projects ?? defaults.translationProjects;\n if (!projects?.length) {\n errors.push({\n step: 'translations',\n message:\n 'projects (or handler defaults.translationProjects) is required for translation refresh',\n });\n } else {\n const locales = request.locales ?? defaults.locales;\n try {\n const projectMap = Object.fromEntries(\n projects.map((p) => [p, [] as string[]]),\n );\n translationFiles = await fetchers.fetchTranslationBundles({\n projects: projectMap,\n locales,\n });\n } catch (err) {\n errors.push({\n step: 'translations',\n message: err instanceof Error ? err.message : String(err),\n });\n }\n }\n }\n\n return {\n ok: errors.length === 0,\n scope,\n cmsFiles,\n translationFiles,\n errors,\n durationMs: Date.now() - started,\n };\n}\n\nexport interface PostContentRefreshResponse {\n target: string;\n ok: boolean;\n status: number;\n body?: unknown;\n error?: string;\n}\n\n/**\n * Ask a remote application (web-site, web-app, …) to refresh its local content cache.\n */\nexport async function postContentRefresh(\n target: string,\n url: string,\n apiToken: string,\n request: ContentRefreshRequest,\n): Promise<PostContentRefreshResponse> {\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiToken}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(request),\n });\n\n const text = await response.text();\n let body: unknown;\n if (text) {\n try {\n body = JSON.parse(text) as unknown;\n } catch {\n body = text;\n }\n }\n\n return {\n target,\n ok: response.ok,\n status: response.status,\n body,\n error: response.ok\n ? undefined\n : typeof body === 'object' && body !== null && 'error' in body\n ? String((body as { error: unknown }).error)\n : `HTTP ${response.status}`,\n };\n } catch (err) {\n return {\n target,\n ok: false,\n status: 0,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n","import type { AzureDevOpsProjectKey } from './adapters/azure';\nimport { AZURE_DEVOPS_PROJECT_KEYS } from './adapters/azure';\nimport { normalizeToAzureEnvironment } from './adapters/azure';\nimport {\n postContentRefresh,\n type ContentRefreshRequest,\n type PostContentRefreshResponse,\n} from '../shared/content-refresh';\n\nexport interface ContentRefreshTargetConfig {\n url?: string;\n}\n\nexport interface ContentRefreshNotifyConfig {\n /** When false, never call client refresh URLs (e.g. production uses pipeline builds). */\n enabled: boolean;\n apiToken: string;\n targets: Record<AzureDevOpsProjectKey, ContentRefreshTargetConfig>;\n}\n\nexport function isContentRefreshEnabledForInstance(\n instanceEnvironment: string,\n): boolean {\n return normalizeToAzureEnvironment(instanceEnvironment) === 'staging';\n}\n\nexport async function notifyContentRefreshTargets(\n notifyConfig: ContentRefreshNotifyConfig,\n request: ContentRefreshRequest,\n projects?: AzureDevOpsProjectKey[],\n): Promise<PostContentRefreshResponse[]> {\n if (!notifyConfig.enabled || !notifyConfig.apiToken) {\n return [];\n }\n\n const keys =\n projects ??\n (AZURE_DEVOPS_PROJECT_KEYS.filter(\n (p) => notifyConfig.targets[p]?.url,\n ) as AzureDevOpsProjectKey[]);\n\n const results: PostContentRefreshResponse[] = [];\n\n for (const project of keys) {\n const url = notifyConfig.targets[project]?.url;\n if (!url) {\n continue;\n }\n const result = await postContentRefresh(\n project,\n url,\n notifyConfig.apiToken,\n request,\n );\n results.push(result);\n if (result.ok) {\n console.log(\n `[content-refresh] Notified ${project} (${result.status})`,\n );\n } else {\n console.error(\n `[content-refresh] Failed to notify ${project}: ${result.error ?? result.status}`,\n );\n }\n }\n\n return results;\n}\n","import dotenv from 'dotenv';\nimport type { S3Config, CMSProvider } from '../shared/types';\nimport { SharedConfig, config as sharedConfig } from '../shared/config';\nimport type { AzureDevOpsConfig, AzureDevOpsProjectKey } from './adapters/azure';\nimport {\n AZURE_DEVOPS_PROJECT_KEYS,\n normalizeToAzureEnvironment,\n pipelineRefForAzure,\n} from './adapters/azure';\nimport type { AzurePipelineEnvironment } from './adapters/azure';\nimport type { ContentRefreshNotifyConfig } from './content-refresh-notify';\nimport { isContentRefreshEnabledForInstance } from './content-refresh-notify';\nimport { validateScheduleCronExpression } from './restrictedCron';\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 /** Two-field cron: minute then hour only. Example: `30 3` or a minute step with star in the hour field. Empty when disabled. */\n cronExpression: string;\n /** When true, run once when the server starts, then on the cron wall clock. */\n runOnStart: boolean;\n syncCms?: CMSProvider;\n syncTypes?: string[];\n}\n\nexport interface ScheduledTranslationJobConfig {\n enabled: boolean;\n cronExpression: string;\n runOnStart: boolean;\n syncProjects?: string[];\n}\n\nfunction readScheduleCronEnv(): {\n scheduleCron: string;\n cmsCronRaw: string;\n translationCronRaw: string;\n} {\n return {\n scheduleCron: (process.env.SCHEDULE_CRON ?? '').trim(),\n cmsCronRaw: (process.env.SCHEDULE_CMS_CRON ?? '').trim(),\n translationCronRaw: (process.env.SCHEDULE_TRANSLATION_CRON ?? '').trim(),\n };\n}\n\nfunction resolveScheduleCron(\n jobSpecific: string,\n globalCron: string,\n jobLabel: string,\n): string | null {\n const raw = jobSpecific || globalCron;\n if (!raw) return null;\n try {\n return validateScheduleCronExpression(raw);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n console.error(`[config] Invalid ${jobLabel} schedule cron: ${msg}`);\n return null;\n }\n}\n\nfunction parseScheduledCmsJobConfig(): ScheduledCmsJobConfig {\n const { scheduleCron, cmsCronRaw } = readScheduleCronEnv();\n const runOnStart =\n (process.env.SCHEDULE_RUN_ON_START ?? 'true').toLowerCase() !== 'false';\n const syncCms = (process.env.SCHEDULE_SYNC_CMS ?? '').trim() as CMSProvider;\n const rawTypes = process.env.SCHEDULE_SYNC_CONTENT_TYPES?.trim();\n const syncTypes = rawTypes\n ? rawTypes.split(',').map((s) => s.trim()).filter(Boolean)\n : undefined;\n\n const resolved = resolveScheduleCron(cmsCronRaw, scheduleCron, 'CMS');\n const cmsOk =\n !!syncCms && (syncCms === 'contentful' || syncCms === 'sanity');\n if (resolved !== null && !cmsOk) {\n console.warn(\n '[config] CMS schedule cron is set but SCHEDULE_SYNC_CMS is missing or not \"contentful\"|\"sanity\"; scheduled CMS sync is disabled.',\n );\n }\n const enabled = resolved !== null && cmsOk;\n\n return {\n enabled,\n cronExpression: resolved ?? '',\n runOnStart,\n syncCms: syncCms || undefined,\n syncTypes,\n };\n}\n\nfunction parseScheduledTranslationJobConfig(): ScheduledTranslationJobConfig {\n const { scheduleCron, translationCronRaw } = readScheduleCronEnv();\n const runOnStart =\n (process.env.SCHEDULE_RUN_ON_START ?? 'true').toLowerCase() !== 'false';\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 const resolved = resolveScheduleCron(\n translationCronRaw,\n scheduleCron,\n 'translation',\n );\n const enabled = resolved !== null;\n\n return {\n enabled,\n cronExpression: resolved ?? '',\n runOnStart,\n syncProjects,\n };\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 /** Channel ID or name for async notifications (e.g. sync results). */\n notifyChannel: string;\n /** Slash command name for CMS sync (default `/sync-content`). */\n cmdSyncContent: string;\n /** Slash command name for translation sync (default `/sync-translations`). */\n cmdSyncTranslations: string;\n /** Slash command to queue an Azure Pipelines run (default `/trigger-pipeline`). */\n cmdTriggerBuild: string;\n}\n\nfunction projectEnvSlug(project: AzureDevOpsProjectKey): string {\n return project.toUpperCase().replace(/-/g, '_');\n}\n\nfunction readProjectPipeline(\n project: AzureDevOpsProjectKey,\n pipelineEnvironment: AzurePipelineEnvironment,\n): { id: number; refName: string } {\n const slug = projectEnvSlug(project);\n const id = parseInt(process.env[`AZURE_${slug}_PIPELINE_ID`]?.trim() ?? '0', 10);\n return { id, refName: pipelineRefForAzure(pipelineEnvironment) };\n}\n\nfunction parseAzureDevOpsConfig(\n instanceEnvironment: string,\n): AzureDevOpsConfig & { enabled: boolean } {\n const pipelineEnvironment = normalizeToAzureEnvironment(instanceEnvironment);\n const pat = process.env.AZURE_DEVOPS_ACCESS_TOKEN?.trim() ?? '';\n\n const defaultProjectRaw = (\n process.env.AZURE_DEVOPS_DEFAULT_PROJECT ?? 'web-site'\n ).trim() as AzureDevOpsProjectKey;\n\n const defaultProject = (AZURE_DEVOPS_PROJECT_KEYS as string[]).includes(\n defaultProjectRaw,\n )\n ? defaultProjectRaw\n : 'web-site';\n\n const projects = Object.fromEntries(\n AZURE_DEVOPS_PROJECT_KEYS.map((project) => [\n project,\n { pipeline: readProjectPipeline(project, pipelineEnvironment) },\n ]),\n ) as AzureDevOpsConfig['projects'];\n\n return {\n enabled: pat.length > 0,\n organization: process.env.AZURE_DEVOPS_ORGANIZATION?.trim() || 'tripod-technology',\n pat,\n apiVersion: process.env.AZURE_DEVOPS_API_VERSION?.trim() || '7.1',\n defaultProject,\n instanceEnvironment,\n pipelineEnvironment,\n projects,\n };\n}\n\nfunction readContentRefreshUrl(project: AzureDevOpsProjectKey): string | undefined {\n const slug = projectEnvSlug(project);\n return process.env[`AZURE_${slug}_CONTENT_REFRESH_URL`]?.trim() || undefined;\n}\n\nfunction parseContentRefreshConfig(\n instanceEnvironment: string,\n contentStoreApiToken: string,\n): ContentRefreshNotifyConfig {\n const targets = Object.fromEntries(\n AZURE_DEVOPS_PROJECT_KEYS.map((project) => [\n project,\n { url: readContentRefreshUrl(project) },\n ]),\n ) as ContentRefreshNotifyConfig['targets'];\n\n return {\n enabled: isContentRefreshEnabledForInstance(instanceEnvironment),\n apiToken:\n process.env.CONTENT_REFRESH_API_TOKEN?.trim() || contentStoreApiToken,\n targets,\n };\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 azure: AzureDevOpsConfig & { enabled: boolean };\n contentRefresh: ContentRefreshNotifyConfig;\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 ?? '3030', 10),\n apiToken: process.env.CONTENT_STORE_API_TOKEN ?? '',\n },\n\n scheduledCmsJob: parseScheduledCmsJobConfig(),\n scheduledTranslationJob: parseScheduledTranslationJobConfig(),\n\n azure: parseAzureDevOpsConfig(sharedConfig.environment),\n\n contentRefresh: parseContentRefreshConfig(\n sharedConfig.environment,\n process.env.CONTENT_STORE_API_TOKEN ?? '',\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 notifyChannel: process.env.SLACK_NOTIFY_CHANNEL ?? '',\n cmdSyncContent: process.env.SLACK_CMD_SYNC_CONTENT ?? '/sync-content',\n cmdSyncTranslations: process.env.SLACK_CMD_SYNC_TRANSLATIONS ?? '/sync-translations',\n cmdTriggerBuild:\n process.env.SLACK_CMD_TRIGGER_BUILD ?? '/trigger-build',\n },\n};\n","/**\n * Schedule expressions: exactly **two** whitespace-separated fields: minute, then hour\n * (same meaning as the first two fields of standard cron). No day-of-month, month, or\n * day-of-week — those are not accepted.\n *\n * Supported per field: `*`, `n`, `a-b`, steps (asterisk + slash + step), and comma lists.\n * Step must be >= 1.\n */\n\nconst MAX_SEARCH_MINUTES = 366 * 24 * 60;\n\nfunction tokenize(expr: string): string[] {\n return expr\n .trim()\n .split(/\\s+/)\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\n/**\n * Validates that `expr` is exactly two cron fields (minute hour) and returns them\n * as a single normalized string `\"<minute> <hour>\"` for storage and logging.\n */\nexport function validateScheduleCronExpression(expr: string): string {\n const parts = tokenize(expr);\n if (parts.length !== 2) {\n throw new Error(\n `Schedule cron must be exactly two fields (minute hour), whitespace-separated; got ${parts.length} field(s): \"${expr}\"`,\n );\n }\n const minuteSpec = parts[0]!;\n const hourSpec = parts[1]!;\n parseField(minuteSpec, 0, 59, 'minute');\n parseField(hourSpec, 0, 23, 'hour');\n return `${minuteSpec} ${hourSpec}`;\n}\n\nfunction parseField(\n spec: string,\n lo: number,\n hi: number,\n fieldName: string,\n): (n: number) => boolean {\n const subs = spec.split(',').map((s) => s.trim()).filter(Boolean);\n if (subs.length === 0) {\n throw new Error(`Empty ${fieldName} field in cron`);\n }\n const preds = subs.map((sub) => parseSubfield(sub, lo, hi, fieldName));\n return (n: number) => preds.some((p) => p(n));\n}\n\nfunction parseSubfield(\n sub: string,\n lo: number,\n hi: number,\n fieldName: string,\n): (n: number) => boolean {\n if (sub === '*') {\n return () => true;\n }\n if (sub.startsWith('*/')) {\n const step = parseInt(sub.slice(2), 10);\n if (!Number.isFinite(step) || step < 1) {\n throw new Error(`Invalid step in ${fieldName} field: \"${sub}\"`);\n }\n return (n: number) => n >= lo && n <= hi && n % step === 0;\n }\n if (sub.includes('-')) {\n const [a, b] = sub.split('-').map((x) => parseInt(x.trim(), 10));\n if (!Number.isFinite(a) || !Number.isFinite(b)) {\n throw new Error(`Invalid range in ${fieldName} field: \"${sub}\"`);\n }\n if (a < lo || b > hi || a > b) {\n throw new Error(`Range out of bounds in ${fieldName} field: \"${sub}\"`);\n }\n return (n: number) => n >= a && n <= b;\n }\n const v = parseInt(sub, 10);\n if (!Number.isFinite(v) || v < lo || v > hi) {\n throw new Error(`Invalid value in ${fieldName} field: \"${sub}\"`);\n }\n return (n: number) => n === v;\n}\n\nfunction floorToMinuteStart(d: Date): Date {\n const t = new Date(d.getTime());\n t.setSeconds(0, 0);\n return t;\n}\n\nfunction addOneMinute(d: Date): Date {\n const t = new Date(d.getTime());\n t.setMinutes(t.getMinutes() + 1, 0, 0);\n return t;\n}\n\nfunction matchesAtMinuteStart(\n minutePred: (m: number) => boolean,\n hourPred: (h: number) => boolean,\n d: Date,\n): boolean {\n return minutePred(d.getMinutes()) && hourPred(d.getHours());\n}\n\n/**\n * Earliest minute boundary strictly after `after` that satisfies the two-field expression.\n */\nexport function nextCronFireAfter(expr: string, after: Date): Date {\n const parts = tokenize(expr);\n if (parts.length !== 2) {\n throw new Error(\n `nextCronFireAfter expects exactly two fields (minute hour); got ${parts.length}: \"${expr}\"`,\n );\n }\n const minutePred = parseField(parts[0]!, 0, 59, 'minute');\n const hourPred = parseField(parts[1]!, 0, 23, 'hour');\n\n let d = floorToMinuteStart(after);\n if (d.getTime() <= after.getTime()) {\n d = addOneMinute(d);\n }\n\n for (let i = 0; i < MAX_SEARCH_MINUTES; i++) {\n if (matchesAtMinuteStart(minutePred, hourPred, d)) {\n return d;\n }\n d = addOneMinute(d);\n }\n\n throw new Error(`No cron match within ${MAX_SEARCH_MINUTES} minutes for \"${expr}\"`);\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 \\`${resource.fileName}\\` (${locale}): ${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';\nimport { withRetry } from './retry';\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 resource: 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 function summariseCmsEntries(result: CmsSyncResult): string[] {\n return result.entries.map(\n (e) => `• \\`${e.contentType}\\` — ${e.itemCount} items`,\n );\n}\n\nexport function summariseCmsErrors(result: CmsSyncResult): string[] {\n return result.errors.map(\n (e) => `• \\`${e.contentType}\\` — ${e.error}`,\n );\n}\n\n/** Group translation sync entries into one summary line per project/resource. */\nexport function summariseTranslationEntries(entries: TranslationSyncResultEntry[]): string[] {\n const grouped = new Map<string, { locales: number; bytes: number }>();\n for (const e of entries) {\n const key = `${e.project} / ${e.resource}`;\n const existing = grouped.get(key);\n if (existing) {\n existing.locales += 1;\n existing.bytes += e.itemCount;\n } else {\n grouped.set(key, { locales: 1, bytes: e.itemCount });\n }\n }\n return [...grouped.entries()].map(\n ([key, { locales, bytes }]) => `• \\`${key}\\` / [${locales} locale${locales === 1 ? '' : 's'}] — ${bytes} bytes`,\n );\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 withRetry(\n () => fetchLingohubResourceRaw(project, resource, locale),\n config.retry,\n );\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 resource: resource.resource,\n locale,\n itemCount: byteLength,\n objectKey,\n });\n\n console.log(\n ` + ${project} - ${locale}: ${byteLength} bytes -> ${objectKey}`,\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","config","result","cfg","createClient","cfg","createClient","config","config","config"]}
|
|
File without changes
|