@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
@@ -1,54 +1,471 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  config
4
- } from "./chunk-Y6HC4NYU.js";
4
+ } from "./chunk-OCAIIQZW.js";
5
5
  import {
6
6
  ContentStore,
7
7
  buildCmsObjectKey,
8
8
  buildTranslationObjectKey,
9
9
  contentTypeForTranslationKey
10
- } from "./chunk-LZHYKLAU.js";
10
+ } from "./chunk-VBJ6LVMY.js";
11
11
  import {
12
12
  allProjects,
13
13
  defaultLocales
14
- } from "./chunk-SF7FCBR2.js";
14
+ } from "./chunk-RZLDRXNQ.js";
15
+
16
+ // src/server/adapters/azure/types.ts
17
+ var AZURE_DEVOPS_PROJECT_KEYS = [
18
+ "web-site",
19
+ "web-app",
20
+ "web-invites"
21
+ ];
22
+
23
+ // src/server/adapters/azure/client.ts
24
+ var AzureDevOpsClient = class {
25
+ constructor(config3) {
26
+ this.config = config3;
27
+ const org = config3.organization.replace(/^\/+|\/+$/g, "");
28
+ const project = encodeURIComponent(config3.project);
29
+ this.baseUrl = `https://dev.azure.com/${org}/${project}`;
30
+ }
31
+ baseUrl;
32
+ /**
33
+ * Call any Azure DevOps REST endpoint under the configured organization and project.
34
+ */
35
+ async request(options) {
36
+ const {
37
+ method = "GET",
38
+ path,
39
+ query = {},
40
+ body,
41
+ apiVersion = this.config.apiVersion,
42
+ headers: extraHeaders = {}
43
+ } = options;
44
+ if (!this.config.pat) {
45
+ throw new Error(
46
+ "Azure DevOps is not configured (set AZURE_DEVOPS_ACCESS_TOKEN)"
47
+ );
48
+ }
49
+ const url = this.buildUrl(path, { ...query, "api-version": apiVersion });
50
+ const headers = {
51
+ Authorization: this.basicAuthHeader(),
52
+ Accept: "application/json",
53
+ ...extraHeaders
54
+ };
55
+ const init = { method, headers };
56
+ if (body !== void 0) {
57
+ headers["Content-Type"] = "application/json";
58
+ init.body = JSON.stringify(body);
59
+ }
60
+ const response = await fetch(url, init);
61
+ const text = await response.text();
62
+ let parsed;
63
+ if (text) {
64
+ try {
65
+ parsed = JSON.parse(text);
66
+ } catch {
67
+ parsed = text;
68
+ }
69
+ }
70
+ if (!response.ok) {
71
+ const errBody = parsed;
72
+ const detail = errBody?.message ?? (typeof parsed === "string" ? parsed : JSON.stringify(parsed));
73
+ throw new Error(
74
+ `Azure DevOps ${method} ${path} failed (${response.status}): ${detail}`
75
+ );
76
+ }
77
+ return parsed;
78
+ }
79
+ buildUrl(path, query) {
80
+ const base = /^https?:\/\//i.test(path) ? path : `${this.baseUrl}${path}`;
81
+ const url = new URL(base);
82
+ for (const [key, value] of Object.entries(query)) {
83
+ if (value !== void 0) {
84
+ url.searchParams.set(key, String(value));
85
+ }
86
+ }
87
+ return url.toString();
88
+ }
89
+ basicAuthHeader() {
90
+ const encoded = Buffer.from(`:${this.config.pat}`).toString("base64");
91
+ return `Basic ${encoded}`;
92
+ }
93
+ };
94
+ function createAzureDevOpsClient(config3) {
95
+ return new AzureDevOpsClient(config3);
96
+ }
97
+
98
+ // src/server/adapters/azure/pipelines.ts
99
+ function isAzureDevOpsProjectKey(value) {
100
+ return AZURE_DEVOPS_PROJECT_KEYS.includes(value);
101
+ }
102
+ function resolveProjectConfig(azureConfig, project) {
103
+ const projectConfig = azureConfig.projects[project];
104
+ if (!projectConfig) {
105
+ throw new Error(`Unknown Azure DevOps project "${project}"`);
106
+ }
107
+ return projectConfig;
108
+ }
109
+ async function triggerPipelineBuild(azureConfig, project, variables = {}) {
110
+ const { pipeline } = resolveProjectConfig(azureConfig, project);
111
+ if (!pipeline?.id) {
112
+ throw new Error(
113
+ `No pipeline id configured for project "${project}" (instance ENVIRONMENT=${azureConfig.instanceEnvironment}, Azure=${azureConfig.pipelineEnvironment})`
114
+ );
115
+ }
116
+ const client = createAzureDevOpsClient({
117
+ organization: azureConfig.organization,
118
+ project,
119
+ pat: azureConfig.pat,
120
+ apiVersion: azureConfig.apiVersion
121
+ });
122
+ const run = await client.request({
123
+ method: "POST",
124
+ path: `/_apis/pipelines/${pipeline.id}/runs`,
125
+ body: {
126
+ resources: {
127
+ repositories: {
128
+ self: {
129
+ refName: pipeline.refName
130
+ }
131
+ }
132
+ },
133
+ variables
134
+ }
135
+ });
136
+ return {
137
+ project,
138
+ instanceEnvironment: azureConfig.instanceEnvironment,
139
+ pipelineEnvironment: azureConfig.pipelineEnvironment,
140
+ pipelineId: pipeline.id,
141
+ refName: pipeline.refName,
142
+ run
143
+ };
144
+ }
145
+ function formatPipelineRunSummary(result) {
146
+ const {
147
+ run,
148
+ project,
149
+ instanceEnvironment,
150
+ pipelineEnvironment,
151
+ pipelineId,
152
+ refName
153
+ } = result;
154
+ const webUrl = run._links?.web?.href ?? run.url;
155
+ const envLabel = instanceEnvironment === pipelineEnvironment ? `\`${pipelineEnvironment}\`` : `\`${instanceEnvironment}\` \u2192 Azure \`${pipelineEnvironment}\``;
156
+ const lines = [
157
+ `Environment ${envLabel} \u2014 project \`${project}\`, pipeline id \`${pipelineId}\`, ref \`${refName}\``,
158
+ `Run #${run.id}${run.state ? ` \u2014 state: \`${run.state}\`` : ""}`
159
+ ];
160
+ if (webUrl) {
161
+ lines.push(`<${webUrl}|Open run in Azure DevOps>`);
162
+ }
163
+ return lines.join("\n");
164
+ }
165
+
166
+ // src/server/adapters/azure/environment.ts
167
+ var PRODUCTION_ALIASES = /* @__PURE__ */ new Set(["production", "live", "prod"]);
168
+ var STAGING_ALIASES = /* @__PURE__ */ new Set(["staging", "beta", "development", "dev", "local"]);
169
+ function normalizeToAzureEnvironment(instanceEnvironment) {
170
+ const key = instanceEnvironment.trim().toLowerCase();
171
+ if (!key) {
172
+ console.warn(
173
+ "[azure] Empty ENVIRONMENT; defaulting Azure pipeline environment to staging"
174
+ );
175
+ return "staging";
176
+ }
177
+ if (PRODUCTION_ALIASES.has(key)) {
178
+ return "production";
179
+ }
180
+ if (STAGING_ALIASES.has(key)) {
181
+ return "staging";
182
+ }
183
+ console.warn(
184
+ `[azure] Unrecognized ENVIRONMENT="${instanceEnvironment}"; defaulting Azure pipeline environment to staging`
185
+ );
186
+ return "staging";
187
+ }
188
+ function pipelineRefForAzure(pipelineEnvironment) {
189
+ return `refs/heads/${pipelineEnvironment}`;
190
+ }
191
+
192
+ // src/shared/content-refresh.ts
193
+ import { timingSafeEqual } from "crypto";
194
+ async function postContentRefresh(target, url, apiToken, request) {
195
+ try {
196
+ const response = await fetch(url, {
197
+ method: "POST",
198
+ headers: {
199
+ Authorization: `Bearer ${apiToken}`,
200
+ "Content-Type": "application/json",
201
+ Accept: "application/json"
202
+ },
203
+ body: JSON.stringify(request)
204
+ });
205
+ const text = await response.text();
206
+ let body;
207
+ if (text) {
208
+ try {
209
+ body = JSON.parse(text);
210
+ } catch {
211
+ body = text;
212
+ }
213
+ }
214
+ return {
215
+ target,
216
+ ok: response.ok,
217
+ status: response.status,
218
+ body,
219
+ error: response.ok ? void 0 : typeof body === "object" && body !== null && "error" in body ? String(body.error) : `HTTP ${response.status}`
220
+ };
221
+ } catch (err) {
222
+ return {
223
+ target,
224
+ ok: false,
225
+ status: 0,
226
+ error: err instanceof Error ? err.message : String(err)
227
+ };
228
+ }
229
+ }
230
+
231
+ // src/server/content-refresh-notify.ts
232
+ function isContentRefreshEnabledForInstance(instanceEnvironment) {
233
+ return normalizeToAzureEnvironment(instanceEnvironment) === "staging";
234
+ }
235
+ async function notifyContentRefreshTargets(notifyConfig, request, projects) {
236
+ if (!notifyConfig.enabled || !notifyConfig.apiToken) {
237
+ return [];
238
+ }
239
+ const keys = projects ?? AZURE_DEVOPS_PROJECT_KEYS.filter(
240
+ (p) => notifyConfig.targets[p]?.url
241
+ );
242
+ const results = [];
243
+ for (const project of keys) {
244
+ const url = notifyConfig.targets[project]?.url;
245
+ if (!url) {
246
+ continue;
247
+ }
248
+ const result = await postContentRefresh(
249
+ project,
250
+ url,
251
+ notifyConfig.apiToken,
252
+ request
253
+ );
254
+ results.push(result);
255
+ if (result.ok) {
256
+ console.log(
257
+ `[content-refresh] Notified ${project} (${result.status})`
258
+ );
259
+ } else {
260
+ console.error(
261
+ `[content-refresh] Failed to notify ${project}: ${result.error ?? result.status}`
262
+ );
263
+ }
264
+ }
265
+ return results;
266
+ }
15
267
 
16
268
  // src/server/config.ts
17
269
  import dotenv from "dotenv";
270
+
271
+ // src/server/restrictedCron.ts
272
+ var MAX_SEARCH_MINUTES = 366 * 24 * 60;
273
+ function tokenize(expr) {
274
+ return expr.trim().split(/\s+/).map((s) => s.trim()).filter(Boolean);
275
+ }
276
+ function validateScheduleCronExpression(expr) {
277
+ const parts = tokenize(expr);
278
+ if (parts.length !== 2) {
279
+ throw new Error(
280
+ `Schedule cron must be exactly two fields (minute hour), whitespace-separated; got ${parts.length} field(s): "${expr}"`
281
+ );
282
+ }
283
+ const minuteSpec = parts[0];
284
+ const hourSpec = parts[1];
285
+ parseField(minuteSpec, 0, 59, "minute");
286
+ parseField(hourSpec, 0, 23, "hour");
287
+ return `${minuteSpec} ${hourSpec}`;
288
+ }
289
+ function parseField(spec, lo, hi, fieldName) {
290
+ const subs = spec.split(",").map((s) => s.trim()).filter(Boolean);
291
+ if (subs.length === 0) {
292
+ throw new Error(`Empty ${fieldName} field in cron`);
293
+ }
294
+ const preds = subs.map((sub) => parseSubfield(sub, lo, hi, fieldName));
295
+ return (n) => preds.some((p) => p(n));
296
+ }
297
+ function parseSubfield(sub, lo, hi, fieldName) {
298
+ if (sub === "*") {
299
+ return () => true;
300
+ }
301
+ if (sub.startsWith("*/")) {
302
+ const step = parseInt(sub.slice(2), 10);
303
+ if (!Number.isFinite(step) || step < 1) {
304
+ throw new Error(`Invalid step in ${fieldName} field: "${sub}"`);
305
+ }
306
+ return (n) => n >= lo && n <= hi && n % step === 0;
307
+ }
308
+ if (sub.includes("-")) {
309
+ const [a, b] = sub.split("-").map((x) => parseInt(x.trim(), 10));
310
+ if (!Number.isFinite(a) || !Number.isFinite(b)) {
311
+ throw new Error(`Invalid range in ${fieldName} field: "${sub}"`);
312
+ }
313
+ if (a < lo || b > hi || a > b) {
314
+ throw new Error(`Range out of bounds in ${fieldName} field: "${sub}"`);
315
+ }
316
+ return (n) => n >= a && n <= b;
317
+ }
318
+ const v = parseInt(sub, 10);
319
+ if (!Number.isFinite(v) || v < lo || v > hi) {
320
+ throw new Error(`Invalid value in ${fieldName} field: "${sub}"`);
321
+ }
322
+ return (n) => n === v;
323
+ }
324
+ function floorToMinuteStart(d) {
325
+ const t = new Date(d.getTime());
326
+ t.setSeconds(0, 0);
327
+ return t;
328
+ }
329
+ function addOneMinute(d) {
330
+ const t = new Date(d.getTime());
331
+ t.setMinutes(t.getMinutes() + 1, 0, 0);
332
+ return t;
333
+ }
334
+ function matchesAtMinuteStart(minutePred, hourPred, d) {
335
+ return minutePred(d.getMinutes()) && hourPred(d.getHours());
336
+ }
337
+ function nextCronFireAfter(expr, after) {
338
+ const parts = tokenize(expr);
339
+ if (parts.length !== 2) {
340
+ throw new Error(
341
+ `nextCronFireAfter expects exactly two fields (minute hour); got ${parts.length}: "${expr}"`
342
+ );
343
+ }
344
+ const minutePred = parseField(parts[0], 0, 59, "minute");
345
+ const hourPred = parseField(parts[1], 0, 23, "hour");
346
+ let d = floorToMinuteStart(after);
347
+ if (d.getTime() <= after.getTime()) {
348
+ d = addOneMinute(d);
349
+ }
350
+ for (let i = 0; i < MAX_SEARCH_MINUTES; i++) {
351
+ if (matchesAtMinuteStart(minutePred, hourPred, d)) {
352
+ return d;
353
+ }
354
+ d = addOneMinute(d);
355
+ }
356
+ throw new Error(`No cron match within ${MAX_SEARCH_MINUTES} minutes for "${expr}"`);
357
+ }
358
+
359
+ // src/server/config.ts
18
360
  dotenv.config({ path: ".env.local" });
19
361
  dotenv.config();
362
+ function readScheduleCronEnv() {
363
+ return {
364
+ scheduleCron: (process.env.SCHEDULE_CRON ?? "").trim(),
365
+ cmsCronRaw: (process.env.SCHEDULE_CMS_CRON ?? "").trim(),
366
+ translationCronRaw: (process.env.SCHEDULE_TRANSLATION_CRON ?? "").trim()
367
+ };
368
+ }
369
+ function resolveScheduleCron(jobSpecific, globalCron, jobLabel) {
370
+ const raw = jobSpecific || globalCron;
371
+ if (!raw) return null;
372
+ try {
373
+ return validateScheduleCronExpression(raw);
374
+ } catch (e) {
375
+ const msg = e instanceof Error ? e.message : String(e);
376
+ console.error(`[config] Invalid ${jobLabel} schedule cron: ${msg}`);
377
+ return null;
378
+ }
379
+ }
20
380
  function parseScheduledCmsJobConfig() {
21
- const intervalMinutes = parseInt(process.env.SCHEDULE_INTERVAL_MINUTES ?? "0", 10);
22
- const enabled = Number.isFinite(intervalMinutes) && intervalMinutes > 0;
381
+ const { scheduleCron, cmsCronRaw } = readScheduleCronEnv();
23
382
  const runOnStart = (process.env.SCHEDULE_RUN_ON_START ?? "true").toLowerCase() !== "false";
24
- const task = (process.env.SCHEDULE_TASK ?? "sync").trim();
25
383
  const syncCms = (process.env.SCHEDULE_SYNC_CMS ?? "").trim();
26
- const rawTypes = process.env.SCHEDULE_SYNC_TYPES?.trim();
384
+ const rawTypes = process.env.SCHEDULE_SYNC_CONTENT_TYPES?.trim();
27
385
  const syncTypes = rawTypes ? rawTypes.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
386
+ const resolved = resolveScheduleCron(cmsCronRaw, scheduleCron, "CMS");
387
+ const cmsOk = !!syncCms && (syncCms === "contentful" || syncCms === "sanity");
388
+ if (resolved !== null && !cmsOk) {
389
+ console.warn(
390
+ '[config] CMS schedule cron is set but SCHEDULE_SYNC_CMS is missing or not "contentful"|"sanity"; scheduled CMS sync is disabled.'
391
+ );
392
+ }
393
+ const enabled = resolved !== null && cmsOk;
28
394
  return {
29
395
  enabled,
30
- intervalMinutes,
396
+ cronExpression: resolved ?? "",
31
397
  runOnStart,
32
- task,
33
398
  syncCms: syncCms || void 0,
34
399
  syncTypes
35
400
  };
36
401
  }
37
402
  function parseScheduledTranslationJobConfig() {
38
- const intervalMinutes = parseInt(process.env.SCHEDULE_INTERVAL_MINUTES ?? "0", 10);
39
- const enabled = Number.isFinite(intervalMinutes) && intervalMinutes > 0;
403
+ const { scheduleCron, translationCronRaw } = readScheduleCronEnv();
40
404
  const runOnStart = (process.env.SCHEDULE_RUN_ON_START ?? "true").toLowerCase() !== "false";
41
- const task = (process.env.SCHEDULE_TASK ?? "sync").trim();
42
405
  const rawProjects = process.env.SCHEDULE_SYNC_TRANSLATION_PROJECTS?.trim();
43
406
  const syncProjects = rawProjects ? rawProjects.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
407
+ const resolved = resolveScheduleCron(
408
+ translationCronRaw,
409
+ scheduleCron,
410
+ "translation"
411
+ );
412
+ const enabled = resolved !== null;
44
413
  return {
45
414
  enabled,
46
- intervalMinutes,
415
+ cronExpression: resolved ?? "",
47
416
  runOnStart,
48
- task,
49
417
  syncProjects
50
418
  };
51
419
  }
420
+ function projectEnvSlug(project) {
421
+ return project.toUpperCase().replace(/-/g, "_");
422
+ }
423
+ function readProjectPipeline(project, pipelineEnvironment) {
424
+ const slug = projectEnvSlug(project);
425
+ const id = parseInt(process.env[`AZURE_${slug}_PIPELINE_ID`]?.trim() ?? "0", 10);
426
+ return { id, refName: pipelineRefForAzure(pipelineEnvironment) };
427
+ }
428
+ function parseAzureDevOpsConfig(instanceEnvironment) {
429
+ const pipelineEnvironment = normalizeToAzureEnvironment(instanceEnvironment);
430
+ const pat = process.env.AZURE_DEVOPS_ACCESS_TOKEN?.trim() ?? "";
431
+ const defaultProjectRaw = (process.env.AZURE_DEVOPS_DEFAULT_PROJECT ?? "web-site").trim();
432
+ const defaultProject = AZURE_DEVOPS_PROJECT_KEYS.includes(
433
+ defaultProjectRaw
434
+ ) ? defaultProjectRaw : "web-site";
435
+ const projects = Object.fromEntries(
436
+ AZURE_DEVOPS_PROJECT_KEYS.map((project) => [
437
+ project,
438
+ { pipeline: readProjectPipeline(project, pipelineEnvironment) }
439
+ ])
440
+ );
441
+ return {
442
+ enabled: pat.length > 0,
443
+ organization: process.env.AZURE_DEVOPS_ORGANIZATION?.trim() || "tripod-technology",
444
+ pat,
445
+ apiVersion: process.env.AZURE_DEVOPS_API_VERSION?.trim() || "7.1",
446
+ defaultProject,
447
+ instanceEnvironment,
448
+ pipelineEnvironment,
449
+ projects
450
+ };
451
+ }
452
+ function readContentRefreshUrl(project) {
453
+ const slug = projectEnvSlug(project);
454
+ return process.env[`AZURE_${slug}_CONTENT_REFRESH_URL`]?.trim() || void 0;
455
+ }
456
+ function parseContentRefreshConfig(instanceEnvironment, contentStoreApiToken) {
457
+ const targets = Object.fromEntries(
458
+ AZURE_DEVOPS_PROJECT_KEYS.map((project) => [
459
+ project,
460
+ { url: readContentRefreshUrl(project) }
461
+ ])
462
+ );
463
+ return {
464
+ enabled: isContentRefreshEnabledForInstance(instanceEnvironment),
465
+ apiToken: process.env.CONTENT_REFRESH_API_TOKEN?.trim() || contentStoreApiToken,
466
+ targets
467
+ };
468
+ }
52
469
  var config2 = {
53
470
  ...config,
54
471
  contentful: {
@@ -89,6 +506,11 @@ var config2 = {
89
506
  },
90
507
  scheduledCmsJob: parseScheduledCmsJobConfig(),
91
508
  scheduledTranslationJob: parseScheduledTranslationJobConfig(),
509
+ azure: parseAzureDevOpsConfig(config.environment),
510
+ contentRefresh: parseContentRefreshConfig(
511
+ config.environment,
512
+ process.env.CONTENT_STORE_API_TOKEN ?? ""
513
+ ),
92
514
  slack: {
93
515
  enabled: (process.env.SLACK_BOT_TOKEN ?? "").length > 0,
94
516
  botToken: process.env.SLACK_BOT_TOKEN ?? "",
@@ -96,7 +518,8 @@ var config2 = {
96
518
  appToken: process.env.SLACK_APP_TOKEN ?? "",
97
519
  notifyChannel: process.env.SLACK_NOTIFY_CHANNEL ?? "",
98
520
  cmdSyncContent: process.env.SLACK_CMD_SYNC_CONTENT ?? "/sync-content",
99
- cmdSyncTranslations: process.env.SLACK_CMD_SYNC_TRANSLATIONS ?? "/sync-translations"
521
+ cmdSyncTranslations: process.env.SLACK_CMD_SYNC_TRANSLATIONS ?? "/sync-translations",
522
+ cmdTriggerBuild: process.env.SLACK_CMD_TRIGGER_BUILD ?? "/trigger-build"
100
523
  }
101
524
  };
102
525
 
@@ -452,8 +875,8 @@ async function syncTranslations(projects, locales) {
452
875
  );
453
876
  } catch (err) {
454
877
  const message = err instanceof Error ? err.message : String(err);
455
- errors.push({ project, locale, resource: resource.resource, error: message });
456
- console.error(` x ${project} / ${resource.resource} - ${locale}: ${message}`);
878
+ errors.push({ project, locale, error: message });
879
+ console.error(` x ${project} - ${locale}: ${message}`);
457
880
  }
458
881
  }
459
882
  }
@@ -462,6 +885,12 @@ async function syncTranslations(projects, locales) {
462
885
  }
463
886
 
464
887
  export {
888
+ AZURE_DEVOPS_PROJECT_KEYS,
889
+ isAzureDevOpsProjectKey,
890
+ triggerPipelineBuild,
891
+ formatPipelineRunSummary,
892
+ notifyContentRefreshTargets,
893
+ nextCronFireAfter,
465
894
  config2 as config,
466
895
  summariseCmsEntries,
467
896
  summariseCmsErrors,
@@ -469,4 +898,4 @@ export {
469
898
  syncCmsContent,
470
899
  syncTranslations
471
900
  };
472
- //# sourceMappingURL=chunk-NQHWG4XM.js.map
901
+ //# sourceMappingURL=chunk-ZX5KC4F5.js.map