@tandem-language-exchange/content-store 1.2.23 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +103 -5
  2. package/dist/{chunk-D2F7FQEM.js → chunk-D723FMZ2.js} +3 -3
  3. package/dist/{chunk-YWUFALDR.js → chunk-HD7E5M5O.js} +55 -5
  4. package/dist/chunk-HD7E5M5O.js.map +1 -0
  5. package/dist/{chunk-Y6HC4NYU.js → chunk-OCAIIQZW.js} +2 -2
  6. package/dist/chunk-OCAIIQZW.js.map +1 -0
  7. package/dist/{chunk-SF7FCBR2.js → chunk-RZLDRXNQ.js} +2 -2
  8. package/dist/chunk-RZLDRXNQ.js.map +1 -0
  9. package/dist/{chunk-MOGVAQ2N.js → chunk-SDEERVPV.js} +2 -2
  10. package/dist/{chunk-LZHYKLAU.js → chunk-VBJ6LVMY.js} +2 -4
  11. package/dist/chunk-VBJ6LVMY.js.map +1 -0
  12. package/dist/{chunk-NQHWG4XM.js → chunk-ZX5KC4F5.js} +447 -18
  13. package/dist/chunk-ZX5KC4F5.js.map +1 -0
  14. package/dist/client/fetch-content-bundles.js +5 -5
  15. package/dist/client/fetch-merged-translation-bundles.js +5 -5
  16. package/dist/client/fetch-translation-bundles.js +5 -5
  17. package/dist/client/list-projects.js +1 -1
  18. package/dist/client/list-resources.js +1 -1
  19. package/dist/client/query-cms.js +3 -3
  20. package/dist/node.d.ts +89 -2
  21. package/dist/node.js +212 -4
  22. package/dist/node.js.map +1 -1
  23. package/package.json +2 -24
  24. package/dist/chunk-LZHYKLAU.js.map +0 -1
  25. package/dist/chunk-NQHWG4XM.js.map +0 -1
  26. package/dist/chunk-SF7FCBR2.js.map +0 -1
  27. package/dist/chunk-Y6HC4NYU.js.map +0 -1
  28. package/dist/chunk-YWUFALDR.js.map +0 -1
  29. package/dist/sanity-studio.d.ts +0 -11
  30. package/dist/sanity-studio.js +0 -3256
  31. package/dist/sanity-studio.js.map +0 -1
  32. package/dist/sanity-studio.node.d.ts +0 -14
  33. package/dist/sanity-studio.node.js +0 -6
  34. package/dist/sanity-studio.node.js.map +0 -1
  35. package/dist/sanity.d.ts +0 -563
  36. package/dist/sanity.js +0 -2966
  37. package/dist/sanity.js.map +0 -1
  38. /package/dist/{chunk-D2F7FQEM.js.map → chunk-D723FMZ2.js.map} +0 -0
  39. /package/dist/{chunk-MOGVAQ2N.js.map → chunk-SDEERVPV.js.map} +0 -0
package/README.md CHANGED
@@ -63,8 +63,8 @@ const sdk = new ContentStoreSDK({
63
63
  s3: {
64
64
  bucket: process.env.CONTENT_STORE_S3_BUCKET!,
65
65
  region: process.env.CONTENT_STORE_S3_REGION!,
66
- accessKeyId: process.env.CONTENT_STORE_AWS_ACCESS_KEY!,
67
- secretAccessKey: process.env.CONTENT_STORE_AWS_SECRET_ACCESS_KEY!,
66
+ accessKeyId: process.env.AWS_ACCESS_KEY!,
67
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
68
68
  },
69
69
  outputDir: './content-cache',
70
70
  });
