@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.
- package/README.md +103 -5
- package/dist/{chunk-D2F7FQEM.js → chunk-D723FMZ2.js} +3 -3
- package/dist/{chunk-YWUFALDR.js → chunk-HD7E5M5O.js} +55 -5
- package/dist/chunk-HD7E5M5O.js.map +1 -0
- package/dist/{chunk-Y6HC4NYU.js → chunk-OCAIIQZW.js} +2 -2
- package/dist/chunk-OCAIIQZW.js.map +1 -0
- package/dist/{chunk-SF7FCBR2.js → chunk-RZLDRXNQ.js} +2 -2
- package/dist/chunk-RZLDRXNQ.js.map +1 -0
- package/dist/{chunk-MOGVAQ2N.js → chunk-SDEERVPV.js} +2 -2
- package/dist/{chunk-LZHYKLAU.js → chunk-VBJ6LVMY.js} +2 -4
- package/dist/chunk-VBJ6LVMY.js.map +1 -0
- package/dist/{chunk-NQHWG4XM.js → chunk-ZX5KC4F5.js} +447 -18
- package/dist/chunk-ZX5KC4F5.js.map +1 -0
- package/dist/client/fetch-content-bundles.js +5 -5
- package/dist/client/fetch-merged-translation-bundles.js +5 -5
- package/dist/client/fetch-translation-bundles.js +5 -5
- package/dist/client/list-projects.js +1 -1
- package/dist/client/list-resources.js +1 -1
- package/dist/client/query-cms.js +3 -3
- package/dist/node.d.ts +89 -2
- package/dist/node.js +212 -4
- package/dist/node.js.map +1 -1
- package/package.json +2 -24
- package/dist/chunk-LZHYKLAU.js.map +0 -1
- package/dist/chunk-NQHWG4XM.js.map +0 -1
- package/dist/chunk-SF7FCBR2.js.map +0 -1
- package/dist/chunk-Y6HC4NYU.js.map +0 -1
- package/dist/chunk-YWUFALDR.js.map +0 -1
- package/dist/sanity-studio.d.ts +0 -11
- package/dist/sanity-studio.js +0 -3256
- package/dist/sanity-studio.js.map +0 -1
- package/dist/sanity-studio.node.d.ts +0 -14
- package/dist/sanity-studio.node.js +0 -6
- package/dist/sanity-studio.node.js.map +0 -1
- package/dist/sanity.d.ts +0 -563
- package/dist/sanity.js +0 -2966
- package/dist/sanity.js.map +0 -1
- /package/dist/{chunk-D2F7FQEM.js.map → chunk-D723FMZ2.js.map} +0 -0
- /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-
|
|
4
|
+
} from "./chunk-OCAIIQZW.js";
|
|
5
5
|
import {
|
|
6
6
|
ContentStore,
|
|
7
7
|
buildCmsObjectKey,
|
|
8
8
|
buildTranslationObjectKey,
|
|
9
9
|
contentTypeForTranslationKey
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-VBJ6LVMY.js";
|
|
11
11
|
import {
|
|
12
12
|
allProjects,
|
|
13
13
|
defaultLocales
|
|
14
|
-
} from "./chunk-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
456
|
-
console.error(` x ${project}
|
|
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-
|
|
901
|
+
//# sourceMappingURL=chunk-ZX5KC4F5.js.map
|