@verbatra/sdk 0.8.0 → 0.9.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 +44 -15
- package/dist/config-schema.json +306 -0
- package/dist/index.cjs +725 -290
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +390 -75
- package/dist/index.d.ts +390 -75
- package/dist/index.js +724 -292
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var path = require('path');
|
|
4
4
|
var zod = require('zod');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
var promises = require('fs/promises');
|
|
5
7
|
var fs = require('fs');
|
|
6
8
|
var cosmiconfig = require('cosmiconfig');
|
|
7
9
|
var cosmiconfigTypescriptLoader = require('cosmiconfig-typescript-loader');
|
|
8
|
-
var crypto = require('crypto');
|
|
9
|
-
var promises = require('fs/promises');
|
|
10
10
|
var Anthropic = require('@anthropic-ai/sdk');
|
|
11
11
|
var deepl = require('deepl-node');
|
|
12
12
|
var module$1 = require('module');
|
|
@@ -297,52 +297,238 @@ var defaultFs = {
|
|
|
297
297
|
await promises.mkdir(path, { recursive: true });
|
|
298
298
|
}
|
|
299
299
|
};
|
|
300
|
+
var LOCAL_DIR_NAME = ".verbatra-local";
|
|
301
|
+
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
302
|
+
var GLOSSARY_GUARD_STEM = "_glossary";
|
|
303
|
+
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
304
|
+
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
305
|
+
var WAIT_NOTICE_INTERVAL_MS = 1e3;
|
|
306
|
+
var MAX_LOCK_PAYLOAD_BYTES = 64 * 1024;
|
|
307
|
+
function lockPath(cwd, stem) {
|
|
308
|
+
return path.resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
309
|
+
}
|
|
310
|
+
function localeLockPath(cwd, locale) {
|
|
311
|
+
return lockPath(cwd, locale);
|
|
312
|
+
}
|
|
313
|
+
function lockFileGuardPath(cwd) {
|
|
314
|
+
return lockPath(cwd, LOCK_FILE_GUARD_STEM);
|
|
315
|
+
}
|
|
316
|
+
function glossaryGuardPath(cwd) {
|
|
317
|
+
return lockPath(cwd, GLOSSARY_GUARD_STEM);
|
|
318
|
+
}
|
|
319
|
+
function sleep(ms) {
|
|
320
|
+
return new Promise((res) => {
|
|
321
|
+
setTimeout(res, ms);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
function lockPayload() {
|
|
325
|
+
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
326
|
+
}
|
|
327
|
+
function parseHolder(read) {
|
|
328
|
+
if (read.kind !== "ok") {
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
let parsed;
|
|
332
|
+
try {
|
|
333
|
+
parsed = JSON.parse(read.content);
|
|
334
|
+
} catch {
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
const record = parsed;
|
|
341
|
+
const holder = {};
|
|
342
|
+
if (typeof record.pid === "number") {
|
|
343
|
+
holder.pid = record.pid;
|
|
344
|
+
}
|
|
345
|
+
if (typeof record.acquiredAt === "string") {
|
|
346
|
+
holder.acquiredAt = record.acquiredAt;
|
|
347
|
+
}
|
|
348
|
+
return holder;
|
|
349
|
+
}
|
|
350
|
+
function makeWaitNotifier(path, fs, onWait, start) {
|
|
351
|
+
let holder;
|
|
352
|
+
let holderRead = false;
|
|
353
|
+
let lastEmit;
|
|
354
|
+
return async () => {
|
|
355
|
+
if (!holderRead) {
|
|
356
|
+
holderRead = true;
|
|
357
|
+
holder = parseHolder(await fs.readFileBounded(path, MAX_LOCK_PAYLOAD_BYTES));
|
|
358
|
+
}
|
|
359
|
+
const elapsedMs = Date.now() - start;
|
|
360
|
+
if (lastEmit !== void 0 && elapsedMs - lastEmit < WAIT_NOTICE_INTERVAL_MS) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
lastEmit = elapsedMs;
|
|
364
|
+
onWait({ lockPath: path, elapsedMs, ...holder !== void 0 ? { holder } : {} });
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
async function acquireLock(path, fs, pollIntervalMs, deadline, notify) {
|
|
368
|
+
for (; ; ) {
|
|
369
|
+
if (await fs.createExclusive(path, lockPayload())) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (notify !== void 0) {
|
|
373
|
+
await notify();
|
|
374
|
+
}
|
|
375
|
+
if (Date.now() >= deadline) {
|
|
376
|
+
throw new SdkError(
|
|
377
|
+
"LOCK_CONTENDED",
|
|
378
|
+
`Could not acquire the write lock at ${path}: another process may be holding it. If no verbatra process is currently running, this lock file was likely left behind by one that was killed; delete it and retry.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const jitter = Math.random() * pollIntervalMs;
|
|
382
|
+
await sleep(pollIntervalMs + jitter);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function withFileLock(path, fs, fn, options) {
|
|
386
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
387
|
+
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
388
|
+
const start = Date.now();
|
|
389
|
+
const notify = options.onWait !== void 0 ? makeWaitNotifier(path, fs, options.onWait, start) : void 0;
|
|
390
|
+
await acquireLock(path, fs, pollIntervalMs, start + acquireTimeoutMs, notify);
|
|
391
|
+
try {
|
|
392
|
+
return await fn();
|
|
393
|
+
} finally {
|
|
394
|
+
await fs.deleteFile(path);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async function withLocaleWriteLock(cwd, locale, fs, fn, options = {}) {
|
|
398
|
+
return withFileLock(localeLockPath(cwd, locale), fs, fn, options);
|
|
399
|
+
}
|
|
400
|
+
async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
401
|
+
return withFileLock(lockFileGuardPath(cwd), fs, fn, options);
|
|
402
|
+
}
|
|
403
|
+
async function withGlossaryGuard(cwd, fs, fn, options = {}) {
|
|
404
|
+
return withFileLock(glossaryGuardPath(cwd), fs, fn, options);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/config/glossary-file.ts
|
|
300
408
|
var MAX_GLOSSARY_FILE_BYTES = 1024 * 1024;
|
|
301
|
-
var
|
|
409
|
+
var DEFAULT_INDENT = " ";
|
|
410
|
+
var INDENT_PATTERN = /^([ \t]+)"/m;
|
|
302
411
|
var BOM = "\uFEFF";
|
|
303
412
|
var REPLACEMENT_CHARACTER = "\uFFFD";
|
|
304
413
|
var NUL = "\0";
|
|
414
|
+
var glossaryRecordSchema = zod.z.record(zod.z.string(), zod.z.string());
|
|
305
415
|
function stripBom(content) {
|
|
306
416
|
return content.startsWith(BOM) ? content.slice(BOM.length) : content;
|
|
307
417
|
}
|
|
308
418
|
function looksLikeInvalidEncoding(content) {
|
|
309
419
|
return content.startsWith(REPLACEMENT_CHARACTER) || content.includes(NUL);
|
|
310
420
|
}
|
|
311
|
-
|
|
312
|
-
|
|
421
|
+
function detectIndent(content) {
|
|
422
|
+
return INDENT_PATTERN.exec(content)?.[1] ?? DEFAULT_INDENT;
|
|
423
|
+
}
|
|
424
|
+
async function readGlossaryDocument(path, fs) {
|
|
425
|
+
const read = await fs.readFileBounded(path, MAX_GLOSSARY_FILE_BYTES);
|
|
313
426
|
if (read.kind === "missing") {
|
|
314
427
|
throw new SdkError(
|
|
315
428
|
"CONFIG_INVALID",
|
|
316
|
-
`The glossary file at ${
|
|
429
|
+
`The glossary file at ${path} was not found or could not be read.`
|
|
317
430
|
);
|
|
318
431
|
}
|
|
319
432
|
if (read.kind === "too-large") {
|
|
320
433
|
throw new SdkError(
|
|
321
434
|
"CONFIG_INVALID",
|
|
322
|
-
`The glossary file at ${
|
|
435
|
+
`The glossary file at ${path} exceeds the maximum allowed size of ${MAX_GLOSSARY_FILE_BYTES} bytes.`
|
|
323
436
|
);
|
|
324
437
|
}
|
|
325
438
|
const content = stripBom(read.content);
|
|
326
439
|
if (looksLikeInvalidEncoding(content)) {
|
|
327
|
-
throw new SdkError(
|
|
328
|
-
"CONFIG_INVALID",
|
|
329
|
-
`The glossary file at ${resolvedPath} must be UTF-8 encoded.`
|
|
330
|
-
);
|
|
440
|
+
throw new SdkError("CONFIG_INVALID", `The glossary file at ${path} must be UTF-8 encoded.`);
|
|
331
441
|
}
|
|
332
442
|
let parsed;
|
|
333
443
|
try {
|
|
334
444
|
parsed = JSON.parse(content);
|
|
335
445
|
} catch {
|
|
336
|
-
throw new SdkError("CONFIG_INVALID", `The glossary file at ${
|
|
446
|
+
throw new SdkError("CONFIG_INVALID", `The glossary file at ${path} is not valid JSON.`);
|
|
337
447
|
}
|
|
338
448
|
const result = glossaryRecordSchema.safeParse(parsed);
|
|
339
449
|
if (!result.success) {
|
|
340
450
|
throw new SdkError(
|
|
341
451
|
"CONFIG_INVALID",
|
|
342
|
-
`The glossary file at ${
|
|
452
|
+
`The glossary file at ${path} must contain a flat object of string keys to string values.`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
entries: result.data,
|
|
457
|
+
indent: detectIndent(content),
|
|
458
|
+
trailingNewline: content.endsWith("\n") ? "\n" : ""
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
async function readGlossaryRecord(path, fs) {
|
|
462
|
+
return (await readGlossaryDocument(path, fs)).entries;
|
|
463
|
+
}
|
|
464
|
+
function glossaryFilePath(glossary) {
|
|
465
|
+
if (glossary.source !== "file") {
|
|
466
|
+
throw new SdkError(
|
|
467
|
+
"GLOSSARY_NOT_FILE_BACKED",
|
|
468
|
+
`The glossary is ${glossary.source === "inline" ? "written inline in the config" : "not configured"}, so there is no glossary file to work with. Point the config's glossary at a JSON file first.`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
return glossary.path;
|
|
472
|
+
}
|
|
473
|
+
function assertWritableTerm(term, translation) {
|
|
474
|
+
if (term.trim().length === 0) {
|
|
475
|
+
throw new SdkError("CONFIG_INVALID", "A glossary term must not be blank.");
|
|
476
|
+
}
|
|
477
|
+
if (translation !== null && translation.trim().length === 0) {
|
|
478
|
+
throw new SdkError(
|
|
479
|
+
"CONFIG_INVALID",
|
|
480
|
+
`The translation for the glossary term "${term}" must not be blank. Remove the term instead.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function applyTerm(current, term, translation) {
|
|
485
|
+
const pairs = Object.entries(current);
|
|
486
|
+
const index = pairs.findIndex(([existing]) => existing === term);
|
|
487
|
+
if (translation === null) {
|
|
488
|
+
if (index >= 0) {
|
|
489
|
+
pairs.splice(index, 1);
|
|
490
|
+
}
|
|
491
|
+
} else if (index >= 0) {
|
|
492
|
+
pairs[index] = [term, translation];
|
|
493
|
+
} else {
|
|
494
|
+
pairs.push([term, translation]);
|
|
495
|
+
}
|
|
496
|
+
return Object.fromEntries(pairs);
|
|
497
|
+
}
|
|
498
|
+
function serializeGlossary(entries, document, path) {
|
|
499
|
+
const serialized = `${JSON.stringify(entries, null, document.indent)}${document.trailingNewline}`;
|
|
500
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_GLOSSARY_FILE_BYTES) {
|
|
501
|
+
throw new SdkError(
|
|
502
|
+
"CONFIG_INVALID",
|
|
503
|
+
`The updated glossary file at ${path} would exceed the maximum allowed size of ${MAX_GLOSSARY_FILE_BYTES} bytes.`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return serialized;
|
|
507
|
+
}
|
|
508
|
+
async function writeGlossary(path, serialized, fs) {
|
|
509
|
+
try {
|
|
510
|
+
await fs.writeFile(path, serialized);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
throw new SdkError(
|
|
513
|
+
"GLOSSARY_UNWRITABLE",
|
|
514
|
+
`The glossary file at ${path} could not be written: ${error instanceof Error ? error.message : String(error)}`
|
|
343
515
|
);
|
|
344
516
|
}
|
|
345
|
-
|
|
517
|
+
}
|
|
518
|
+
async function readGlossaryFile(input, deps = {}) {
|
|
519
|
+
return readGlossaryRecord(glossaryFilePath(input.glossary), deps.fs ?? defaultFs);
|
|
520
|
+
}
|
|
521
|
+
async function updateGlossaryTerm(input, deps = {}) {
|
|
522
|
+
const path = glossaryFilePath(input.glossary);
|
|
523
|
+
assertWritableTerm(input.term, input.translation);
|
|
524
|
+
const fs = deps.fs ?? defaultFs;
|
|
525
|
+
const cwd = input.cwd ?? process.cwd();
|
|
526
|
+
return withGlossaryGuard(cwd, fs, async () => {
|
|
527
|
+
const document = await readGlossaryDocument(path, fs);
|
|
528
|
+
const entries = applyTerm(document.entries, input.term, input.translation);
|
|
529
|
+
await writeGlossary(path, serializeGlossary(entries, document, path), fs);
|
|
530
|
+
return entries;
|
|
531
|
+
});
|
|
346
532
|
}
|
|
347
533
|
async function resolveGlossary(glossary, baseDir, fs) {
|
|
348
534
|
if (glossary === void 0) {
|
|
@@ -350,7 +536,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
350
536
|
}
|
|
351
537
|
if (typeof glossary === "string") {
|
|
352
538
|
const resolvedPath = path.resolve(baseDir, glossary);
|
|
353
|
-
const record = await
|
|
539
|
+
const record = await readGlossaryRecord(resolvedPath, fs);
|
|
354
540
|
return { glossary: record, provenance: { source: "file", path: resolvedPath } };
|
|
355
541
|
}
|
|
356
542
|
return { glossary, provenance: { source: "inline" } };
|
|
@@ -1001,6 +1187,66 @@ function isAbortError(error, signal) {
|
|
|
1001
1187
|
}
|
|
1002
1188
|
return isAbortShapedError(error);
|
|
1003
1189
|
}
|
|
1190
|
+
var REFUSED = {
|
|
1191
|
+
cause: "the connection was refused",
|
|
1192
|
+
remedy: "Check that the endpoint is running and that the configured host and port are right, then run again."
|
|
1193
|
+
};
|
|
1194
|
+
var UNRESOLVED = {
|
|
1195
|
+
cause: "the host name could not be resolved",
|
|
1196
|
+
remedy: "Check the spelling of the configured endpoint and your DNS or network access."
|
|
1197
|
+
};
|
|
1198
|
+
var CLOSED = {
|
|
1199
|
+
cause: "the connection was closed before a reply arrived",
|
|
1200
|
+
remedy: "Retry, and check any proxy or firewall between you and the endpoint."
|
|
1201
|
+
};
|
|
1202
|
+
var UNREACHABLE = {
|
|
1203
|
+
cause: "the host could not be reached",
|
|
1204
|
+
remedy: "Check your network access and any proxy or firewall between you and the endpoint."
|
|
1205
|
+
};
|
|
1206
|
+
var UNTRUSTED_CERTIFICATE = {
|
|
1207
|
+
cause: "the endpoint's TLS certificate could not be verified",
|
|
1208
|
+
remedy: "Check the certificate the endpoint serves, or point at a host your system already trusts."
|
|
1209
|
+
};
|
|
1210
|
+
var NETWORK_CAUSE_BY_CODE = {
|
|
1211
|
+
ECONNREFUSED: REFUSED,
|
|
1212
|
+
ENOTFOUND: UNRESOLVED,
|
|
1213
|
+
EAI_AGAIN: UNRESOLVED,
|
|
1214
|
+
ECONNRESET: CLOSED,
|
|
1215
|
+
ECONNABORTED: CLOSED,
|
|
1216
|
+
EPIPE: CLOSED,
|
|
1217
|
+
ETIMEDOUT: UNREACHABLE,
|
|
1218
|
+
EHOSTUNREACH: UNREACHABLE,
|
|
1219
|
+
ENETUNREACH: UNREACHABLE,
|
|
1220
|
+
CERT_HAS_EXPIRED: UNTRUSTED_CERTIFICATE,
|
|
1221
|
+
DEPTH_ZERO_SELF_SIGNED_CERT: UNTRUSTED_CERTIFICATE,
|
|
1222
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: UNTRUSTED_CERTIFICATE,
|
|
1223
|
+
SELF_SIGNED_CERT_IN_CHAIN: UNTRUSTED_CERTIFICATE
|
|
1224
|
+
};
|
|
1225
|
+
var MAX_CAUSE_DEPTH = 4;
|
|
1226
|
+
function ownCode(error) {
|
|
1227
|
+
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
1228
|
+
return void 0;
|
|
1229
|
+
}
|
|
1230
|
+
const code = error.code;
|
|
1231
|
+
return typeof code === "string" ? code : void 0;
|
|
1232
|
+
}
|
|
1233
|
+
function nextCause(error) {
|
|
1234
|
+
return typeof error === "object" && error !== null && "cause" in error ? error.cause : void 0;
|
|
1235
|
+
}
|
|
1236
|
+
function findNetworkCause(error) {
|
|
1237
|
+
let current = error;
|
|
1238
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && current !== void 0; depth += 1) {
|
|
1239
|
+
const code = ownCode(current);
|
|
1240
|
+
if (code !== void 0 && Object.hasOwn(NETWORK_CAUSE_BY_CODE, code)) {
|
|
1241
|
+
return NETWORK_CAUSE_BY_CODE[code];
|
|
1242
|
+
}
|
|
1243
|
+
current = nextCause(current);
|
|
1244
|
+
}
|
|
1245
|
+
return void 0;
|
|
1246
|
+
}
|
|
1247
|
+
function describeNetworkCause(cause) {
|
|
1248
|
+
return `${cause.cause}. ${cause.remedy}`;
|
|
1249
|
+
}
|
|
1004
1250
|
var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
1005
1251
|
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
1006
1252
|
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
@@ -1013,7 +1259,19 @@ var MESSAGE_BY_CODE = {
|
|
|
1013
1259
|
PROVIDER_UNAVAILABLE: PROVIDER_UNAVAILABLE_MESSAGE,
|
|
1014
1260
|
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
1015
1261
|
};
|
|
1016
|
-
|
|
1262
|
+
function providerErrorMessage(error, endpointHost) {
|
|
1263
|
+
const cause = findNetworkCause(error);
|
|
1264
|
+
if (cause === void 0 && endpointHost === void 0) {
|
|
1265
|
+
return PROVIDER_CALL_FAILED_MESSAGE;
|
|
1266
|
+
}
|
|
1267
|
+
const where = endpointHost === void 0 ? "" : ` to ${endpointHost}`;
|
|
1268
|
+
const why = cause === void 0 ? "" : `: ${describeNetworkCause(cause)}`;
|
|
1269
|
+
return `The translation provider request${where} failed${why === "" ? "." : why}`;
|
|
1270
|
+
}
|
|
1271
|
+
function messageFor(code, error, context) {
|
|
1272
|
+
return code === "PROVIDER_ERROR" ? providerErrorMessage(error, context?.endpointHost) : MESSAGE_BY_CODE[code];
|
|
1273
|
+
}
|
|
1274
|
+
async function guardProviderCall(call, signal, context) {
|
|
1017
1275
|
signal?.throwIfAborted();
|
|
1018
1276
|
try {
|
|
1019
1277
|
return await call();
|
|
@@ -1022,7 +1280,7 @@ async function guardProviderCall(call, signal) {
|
|
|
1022
1280
|
throw error;
|
|
1023
1281
|
}
|
|
1024
1282
|
const code = classifyProviderError(error);
|
|
1025
|
-
throw new ProviderError(code,
|
|
1283
|
+
throw new ProviderError(code, messageFor(code, error, context));
|
|
1026
1284
|
}
|
|
1027
1285
|
}
|
|
1028
1286
|
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
@@ -1032,7 +1290,7 @@ function requestTimedOutMessage(timeoutMs) {
|
|
|
1032
1290
|
function combineSignals(caller, timeout) {
|
|
1033
1291
|
return caller === void 0 ? timeout : AbortSignal.any([caller, timeout]);
|
|
1034
1292
|
}
|
|
1035
|
-
async function withRequestTimeout(timeoutMs, callerSignal, call) {
|
|
1293
|
+
async function withRequestTimeout(timeoutMs, callerSignal, call, context) {
|
|
1036
1294
|
const timeoutController = new AbortController();
|
|
1037
1295
|
const signal = combineSignals(callerSignal, timeoutController.signal);
|
|
1038
1296
|
let timer;
|
|
@@ -1043,7 +1301,7 @@ async function withRequestTimeout(timeoutMs, callerSignal, call) {
|
|
|
1043
1301
|
}, timeoutMs);
|
|
1044
1302
|
});
|
|
1045
1303
|
try {
|
|
1046
|
-
return await Promise.race([guardProviderCall(() => call(signal), signal), timedOut]);
|
|
1304
|
+
return await Promise.race([guardProviderCall(() => call(signal), signal, context), timedOut]);
|
|
1047
1305
|
} finally {
|
|
1048
1306
|
if (timer !== void 0) {
|
|
1049
1307
|
clearTimeout(timer);
|
|
@@ -1777,22 +2035,22 @@ var HOSTED_PROVIDER_ENV_VARS = new Set(
|
|
|
1777
2035
|
function isNotHostedProviderEnvVar(value) {
|
|
1778
2036
|
return !HOSTED_PROVIDER_ENV_VARS.has(value.toUpperCase());
|
|
1779
2037
|
}
|
|
1780
|
-
|
|
1781
|
-
try {
|
|
1782
|
-
const { protocol } = new URL(value);
|
|
1783
|
-
return protocol === "http:" || protocol === "https:";
|
|
1784
|
-
} catch {
|
|
1785
|
-
return false;
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
2038
|
+
var HTTP_OR_HTTPS_SCHEME = /^https?:\/\//i;
|
|
1788
2039
|
var openAiCompatibleConfigSchema = zod.z.object({
|
|
1789
|
-
baseUrl: zod.z.url({ message: "baseUrl must be a valid absolute URL." }).
|
|
2040
|
+
baseUrl: zod.z.url({ message: "baseUrl must be a valid absolute URL." }).regex(HTTP_OR_HTTPS_SCHEME, { message: "baseUrl must use the http or https scheme." }),
|
|
1790
2041
|
model: zod.z.string().min(1),
|
|
1791
2042
|
maxOutputTokens: zod.z.number().int().positive(),
|
|
1792
2043
|
apiKeyEnvVar: zod.z.string().min(1).refine(isNotHostedProviderEnvVar, {
|
|
1793
2044
|
message: "apiKeyEnvVar must not name a hosted provider's environment variable."
|
|
1794
2045
|
}).optional()
|
|
1795
2046
|
}).extend(requestTimeoutConfigSchema.shape);
|
|
2047
|
+
function endpointContextOf(baseUrl) {
|
|
2048
|
+
try {
|
|
2049
|
+
return { endpointHost: new URL(baseUrl).host };
|
|
2050
|
+
} catch {
|
|
2051
|
+
return void 0;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
1796
2054
|
function createDefaultClient5(config) {
|
|
1797
2055
|
const sdk = new OpenAI__default.default({
|
|
1798
2056
|
apiKey: resolveOpenAiCompatibleKey(config.apiKeyEnvVar),
|
|
@@ -1824,19 +2082,21 @@ function createOpenAiCompatibleProvider(config, deps = {}) {
|
|
|
1824
2082
|
}
|
|
1825
2083
|
function createMechanism4(client, config) {
|
|
1826
2084
|
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2085
|
+
const endpoint = endpointContextOf(config.baseUrl);
|
|
1827
2086
|
return {
|
|
1828
2087
|
translate: async ({ payloadJson, signal }) => {
|
|
1829
2088
|
const body = buildOpenAiRequest(config, payloadJson, "strict-schema", "max_tokens");
|
|
1830
|
-
const completion = await callClient4(client, body, timeoutMs, signal);
|
|
2089
|
+
const completion = await callClient4(client, body, timeoutMs, signal, endpoint);
|
|
1831
2090
|
return extractOpenAiResult(completion, true);
|
|
1832
2091
|
}
|
|
1833
2092
|
};
|
|
1834
2093
|
}
|
|
1835
|
-
function callClient4(client, body, timeoutMs, signal) {
|
|
2094
|
+
function callClient4(client, body, timeoutMs, signal, endpoint) {
|
|
1836
2095
|
return withRequestTimeout(
|
|
1837
2096
|
timeoutMs,
|
|
1838
2097
|
signal,
|
|
1839
|
-
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal })
|
|
2098
|
+
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal }),
|
|
2099
|
+
endpoint
|
|
1840
2100
|
);
|
|
1841
2101
|
}
|
|
1842
2102
|
var SCAFFOLD_MODELS = {
|
|
@@ -1866,6 +2126,10 @@ var providerFactories = {
|
|
|
1866
2126
|
deepl: (options) => createDeepLProvider(options),
|
|
1867
2127
|
"openai-compatible": (options) => createOpenAiCompatibleProvider(options)
|
|
1868
2128
|
};
|
|
2129
|
+
var PROVIDER_IDS = Object.keys(providerFactories);
|
|
2130
|
+
function hasProviderFactory(id) {
|
|
2131
|
+
return Object.hasOwn(providerFactories, id);
|
|
2132
|
+
}
|
|
1869
2133
|
function buildProvider(config) {
|
|
1870
2134
|
const create = providerFactories[config.id];
|
|
1871
2135
|
return create(config.options);
|
|
@@ -1886,11 +2150,12 @@ function findCaseInsensitiveDuplicate(locales) {
|
|
|
1886
2150
|
return void 0;
|
|
1887
2151
|
}
|
|
1888
2152
|
var verbatraConfigSchema = zod.z.strictObject({
|
|
2153
|
+
$schema: zod.z.string().optional(),
|
|
1889
2154
|
sourceLocale: zod.z.string().min(1),
|
|
1890
2155
|
targetLocales: zod.z.array(zod.z.string().min(1)).min(1),
|
|
1891
2156
|
format: supportedFormatSchema,
|
|
1892
2157
|
files: zod.z.strictObject({
|
|
1893
|
-
pattern: zod.z.string().min(1),
|
|
2158
|
+
pattern: zod.z.string().min(1).regex(/\{locale\}/, { message: `files.pattern must contain the ${LOCALE_TOKEN} token` }),
|
|
1894
2159
|
localeStyle: zod.z.enum(LOCALE_STYLES).optional()
|
|
1895
2160
|
}),
|
|
1896
2161
|
provider: providerConfigSchema,
|
|
@@ -1912,9 +2177,6 @@ var verbatraConfigSchema = zod.z.strictObject({
|
|
|
1912
2177
|
return `targetLocales must not contain case-insensitively duplicate locales: "${duplicate}"`;
|
|
1913
2178
|
},
|
|
1914
2179
|
path: ["targetLocales"]
|
|
1915
|
-
}).refine((config) => config.files.pattern.includes(LOCALE_TOKEN), {
|
|
1916
|
-
message: `files.pattern must contain the ${LOCALE_TOKEN} token`,
|
|
1917
|
-
path: ["files", "pattern"]
|
|
1918
2180
|
});
|
|
1919
2181
|
|
|
1920
2182
|
// src/config/load-config.ts
|
|
@@ -2059,114 +2321,13 @@ function createLocalePathResolver(cwd, config) {
|
|
|
2059
2321
|
const style = config.files.localeStyle ?? DEFAULT_LOCALE_STYLE;
|
|
2060
2322
|
const pattern = config.files.pattern;
|
|
2061
2323
|
validatePattern(pattern, style);
|
|
2062
|
-
const pathFor = (locale) => path.resolve(cwd, expandPattern(pattern, safeSpelling(locale, style, config.sourceLocale)));
|
|
2063
|
-
const forward = buildForwardMap(config, pathFor);
|
|
2064
|
-
return {
|
|
2065
|
-
pathFor,
|
|
2066
|
-
localeFor: (absolutePath) => forward.get(path.resolve(cwd, absolutePath))
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
var LOCAL_DIR_NAME = ".verbatra-local";
|
|
2070
|
-
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
2071
|
-
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
2072
|
-
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
2073
|
-
var WAIT_NOTICE_INTERVAL_MS = 1e3;
|
|
2074
|
-
var MAX_LOCK_PAYLOAD_BYTES = 64 * 1024;
|
|
2075
|
-
function lockPath(cwd, stem) {
|
|
2076
|
-
return path.resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
2077
|
-
}
|
|
2078
|
-
function localeLockPath(cwd, locale) {
|
|
2079
|
-
return lockPath(cwd, locale);
|
|
2080
|
-
}
|
|
2081
|
-
function lockFileGuardPath(cwd) {
|
|
2082
|
-
return lockPath(cwd, LOCK_FILE_GUARD_STEM);
|
|
2083
|
-
}
|
|
2084
|
-
function sleep(ms) {
|
|
2085
|
-
return new Promise((res) => {
|
|
2086
|
-
setTimeout(res, ms);
|
|
2087
|
-
});
|
|
2088
|
-
}
|
|
2089
|
-
function lockPayload() {
|
|
2090
|
-
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2091
|
-
}
|
|
2092
|
-
function parseHolder(read) {
|
|
2093
|
-
if (read.kind !== "ok") {
|
|
2094
|
-
return void 0;
|
|
2095
|
-
}
|
|
2096
|
-
let parsed;
|
|
2097
|
-
try {
|
|
2098
|
-
parsed = JSON.parse(read.content);
|
|
2099
|
-
} catch {
|
|
2100
|
-
return void 0;
|
|
2101
|
-
}
|
|
2102
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
2103
|
-
return void 0;
|
|
2104
|
-
}
|
|
2105
|
-
const record = parsed;
|
|
2106
|
-
const holder = {};
|
|
2107
|
-
if (typeof record.pid === "number") {
|
|
2108
|
-
holder.pid = record.pid;
|
|
2109
|
-
}
|
|
2110
|
-
if (typeof record.acquiredAt === "string") {
|
|
2111
|
-
holder.acquiredAt = record.acquiredAt;
|
|
2112
|
-
}
|
|
2113
|
-
return holder;
|
|
2114
|
-
}
|
|
2115
|
-
function makeWaitNotifier(path, fs, onWait, start) {
|
|
2116
|
-
let holder;
|
|
2117
|
-
let holderRead = false;
|
|
2118
|
-
let lastEmit;
|
|
2119
|
-
return async () => {
|
|
2120
|
-
if (!holderRead) {
|
|
2121
|
-
holderRead = true;
|
|
2122
|
-
holder = parseHolder(await fs.readFileBounded(path, MAX_LOCK_PAYLOAD_BYTES));
|
|
2123
|
-
}
|
|
2124
|
-
const elapsedMs = Date.now() - start;
|
|
2125
|
-
if (lastEmit !== void 0 && elapsedMs - lastEmit < WAIT_NOTICE_INTERVAL_MS) {
|
|
2126
|
-
return;
|
|
2127
|
-
}
|
|
2128
|
-
lastEmit = elapsedMs;
|
|
2129
|
-
onWait({ lockPath: path, elapsedMs, ...holder !== void 0 ? { holder } : {} });
|
|
2130
|
-
};
|
|
2131
|
-
}
|
|
2132
|
-
async function acquireLock(path, fs, pollIntervalMs, deadline, notify) {
|
|
2133
|
-
for (; ; ) {
|
|
2134
|
-
if (await fs.createExclusive(path, lockPayload())) {
|
|
2135
|
-
return;
|
|
2136
|
-
}
|
|
2137
|
-
if (notify !== void 0) {
|
|
2138
|
-
await notify();
|
|
2139
|
-
}
|
|
2140
|
-
if (Date.now() >= deadline) {
|
|
2141
|
-
throw new SdkError(
|
|
2142
|
-
"LOCK_CONTENDED",
|
|
2143
|
-
`Could not acquire the write lock at ${path}: another process may be holding it. If no verbatra process is currently running, this lock file was likely left behind by one that was killed; delete it and retry.`
|
|
2144
|
-
);
|
|
2145
|
-
}
|
|
2146
|
-
const jitter = Math.random() * pollIntervalMs;
|
|
2147
|
-
await sleep(pollIntervalMs + jitter);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
async function withFileLock(path, fs, fn, options) {
|
|
2151
|
-
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
2152
|
-
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
2153
|
-
const start = Date.now();
|
|
2154
|
-
const notify = options.onWait !== void 0 ? makeWaitNotifier(path, fs, options.onWait, start) : void 0;
|
|
2155
|
-
await acquireLock(path, fs, pollIntervalMs, start + acquireTimeoutMs, notify);
|
|
2156
|
-
try {
|
|
2157
|
-
return await fn();
|
|
2158
|
-
} finally {
|
|
2159
|
-
await fs.deleteFile(path);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
async function withLocaleWriteLock(cwd, locale, fs, fn, options = {}) {
|
|
2163
|
-
return withFileLock(localeLockPath(cwd, locale), fs, fn, options);
|
|
2164
|
-
}
|
|
2165
|
-
async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
2166
|
-
return withFileLock(lockFileGuardPath(cwd), fs, fn, options);
|
|
2324
|
+
const pathFor = (locale) => path.resolve(cwd, expandPattern(pattern, safeSpelling(locale, style, config.sourceLocale)));
|
|
2325
|
+
const forward = buildForwardMap(config, pathFor);
|
|
2326
|
+
return {
|
|
2327
|
+
pathFor,
|
|
2328
|
+
localeFor: (absolutePath) => forward.get(path.resolve(cwd, absolutePath))
|
|
2329
|
+
};
|
|
2167
2330
|
}
|
|
2168
|
-
|
|
2169
|
-
// src/lock/lock-file.ts
|
|
2170
2331
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
2171
2332
|
var CURRENT_VERSION2 = 1;
|
|
2172
2333
|
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
@@ -2249,6 +2410,87 @@ async function updateLockFileLocale(cwd, fs, locale, patch) {
|
|
|
2249
2410
|
return next;
|
|
2250
2411
|
});
|
|
2251
2412
|
}
|
|
2413
|
+
async function cleanup(ops, tmp) {
|
|
2414
|
+
try {
|
|
2415
|
+
await ops.rm(tmp);
|
|
2416
|
+
} catch {
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
function tempFileName2(path$1) {
|
|
2420
|
+
return path.join(path.dirname(path$1), `.${path.basename(path$1)}.tmp-${process.pid}-${Date.now()}-${crypto.randomUUID()}`);
|
|
2421
|
+
}
|
|
2422
|
+
async function atomicWriteFile(path$1, data, ops) {
|
|
2423
|
+
const directory = path.dirname(path$1);
|
|
2424
|
+
const tmp = tempFileName2(path$1);
|
|
2425
|
+
try {
|
|
2426
|
+
await ops.mkdir(directory);
|
|
2427
|
+
await ops.writeFile(tmp, data);
|
|
2428
|
+
await ops.fsyncFile(tmp);
|
|
2429
|
+
await ops.rename(tmp, path$1);
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
await cleanup(ops, tmp);
|
|
2432
|
+
throw error;
|
|
2433
|
+
}
|
|
2434
|
+
try {
|
|
2435
|
+
await ops.fsyncDir(directory);
|
|
2436
|
+
} catch {
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
async function readUtf8(handle, size) {
|
|
2440
|
+
const buffer = Buffer.allocUnsafe(size);
|
|
2441
|
+
let offset = 0;
|
|
2442
|
+
while (offset < size) {
|
|
2443
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
2444
|
+
if (bytesRead === 0) {
|
|
2445
|
+
break;
|
|
2446
|
+
}
|
|
2447
|
+
offset += bytesRead;
|
|
2448
|
+
}
|
|
2449
|
+
return buffer.toString("utf8", 0, offset);
|
|
2450
|
+
}
|
|
2451
|
+
async function nodeReadBounded(path, maxBytes) {
|
|
2452
|
+
const handle = await promises.open(path, "r");
|
|
2453
|
+
try {
|
|
2454
|
+
const info = await handle.stat();
|
|
2455
|
+
if (!info.isFile()) {
|
|
2456
|
+
return { kind: "not-a-file" };
|
|
2457
|
+
}
|
|
2458
|
+
if (info.size > maxBytes) {
|
|
2459
|
+
return { kind: "too-large" };
|
|
2460
|
+
}
|
|
2461
|
+
return { kind: "ok", content: await readUtf8(handle, info.size) };
|
|
2462
|
+
} finally {
|
|
2463
|
+
await handle.close();
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
async function fsyncPath(path) {
|
|
2467
|
+
const handle = await promises.open(path, "r");
|
|
2468
|
+
try {
|
|
2469
|
+
await handle.sync();
|
|
2470
|
+
} finally {
|
|
2471
|
+
await handle.close();
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
async function fsyncDirBestEffort(path) {
|
|
2475
|
+
try {
|
|
2476
|
+
await fsyncPath(path);
|
|
2477
|
+
} catch {
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
var nodeOps = {
|
|
2481
|
+
mkdir: async (path) => {
|
|
2482
|
+
await promises.mkdir(path, { recursive: true });
|
|
2483
|
+
},
|
|
2484
|
+
writeFile: (path, data) => promises.writeFile(path, data, "utf8"),
|
|
2485
|
+
fsyncFile: (path) => fsyncPath(path),
|
|
2486
|
+
rename: (from, to) => promises.rename(from, to),
|
|
2487
|
+
fsyncDir: (path) => fsyncDirBestEffort(path),
|
|
2488
|
+
rm: (path) => promises.rm(path, { force: true })
|
|
2489
|
+
};
|
|
2490
|
+
var nodeAdapterFs = {
|
|
2491
|
+
readBounded: nodeReadBounded,
|
|
2492
|
+
writeFileAtomic: (path, data) => atomicWriteFile(path, data, nodeOps)
|
|
2493
|
+
};
|
|
2252
2494
|
var VALID_EMPTY = { placeholders: [], isPlural: false, valid: true };
|
|
2253
2495
|
var INVALID = { placeholders: [], isPlural: false, valid: false };
|
|
2254
2496
|
function tokenOf(element) {
|
|
@@ -2718,85 +2960,12 @@ function buildCanHandle(extensions, sniff) {
|
|
|
2718
2960
|
return sample === void 0 || sniff === void 0 || sniff(sample);
|
|
2719
2961
|
};
|
|
2720
2962
|
}
|
|
2721
|
-
async function fsyncPath(path) {
|
|
2722
|
-
const handle = await promises.open(path, "r");
|
|
2723
|
-
try {
|
|
2724
|
-
await handle.sync();
|
|
2725
|
-
} finally {
|
|
2726
|
-
await handle.close();
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
async function fsyncDirBestEffort(path) {
|
|
2730
|
-
try {
|
|
2731
|
-
await fsyncPath(path);
|
|
2732
|
-
} catch {
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
var nodeOps = {
|
|
2736
|
-
mkdir: async (path) => {
|
|
2737
|
-
await promises.mkdir(path, { recursive: true });
|
|
2738
|
-
},
|
|
2739
|
-
writeFile: (path, data) => promises.writeFile(path, data, "utf8"),
|
|
2740
|
-
fsyncFile: (path) => fsyncPath(path),
|
|
2741
|
-
rename: (from, to) => promises.rename(from, to),
|
|
2742
|
-
fsyncDir: (path) => fsyncDirBestEffort(path),
|
|
2743
|
-
rm: (path) => promises.rm(path, { force: true })
|
|
2744
|
-
};
|
|
2745
|
-
async function cleanup(ops, tmp) {
|
|
2746
|
-
try {
|
|
2747
|
-
await ops.rm(tmp);
|
|
2748
|
-
} catch {
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
function tempFileName2(path$1) {
|
|
2752
|
-
return path.join(path.dirname(path$1), `.${path.basename(path$1)}.tmp-${process.pid}-${Date.now()}-${crypto.randomUUID()}`);
|
|
2753
|
-
}
|
|
2754
|
-
async function atomicWriteFile(path$1, data, ops = nodeOps) {
|
|
2755
|
-
const directory = path.dirname(path$1);
|
|
2756
|
-
const tmp = tempFileName2(path$1);
|
|
2757
|
-
try {
|
|
2758
|
-
await ops.mkdir(directory);
|
|
2759
|
-
await ops.writeFile(tmp, data);
|
|
2760
|
-
await ops.fsyncFile(tmp);
|
|
2761
|
-
await ops.rename(tmp, path$1);
|
|
2762
|
-
} catch (error) {
|
|
2763
|
-
await cleanup(ops, tmp);
|
|
2764
|
-
throw error;
|
|
2765
|
-
}
|
|
2766
|
-
try {
|
|
2767
|
-
await ops.fsyncDir(directory);
|
|
2768
|
-
} catch {
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
2963
|
function stripLeadingBom(content) {
|
|
2772
2964
|
return content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
2773
2965
|
}
|
|
2774
|
-
async function
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
while (offset < size) {
|
|
2778
|
-
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
2779
|
-
if (bytesRead === 0) {
|
|
2780
|
-
break;
|
|
2781
|
-
}
|
|
2782
|
-
offset += bytesRead;
|
|
2783
|
-
}
|
|
2784
|
-
return stripLeadingBom(buffer.toString("utf8", 0, offset));
|
|
2785
|
-
}
|
|
2786
|
-
async function readBounded2(filePath) {
|
|
2787
|
-
const handle = await promises.open(filePath, "r");
|
|
2788
|
-
try {
|
|
2789
|
-
const info = await handle.stat();
|
|
2790
|
-
if (!info.isFile()) {
|
|
2791
|
-
return { kind: "not-a-file" };
|
|
2792
|
-
}
|
|
2793
|
-
if (info.size > MAX_INPUT_BYTES) {
|
|
2794
|
-
return { kind: "too-large" };
|
|
2795
|
-
}
|
|
2796
|
-
return { kind: "ok", content: await readBoundedUtf82(handle, info.size) };
|
|
2797
|
-
} finally {
|
|
2798
|
-
await handle.close();
|
|
2799
|
-
}
|
|
2966
|
+
async function readBoundedFile(fs, filePath) {
|
|
2967
|
+
const outcome = await fs.readBounded(filePath, MAX_INPUT_BYTES);
|
|
2968
|
+
return outcome.kind === "ok" ? { kind: "ok", content: stripLeadingBom(outcome.content) } : outcome;
|
|
2800
2969
|
}
|
|
2801
2970
|
function outcomeToContent(outcome, notAFileMessage) {
|
|
2802
2971
|
if (outcome.kind === "not-a-file") {
|
|
@@ -2807,8 +2976,8 @@ function outcomeToContent(outcome, notAFileMessage) {
|
|
|
2807
2976
|
}
|
|
2808
2977
|
return outcome.content;
|
|
2809
2978
|
}
|
|
2810
|
-
async function readFileContent(filePath) {
|
|
2811
|
-
const outcome = await
|
|
2979
|
+
async function readFileContent(fs, filePath) {
|
|
2980
|
+
const outcome = await readBoundedFile(fs, filePath);
|
|
2812
2981
|
return outcomeToContent(outcome, "The path is not a regular file.");
|
|
2813
2982
|
}
|
|
2814
2983
|
var BACKSLASH = "\\";
|
|
@@ -3019,7 +3188,8 @@ function createTreeFileAdapter(options) {
|
|
|
3019
3188
|
buildWriteTree,
|
|
3020
3189
|
comparePlaceholders,
|
|
3021
3190
|
deriveDescriptions,
|
|
3022
|
-
keyMode = "literal-leaf"
|
|
3191
|
+
keyMode = "literal-leaf",
|
|
3192
|
+
fs = nodeAdapterFs
|
|
3023
3193
|
} = options;
|
|
3024
3194
|
return {
|
|
3025
3195
|
format,
|
|
@@ -3028,7 +3198,7 @@ function createTreeFileAdapter(options) {
|
|
|
3028
3198
|
validateMessage: validateMessage ?? (() => true),
|
|
3029
3199
|
...comparePlaceholders !== void 0 ? { comparePlaceholders } : {},
|
|
3030
3200
|
async read(filePath, locale) {
|
|
3031
|
-
const content = await readFileContent(filePath);
|
|
3201
|
+
const content = await readFileContent(fs, filePath);
|
|
3032
3202
|
const namespace = namespaceOf(filePath);
|
|
3033
3203
|
const { entries, excludedLeafPaths } = toEntries(
|
|
3034
3204
|
content,
|
|
@@ -3044,8 +3214,8 @@ function createTreeFileAdapter(options) {
|
|
|
3044
3214
|
return { resource, invalidIcuKeys, excludedLeafPaths };
|
|
3045
3215
|
},
|
|
3046
3216
|
async write(resource, filePath) {
|
|
3047
|
-
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
3048
|
-
await
|
|
3217
|
+
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath, fs) : unflattenEntries(resource.entries);
|
|
3218
|
+
await fs.writeFileAtomic(filePath, serialize2(tree));
|
|
3049
3219
|
}
|
|
3050
3220
|
};
|
|
3051
3221
|
}
|
|
@@ -3106,10 +3276,10 @@ function messagesFromEntries(entries) {
|
|
|
3106
3276
|
}
|
|
3107
3277
|
return out;
|
|
3108
3278
|
}
|
|
3109
|
-
async function readDestinationPairs(filePath) {
|
|
3279
|
+
async function readDestinationPairs(filePath, fs) {
|
|
3110
3280
|
let content;
|
|
3111
3281
|
try {
|
|
3112
|
-
content = await readFileContent(filePath);
|
|
3282
|
+
content = await readFileContent(fs, filePath);
|
|
3113
3283
|
} catch (error) {
|
|
3114
3284
|
if (isEnoent(error)) {
|
|
3115
3285
|
return null;
|
|
@@ -3118,9 +3288,9 @@ async function readDestinationPairs(filePath) {
|
|
|
3118
3288
|
}
|
|
3119
3289
|
return [...parseArbObject(content)];
|
|
3120
3290
|
}
|
|
3121
|
-
async function buildArbWriteTree(entries, filePath) {
|
|
3291
|
+
async function buildArbWriteTree(entries, filePath, fs) {
|
|
3122
3292
|
const messages = messagesFromEntries(entries);
|
|
3123
|
-
const pairs = await readDestinationPairs(filePath);
|
|
3293
|
+
const pairs = await readDestinationPairs(filePath, fs);
|
|
3124
3294
|
const out = /* @__PURE__ */ new Map();
|
|
3125
3295
|
const consumed = /* @__PURE__ */ new Set();
|
|
3126
3296
|
for (const [key, value] of pairs ?? []) {
|
|
@@ -3142,8 +3312,9 @@ async function buildArbWriteTree(entries, filePath) {
|
|
|
3142
3312
|
function parseArb(content) {
|
|
3143
3313
|
return assertJsonRecord(stripArbMetadata(parseArbObject(content)));
|
|
3144
3314
|
}
|
|
3145
|
-
function createArbAdapter() {
|
|
3315
|
+
function createArbAdapter(fs = nodeAdapterFs) {
|
|
3146
3316
|
return createTreeFileAdapter({
|
|
3317
|
+
fs,
|
|
3147
3318
|
format: "arb",
|
|
3148
3319
|
extensions: [".arb"],
|
|
3149
3320
|
sniff: sniffJsonObject,
|
|
@@ -3167,6 +3338,38 @@ function createJsonFileAdapter(options) {
|
|
|
3167
3338
|
serialize: serializeJsonTree
|
|
3168
3339
|
});
|
|
3169
3340
|
}
|
|
3341
|
+
var SINGLE_BRACE_PATTERN = /(?<!\{)\{\s*([A-Za-z_][\w$-]*|\d+)\s*\}(?!\})/g;
|
|
3342
|
+
function extractSingleBraceTokens(value) {
|
|
3343
|
+
return scanTokens(value, SINGLE_BRACE_PATTERN, (match) => {
|
|
3344
|
+
const key = match[1];
|
|
3345
|
+
return key !== void 0 ? `{${key}}` : void 0;
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
function fabricatedTokens(sourceValue, targetValue) {
|
|
3349
|
+
const known = new Set(extractSingleBraceTokens(sourceValue));
|
|
3350
|
+
const invented = /* @__PURE__ */ new Set();
|
|
3351
|
+
for (const token of extractSingleBraceTokens(targetValue)) {
|
|
3352
|
+
if (!known.has(token)) {
|
|
3353
|
+
invented.add(token);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
return [...invented].sort();
|
|
3357
|
+
}
|
|
3358
|
+
function createSingleBraceFabricationComparator(extract) {
|
|
3359
|
+
return (sourceValue, targetValue) => {
|
|
3360
|
+
const format = checkPlaceholders(extract(sourceValue), extract(targetValue));
|
|
3361
|
+
const invented = fabricatedTokens(sourceValue, targetValue);
|
|
3362
|
+
if (invented.length === 0) {
|
|
3363
|
+
return format;
|
|
3364
|
+
}
|
|
3365
|
+
return {
|
|
3366
|
+
matches: false,
|
|
3367
|
+
missing: format.missing,
|
|
3368
|
+
extra: [...format.extra, ...invented].sort(),
|
|
3369
|
+
reordered: false
|
|
3370
|
+
};
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3170
3373
|
var DOUBLE_BRACE_PATTERN = /\{\{[^{}]*\}\}/g;
|
|
3171
3374
|
var I18NEXT_PATTERN = /\{\{[^{}]*\}\}|\$t\([^()]*\)/g;
|
|
3172
3375
|
function extractDoubleBracePlaceholders(value) {
|
|
@@ -3192,18 +3395,21 @@ function pluralBaseKey(key) {
|
|
|
3192
3395
|
function makePluralKey(baseKey, category) {
|
|
3193
3396
|
return `${baseKey}_${category}`;
|
|
3194
3397
|
}
|
|
3195
|
-
function createI18nextJsonAdapter() {
|
|
3398
|
+
function createI18nextJsonAdapter(fs = nodeAdapterFs) {
|
|
3196
3399
|
return createJsonFileAdapter({
|
|
3400
|
+
fs,
|
|
3197
3401
|
format: "i18next-json",
|
|
3198
3402
|
extractPlaceholders: extractI18nextPlaceholders,
|
|
3199
3403
|
deriveEntry: (key, value) => ({
|
|
3200
3404
|
placeholders: extractI18nextPlaceholders(value),
|
|
3201
3405
|
isPlural: isPluralKey(key)
|
|
3202
|
-
})
|
|
3406
|
+
}),
|
|
3407
|
+
comparePlaceholders: createSingleBraceFabricationComparator(extractI18nextPlaceholders)
|
|
3203
3408
|
});
|
|
3204
3409
|
}
|
|
3205
|
-
function createNextIntlJsonAdapter() {
|
|
3410
|
+
function createNextIntlJsonAdapter(fs = nodeAdapterFs) {
|
|
3206
3411
|
return createJsonFileAdapter({
|
|
3412
|
+
fs,
|
|
3207
3413
|
format: "next-intl-json",
|
|
3208
3414
|
extractPlaceholders: icuPlaceholders,
|
|
3209
3415
|
deriveEntry: icuDeriveEntry,
|
|
@@ -3244,10 +3450,10 @@ function assertNotMixed(tree) {
|
|
|
3244
3450
|
}
|
|
3245
3451
|
assertNoDottedNestedKey(tree);
|
|
3246
3452
|
}
|
|
3247
|
-
async function detectStyle(filePath) {
|
|
3453
|
+
async function detectStyle(filePath, fs) {
|
|
3248
3454
|
let parsed;
|
|
3249
3455
|
try {
|
|
3250
|
-
const outcome = await
|
|
3456
|
+
const outcome = await readBoundedFile(fs, filePath);
|
|
3251
3457
|
if (outcome.kind !== "ok") {
|
|
3252
3458
|
return "nested";
|
|
3253
3459
|
}
|
|
@@ -3272,18 +3478,20 @@ function buildFlatTree(entries) {
|
|
|
3272
3478
|
}
|
|
3273
3479
|
return out;
|
|
3274
3480
|
}
|
|
3275
|
-
async function buildNgxWriteTree(entries, filePath) {
|
|
3276
|
-
const style = await detectStyle(filePath);
|
|
3481
|
+
async function buildNgxWriteTree(entries, filePath, fs) {
|
|
3482
|
+
const style = await detectStyle(filePath, fs);
|
|
3277
3483
|
return style === "flat" ? buildFlatTree(entries) : unflattenEntries(entries);
|
|
3278
3484
|
}
|
|
3279
|
-
function createNgxTranslateJsonAdapter() {
|
|
3485
|
+
function createNgxTranslateJsonAdapter(fs = nodeAdapterFs) {
|
|
3280
3486
|
return createJsonFileAdapter({
|
|
3487
|
+
fs,
|
|
3281
3488
|
format: "ngx-translate-json",
|
|
3282
3489
|
extractPlaceholders: extractDoubleBracePlaceholders,
|
|
3283
3490
|
deriveEntry: (_key, value) => ({
|
|
3284
3491
|
placeholders: extractDoubleBracePlaceholders(value),
|
|
3285
3492
|
isPlural: false
|
|
3286
3493
|
}),
|
|
3494
|
+
comparePlaceholders: createSingleBraceFabricationComparator(extractDoubleBracePlaceholders),
|
|
3287
3495
|
validateTree: assertNotMixed,
|
|
3288
3496
|
buildWriteTree: buildNgxWriteTree,
|
|
3289
3497
|
keyMode: "path-notation"
|
|
@@ -3305,7 +3513,8 @@ function createFlatFileAdapter(options) {
|
|
|
3305
3513
|
serializeEntries,
|
|
3306
3514
|
extractPlaceholders,
|
|
3307
3515
|
validateMessage,
|
|
3308
|
-
computeInvalidIcuKeys
|
|
3516
|
+
computeInvalidIcuKeys,
|
|
3517
|
+
fs = nodeAdapterFs
|
|
3309
3518
|
} = options;
|
|
3310
3519
|
return {
|
|
3311
3520
|
format,
|
|
@@ -3313,7 +3522,7 @@ function createFlatFileAdapter(options) {
|
|
|
3313
3522
|
extractPlaceholders,
|
|
3314
3523
|
validateMessage: validateMessage ?? (() => true),
|
|
3315
3524
|
async read(filePath, locale) {
|
|
3316
|
-
const content = await readFileContent(filePath);
|
|
3525
|
+
const content = await readFileContent(fs, filePath);
|
|
3317
3526
|
const namespace = namespaceOf(filePath);
|
|
3318
3527
|
const entries = toEntries2(content, namespace, parseEntries);
|
|
3319
3528
|
const resource = { locale, namespace, format, entries };
|
|
@@ -3321,8 +3530,8 @@ function createFlatFileAdapter(options) {
|
|
|
3321
3530
|
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3322
3531
|
},
|
|
3323
3532
|
async write(resource, filePath) {
|
|
3324
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3325
|
-
await
|
|
3533
|
+
const data = await serializeEntries(resource.entries, filePath, fs);
|
|
3534
|
+
await fs.writeFileAtomic(filePath, data);
|
|
3326
3535
|
}
|
|
3327
3536
|
};
|
|
3328
3537
|
}
|
|
@@ -3330,13 +3539,13 @@ var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordin
|
|
|
3330
3539
|
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3331
3540
|
function matchingBraces(value) {
|
|
3332
3541
|
const close = new Int32Array(value.length).fill(-1);
|
|
3333
|
-
const
|
|
3542
|
+
const open22 = [];
|
|
3334
3543
|
for (let i = 0; i < value.length; i += 1) {
|
|
3335
3544
|
const char = value[i];
|
|
3336
3545
|
if (char === "{") {
|
|
3337
|
-
|
|
3546
|
+
open22.push(i);
|
|
3338
3547
|
} else if (char === "}") {
|
|
3339
|
-
const start =
|
|
3548
|
+
const start = open22.pop();
|
|
3340
3549
|
if (start !== void 0) {
|
|
3341
3550
|
close[start] = i;
|
|
3342
3551
|
}
|
|
@@ -3356,13 +3565,13 @@ function findNameEnd(value, from, limit) {
|
|
|
3356
3565
|
}
|
|
3357
3566
|
return { comma: -1, hasBrace: false };
|
|
3358
3567
|
}
|
|
3359
|
-
function parseArgumentAt(value,
|
|
3360
|
-
const { comma, hasBrace } = findNameEnd(value,
|
|
3568
|
+
function parseArgumentAt(value, open22, close) {
|
|
3569
|
+
const { comma, hasBrace } = findNameEnd(value, open22 + 1, close);
|
|
3361
3570
|
if (hasBrace) {
|
|
3362
3571
|
return null;
|
|
3363
3572
|
}
|
|
3364
3573
|
const nameEnd = comma === -1 ? close : comma;
|
|
3365
|
-
const name = value.slice(
|
|
3574
|
+
const name = value.slice(open22 + 1, nameEnd).trim();
|
|
3366
3575
|
if (!ARGUMENT_NAME.test(name)) {
|
|
3367
3576
|
return null;
|
|
3368
3577
|
}
|
|
@@ -3629,10 +3838,10 @@ function escapeString(input, isKey) {
|
|
|
3629
3838
|
function formatEntry(key, value) {
|
|
3630
3839
|
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3631
3840
|
}
|
|
3632
|
-
async function readStructure(filePath) {
|
|
3841
|
+
async function readStructure(filePath, fs) {
|
|
3633
3842
|
let outcome;
|
|
3634
3843
|
try {
|
|
3635
|
-
outcome = await
|
|
3844
|
+
outcome = await readBoundedFile(fs, filePath);
|
|
3636
3845
|
} catch (error) {
|
|
3637
3846
|
if (isEnoent(error)) {
|
|
3638
3847
|
return { items: [], terminator: "\n" };
|
|
@@ -3645,8 +3854,8 @@ async function readStructure(filePath) {
|
|
|
3645
3854
|
terminator: detectLineTerminator(content)
|
|
3646
3855
|
};
|
|
3647
3856
|
}
|
|
3648
|
-
async function serializePropertiesEntries(entries, filePath) {
|
|
3649
|
-
const { items, terminator } = await readStructure(filePath);
|
|
3857
|
+
async function serializePropertiesEntries(entries, filePath, fs) {
|
|
3858
|
+
const { items, terminator } = await readStructure(filePath, fs);
|
|
3650
3859
|
const emitted = /* @__PURE__ */ new Set();
|
|
3651
3860
|
const lines = [];
|
|
3652
3861
|
for (const item of items) {
|
|
@@ -3667,8 +3876,9 @@ async function serializePropertiesEntries(entries, filePath) {
|
|
|
3667
3876
|
}
|
|
3668
3877
|
return lines.length === 0 ? "" : `${lines.join(terminator)}${terminator}`;
|
|
3669
3878
|
}
|
|
3670
|
-
function createPropertiesAdapter() {
|
|
3879
|
+
function createPropertiesAdapter(fs = nodeAdapterFs) {
|
|
3671
3880
|
return createFlatFileAdapter({
|
|
3881
|
+
fs,
|
|
3672
3882
|
format: "properties",
|
|
3673
3883
|
extensions: [".properties"],
|
|
3674
3884
|
parseEntries: parsePropertiesEntries,
|
|
@@ -3725,18 +3935,15 @@ var AdapterRegistry = class {
|
|
|
3725
3935
|
return this.resolveByDetection(filePath, options.sample);
|
|
3726
3936
|
}
|
|
3727
3937
|
};
|
|
3728
|
-
var PLACEHOLDER_PATTERN = /(?<!\{)\{\s*([A-Za-z_][\w$-]*|\d+)\s*\}(?!\})/g;
|
|
3729
3938
|
function extractVueI18nPlaceholders(value) {
|
|
3730
|
-
return
|
|
3731
|
-
const key = match[1];
|
|
3732
|
-
return key !== void 0 ? `{${key}}` : void 0;
|
|
3733
|
-
});
|
|
3939
|
+
return extractSingleBraceTokens(value);
|
|
3734
3940
|
}
|
|
3735
3941
|
function isPluralValue(value) {
|
|
3736
3942
|
return value.includes("|");
|
|
3737
3943
|
}
|
|
3738
|
-
function createVueI18nJsonAdapter() {
|
|
3944
|
+
function createVueI18nJsonAdapter(fs = nodeAdapterFs) {
|
|
3739
3945
|
return createJsonFileAdapter({
|
|
3946
|
+
fs,
|
|
3740
3947
|
format: "vue-i18n-json",
|
|
3741
3948
|
extractPlaceholders: extractVueI18nPlaceholders,
|
|
3742
3949
|
deriveEntry: (_key, value) => ({
|
|
@@ -3883,10 +4090,10 @@ function parseXliffEntries(content, namespace) {
|
|
|
3883
4090
|
}
|
|
3884
4091
|
return out;
|
|
3885
4092
|
}
|
|
3886
|
-
async function readDestination(filePath) {
|
|
4093
|
+
async function readDestination(filePath, fs) {
|
|
3887
4094
|
let outcome;
|
|
3888
4095
|
try {
|
|
3889
|
-
outcome = await
|
|
4096
|
+
outcome = await readBoundedFile(fs, filePath);
|
|
3890
4097
|
} catch {
|
|
3891
4098
|
throw new AdapterError("INVALID_STRUCTURE", "The destination XLIFF file does not exist.");
|
|
3892
4099
|
}
|
|
@@ -3970,8 +4177,8 @@ function setTargetValue(doc, parser, element, value) {
|
|
|
3970
4177
|
element.appendChild(doc.importNode(node, true));
|
|
3971
4178
|
}
|
|
3972
4179
|
}
|
|
3973
|
-
async function serializeXliffEntries(entries, filePath) {
|
|
3974
|
-
const { doc, root } = parseXml(await readDestination(filePath));
|
|
4180
|
+
async function serializeXliffEntries(entries, filePath, fs) {
|
|
4181
|
+
const { doc, root } = parseXml(await readDestination(filePath, fs));
|
|
3975
4182
|
const parser = new xmldom.DOMParser({ onError: onFatal });
|
|
3976
4183
|
for (const unit of walkUnits(root)) {
|
|
3977
4184
|
const entry = entries.get(unit.key);
|
|
@@ -3989,8 +4196,9 @@ function sniffXliff(sample) {
|
|
|
3989
4196
|
const head = sample.trimStart();
|
|
3990
4197
|
return head.startsWith("<xliff") || head.startsWith("<?xml");
|
|
3991
4198
|
}
|
|
3992
|
-
function createXliffAdapter() {
|
|
4199
|
+
function createXliffAdapter(fs = nodeAdapterFs) {
|
|
3993
4200
|
return createFlatFileAdapter({
|
|
4201
|
+
fs,
|
|
3994
4202
|
format: "xliff",
|
|
3995
4203
|
extensions: [".xlf", ".xliff"],
|
|
3996
4204
|
sniff: sniffXliff,
|
|
@@ -4028,8 +4236,9 @@ function parseYamlObject(content) {
|
|
|
4028
4236
|
assertWithinDepth(parsed, MAX_DEPTH);
|
|
4029
4237
|
return assertJsonRecord(normalizeYamlTree(parsed));
|
|
4030
4238
|
}
|
|
4031
|
-
function createYamlAdapter() {
|
|
4239
|
+
function createYamlAdapter(fs = nodeAdapterFs) {
|
|
4032
4240
|
return createTreeFileAdapter({
|
|
4241
|
+
fs,
|
|
4033
4242
|
format: "yaml",
|
|
4034
4243
|
extensions: [".yml", ".yaml"],
|
|
4035
4244
|
parse: parseYamlObject,
|
|
@@ -4038,16 +4247,41 @@ function createYamlAdapter() {
|
|
|
4038
4247
|
deriveEntry: (_key, value) => ({
|
|
4039
4248
|
placeholders: extractDoubleBracePlaceholders(value),
|
|
4040
4249
|
isPlural: false
|
|
4041
|
-
})
|
|
4250
|
+
}),
|
|
4251
|
+
comparePlaceholders: createSingleBraceFabricationComparator(extractDoubleBracePlaceholders)
|
|
4252
|
+
});
|
|
4253
|
+
}
|
|
4254
|
+
function createDefaultRegistry(fs = nodeAdapterFs) {
|
|
4255
|
+
return new AdapterRegistry().register(createI18nextJsonAdapter(fs)).register(createVueI18nJsonAdapter(fs)).register(createNextIntlJsonAdapter(fs)).register(createNgxTranslateJsonAdapter(fs)).register(createXliffAdapter(fs)).register(createYamlAdapter(fs)).register(createArbAdapter(fs)).register(createPropertiesAdapter(fs));
|
|
4256
|
+
}
|
|
4257
|
+
function enoent(path) {
|
|
4258
|
+
return Object.assign(new Error(`ENOENT: no such file or directory, open '${path}'`), {
|
|
4259
|
+
code: "ENOENT"
|
|
4042
4260
|
});
|
|
4043
4261
|
}
|
|
4044
|
-
function
|
|
4045
|
-
return
|
|
4262
|
+
function toAdapterFs(fs) {
|
|
4263
|
+
return {
|
|
4264
|
+
async readBounded(path, maxBytes) {
|
|
4265
|
+
const outcome = await fs.readFileBounded(path, maxBytes);
|
|
4266
|
+
if (outcome.kind === "missing") {
|
|
4267
|
+
throw enoent(path);
|
|
4268
|
+
}
|
|
4269
|
+
if (outcome.kind === "too-large") {
|
|
4270
|
+
return { kind: "too-large" };
|
|
4271
|
+
}
|
|
4272
|
+
return { kind: "ok", content: outcome.content };
|
|
4273
|
+
},
|
|
4274
|
+
async writeFileAtomic(path$1, data) {
|
|
4275
|
+
await fs.mkdir?.(path.dirname(path$1));
|
|
4276
|
+
await fs.writeFile(path$1, data);
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4046
4279
|
}
|
|
4047
4280
|
|
|
4048
4281
|
// src/selection/select-adapter.ts
|
|
4049
|
-
function selectAdapter(format, registry
|
|
4050
|
-
const
|
|
4282
|
+
function selectAdapter(format, registry, fs) {
|
|
4283
|
+
const resolved = registry ?? createDefaultRegistry(fs === void 0 ? void 0 : toAdapterFs(fs));
|
|
4284
|
+
const resolution = resolved.resolve("", { format });
|
|
4051
4285
|
if (resolution.status === "resolved") {
|
|
4052
4286
|
return resolution.adapter;
|
|
4053
4287
|
}
|
|
@@ -4058,12 +4292,25 @@ function selectAdapter(format, registry = createDefaultRegistry()) {
|
|
|
4058
4292
|
}
|
|
4059
4293
|
|
|
4060
4294
|
// src/flow/read-target.ts
|
|
4295
|
+
function attributeTargetRead(error, locale, path) {
|
|
4296
|
+
if (!(error instanceof AdapterError)) {
|
|
4297
|
+
return error;
|
|
4298
|
+
}
|
|
4299
|
+
return new AdapterError(
|
|
4300
|
+
error.code,
|
|
4301
|
+
`The ${locale} locale file at ${path} could not be read: ${error.message}`
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
4061
4304
|
async function readTargetResource(input) {
|
|
4062
4305
|
const path = input.resolver.pathFor(input.locale);
|
|
4063
4306
|
if (!await input.fs.fileExists(path)) {
|
|
4064
4307
|
return { locale: input.locale, namespace: "", format: input.format, entries: /* @__PURE__ */ new Map() };
|
|
4065
4308
|
}
|
|
4066
|
-
|
|
4309
|
+
try {
|
|
4310
|
+
return (await input.adapter.read(path, input.locale)).resource;
|
|
4311
|
+
} catch (error) {
|
|
4312
|
+
throw attributeTargetRead(error, input.locale, path);
|
|
4313
|
+
}
|
|
4067
4314
|
}
|
|
4068
4315
|
|
|
4069
4316
|
// src/flow/select-locales.ts
|
|
@@ -4121,7 +4368,7 @@ async function diffLocales(input, deps = {}) {
|
|
|
4121
4368
|
const config = input.config;
|
|
4122
4369
|
const cwd = input.cwd ?? process.cwd();
|
|
4123
4370
|
const fs = deps.fs ?? defaultFs;
|
|
4124
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4371
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4125
4372
|
const resolver = createLocalePathResolver(cwd, config);
|
|
4126
4373
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
4127
4374
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
@@ -4172,6 +4419,133 @@ async function diff(input, deps = {}) {
|
|
|
4172
4419
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
4173
4420
|
}
|
|
4174
4421
|
|
|
4422
|
+
// src/flow/doctor.ts
|
|
4423
|
+
var CHECK_TITLES = {
|
|
4424
|
+
config: "Configuration",
|
|
4425
|
+
"format-adapter": "Format adapter",
|
|
4426
|
+
provider: "Provider",
|
|
4427
|
+
"api-key": "API key environment variable",
|
|
4428
|
+
"source-file": "Source locale file"
|
|
4429
|
+
};
|
|
4430
|
+
var CONFIG_DEPENDENT_IDS = [
|
|
4431
|
+
"format-adapter",
|
|
4432
|
+
"provider",
|
|
4433
|
+
"api-key",
|
|
4434
|
+
"source-file"
|
|
4435
|
+
];
|
|
4436
|
+
var SKIPPED_DETAIL = "Not checked: the configuration could not be loaded.";
|
|
4437
|
+
function check2(id, status, detail) {
|
|
4438
|
+
return { id, title: CHECK_TITLES[id], status, detail };
|
|
4439
|
+
}
|
|
4440
|
+
function verdict(id, passed, detail) {
|
|
4441
|
+
return check2(id, passed ? "pass" : "fail", detail);
|
|
4442
|
+
}
|
|
4443
|
+
function toResult(checks) {
|
|
4444
|
+
return { ok: checks.every((entry) => entry.status !== "fail"), checks };
|
|
4445
|
+
}
|
|
4446
|
+
function loadOptionsFor(input, deps) {
|
|
4447
|
+
return {
|
|
4448
|
+
...input.cwd !== void 0 ? { cwd: input.cwd } : {},
|
|
4449
|
+
...input.configPath !== void 0 ? { configPath: input.configPath } : {},
|
|
4450
|
+
...deps.fs !== void 0 ? { fs: deps.fs } : {}
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
function isMissingExplicitConfig(error, input) {
|
|
4454
|
+
return input.configPath !== void 0 && error instanceof SdkError && error.code === "CONFIG_NOT_FOUND";
|
|
4455
|
+
}
|
|
4456
|
+
async function loadForDoctor(input, deps) {
|
|
4457
|
+
const load = deps.loadConfig ?? loadConfigWithMeta;
|
|
4458
|
+
try {
|
|
4459
|
+
return { kind: "loaded", loaded: await load(loadOptionsFor(input, deps)) };
|
|
4460
|
+
} catch (error) {
|
|
4461
|
+
if (isMissingExplicitConfig(error, input)) {
|
|
4462
|
+
throw error;
|
|
4463
|
+
}
|
|
4464
|
+
return { kind: "failed", detail: errorMessage(error) };
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
function configDetail(source) {
|
|
4468
|
+
return source.kind === "override" ? "Validated the config supplied in memory." : `Loaded ${source.filepath}.`;
|
|
4469
|
+
}
|
|
4470
|
+
function resolveAdapter(config, deps) {
|
|
4471
|
+
try {
|
|
4472
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4473
|
+
return { kind: "resolved", adapter };
|
|
4474
|
+
} catch (error) {
|
|
4475
|
+
return { kind: "failed", detail: errorMessage(error) };
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
function checkAdapter(config, outcome) {
|
|
4479
|
+
return outcome.kind === "resolved" ? verdict("format-adapter", true, `Format "${config.format}" resolves to an adapter.`) : verdict("format-adapter", false, outcome.detail);
|
|
4480
|
+
}
|
|
4481
|
+
function checkProvider(provider) {
|
|
4482
|
+
return hasProviderFactory(provider.id) ? verdict("provider", true, `Provider "${provider.id}" resolves to a factory.`) : verdict(
|
|
4483
|
+
"provider",
|
|
4484
|
+
false,
|
|
4485
|
+
`No factory is registered for provider "${provider.id}". Supported providers: ${PROVIDER_IDS.join(", ")}.`
|
|
4486
|
+
);
|
|
4487
|
+
}
|
|
4488
|
+
function isEnvVarSet(name) {
|
|
4489
|
+
const value = process.env[name];
|
|
4490
|
+
return value !== void 0 && value.length > 0;
|
|
4491
|
+
}
|
|
4492
|
+
function envVarVerdict(name) {
|
|
4493
|
+
return isEnvVarSet(name) ? verdict("api-key", true, `${name} is set.`) : verdict("api-key", false, `The ${name} environment variable is not set.`);
|
|
4494
|
+
}
|
|
4495
|
+
function checkOpenAiCompatibleKey(apiKeyEnvVar) {
|
|
4496
|
+
if (apiKeyEnvVar === void 0) {
|
|
4497
|
+
return verdict(
|
|
4498
|
+
"api-key",
|
|
4499
|
+
true,
|
|
4500
|
+
`The openai-compatible provider needs no API key. Set ${OPENAI_COMPATIBLE_ENV_VAR} only if your server requires one, or name your own variable with provider.options.apiKeyEnvVar.`
|
|
4501
|
+
);
|
|
4502
|
+
}
|
|
4503
|
+
return envVarVerdict(apiKeyEnvVar);
|
|
4504
|
+
}
|
|
4505
|
+
function checkApiKey(provider) {
|
|
4506
|
+
return provider.id === "openai-compatible" ? checkOpenAiCompatibleKey(provider.options.apiKeyEnvVar) : envVarVerdict(PROVIDER_ENV[provider.id]);
|
|
4507
|
+
}
|
|
4508
|
+
var NOT_PARSED_DETAIL = "Its contents were not checked, because the configured format resolves to no adapter.";
|
|
4509
|
+
async function existenceOnlyVerdict(sourcePath, fs) {
|
|
4510
|
+
return await fs.fileExists(sourcePath) ? verdict("source-file", true, `Found ${sourcePath}. ${NOT_PARSED_DETAIL}`) : verdict("source-file", false, `The source locale file was not found at ${sourcePath}.`);
|
|
4511
|
+
}
|
|
4512
|
+
async function parseSourceVerdict(config, resolver, fs, adapter, sourcePath) {
|
|
4513
|
+
const { resource } = await readSourceResource(config, resolver, fs, adapter);
|
|
4514
|
+
const count = resource.entries.size;
|
|
4515
|
+
return verdict(
|
|
4516
|
+
"source-file",
|
|
4517
|
+
true,
|
|
4518
|
+
`Read ${sourcePath} (${count} translatable ${count === 1 ? "key" : "keys"}).`
|
|
4519
|
+
);
|
|
4520
|
+
}
|
|
4521
|
+
async function checkSourceFile(config, cwd, fs, outcome) {
|
|
4522
|
+
try {
|
|
4523
|
+
const resolver = createLocalePathResolver(cwd, config);
|
|
4524
|
+
const sourcePath = resolver.pathFor(config.sourceLocale);
|
|
4525
|
+
return outcome.kind === "resolved" ? await parseSourceVerdict(config, resolver, fs, outcome.adapter, sourcePath) : await existenceOnlyVerdict(sourcePath, fs);
|
|
4526
|
+
} catch (error) {
|
|
4527
|
+
return verdict("source-file", false, errorMessage(error));
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
async function doctor(input = {}, deps = {}) {
|
|
4531
|
+
const outcome = await loadForDoctor(input, deps);
|
|
4532
|
+
if (outcome.kind === "failed") {
|
|
4533
|
+
return toResult([
|
|
4534
|
+
verdict("config", false, outcome.detail),
|
|
4535
|
+
...CONFIG_DEPENDENT_IDS.map((id) => check2(id, "skipped", SKIPPED_DETAIL))
|
|
4536
|
+
]);
|
|
4537
|
+
}
|
|
4538
|
+
const { config, source } = outcome.loaded;
|
|
4539
|
+
const adapter = resolveAdapter(config, deps);
|
|
4540
|
+
return toResult([
|
|
4541
|
+
verdict("config", true, configDetail(source)),
|
|
4542
|
+
checkAdapter(config, adapter),
|
|
4543
|
+
checkProvider(config.provider),
|
|
4544
|
+
checkApiKey(config.provider),
|
|
4545
|
+
await checkSourceFile(config, input.cwd ?? process.cwd(), deps.fs ?? defaultFs, adapter)
|
|
4546
|
+
]);
|
|
4547
|
+
}
|
|
4548
|
+
|
|
4175
4549
|
// src/cache/fingerprint.ts
|
|
4176
4550
|
function fingerprintModel(provider) {
|
|
4177
4551
|
const options = provider.options;
|
|
@@ -4208,13 +4582,51 @@ function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
|
4208
4582
|
}
|
|
4209
4583
|
return { accepted: true };
|
|
4210
4584
|
}
|
|
4585
|
+
var REMEDY_BY_CODE = {
|
|
4586
|
+
EACCES: "Check the write permissions on the containing directory, then run again.",
|
|
4587
|
+
EPERM: "Check the write permissions on the containing directory, then run again.",
|
|
4588
|
+
EROFS: "That file system is mounted read-only, so nothing can be written there.",
|
|
4589
|
+
ENOSPC: "The device it lives on has no space left.",
|
|
4590
|
+
ENOENT: "Check that the containing directory exists and is reachable, then run again.",
|
|
4591
|
+
EISDIR: "A directory already exists at that path, so no file can take its place."
|
|
4592
|
+
};
|
|
4593
|
+
var DEFAULT_REMEDY = "Check that the containing directory exists and is writable, then run again.";
|
|
4594
|
+
function displayPath(targetPath, cwd) {
|
|
4595
|
+
const relativePath = path.relative(cwd, targetPath);
|
|
4596
|
+
if (relativePath.length === 0 || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
4597
|
+
return targetPath;
|
|
4598
|
+
}
|
|
4599
|
+
return relativePath.split(path.sep).join("/");
|
|
4600
|
+
}
|
|
4601
|
+
function fsErrorCode(error) {
|
|
4602
|
+
if (error instanceof Error && "code" in error && typeof error.code === "string") {
|
|
4603
|
+
return error.code;
|
|
4604
|
+
}
|
|
4605
|
+
return void 0;
|
|
4606
|
+
}
|
|
4607
|
+
function targetUnwritableMessage(targetPath, cwd, error) {
|
|
4608
|
+
const code = fsErrorCode(error);
|
|
4609
|
+
const remedy = (code === void 0 ? void 0 : REMEDY_BY_CODE[code]) ?? DEFAULT_REMEDY;
|
|
4610
|
+
const detail = code === void 0 ? "" : ` (${code})`;
|
|
4611
|
+
return `Could not write the locale file ${displayPath(targetPath, cwd)}${detail}. ${remedy}`;
|
|
4612
|
+
}
|
|
4613
|
+
async function writeTargetResource(adapter, resource, targetPath, cwd) {
|
|
4614
|
+
try {
|
|
4615
|
+
await adapter.write(resource, targetPath);
|
|
4616
|
+
} catch (error) {
|
|
4617
|
+
if (error instanceof AdapterError || error instanceof SdkError) {
|
|
4618
|
+
throw error;
|
|
4619
|
+
}
|
|
4620
|
+
throw new SdkError("TARGET_UNWRITABLE", targetUnwritableMessage(targetPath, cwd, error));
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4211
4623
|
|
|
4212
4624
|
// src/flow/edit-entry.ts
|
|
4213
4625
|
async function editEntry(input, deps = {}) {
|
|
4214
4626
|
const config = input.config;
|
|
4215
4627
|
const cwd = input.cwd ?? process.cwd();
|
|
4216
4628
|
const fs = deps.fs ?? defaultFs;
|
|
4217
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4629
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4218
4630
|
const [locale] = selectLocales(config, [input.locale]);
|
|
4219
4631
|
if (locale === void 0) {
|
|
4220
4632
|
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
@@ -4236,9 +4648,11 @@ async function editEntry(input, deps = {}) {
|
|
|
4236
4648
|
const merged = new Map(target.entries);
|
|
4237
4649
|
merged.set(input.key, { ...sourceEntry, value: input.value, namespace: target.namespace });
|
|
4238
4650
|
const path = createLocalePathResolver(cwd, config).pathFor(locale);
|
|
4239
|
-
await
|
|
4651
|
+
await writeTargetResource(
|
|
4652
|
+
adapter,
|
|
4240
4653
|
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
4241
|
-
path
|
|
4654
|
+
path,
|
|
4655
|
+
cwd
|
|
4242
4656
|
);
|
|
4243
4657
|
await updateLockFileLocale(cwd, fs, locale, {
|
|
4244
4658
|
mode: "merge",
|
|
@@ -4289,7 +4703,7 @@ async function keyIntegrity(input, deps = {}) {
|
|
|
4289
4703
|
const config = input.config;
|
|
4290
4704
|
const cwd = input.cwd ?? process.cwd();
|
|
4291
4705
|
const fs = deps.fs ?? defaultFs;
|
|
4292
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4706
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4293
4707
|
const resolver = createLocalePathResolver(cwd, config);
|
|
4294
4708
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
4295
4709
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
@@ -4317,7 +4731,7 @@ async function keyValue(input, deps = {}) {
|
|
|
4317
4731
|
const config = input.config;
|
|
4318
4732
|
const cwd = input.cwd ?? process.cwd();
|
|
4319
4733
|
const fs = deps.fs ?? defaultFs;
|
|
4320
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4734
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4321
4735
|
const [locale] = selectLocales(config, [input.locale]);
|
|
4322
4736
|
if (locale === void 0) {
|
|
4323
4737
|
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
@@ -4342,7 +4756,7 @@ async function keyValue(input, deps = {}) {
|
|
|
4342
4756
|
async function readLocaleFileSnapshot(input, deps = {}) {
|
|
4343
4757
|
const cwd = input.cwd ?? process.cwd();
|
|
4344
4758
|
const fs = deps.fs ?? defaultFs;
|
|
4345
|
-
const adapter = selectAdapter(input.config.format, deps.adapterRegistry);
|
|
4759
|
+
const adapter = selectAdapter(input.config.format, deps.adapterRegistry, deps.fs);
|
|
4346
4760
|
const resource = await readTarget(cwd, input.config, adapter, fs, input.locale);
|
|
4347
4761
|
const hashes = /* @__PURE__ */ new Map();
|
|
4348
4762
|
for (const [key, entry] of resource.entries) {
|
|
@@ -4391,7 +4805,7 @@ async function lockState(input, deps = {}) {
|
|
|
4391
4805
|
return { exists: false };
|
|
4392
4806
|
}
|
|
4393
4807
|
const lock = await readLockFile(path, fs);
|
|
4394
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4808
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4395
4809
|
const source = await readSource(config, cwd, fs, adapter);
|
|
4396
4810
|
const localeStates = await Promise.all(
|
|
4397
4811
|
locales.map(async (locale) => {
|
|
@@ -4435,7 +4849,7 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
4435
4849
|
const config = input.config;
|
|
4436
4850
|
const cwd = input.cwd ?? process.cwd();
|
|
4437
4851
|
const fs = deps.fs ?? defaultFs;
|
|
4438
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4852
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4439
4853
|
const [locale] = selectLocales(config, [input.locale]);
|
|
4440
4854
|
if (locale === void 0) {
|
|
4441
4855
|
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
@@ -4477,9 +4891,11 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
4477
4891
|
const merged = new Map(target.entries);
|
|
4478
4892
|
merged.set(input.key, { ...sourceEntry, value, namespace: target.namespace });
|
|
4479
4893
|
const path = createLocalePathResolver(cwd, config).pathFor(locale);
|
|
4480
|
-
await
|
|
4894
|
+
await writeTargetResource(
|
|
4895
|
+
adapter,
|
|
4481
4896
|
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
4482
|
-
path
|
|
4897
|
+
path,
|
|
4898
|
+
cwd
|
|
4483
4899
|
);
|
|
4484
4900
|
await updateLockFileLocale(cwd, fs, locale, {
|
|
4485
4901
|
mode: "merge",
|
|
@@ -4569,7 +4985,12 @@ function fromParsed(data) {
|
|
|
4569
4985
|
};
|
|
4570
4986
|
}
|
|
4571
4987
|
async function readRunStatusFile(path, fs) {
|
|
4572
|
-
|
|
4988
|
+
let read;
|
|
4989
|
+
try {
|
|
4990
|
+
read = await fs.readFileBounded(path, MAX_RUN_STATUS_FILE_BYTES);
|
|
4991
|
+
} catch {
|
|
4992
|
+
return void 0;
|
|
4993
|
+
}
|
|
4573
4994
|
if (read.kind !== "ok") {
|
|
4574
4995
|
return void 0;
|
|
4575
4996
|
}
|
|
@@ -5185,14 +5606,16 @@ async function runLocale(params) {
|
|
|
5185
5606
|
generated: generation.accepted.length
|
|
5186
5607
|
});
|
|
5187
5608
|
if (writeNeeded) {
|
|
5188
|
-
await
|
|
5609
|
+
await writeTargetResource(
|
|
5610
|
+
params.adapter,
|
|
5189
5611
|
{
|
|
5190
5612
|
locale: params.targetLocale,
|
|
5191
5613
|
namespace: target.namespace,
|
|
5192
5614
|
format: params.format,
|
|
5193
5615
|
entries: merged
|
|
5194
5616
|
},
|
|
5195
|
-
path
|
|
5617
|
+
path,
|
|
5618
|
+
params.cwd
|
|
5196
5619
|
);
|
|
5197
5620
|
}
|
|
5198
5621
|
const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
|
|
@@ -5611,6 +6034,7 @@ async function translate2(input, deps = {}) {
|
|
|
5611
6034
|
const config = input.config;
|
|
5612
6035
|
const cwd = input.cwd ?? process.cwd();
|
|
5613
6036
|
const dryRun = input.dryRun ?? false;
|
|
6037
|
+
const targetLocales = selectLocales(config, input.locales);
|
|
5614
6038
|
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
5615
6039
|
const prune = input.prune ?? config.prune ?? false;
|
|
5616
6040
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
@@ -5621,7 +6045,7 @@ async function translate2(input, deps = {}) {
|
|
|
5621
6045
|
config.budgetBehavior ?? DEFAULT_BUDGET_BEHAVIOR
|
|
5622
6046
|
);
|
|
5623
6047
|
const resolver = createLocalePathResolver(cwd, config);
|
|
5624
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
6048
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
5625
6049
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
5626
6050
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
5627
6051
|
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
@@ -5642,7 +6066,7 @@ async function translate2(input, deps = {}) {
|
|
|
5642
6066
|
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
5643
6067
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
5644
6068
|
};
|
|
5645
|
-
const summaries = dryRun ? await runAllLocalesDry(context,
|
|
6069
|
+
const summaries = dryRun ? await runAllLocalesDry(context, targetLocales, concurrency) : await runAllLocalesLive(context, targetLocales, concurrency);
|
|
5646
6070
|
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
5647
6071
|
const locales = withCacheNotices(summaries, cache);
|
|
5648
6072
|
const { succeeded, partial, failed } = partition(locales);
|
|
@@ -6509,7 +6933,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
6509
6933
|
const config = input.config;
|
|
6510
6934
|
const cwd = input.cwd ?? process.cwd();
|
|
6511
6935
|
const fs = deps.fs ?? defaultFs;
|
|
6512
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
6936
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
6513
6937
|
const resolver = createLocalePathResolver(cwd, config);
|
|
6514
6938
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
6515
6939
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
@@ -6860,14 +7284,16 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
6860
7284
|
const merged = mergeAccepted(target, accepted);
|
|
6861
7285
|
if (accepted.size > 0) {
|
|
6862
7286
|
const path = ctx.resolver.pathFor(sheet.locale);
|
|
6863
|
-
await
|
|
7287
|
+
await writeTargetResource(
|
|
7288
|
+
ctx.adapter,
|
|
6864
7289
|
{
|
|
6865
7290
|
locale: sheet.locale,
|
|
6866
7291
|
namespace: target.namespace,
|
|
6867
7292
|
format: ctx.config.format,
|
|
6868
7293
|
entries: merged
|
|
6869
7294
|
},
|
|
6870
|
-
path
|
|
7295
|
+
path,
|
|
7296
|
+
ctx.cwd
|
|
6871
7297
|
);
|
|
6872
7298
|
}
|
|
6873
7299
|
return {
|
|
@@ -6881,7 +7307,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6881
7307
|
const cwd = input.cwd ?? process.cwd();
|
|
6882
7308
|
const dryRun = input.dryRun ?? false;
|
|
6883
7309
|
const fs = deps.fs ?? defaultFs;
|
|
6884
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
7310
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
6885
7311
|
const resolver = createLocalePathResolver(cwd, config);
|
|
6886
7312
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
6887
7313
|
const format = input.format ?? DEFAULT_EXCHANGE_FORMAT;
|
|
@@ -6894,6 +7320,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6894
7320
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
6895
7321
|
const ctx = {
|
|
6896
7322
|
config,
|
|
7323
|
+
cwd,
|
|
6897
7324
|
resolver,
|
|
6898
7325
|
adapter,
|
|
6899
7326
|
fs,
|
|
@@ -6924,6 +7351,9 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6924
7351
|
}
|
|
6925
7352
|
summaries.push(summary);
|
|
6926
7353
|
} catch (error) {
|
|
7354
|
+
if (error instanceof SdkError && error.code === "LOCK_FILE_INVALID") {
|
|
7355
|
+
throw error;
|
|
7356
|
+
}
|
|
6927
7357
|
summaries.push(failureSummary(sheet.locale, error));
|
|
6928
7358
|
}
|
|
6929
7359
|
}
|
|
@@ -6983,6 +7413,7 @@ async function watch(input, deps = {}) {
|
|
|
6983
7413
|
const cwd = input.cwd ?? process.cwd();
|
|
6984
7414
|
const debounceMs = input.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
6985
7415
|
const fs = deps.fs ?? defaultFs;
|
|
7416
|
+
selectLocales(input.config, input.locales);
|
|
6986
7417
|
resolveRunConcurrency(input.concurrency, false, input.config);
|
|
6987
7418
|
const resolver = createLocalePathResolver(cwd, input.config);
|
|
6988
7419
|
const sourcePath = resolver.pathFor(input.config.sourceLocale);
|
|
@@ -6996,6 +7427,7 @@ async function watch(input, deps = {}) {
|
|
|
6996
7427
|
const runInput = {
|
|
6997
7428
|
config: input.config,
|
|
6998
7429
|
cwd,
|
|
7430
|
+
...input.locales !== void 0 ? { locales: input.locales } : {},
|
|
6999
7431
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
7000
7432
|
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
7001
7433
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
@@ -7080,6 +7512,7 @@ exports.createLocalePathResolver = createLocalePathResolver;
|
|
|
7080
7512
|
exports.defineConfig = defineConfig;
|
|
7081
7513
|
exports.diff = diff;
|
|
7082
7514
|
exports.diffLocaleSnapshots = diffLocaleSnapshots;
|
|
7515
|
+
exports.doctor = doctor;
|
|
7083
7516
|
exports.editEntry = editEntry;
|
|
7084
7517
|
exports.exportWorkbook = exportWorkbook;
|
|
7085
7518
|
exports.importWorkbook = importWorkbook;
|
|
@@ -7089,11 +7522,13 @@ exports.loadConfig = loadConfig;
|
|
|
7089
7522
|
exports.loadConfigWithMeta = loadConfigWithMeta;
|
|
7090
7523
|
exports.loadLockFile = loadLockFile;
|
|
7091
7524
|
exports.lockState = lockState;
|
|
7525
|
+
exports.readGlossaryFile = readGlossaryFile;
|
|
7092
7526
|
exports.readLocaleFileSnapshot = readLocaleFileSnapshot;
|
|
7093
7527
|
exports.retranslateEntry = retranslateEntry;
|
|
7094
7528
|
exports.runStatus = runStatus;
|
|
7095
7529
|
exports.scaffoldingMetadata = scaffoldingMetadata;
|
|
7096
7530
|
exports.translate = translate2;
|
|
7531
|
+
exports.updateGlossaryTerm = updateGlossaryTerm;
|
|
7097
7532
|
exports.verbatraConfigSchema = verbatraConfigSchema;
|
|
7098
7533
|
exports.watch = watch;
|
|
7099
7534
|
//# sourceMappingURL=index.cjs.map
|