@@ -105,6 +105,104 @@ Files are written to `outputDir` with the naming pattern `{cms}-{contentType}.js
105
105
 
106
106
  ---
107
107
 
108
+ ## Staging content refresh endpoint (Next.js web-site / web-app)
109
+
110
+ On **staging** (pages rebuild on request), content-store can tell your app to pull fresh bundles from S3 **without** an Azure pipeline build. Your Next.js app exposes an HTTP endpoint; content-store calls it after a successful CMS sync (or via `POST /notifyContentRefresh` on content-store).
111
+
112
+ **Request (from content-store):**
113
+
114
+ ```http
115
+ POST /api/internal/content-refresh
116
+ Authorization: Bearer <CONTENT_REFRESH_API_TOKEN>
117
+ Content-Type: application/json
118
+
119
+ {"scope":"cms","cms":"contentful","content_types":["page","banner"]}
120
+ ```
121
+
122
+ The handler runs `fetchCmsBundles` / `fetchTranslationBundles` and returns paths written under `outputDir`. You can also call `sdk.refreshContent(body, defaults)` directly (no HTTP).
123
+
124
+ Import `@tandem-language-exchange/content-store/node` only from **server-only** code (`pages/api/*`, `app/api/**/route.ts`, or modules they import) — not from `_app`, layouts, or client components.
125
+
126
+ Shared SDK setup (use in both examples below):
127
+
128
+ ```typescript
129
+ import { ContentStoreSDK } from '@tandem-language-exchange/content-store/node';
130
+
131
+ export const contentStoreSdk = new ContentStoreSDK({
132
+ s3: {
133
+ bucket: process.env.CONTENT_STORE_S3_BUCKET!,
134
+ region: process.env.CONTENT_STORE_S3_REGION ?? 'eu-central-1',
135
+ accessKeyId: process.env.CONTENT_STORE_AWS_ACCESS_KEY!,
136
+ secretAccessKey: process.env.CONTENT_STORE_AWS_SECRET_ACCESS_KEY!,
137
+ },
138
+ outputDir: process.env.CONTENT_CACHE_DIR ?? './content-cache',
139
+ });
140
+
141
+ export const contentRefreshDefaults = {
142
+ scope: 'cms' as const,
143
+ cms: 'contentful' as const,
144
+ contentTypes: ['page', 'banner', 'cookieBanner', 'downloadPage'],
145
+ translationProjects: ['tandem-(website)'],
146
+ };
147
+ ```
148
+
149
+ ### Pages Router (`pages/api`)
150
+
151
+ **`pages/api/internal/content-refresh.ts`**
152
+
153
+ ```typescript
154
+ import type { NextApiRequest, NextApiResponse } from 'next';
155
+ import { createNextPagesApiContentRefreshHandler } from '@tandem-language-exchange/content-store/node';
156
+ import { contentRefreshDefaults, contentStoreSdk } from '../../lib/content-store';
157
+
158
+ const handler = createNextPagesApiContentRefreshHandler({
159
+ sdk: contentStoreSdk,
160
+ apiToken: process.env.CONTENT_REFRESH_API_TOKEN!,
161
+ defaults: contentRefreshDefaults,
162
+ });
163
+
164
+ export default async function contentRefresh(
165
+ req: NextApiRequest,
166
+ res: NextApiResponse,
167
+ ) {
168
+ return handler(req, res);
169
+ }
170
+ ```
171
+
172
+ ### App Router (`app/api/.../route.ts`)
173
+
174
+ **`app/api/internal/content-refresh/route.ts`**
175
+
176
+ ```typescript
177
+ import { handleNextAppRouterContentRefresh } from '@tandem-language-exchange/content-store/node';
178
+ import { contentRefreshDefaults, contentStoreSdk } from '@/lib/content-store';
179
+
180
+ export async function POST(request: Request) {
181
+ return handleNextAppRouterContentRefresh(request, {
182
+ sdk: contentStoreSdk,
183
+ apiToken: process.env.CONTENT_REFRESH_API_TOKEN!,
184
+ defaults: contentRefreshDefaults,
185
+ });
186
+ }
187
+ ```
188
+
189
+ Point content-store staging at the deployed URL, e.g.
190
+ `AZURE_WEB_SITE_CONTENT_REFRESH_URL=https://staging.example.com/api/internal/content-refresh`
191
+
192
+ After refresh, load bundles from `outputDir` in `getStaticProps`, `getServerSideProps`, or Server Components on the next request.
193
+
194
+ ### Configure content-store (staging instance)
195
+
196
+ | Variable | Description |
197
+ | --- | --- |
198
+ | `AZURE_WEB_SITE_CONTENT_REFRESH_URL` | Full URL to web-site refresh endpoint |
199
+ | `AZURE_WEB_APP_CONTENT_REFRESH_URL` | Full URL to web-app refresh endpoint |
200
+ | `CONTENT_REFRESH_API_TOKEN` | Shared secret (defaults to `CONTENT_STORE_API_TOKEN`) |
201
+
202
+ After `POST /syncCmsContent` completes successfully on a **beta/staging** instance, content-store notifies every configured URL automatically.
203
+
204
+ ---
205
+
108
206
  ## `fetchTranslationBundles(options)`
109
207
 
110
208
  Downloads translation objects from S3. The **sync** stores each Lingohub file **verbatim** (raw UTF-8); this call **parses** each file (JSON / `.strings` / Android XML per `src/shared/lingohub.ts`) and writes **normalized JSON** under `outputDir` (see file naming below).
@@ -327,7 +425,7 @@ const store = new ContentStore({
327
425
  bucket: 'beta-content-store',
328
426
  region: 'eu-central-1',
329
427
  accessKeyId: process.env.CONTENT_STORE_AWS_ACCESS_KEY!,
330
- secretAccessKey: process.env.CONTENT_STORE_AWS_SECRET_ACCESS_KEY!,
428
+ secretAccessKey: process.CONTENT_STORE_env.AWS_SECRET_ACCESS_KEY!,
331
429
  });
332
430
 
333
431
  await fetchCmsBundles(store, './content-cache', {
@@ -361,8 +459,8 @@ const sdk = new ContentStoreSDK({
361
459
  s3: {
362
460
  bucket: process.env.CONTENT_STORE_S3_BUCKET!,
363
461
  region: process.env.CONTENT_STORE_S3_REGION!,
364
- accessKeyId: process.env.CONTENT_STORE_AWS_ACCESS_KEY!,
365
- secretAccessKey: process.env.CONTENT_STORE_AWS_SECRET_ACCESS_KEY!,
462
+ accessKeyId: process.env.AWS_ACCESS_KEY!,
463
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
366
464
  },
367
465
  outputDir: './.content-cache',
368
466
  });
@@ -10,11 +10,11 @@ import {
10
10
  parseTranslationResourceRaw,
11
11
  toFlatStringMap,
12
12
  translationJsonOutputPath
13
- } from "./chunk-LZHYKLAU.js";
13
+ } from "./chunk-VBJ6LVMY.js";
14
14
  import {
15
15
  allProjects,
16
16
  defaultLocales
17
- } from "./chunk-SF7FCBR2.js";
17
+ } from "./chunk-RZLDRXNQ.js";
18
18
 
19
19
  // src/shared/bundles.ts
20
20
  import fs from "fs/promises";
@@ -286,4 +286,4 @@ export {
286
286
  fetchMergedTranslationBundles,
287
287
  queryCmsBundle
288
288
  };
289
- //# sourceMappingURL=chunk-D2F7FQEM.js.map
289
+ //# sourceMappingURL=chunk-D723FMZ2.js.map
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AZURE_DEVOPS_PROJECT_KEYS,
3
4
  config,
5
+ formatPipelineRunSummary,
6
+ isAzureDevOpsProjectKey,
4
7
  summariseCmsEntries,
5
8
  summariseCmsErrors,
6
9
  summariseTranslationEntries,
7
10
  syncCmsContent,
8
- syncTranslations
9
- } from "./chunk-NQHWG4XM.js";
11
+ syncTranslations,
12
+ triggerPipelineBuild
13
+ } from "./chunk-ZX5KC4F5.js";
10
14
  import {
11
15
  allProjects
12
- } from "./chunk-SF7FCBR2.js";
16
+ } from "./chunk-RZLDRXNQ.js";
13
17
 
14
18
  // src/server/slack.ts
15
19
  import { App } from "@slack/bolt";
@@ -110,7 +114,7 @@ async function startSlackBot() {
110
114
  const result = await syncTranslations(projects, locales);
111
115
  const lines = summariseTranslationEntries(result.entries);
112
116
  const errorLines = result.errors.map(
113
- (e) => `\u2022 \`${e.project}\` / \`${e.resource}\` / \`${e.locale}\` \u2014 ${e.error}`
117
+ (e) => `\u2022 \`${e.project}\` / \`${e.locale}\` \u2014 ${e.error}`
114
118
  );
115
119
  const blocks = [
116
120
  result.errors.length === 0 ? `:white_check_mark: *Translation sync complete* \u2014 ${result.entries.length} resource \xD7 locale uploads` : `:warning: *Translation sync complete with errors* \u2014 ${result.entries.length} succeeded, ${result.errors.length} failed`,
@@ -130,6 +134,52 @@ async function startSlackBot() {
130
134
  });
131
135
  }
132
136
  });
137
+ app.command(slack.cmdTriggerBuild, async ({ command, ack, respond }) => {
138
+ await ack();
139
+ const args = command.text.trim().split(/\s+/).filter(Boolean);
140
+ const projectArg = (args[0] ?? config.azure.defaultProject).toLowerCase();
141
+ if (args.length > 1) {
142
+ const projects = AZURE_DEVOPS_PROJECT_KEYS.join("`, `");
143
+ await respond({
144
+ response_type: "ephemeral",
145
+ text: "Usage: `/trigger-build [project]`\nQueues the pipeline configured for this instance (`ENVIRONMENT=" + config.azure.instanceEnvironment + "`, Azure `" + config.azure.pipelineEnvironment + "`).\nDefault project: `" + config.azure.defaultProject + "`. Projects: `" + projects + "`"
146
+ });
147
+ return;
148
+ }
149
+ if (!isAzureDevOpsProjectKey(projectArg)) {
150
+ await respond({
151
+ response_type: "ephemeral",
152
+ text: `Unknown project \`${projectArg}\`. Use one of: \`${AZURE_DEVOPS_PROJECT_KEYS.join("`, `")}\`.`
153
+ });
154
+ return;
155
+ }
156
+ if (!config.azure.enabled) {
157
+ await respond({
158
+ response_type: "ephemeral",
159
+ text: "Azure DevOps is not configured on this server (set `AZURE_DEVOPS_ACCESS_TOKEN`)."
160
+ });
161
+ return;
162
+ }
163
+ await respond({
164
+ response_type: "in_channel",
165
+ text: `:hourglass_flowing_sand: Queuing pipeline for \`${projectArg}\` (\`${config.azure.instanceEnvironment}\` / Azure \`${config.azure.pipelineEnvironment}\`)\u2026`
166
+ });
167
+ try {
168
+ const result = await triggerPipelineBuild(config.azure, projectArg);
169
+ await respond({
170
+ response_type: "in_channel",
171
+ text: `:rocket: Pipeline run queued
172
+ ${formatPipelineRunSummary(result)}`
173
+ });
174
+ } catch (err) {
175
+ const message = err instanceof Error ? err.message : String(err);
176
+ console.error("[slack] Pipeline trigger failed:", message);
177
+ await respond({
178
+ response_type: "ephemeral",
179
+ text: `:x: Failed to trigger build: ${message}`
180
+ });
181
+ }
182
+ });
133
183
  app.command("/list-projects", async ({ ack }) => {
134
184
  const projectNames = Object.keys(allProjects);
135
185
  const lines = projectNames.map((name, i) => `${i + 1}. ${name}`);
@@ -180,4 +230,4 @@ export {
180
230
  notifySlack,
181
231
  startSlackBot
182
232
  };
183
- //# sourceMappingURL=chunk-YWUFALDR.js.map
233
+ //# sourceMappingURL=chunk-HD7E5M5O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/slack.ts"],"sourcesContent":["import { App } from '@slack/bolt';\nimport {\n AZURE_DEVOPS_PROJECT_KEYS,\n formatPipelineRunSummary,\n isAzureDevOpsProjectKey,\n triggerPipelineBuild,\n} from './adapters/azure';\nimport { config, type CMSProvider } from './config';\nimport { syncCmsContent, syncTranslations, summariseTranslationEntries, summariseCmsEntries, summariseCmsErrors } from './sync/engine';\nimport { allProjects } from '../shared/lingohub';\n\nconst VALID_CMS: CMSProvider[] = ['contentful', 'sanity'];\n\nlet app: App | null = null;\n\n/**\n * Post a message to the configured Slack notify channel.\n * Silently logs and returns if Slack is not configured or no channel is set.\n */\nexport async function notifySlack(text: string): Promise<void> {\n const { notifyChannel } = config.slack;\n if (!app || !notifyChannel) {\n console.log('[slack] Notification skipped (no app or channel configured)');\n return;\n }\n try {\n await app.client.chat.postMessage({ channel: notifyChannel, text: `[${config.environment}] ${text}` });\n } catch (err) {\n console.error('[slack] Failed to send notification:', err);\n }\n}\n\nexport async function startSlackBot(): Promise<void> {\n const { slack } = config;\n if (!slack.enabled) {\n console.log('[slack] Disabled (SLACK_BOT_TOKEN not set)');\n return;\n }\n\n app = new App({\n token: slack.botToken,\n signingSecret: slack.signingSecret,\n appToken: slack.appToken,\n socketMode: true,\n });\n\n app.command(slack.cmdSyncContent, async ({ command, ack, respond }) => {\n await ack();\n\n const args = command.text.trim().split(/\\s+/).filter(Boolean);\n const cms = (args[0] ?? 'contentful') as CMSProvider;\n const contentTypes = args.length > 1 ? args.slice(1) : undefined;\n\n if (!VALID_CMS.includes(cms)) {\n await respond({\n response_type: 'ephemeral',\n text: `Invalid CMS provider \\`${cms}\\`. Use \\`contentful\\` or \\`sanity\\`.`,\n });\n return;\n }\n\n const typesHint = contentTypes?.length ? ` (types: ${contentTypes.join(', ')})` : ' (all types)';\n await respond({\n response_type: 'in_channel',\n text: `:hourglass_flowing_sand: Sync started for *${cms}*${typesHint}…`,\n });\n\n try {\n const result = await syncCmsContent(cms, contentTypes);\n\n const blocks: string[] = [\n result.errors.length === 0\n ? `:white_check_mark: *Sync complete* — ${result.entries.length} content types synced`\n : `:warning: *Sync complete with errors* — ${result.entries.length} succeeded, ${result.errors.length} failed`,\n '',\n ...summariseCmsEntries(result),\n ];\n\n if (result.errors.length > 0) {\n blocks.push('', '*Errors:*', ...summariseCmsErrors(result));\n }\n\n await respond({ response_type: 'in_channel', text: blocks.join('\\n') });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error('[slack] Sync failed:', message);\n await respond({\n response_type: 'ephemeral',\n text: `:x: Sync failed: ${message}`,\n });\n }\n });\n\n app.command(slack.cmdSyncTranslations, async ({ command, ack, respond }) => {\n await ack();\n\n const raw = command.text.trim();\n if (!raw) {\n await respond({\n response_type: 'ephemeral',\n text:\n 'Usage: `/sync-translations <comma-separated projects> [<comma-separated locales>]`\\n' +\n 'Example: `/sync-translations tandem,tandem-(website) en,de,fr`\\n' +\n 'Omit locales to use the default locale list.',\n });\n return;\n }\n\n const spaceIdx = raw.indexOf(' ');\n const projectsPart = spaceIdx === -1 ? raw : raw.slice(0, spaceIdx).trim();\n const localesPart = spaceIdx === -1 ? '' : raw.slice(spaceIdx + 1).trim();\n\n const projects = projectsPart\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n if (projects.length === 0) {\n await respond({\n response_type: 'ephemeral',\n text:\n 'No valid projects. Pass a comma-separated list as the first argument, e.g. `tandem,tandem-(website)`.',\n });\n return;\n }\n\n let locales: string[] | undefined = localesPart.length\n ? localesPart\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : undefined;\n if (locales && locales.length === 0) {\n locales = undefined;\n }\n\n const localesHint =\n locales?.length ? ` — locales: ${locales?.join(', ')}` : ' — default locales';\n await respond({\n response_type: 'in_channel',\n text: `:hourglass_flowing_sand: Translation sync started for projects: *${projects.join(', ')}*${localesHint}…`,\n });\n\n try {\n const result = await syncTranslations(projects, locales);\n\n const lines = summariseTranslationEntries(result.entries);\n const errorLines = result.errors.map(\n (e) =>\n `• \\`${e.project}\\` / \\`${e.locale}\\` — ${e.error}`,\n );\n\n const blocks: string[] = [\n result.errors.length === 0\n ? `:white_check_mark: *Translation sync complete* — ${result.entries.length} resource × locale uploads`\n : `:warning: *Translation sync complete with errors* — ${result.entries.length} succeeded, ${result.errors.length} failed`,\n '',\n ...lines,\n ];\n\n if (errorLines.length > 0) {\n blocks.push('', '*Errors:*', ...errorLines);\n }\n\n await respond({ response_type: 'in_channel', text: blocks.join('\\n') });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error('[slack] Sync failed:', message);\n await respond({\n response_type: 'ephemeral',\n text: `:x: Sync failed: ${message}`,\n });\n }\n });\n\n app.command(slack.cmdTriggerBuild, async ({ command, ack, respond }) => {\n await ack();\n\n const args = command.text.trim().split(/\\s+/).filter(Boolean);\n const projectArg = (args[0] ?? config.azure.defaultProject).toLowerCase();\n\n if (args.length > 1) {\n const projects = AZURE_DEVOPS_PROJECT_KEYS.join('`, `');\n await respond({\n response_type: 'ephemeral',\n text:\n 'Usage: `/trigger-build [project]`\\n' +\n 'Queues the pipeline configured for this instance (`ENVIRONMENT=' +\n config.azure.instanceEnvironment +\n '`, Azure `' +\n config.azure.pipelineEnvironment +\n '`).\\n' +\n 'Default project: `' +\n config.azure.defaultProject +\n '`. Projects: `' +\n projects +\n '`',\n });\n return;\n }\n\n if (!isAzureDevOpsProjectKey(projectArg)) {\n await respond({\n response_type: 'ephemeral',\n text:\n `Unknown project \\`${projectArg}\\`. Use one of: \\`${AZURE_DEVOPS_PROJECT_KEYS.join('`, `')}\\`.`,\n });\n return;\n }\n\n if (!config.azure.enabled) {\n await respond({\n response_type: 'ephemeral',\n text: 'Azure DevOps is not configured on this server (set `AZURE_DEVOPS_ACCESS_TOKEN`).',\n });\n return;\n }\n\n await respond({\n response_type: 'in_channel',\n text:\n `:hourglass_flowing_sand: Queuing pipeline for \\`${projectArg}\\` ` +\n `(\\`${config.azure.instanceEnvironment}\\` / Azure \\`${config.azure.pipelineEnvironment}\\`)…`,\n });\n\n try {\n const result = await triggerPipelineBuild(config.azure, projectArg);\n await respond({\n response_type: 'in_channel',\n text: `:rocket: Pipeline run queued\\n${formatPipelineRunSummary(result)}`,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error('[slack] Pipeline trigger failed:', message);\n await respond({\n response_type: 'ephemeral',\n text: `:x: Failed to trigger build: ${message}`,\n });\n }\n });\n\n app.command('/list-projects', async ({ ack }) => {\n const projectNames = Object.keys(allProjects);\n const lines = projectNames.map((name, i) => `${i + 1}. ${name}`);\n await ack({\n response_type: 'ephemeral',\n text: `*Available Lingohub projects:*\\n${lines.join('\\n')}`,\n });\n });\n\n app.command('/list-resources', async ({ command, ack }) => {\n const project = command.text.trim();\n if (!project) {\n const projectNames = Object.keys(allProjects);\n const lines = projectNames.map((name, i) => `${i + 1}. ${name}`);\n await ack({\n response_type: 'ephemeral',\n text: `Usage: \\`/list-resources <project-name>\\`\\n\\n*Available projects:*\\n${lines.join('\\n')}`,\n });\n return;\n }\n\n const resources = allProjects[project];\n if (!resources) {\n const available = Object.keys(allProjects).join(', ');\n await ack({\n response_type: 'ephemeral',\n text: `Unknown project \\`${project}\\`.\\nAvailable projects: ${available}`,\n });\n return;\n }\n\n const lines = resources.map(\n (r, i) => `${i + 1}. \\`${r.resource}\\` (${r.fileName})`,\n );\n await ack({\n response_type: 'ephemeral',\n text: `*Resources for \"${project}\":*\\n${lines.join('\\n')}`,\n });\n });\n\n await app.start();\n console.log('[slack] Bot connected via Socket Mode');\n}\n"],"mappings":"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","names":["lines"]}
@@ -5,7 +5,7 @@ import dotenv from "dotenv";
5
5
  dotenv.config({ path: ".env.local" });
6
6
  dotenv.config();
7
7
  var config = {
8
- environment: process.env.ENVIRONMENT || "development",
8
+ environment: process.env.ENVIRONMENT?.trim() || "development",
9
9
  s3: {
10
10
  bucket: process.env.CONTENT_STORE_S3_BUCKET ?? "",
11
11
  region: process.env.CONTENT_STORE_S3_REGION ?? "eu-central-1",
@@ -17,4 +17,4 @@ var config = {
17
17
  export {
18
18
  config
19
19
  };
20
- //# sourceMappingURL=chunk-Y6HC4NYU.js.map
20
+ //# sourceMappingURL=chunk-OCAIIQZW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/config.ts"],"sourcesContent":["import dotenv from 'dotenv';\nimport type { S3Config, CMSProvider } from './types';\n\ndotenv.config({ path: '.env.local' });\ndotenv.config();\n\nexport type { CMSProvider, S3Config };\n\nexport interface SharedConfig {\n /** Instance label (AWS convention: beta, live, development, …). */\n environment: string;\n s3: S3Config;\n}\n\nexport const config: SharedConfig = {\n environment: process.env.ENVIRONMENT?.trim() || 'development',\n s3: {\n bucket: process.env.CONTENT_STORE_S3_BUCKET ?? '',\n region: process.env.CONTENT_STORE_S3_REGION ?? 'eu-central-1',\n accessKeyId: process.env.CONTENT_STORE_AWS_ACCESS_KEY ?? '',\n secretAccessKey: process.env.CONTENT_STORE_AWS_SECRET_ACCESS_KEY ?? '',\n }\n};\n"],"mappings":";;;AAAA,OAAO,YAAY;AAGnB,OAAO,OAAO,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,OAAO;AAUP,IAAM,SAAuB;AAAA,EAChC,aAAa,QAAQ,IAAI,aAAa,KAAK,KAAK;AAAA,EAChD,IAAI;AAAA,IACA,QAAQ,QAAQ,IAAI,2BAA2B;AAAA,IAC/C,QAAQ,QAAQ,IAAI,2BAA2B;AAAA,IAC/C,aAAa,QAAQ,IAAI,gCAAgC;AAAA,IACzD,iBAAiB,QAAQ,IAAI,uCAAuC;AAAA,EACxE;AACJ;","names":[]}
@@ -54,7 +54,7 @@ var allProjects = {
54
54
  },
55
55
  {
56
56
  resource: "ipad",
57
- fileName: "MainiPad.[locale].strings",
57
+ fileName: "Main_iPad.[locale].strings",
58
58
  type: "strings",
59
59
  localeMapping: localeMapping.ios
60
60
  },
@@ -242,4 +242,4 @@ export {
242
242
  defaultLocales,
243
243
  allProjects
244
244
  };
245
- //# sourceMappingURL=chunk-SF7FCBR2.js.map
245
+ //# sourceMappingURL=chunk-RZLDRXNQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/lingohub.ts"],"sourcesContent":["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"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  config
4
- } from "./chunk-Y6HC4NYU.js";
4
+ } from "./chunk-OCAIIQZW.js";
5
5
 
6
6
  // src/client/config.ts
7
7
  import dotenv from "dotenv";
@@ -14,4 +14,4 @@ var config2 = {
14
14
  export {
15
15
  config2 as config
16
16
  };
17
- //# sourceMappingURL=chunk-MOGVAQ2N.js.map
17
+ //# sourceMappingURL=chunk-SDEERVPV.js.map
@@ -15,9 +15,7 @@ var ContentStore = class {
15
15
  credentials: {
16
16
  accessKeyId: cfg.accessKeyId,
17
17
  secretAccessKey: cfg.secretAccessKey
18
- },
19
- requestChecksumCalculation: "WHEN_REQUIRED",
20
- responseChecksumValidation: "WHEN_REQUIRED"
18
+ }
21
19
  });
22
20
  this.bucket = cfg.bucket;
23
21
  }
@@ -181,4 +179,4 @@ export {
181
179
  toFlatStringMap,
182
180
  translationJsonOutputPath
183
181
  };
184
- //# sourceMappingURL=chunk-LZHYKLAU.js.map
182
+ //# sourceMappingURL=chunk-VBJ6LVMY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/s3.ts","../src/shared/translationResource.ts","../src/shared/utils.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 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};"],"mappings":"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","names":["path","out"]}