@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.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { dirname, resolve, join, basename, extname } from 'path';
|
|
1
|
+
import { dirname, resolve, join, basename, relative, isAbsolute, sep, extname } from 'path';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
import { mkdir, rm, access, open, writeFile, rename } from 'fs/promises';
|
|
3
5
|
import { existsSync } from 'fs';
|
|
4
6
|
import { cosmiconfig } from 'cosmiconfig';
|
|
5
7
|
import { TypeScriptLoader } from 'cosmiconfig-typescript-loader';
|
|
6
|
-
import { randomUUID } from 'crypto';
|
|
7
|
-
import { mkdir, rm, access, open, writeFile, rename } from 'fs/promises';
|
|
8
8
|
import Anthropic from '@anthropic-ai/sdk';
|
|
9
9
|
import * as deepl from 'deepl-node';
|
|
10
10
|
import { createRequire } from 'module';
|
|
@@ -267,52 +267,238 @@ var defaultFs = {
|
|
|
267
267
|
await mkdir(path, { recursive: true });
|
|
268
268
|
}
|
|
269
269
|
};
|
|
270
|
+
var LOCAL_DIR_NAME = ".verbatra-local";
|
|
271
|
+
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
272
|
+
var GLOSSARY_GUARD_STEM = "_glossary";
|
|
273
|
+
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
274
|
+
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
275
|
+
var WAIT_NOTICE_INTERVAL_MS = 1e3;
|
|
276
|
+
var MAX_LOCK_PAYLOAD_BYTES = 64 * 1024;
|
|
277
|
+
function lockPath(cwd, stem) {
|
|
278
|
+
return resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
279
|
+
}
|
|
280
|
+
function localeLockPath(cwd, locale) {
|
|
281
|
+
return lockPath(cwd, locale);
|
|
282
|
+
}
|
|
283
|
+
function lockFileGuardPath(cwd) {
|
|
284
|
+
return lockPath(cwd, LOCK_FILE_GUARD_STEM);
|
|
285
|
+
}
|
|
286
|
+
function glossaryGuardPath(cwd) {
|
|
287
|
+
return lockPath(cwd, GLOSSARY_GUARD_STEM);
|
|
288
|
+
}
|
|
289
|
+
function sleep(ms) {
|
|
290
|
+
return new Promise((res) => {
|
|
291
|
+
setTimeout(res, ms);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function lockPayload() {
|
|
295
|
+
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
296
|
+
}
|
|
297
|
+
function parseHolder(read) {
|
|
298
|
+
if (read.kind !== "ok") {
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
let parsed;
|
|
302
|
+
try {
|
|
303
|
+
parsed = JSON.parse(read.content);
|
|
304
|
+
} catch {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
const record = parsed;
|
|
311
|
+
const holder = {};
|
|
312
|
+
if (typeof record.pid === "number") {
|
|
313
|
+
holder.pid = record.pid;
|
|
314
|
+
}
|
|
315
|
+
if (typeof record.acquiredAt === "string") {
|
|
316
|
+
holder.acquiredAt = record.acquiredAt;
|
|
317
|
+
}
|
|
318
|
+
return holder;
|
|
319
|
+
}
|
|
320
|
+
function makeWaitNotifier(path, fs, onWait, start) {
|
|
321
|
+
let holder;
|
|
322
|
+
let holderRead = false;
|
|
323
|
+
let lastEmit;
|
|
324
|
+
return async () => {
|
|
325
|
+
if (!holderRead) {
|
|
326
|
+
holderRead = true;
|
|
327
|
+
holder = parseHolder(await fs.readFileBounded(path, MAX_LOCK_PAYLOAD_BYTES));
|
|
328
|
+
}
|
|
329
|
+
const elapsedMs = Date.now() - start;
|
|
330
|
+
if (lastEmit !== void 0 && elapsedMs - lastEmit < WAIT_NOTICE_INTERVAL_MS) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
lastEmit = elapsedMs;
|
|
334
|
+
onWait({ lockPath: path, elapsedMs, ...holder !== void 0 ? { holder } : {} });
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
async function acquireLock(path, fs, pollIntervalMs, deadline, notify) {
|
|
338
|
+
for (; ; ) {
|
|
339
|
+
if (await fs.createExclusive(path, lockPayload())) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (notify !== void 0) {
|
|
343
|
+
await notify();
|
|
344
|
+
}
|
|
345
|
+
if (Date.now() >= deadline) {
|
|
346
|
+
throw new SdkError(
|
|
347
|
+
"LOCK_CONTENDED",
|
|
348
|
+
`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.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const jitter = Math.random() * pollIntervalMs;
|
|
352
|
+
await sleep(pollIntervalMs + jitter);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function withFileLock(path, fs, fn, options) {
|
|
356
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
357
|
+
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
358
|
+
const start = Date.now();
|
|
359
|
+
const notify = options.onWait !== void 0 ? makeWaitNotifier(path, fs, options.onWait, start) : void 0;
|
|
360
|
+
await acquireLock(path, fs, pollIntervalMs, start + acquireTimeoutMs, notify);
|
|
361
|
+
try {
|
|
362
|
+
return await fn();
|
|
363
|
+
} finally {
|
|
364
|
+
await fs.deleteFile(path);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function withLocaleWriteLock(cwd, locale, fs, fn, options = {}) {
|
|
368
|
+
return withFileLock(localeLockPath(cwd, locale), fs, fn, options);
|
|
369
|
+
}
|
|
370
|
+
async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
371
|
+
return withFileLock(lockFileGuardPath(cwd), fs, fn, options);
|
|
372
|
+
}
|
|
373
|
+
async function withGlossaryGuard(cwd, fs, fn, options = {}) {
|
|
374
|
+
return withFileLock(glossaryGuardPath(cwd), fs, fn, options);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/config/glossary-file.ts
|
|
270
378
|
var MAX_GLOSSARY_FILE_BYTES = 1024 * 1024;
|
|
271
|
-
var
|
|
379
|
+
var DEFAULT_INDENT = " ";
|
|
380
|
+
var INDENT_PATTERN = /^([ \t]+)"/m;
|
|
272
381
|
var BOM = "\uFEFF";
|
|
273
382
|
var REPLACEMENT_CHARACTER = "\uFFFD";
|
|
274
383
|
var NUL = "\0";
|
|
384
|
+
var glossaryRecordSchema = z.record(z.string(), z.string());
|
|
275
385
|
function stripBom(content) {
|
|
276
386
|
return content.startsWith(BOM) ? content.slice(BOM.length) : content;
|
|
277
387
|
}
|
|
278
388
|
function looksLikeInvalidEncoding(content) {
|
|
279
389
|
return content.startsWith(REPLACEMENT_CHARACTER) || content.includes(NUL);
|
|
280
390
|
}
|
|
281
|
-
|
|
282
|
-
|
|
391
|
+
function detectIndent(content) {
|
|
392
|
+
return INDENT_PATTERN.exec(content)?.[1] ?? DEFAULT_INDENT;
|
|
393
|
+
}
|
|
394
|
+
async function readGlossaryDocument(path, fs) {
|
|
395
|
+
const read = await fs.readFileBounded(path, MAX_GLOSSARY_FILE_BYTES);
|
|
283
396
|
if (read.kind === "missing") {
|
|
284
397
|
throw new SdkError(
|
|
285
398
|
"CONFIG_INVALID",
|
|
286
|
-
`The glossary file at ${
|
|
399
|
+
`The glossary file at ${path} was not found or could not be read.`
|
|
287
400
|
);
|
|
288
401
|
}
|
|
289
402
|
if (read.kind === "too-large") {
|
|
290
403
|
throw new SdkError(
|
|
291
404
|
"CONFIG_INVALID",
|
|
292
|
-
`The glossary file at ${
|
|
405
|
+
`The glossary file at ${path} exceeds the maximum allowed size of ${MAX_GLOSSARY_FILE_BYTES} bytes.`
|
|
293
406
|
);
|
|
294
407
|
}
|
|
295
408
|
const content = stripBom(read.content);
|
|
296
409
|
if (looksLikeInvalidEncoding(content)) {
|
|
297
|
-
throw new SdkError(
|
|
298
|
-
"CONFIG_INVALID",
|
|
299
|
-
`The glossary file at ${resolvedPath} must be UTF-8 encoded.`
|
|
300
|
-
);
|
|
410
|
+
throw new SdkError("CONFIG_INVALID", `The glossary file at ${path} must be UTF-8 encoded.`);
|
|
301
411
|
}
|
|
302
412
|
let parsed;
|
|
303
413
|
try {
|
|
304
414
|
parsed = JSON.parse(content);
|
|
305
415
|
} catch {
|
|
306
|
-
throw new SdkError("CONFIG_INVALID", `The glossary file at ${
|
|
416
|
+
throw new SdkError("CONFIG_INVALID", `The glossary file at ${path} is not valid JSON.`);
|
|
307
417
|
}
|
|
308
418
|
const result = glossaryRecordSchema.safeParse(parsed);
|
|
309
419
|
if (!result.success) {
|
|
310
420
|
throw new SdkError(
|
|
311
421
|
"CONFIG_INVALID",
|
|
312
|
-
`The glossary file at ${
|
|
422
|
+
`The glossary file at ${path} must contain a flat object of string keys to string values.`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
entries: result.data,
|
|
427
|
+
indent: detectIndent(content),
|
|
428
|
+
trailingNewline: content.endsWith("\n") ? "\n" : ""
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async function readGlossaryRecord(path, fs) {
|
|
432
|
+
return (await readGlossaryDocument(path, fs)).entries;
|
|
433
|
+
}
|
|
434
|
+
function glossaryFilePath(glossary) {
|
|
435
|
+
if (glossary.source !== "file") {
|
|
436
|
+
throw new SdkError(
|
|
437
|
+
"GLOSSARY_NOT_FILE_BACKED",
|
|
438
|
+
`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.`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
return glossary.path;
|
|
442
|
+
}
|
|
443
|
+
function assertWritableTerm(term, translation) {
|
|
444
|
+
if (term.trim().length === 0) {
|
|
445
|
+
throw new SdkError("CONFIG_INVALID", "A glossary term must not be blank.");
|
|
446
|
+
}
|
|
447
|
+
if (translation !== null && translation.trim().length === 0) {
|
|
448
|
+
throw new SdkError(
|
|
449
|
+
"CONFIG_INVALID",
|
|
450
|
+
`The translation for the glossary term "${term}" must not be blank. Remove the term instead.`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function applyTerm(current, term, translation) {
|
|
455
|
+
const pairs = Object.entries(current);
|
|
456
|
+
const index = pairs.findIndex(([existing]) => existing === term);
|
|
457
|
+
if (translation === null) {
|
|
458
|
+
if (index >= 0) {
|
|
459
|
+
pairs.splice(index, 1);
|
|
460
|
+
}
|
|
461
|
+
} else if (index >= 0) {
|
|
462
|
+
pairs[index] = [term, translation];
|
|
463
|
+
} else {
|
|
464
|
+
pairs.push([term, translation]);
|
|
465
|
+
}
|
|
466
|
+
return Object.fromEntries(pairs);
|
|
467
|
+
}
|
|
468
|
+
function serializeGlossary(entries, document, path) {
|
|
469
|
+
const serialized = `${JSON.stringify(entries, null, document.indent)}${document.trailingNewline}`;
|
|
470
|
+
if (Buffer.byteLength(serialized, "utf8") > MAX_GLOSSARY_FILE_BYTES) {
|
|
471
|
+
throw new SdkError(
|
|
472
|
+
"CONFIG_INVALID",
|
|
473
|
+
`The updated glossary file at ${path} would exceed the maximum allowed size of ${MAX_GLOSSARY_FILE_BYTES} bytes.`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
return serialized;
|
|
477
|
+
}
|
|
478
|
+
async function writeGlossary(path, serialized, fs) {
|
|
479
|
+
try {
|
|
480
|
+
await fs.writeFile(path, serialized);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
throw new SdkError(
|
|
483
|
+
"GLOSSARY_UNWRITABLE",
|
|
484
|
+
`The glossary file at ${path} could not be written: ${error instanceof Error ? error.message : String(error)}`
|
|
313
485
|
);
|
|
314
486
|
}
|
|
315
|
-
|
|
487
|
+
}
|
|
488
|
+
async function readGlossaryFile(input, deps = {}) {
|
|
489
|
+
return readGlossaryRecord(glossaryFilePath(input.glossary), deps.fs ?? defaultFs);
|
|
490
|
+
}
|
|
491
|
+
async function updateGlossaryTerm(input, deps = {}) {
|
|
492
|
+
const path = glossaryFilePath(input.glossary);
|
|
493
|
+
assertWritableTerm(input.term, input.translation);
|
|
494
|
+
const fs = deps.fs ?? defaultFs;
|
|
495
|
+
const cwd = input.cwd ?? process.cwd();
|
|
496
|
+
return withGlossaryGuard(cwd, fs, async () => {
|
|
497
|
+
const document = await readGlossaryDocument(path, fs);
|
|
498
|
+
const entries = applyTerm(document.entries, input.term, input.translation);
|
|
499
|
+
await writeGlossary(path, serializeGlossary(entries, document, path), fs);
|
|
500
|
+
return entries;
|
|
501
|
+
});
|
|
316
502
|
}
|
|
317
503
|
async function resolveGlossary(glossary, baseDir, fs) {
|
|
318
504
|
if (glossary === void 0) {
|
|
@@ -320,7 +506,7 @@ async function resolveGlossary(glossary, baseDir, fs) {
|
|
|
320
506
|
}
|
|
321
507
|
if (typeof glossary === "string") {
|
|
322
508
|
const resolvedPath = resolve(baseDir, glossary);
|
|
323
|
-
const record = await
|
|
509
|
+
const record = await readGlossaryRecord(resolvedPath, fs);
|
|
324
510
|
return { glossary: record, provenance: { source: "file", path: resolvedPath } };
|
|
325
511
|
}
|
|
326
512
|
return { glossary, provenance: { source: "inline" } };
|
|
@@ -971,6 +1157,66 @@ function isAbortError(error, signal) {
|
|
|
971
1157
|
}
|
|
972
1158
|
return isAbortShapedError(error);
|
|
973
1159
|
}
|
|
1160
|
+
var REFUSED = {
|
|
1161
|
+
cause: "the connection was refused",
|
|
1162
|
+
remedy: "Check that the endpoint is running and that the configured host and port are right, then run again."
|
|
1163
|
+
};
|
|
1164
|
+
var UNRESOLVED = {
|
|
1165
|
+
cause: "the host name could not be resolved",
|
|
1166
|
+
remedy: "Check the spelling of the configured endpoint and your DNS or network access."
|
|
1167
|
+
};
|
|
1168
|
+
var CLOSED = {
|
|
1169
|
+
cause: "the connection was closed before a reply arrived",
|
|
1170
|
+
remedy: "Retry, and check any proxy or firewall between you and the endpoint."
|
|
1171
|
+
};
|
|
1172
|
+
var UNREACHABLE = {
|
|
1173
|
+
cause: "the host could not be reached",
|
|
1174
|
+
remedy: "Check your network access and any proxy or firewall between you and the endpoint."
|
|
1175
|
+
};
|
|
1176
|
+
var UNTRUSTED_CERTIFICATE = {
|
|
1177
|
+
cause: "the endpoint's TLS certificate could not be verified",
|
|
1178
|
+
remedy: "Check the certificate the endpoint serves, or point at a host your system already trusts."
|
|
1179
|
+
};
|
|
1180
|
+
var NETWORK_CAUSE_BY_CODE = {
|
|
1181
|
+
ECONNREFUSED: REFUSED,
|
|
1182
|
+
ENOTFOUND: UNRESOLVED,
|
|
1183
|
+
EAI_AGAIN: UNRESOLVED,
|
|
1184
|
+
ECONNRESET: CLOSED,
|
|
1185
|
+
ECONNABORTED: CLOSED,
|
|
1186
|
+
EPIPE: CLOSED,
|
|
1187
|
+
ETIMEDOUT: UNREACHABLE,
|
|
1188
|
+
EHOSTUNREACH: UNREACHABLE,
|
|
1189
|
+
ENETUNREACH: UNREACHABLE,
|
|
1190
|
+
CERT_HAS_EXPIRED: UNTRUSTED_CERTIFICATE,
|
|
1191
|
+
DEPTH_ZERO_SELF_SIGNED_CERT: UNTRUSTED_CERTIFICATE,
|
|
1192
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: UNTRUSTED_CERTIFICATE,
|
|
1193
|
+
SELF_SIGNED_CERT_IN_CHAIN: UNTRUSTED_CERTIFICATE
|
|
1194
|
+
};
|
|
1195
|
+
var MAX_CAUSE_DEPTH = 4;
|
|
1196
|
+
function ownCode(error) {
|
|
1197
|
+
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
1198
|
+
return void 0;
|
|
1199
|
+
}
|
|
1200
|
+
const code = error.code;
|
|
1201
|
+
return typeof code === "string" ? code : void 0;
|
|
1202
|
+
}
|
|
1203
|
+
function nextCause(error) {
|
|
1204
|
+
return typeof error === "object" && error !== null && "cause" in error ? error.cause : void 0;
|
|
1205
|
+
}
|
|
1206
|
+
function findNetworkCause(error) {
|
|
1207
|
+
let current = error;
|
|
1208
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && current !== void 0; depth += 1) {
|
|
1209
|
+
const code = ownCode(current);
|
|
1210
|
+
if (code !== void 0 && Object.hasOwn(NETWORK_CAUSE_BY_CODE, code)) {
|
|
1211
|
+
return NETWORK_CAUSE_BY_CODE[code];
|
|
1212
|
+
}
|
|
1213
|
+
current = nextCause(current);
|
|
1214
|
+
}
|
|
1215
|
+
return void 0;
|
|
1216
|
+
}
|
|
1217
|
+
function describeNetworkCause(cause) {
|
|
1218
|
+
return `${cause.cause}. ${cause.remedy}`;
|
|
1219
|
+
}
|
|
974
1220
|
var PROVIDER_CALL_FAILED_MESSAGE = "The translation provider request failed.";
|
|
975
1221
|
var RATE_LIMITED_MESSAGE = "The translation provider rate-limited this request.";
|
|
976
1222
|
var TIMEOUT_MESSAGE = "The translation provider request timed out.";
|
|
@@ -983,7 +1229,19 @@ var MESSAGE_BY_CODE = {
|
|
|
983
1229
|
PROVIDER_UNAVAILABLE: PROVIDER_UNAVAILABLE_MESSAGE,
|
|
984
1230
|
PROVIDER_ERROR: PROVIDER_CALL_FAILED_MESSAGE
|
|
985
1231
|
};
|
|
986
|
-
|
|
1232
|
+
function providerErrorMessage(error, endpointHost) {
|
|
1233
|
+
const cause = findNetworkCause(error);
|
|
1234
|
+
if (cause === void 0 && endpointHost === void 0) {
|
|
1235
|
+
return PROVIDER_CALL_FAILED_MESSAGE;
|
|
1236
|
+
}
|
|
1237
|
+
const where = endpointHost === void 0 ? "" : ` to ${endpointHost}`;
|
|
1238
|
+
const why = cause === void 0 ? "" : `: ${describeNetworkCause(cause)}`;
|
|
1239
|
+
return `The translation provider request${where} failed${why === "" ? "." : why}`;
|
|
1240
|
+
}
|
|
1241
|
+
function messageFor(code, error, context) {
|
|
1242
|
+
return code === "PROVIDER_ERROR" ? providerErrorMessage(error, context?.endpointHost) : MESSAGE_BY_CODE[code];
|
|
1243
|
+
}
|
|
1244
|
+
async function guardProviderCall(call, signal, context) {
|
|
987
1245
|
signal?.throwIfAborted();
|
|
988
1246
|
try {
|
|
989
1247
|
return await call();
|
|
@@ -992,7 +1250,7 @@ async function guardProviderCall(call, signal) {
|
|
|
992
1250
|
throw error;
|
|
993
1251
|
}
|
|
994
1252
|
const code = classifyProviderError(error);
|
|
995
|
-
throw new ProviderError(code,
|
|
1253
|
+
throw new ProviderError(code, messageFor(code, error, context));
|
|
996
1254
|
}
|
|
997
1255
|
}
|
|
998
1256
|
var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
@@ -1002,7 +1260,7 @@ function requestTimedOutMessage(timeoutMs) {
|
|
|
1002
1260
|
function combineSignals(caller, timeout) {
|
|
1003
1261
|
return caller === void 0 ? timeout : AbortSignal.any([caller, timeout]);
|
|
1004
1262
|
}
|
|
1005
|
-
async function withRequestTimeout(timeoutMs, callerSignal, call) {
|
|
1263
|
+
async function withRequestTimeout(timeoutMs, callerSignal, call, context) {
|
|
1006
1264
|
const timeoutController = new AbortController();
|
|
1007
1265
|
const signal = combineSignals(callerSignal, timeoutController.signal);
|
|
1008
1266
|
let timer;
|
|
@@ -1013,7 +1271,7 @@ async function withRequestTimeout(timeoutMs, callerSignal, call) {
|
|
|
1013
1271
|
}, timeoutMs);
|
|
1014
1272
|
});
|
|
1015
1273
|
try {
|
|
1016
|
-
return await Promise.race([guardProviderCall(() => call(signal), signal), timedOut]);
|
|
1274
|
+
return await Promise.race([guardProviderCall(() => call(signal), signal, context), timedOut]);
|
|
1017
1275
|
} finally {
|
|
1018
1276
|
if (timer !== void 0) {
|
|
1019
1277
|
clearTimeout(timer);
|
|
@@ -1747,22 +2005,22 @@ var HOSTED_PROVIDER_ENV_VARS = new Set(
|
|
|
1747
2005
|
function isNotHostedProviderEnvVar(value) {
|
|
1748
2006
|
return !HOSTED_PROVIDER_ENV_VARS.has(value.toUpperCase());
|
|
1749
2007
|
}
|
|
1750
|
-
|
|
1751
|
-
try {
|
|
1752
|
-
const { protocol } = new URL(value);
|
|
1753
|
-
return protocol === "http:" || protocol === "https:";
|
|
1754
|
-
} catch {
|
|
1755
|
-
return false;
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
2008
|
+
var HTTP_OR_HTTPS_SCHEME = /^https?:\/\//i;
|
|
1758
2009
|
var openAiCompatibleConfigSchema = z.object({
|
|
1759
|
-
baseUrl: z.url({ message: "baseUrl must be a valid absolute URL." }).
|
|
2010
|
+
baseUrl: z.url({ message: "baseUrl must be a valid absolute URL." }).regex(HTTP_OR_HTTPS_SCHEME, { message: "baseUrl must use the http or https scheme." }),
|
|
1760
2011
|
model: z.string().min(1),
|
|
1761
2012
|
maxOutputTokens: z.number().int().positive(),
|
|
1762
2013
|
apiKeyEnvVar: z.string().min(1).refine(isNotHostedProviderEnvVar, {
|
|
1763
2014
|
message: "apiKeyEnvVar must not name a hosted provider's environment variable."
|
|
1764
2015
|
}).optional()
|
|
1765
2016
|
}).extend(requestTimeoutConfigSchema.shape);
|
|
2017
|
+
function endpointContextOf(baseUrl) {
|
|
2018
|
+
try {
|
|
2019
|
+
return { endpointHost: new URL(baseUrl).host };
|
|
2020
|
+
} catch {
|
|
2021
|
+
return void 0;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1766
2024
|
function createDefaultClient5(config) {
|
|
1767
2025
|
const sdk = new OpenAI({
|
|
1768
2026
|
apiKey: resolveOpenAiCompatibleKey(config.apiKeyEnvVar),
|
|
@@ -1794,19 +2052,21 @@ function createOpenAiCompatibleProvider(config, deps = {}) {
|
|
|
1794
2052
|
}
|
|
1795
2053
|
function createMechanism4(client, config) {
|
|
1796
2054
|
const timeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
2055
|
+
const endpoint = endpointContextOf(config.baseUrl);
|
|
1797
2056
|
return {
|
|
1798
2057
|
translate: async ({ payloadJson, signal }) => {
|
|
1799
2058
|
const body = buildOpenAiRequest(config, payloadJson, "strict-schema", "max_tokens");
|
|
1800
|
-
const completion = await callClient4(client, body, timeoutMs, signal);
|
|
2059
|
+
const completion = await callClient4(client, body, timeoutMs, signal, endpoint);
|
|
1801
2060
|
return extractOpenAiResult(completion, true);
|
|
1802
2061
|
}
|
|
1803
2062
|
};
|
|
1804
2063
|
}
|
|
1805
|
-
function callClient4(client, body, timeoutMs, signal) {
|
|
2064
|
+
function callClient4(client, body, timeoutMs, signal, endpoint) {
|
|
1806
2065
|
return withRequestTimeout(
|
|
1807
2066
|
timeoutMs,
|
|
1808
2067
|
signal,
|
|
1809
|
-
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal })
|
|
2068
|
+
(requestSignal) => client.chat.completions.create(body, { signal: requestSignal }),
|
|
2069
|
+
endpoint
|
|
1810
2070
|
);
|
|
1811
2071
|
}
|
|
1812
2072
|
var SCAFFOLD_MODELS = {
|
|
@@ -1836,6 +2096,10 @@ var providerFactories = {
|
|
|
1836
2096
|
deepl: (options) => createDeepLProvider(options),
|
|
1837
2097
|
"openai-compatible": (options) => createOpenAiCompatibleProvider(options)
|
|
1838
2098
|
};
|
|
2099
|
+
var PROVIDER_IDS = Object.keys(providerFactories);
|
|
2100
|
+
function hasProviderFactory(id) {
|
|
2101
|
+
return Object.hasOwn(providerFactories, id);
|
|
2102
|
+
}
|
|
1839
2103
|
function buildProvider(config) {
|
|
1840
2104
|
const create = providerFactories[config.id];
|
|
1841
2105
|
return create(config.options);
|
|
@@ -1856,11 +2120,12 @@ function findCaseInsensitiveDuplicate(locales) {
|
|
|
1856
2120
|
return void 0;
|
|
1857
2121
|
}
|
|
1858
2122
|
var verbatraConfigSchema = z.strictObject({
|
|
2123
|
+
$schema: z.string().optional(),
|
|
1859
2124
|
sourceLocale: z.string().min(1),
|
|
1860
2125
|
targetLocales: z.array(z.string().min(1)).min(1),
|
|
1861
2126
|
format: supportedFormatSchema,
|
|
1862
2127
|
files: z.strictObject({
|
|
1863
|
-
pattern: z.string().min(1),
|
|
2128
|
+
pattern: z.string().min(1).regex(/\{locale\}/, { message: `files.pattern must contain the ${LOCALE_TOKEN} token` }),
|
|
1864
2129
|
localeStyle: z.enum(LOCALE_STYLES).optional()
|
|
1865
2130
|
}),
|
|
1866
2131
|
provider: providerConfigSchema,
|
|
@@ -1882,9 +2147,6 @@ var verbatraConfigSchema = z.strictObject({
|
|
|
1882
2147
|
return `targetLocales must not contain case-insensitively duplicate locales: "${duplicate}"`;
|
|
1883
2148
|
},
|
|
1884
2149
|
path: ["targetLocales"]
|
|
1885
|
-
}).refine((config) => config.files.pattern.includes(LOCALE_TOKEN), {
|
|
1886
|
-
message: `files.pattern must contain the ${LOCALE_TOKEN} token`,
|
|
1887
|
-
path: ["files", "pattern"]
|
|
1888
2150
|
});
|
|
1889
2151
|
|
|
1890
2152
|
// src/config/load-config.ts
|
|
@@ -2029,114 +2291,13 @@ function createLocalePathResolver(cwd, config) {
|
|
|
2029
2291
|
const style = config.files.localeStyle ?? DEFAULT_LOCALE_STYLE;
|
|
2030
2292
|
const pattern = config.files.pattern;
|
|
2031
2293
|
validatePattern(pattern, style);
|
|
2032
|
-
const pathFor = (locale) => resolve(cwd, expandPattern(pattern, safeSpelling(locale, style, config.sourceLocale)));
|
|
2033
|
-
const forward = buildForwardMap(config, pathFor);
|
|
2034
|
-
return {
|
|
2035
|
-
pathFor,
|
|
2036
|
-
localeFor: (absolutePath) => forward.get(resolve(cwd, absolutePath))
|
|
2037
|
-
};
|
|
2038
|
-
}
|
|
2039
|
-
var LOCAL_DIR_NAME = ".verbatra-local";
|
|
2040
|
-
var LOCK_FILE_GUARD_STEM = "_lockfile";
|
|
2041
|
-
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
2042
|
-
var DEFAULT_ACQUIRE_TIMEOUT_MS = 10 * 6e4;
|
|
2043
|
-
var WAIT_NOTICE_INTERVAL_MS = 1e3;
|
|
2044
|
-
var MAX_LOCK_PAYLOAD_BYTES = 64 * 1024;
|
|
2045
|
-
function lockPath(cwd, stem) {
|
|
2046
|
-
return resolve(cwd, LOCAL_DIR_NAME, "locks", `${stem}.lock`);
|
|
2047
|
-
}
|
|
2048
|
-
function localeLockPath(cwd, locale) {
|
|
2049
|
-
return lockPath(cwd, locale);
|
|
2050
|
-
}
|
|
2051
|
-
function lockFileGuardPath(cwd) {
|
|
2052
|
-
return lockPath(cwd, LOCK_FILE_GUARD_STEM);
|
|
2053
|
-
}
|
|
2054
|
-
function sleep(ms) {
|
|
2055
|
-
return new Promise((res) => {
|
|
2056
|
-
setTimeout(res, ms);
|
|
2057
|
-
});
|
|
2058
|
-
}
|
|
2059
|
-
function lockPayload() {
|
|
2060
|
-
return JSON.stringify({ pid: process.pid, acquiredAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2061
|
-
}
|
|
2062
|
-
function parseHolder(read) {
|
|
2063
|
-
if (read.kind !== "ok") {
|
|
2064
|
-
return void 0;
|
|
2065
|
-
}
|
|
2066
|
-
let parsed;
|
|
2067
|
-
try {
|
|
2068
|
-
parsed = JSON.parse(read.content);
|
|
2069
|
-
} catch {
|
|
2070
|
-
return void 0;
|
|
2071
|
-
}
|
|
2072
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
2073
|
-
return void 0;
|
|
2074
|
-
}
|
|
2075
|
-
const record = parsed;
|
|
2076
|
-
const holder = {};
|
|
2077
|
-
if (typeof record.pid === "number") {
|
|
2078
|
-
holder.pid = record.pid;
|
|
2079
|
-
}
|
|
2080
|
-
if (typeof record.acquiredAt === "string") {
|
|
2081
|
-
holder.acquiredAt = record.acquiredAt;
|
|
2082
|
-
}
|
|
2083
|
-
return holder;
|
|
2084
|
-
}
|
|
2085
|
-
function makeWaitNotifier(path, fs, onWait, start) {
|
|
2086
|
-
let holder;
|
|
2087
|
-
let holderRead = false;
|
|
2088
|
-
let lastEmit;
|
|
2089
|
-
return async () => {
|
|
2090
|
-
if (!holderRead) {
|
|
2091
|
-
holderRead = true;
|
|
2092
|
-
holder = parseHolder(await fs.readFileBounded(path, MAX_LOCK_PAYLOAD_BYTES));
|
|
2093
|
-
}
|
|
2094
|
-
const elapsedMs = Date.now() - start;
|
|
2095
|
-
if (lastEmit !== void 0 && elapsedMs - lastEmit < WAIT_NOTICE_INTERVAL_MS) {
|
|
2096
|
-
return;
|
|
2097
|
-
}
|
|
2098
|
-
lastEmit = elapsedMs;
|
|
2099
|
-
onWait({ lockPath: path, elapsedMs, ...holder !== void 0 ? { holder } : {} });
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
async function acquireLock(path, fs, pollIntervalMs, deadline, notify) {
|
|
2103
|
-
for (; ; ) {
|
|
2104
|
-
if (await fs.createExclusive(path, lockPayload())) {
|
|
2105
|
-
return;
|
|
2106
|
-
}
|
|
2107
|
-
if (notify !== void 0) {
|
|
2108
|
-
await notify();
|
|
2109
|
-
}
|
|
2110
|
-
if (Date.now() >= deadline) {
|
|
2111
|
-
throw new SdkError(
|
|
2112
|
-
"LOCK_CONTENDED",
|
|
2113
|
-
`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.`
|
|
2114
|
-
);
|
|
2115
|
-
}
|
|
2116
|
-
const jitter = Math.random() * pollIntervalMs;
|
|
2117
|
-
await sleep(pollIntervalMs + jitter);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
async function withFileLock(path, fs, fn, options) {
|
|
2121
|
-
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
2122
|
-
const acquireTimeoutMs = options.acquireTimeoutMs ?? DEFAULT_ACQUIRE_TIMEOUT_MS;
|
|
2123
|
-
const start = Date.now();
|
|
2124
|
-
const notify = options.onWait !== void 0 ? makeWaitNotifier(path, fs, options.onWait, start) : void 0;
|
|
2125
|
-
await acquireLock(path, fs, pollIntervalMs, start + acquireTimeoutMs, notify);
|
|
2126
|
-
try {
|
|
2127
|
-
return await fn();
|
|
2128
|
-
} finally {
|
|
2129
|
-
await fs.deleteFile(path);
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
async function withLocaleWriteLock(cwd, locale, fs, fn, options = {}) {
|
|
2133
|
-
return withFileLock(localeLockPath(cwd, locale), fs, fn, options);
|
|
2134
|
-
}
|
|
2135
|
-
async function withLockFileGuard(cwd, fs, fn, options = {}) {
|
|
2136
|
-
return withFileLock(lockFileGuardPath(cwd), fs, fn, options);
|
|
2294
|
+
const pathFor = (locale) => resolve(cwd, expandPattern(pattern, safeSpelling(locale, style, config.sourceLocale)));
|
|
2295
|
+
const forward = buildForwardMap(config, pathFor);
|
|
2296
|
+
return {
|
|
2297
|
+
pathFor,
|
|
2298
|
+
localeFor: (absolutePath) => forward.get(resolve(cwd, absolutePath))
|
|
2299
|
+
};
|
|
2137
2300
|
}
|
|
2138
|
-
|
|
2139
|
-
// src/lock/lock-file.ts
|
|
2140
2301
|
var LOCK_FILE_NAME = "verbatra.lock.json";
|
|
2141
2302
|
var CURRENT_VERSION2 = 1;
|
|
2142
2303
|
var EMPTY_LOCK = { version: CURRENT_VERSION2, locales: {} };
|
|
@@ -2219,6 +2380,87 @@ async function updateLockFileLocale(cwd, fs, locale, patch) {
|
|
|
2219
2380
|
return next;
|
|
2220
2381
|
});
|
|
2221
2382
|
}
|
|
2383
|
+
async function cleanup(ops, tmp) {
|
|
2384
|
+
try {
|
|
2385
|
+
await ops.rm(tmp);
|
|
2386
|
+
} catch {
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
function tempFileName2(path) {
|
|
2390
|
+
return join(dirname(path), `.${basename(path)}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
2391
|
+
}
|
|
2392
|
+
async function atomicWriteFile(path, data, ops) {
|
|
2393
|
+
const directory = dirname(path);
|
|
2394
|
+
const tmp = tempFileName2(path);
|
|
2395
|
+
try {
|
|
2396
|
+
await ops.mkdir(directory);
|
|
2397
|
+
await ops.writeFile(tmp, data);
|
|
2398
|
+
await ops.fsyncFile(tmp);
|
|
2399
|
+
await ops.rename(tmp, path);
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
await cleanup(ops, tmp);
|
|
2402
|
+
throw error;
|
|
2403
|
+
}
|
|
2404
|
+
try {
|
|
2405
|
+
await ops.fsyncDir(directory);
|
|
2406
|
+
} catch {
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
async function readUtf8(handle, size) {
|
|
2410
|
+
const buffer = Buffer.allocUnsafe(size);
|
|
2411
|
+
let offset = 0;
|
|
2412
|
+
while (offset < size) {
|
|
2413
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
2414
|
+
if (bytesRead === 0) {
|
|
2415
|
+
break;
|
|
2416
|
+
}
|
|
2417
|
+
offset += bytesRead;
|
|
2418
|
+
}
|
|
2419
|
+
return buffer.toString("utf8", 0, offset);
|
|
2420
|
+
}
|
|
2421
|
+
async function nodeReadBounded(path, maxBytes) {
|
|
2422
|
+
const handle = await open(path, "r");
|
|
2423
|
+
try {
|
|
2424
|
+
const info = await handle.stat();
|
|
2425
|
+
if (!info.isFile()) {
|
|
2426
|
+
return { kind: "not-a-file" };
|
|
2427
|
+
}
|
|
2428
|
+
if (info.size > maxBytes) {
|
|
2429
|
+
return { kind: "too-large" };
|
|
2430
|
+
}
|
|
2431
|
+
return { kind: "ok", content: await readUtf8(handle, info.size) };
|
|
2432
|
+
} finally {
|
|
2433
|
+
await handle.close();
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
async function fsyncPath(path) {
|
|
2437
|
+
const handle = await open(path, "r");
|
|
2438
|
+
try {
|
|
2439
|
+
await handle.sync();
|
|
2440
|
+
} finally {
|
|
2441
|
+
await handle.close();
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
async function fsyncDirBestEffort(path) {
|
|
2445
|
+
try {
|
|
2446
|
+
await fsyncPath(path);
|
|
2447
|
+
} catch {
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
var nodeOps = {
|
|
2451
|
+
mkdir: async (path) => {
|
|
2452
|
+
await mkdir(path, { recursive: true });
|
|
2453
|
+
},
|
|
2454
|
+
writeFile: (path, data) => writeFile(path, data, "utf8"),
|
|
2455
|
+
fsyncFile: (path) => fsyncPath(path),
|
|
2456
|
+
rename: (from, to) => rename(from, to),
|
|
2457
|
+
fsyncDir: (path) => fsyncDirBestEffort(path),
|
|
2458
|
+
rm: (path) => rm(path, { force: true })
|
|
2459
|
+
};
|
|
2460
|
+
var nodeAdapterFs = {
|
|
2461
|
+
readBounded: nodeReadBounded,
|
|
2462
|
+
writeFileAtomic: (path, data) => atomicWriteFile(path, data, nodeOps)
|
|
2463
|
+
};
|
|
2222
2464
|
var VALID_EMPTY = { placeholders: [], isPlural: false, valid: true };
|
|
2223
2465
|
var INVALID = { placeholders: [], isPlural: false, valid: false };
|
|
2224
2466
|
function tokenOf(element) {
|
|
@@ -2688,85 +2930,12 @@ function buildCanHandle(extensions, sniff) {
|
|
|
2688
2930
|
return sample === void 0 || sniff === void 0 || sniff(sample);
|
|
2689
2931
|
};
|
|
2690
2932
|
}
|
|
2691
|
-
async function fsyncPath(path) {
|
|
2692
|
-
const handle = await open(path, "r");
|
|
2693
|
-
try {
|
|
2694
|
-
await handle.sync();
|
|
2695
|
-
} finally {
|
|
2696
|
-
await handle.close();
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
async function fsyncDirBestEffort(path) {
|
|
2700
|
-
try {
|
|
2701
|
-
await fsyncPath(path);
|
|
2702
|
-
} catch {
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
var nodeOps = {
|
|
2706
|
-
mkdir: async (path) => {
|
|
2707
|
-
await mkdir(path, { recursive: true });
|
|
2708
|
-
},
|
|
2709
|
-
writeFile: (path, data) => writeFile(path, data, "utf8"),
|
|
2710
|
-
fsyncFile: (path) => fsyncPath(path),
|
|
2711
|
-
rename: (from, to) => rename(from, to),
|
|
2712
|
-
fsyncDir: (path) => fsyncDirBestEffort(path),
|
|
2713
|
-
rm: (path) => rm(path, { force: true })
|
|
2714
|
-
};
|
|
2715
|
-
async function cleanup(ops, tmp) {
|
|
2716
|
-
try {
|
|
2717
|
-
await ops.rm(tmp);
|
|
2718
|
-
} catch {
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
function tempFileName2(path) {
|
|
2722
|
-
return join(dirname(path), `.${basename(path)}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
2723
|
-
}
|
|
2724
|
-
async function atomicWriteFile(path, data, ops = nodeOps) {
|
|
2725
|
-
const directory = dirname(path);
|
|
2726
|
-
const tmp = tempFileName2(path);
|
|
2727
|
-
try {
|
|
2728
|
-
await ops.mkdir(directory);
|
|
2729
|
-
await ops.writeFile(tmp, data);
|
|
2730
|
-
await ops.fsyncFile(tmp);
|
|
2731
|
-
await ops.rename(tmp, path);
|
|
2732
|
-
} catch (error) {
|
|
2733
|
-
await cleanup(ops, tmp);
|
|
2734
|
-
throw error;
|
|
2735
|
-
}
|
|
2736
|
-
try {
|
|
2737
|
-
await ops.fsyncDir(directory);
|
|
2738
|
-
} catch {
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
2933
|
function stripLeadingBom(content) {
|
|
2742
2934
|
return content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
2743
2935
|
}
|
|
2744
|
-
async function
|
|
2745
|
-
const
|
|
2746
|
-
|
|
2747
|
-
while (offset < size) {
|
|
2748
|
-
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
2749
|
-
if (bytesRead === 0) {
|
|
2750
|
-
break;
|
|
2751
|
-
}
|
|
2752
|
-
offset += bytesRead;
|
|
2753
|
-
}
|
|
2754
|
-
return stripLeadingBom(buffer.toString("utf8", 0, offset));
|
|
2755
|
-
}
|
|
2756
|
-
async function readBounded2(filePath) {
|
|
2757
|
-
const handle = await open(filePath, "r");
|
|
2758
|
-
try {
|
|
2759
|
-
const info = await handle.stat();
|
|
2760
|
-
if (!info.isFile()) {
|
|
2761
|
-
return { kind: "not-a-file" };
|
|
2762
|
-
}
|
|
2763
|
-
if (info.size > MAX_INPUT_BYTES) {
|
|
2764
|
-
return { kind: "too-large" };
|
|
2765
|
-
}
|
|
2766
|
-
return { kind: "ok", content: await readBoundedUtf82(handle, info.size) };
|
|
2767
|
-
} finally {
|
|
2768
|
-
await handle.close();
|
|
2769
|
-
}
|
|
2936
|
+
async function readBoundedFile(fs, filePath) {
|
|
2937
|
+
const outcome = await fs.readBounded(filePath, MAX_INPUT_BYTES);
|
|
2938
|
+
return outcome.kind === "ok" ? { kind: "ok", content: stripLeadingBom(outcome.content) } : outcome;
|
|
2770
2939
|
}
|
|
2771
2940
|
function outcomeToContent(outcome, notAFileMessage) {
|
|
2772
2941
|
if (outcome.kind === "not-a-file") {
|
|
@@ -2777,8 +2946,8 @@ function outcomeToContent(outcome, notAFileMessage) {
|
|
|
2777
2946
|
}
|
|
2778
2947
|
return outcome.content;
|
|
2779
2948
|
}
|
|
2780
|
-
async function readFileContent(filePath) {
|
|
2781
|
-
const outcome = await
|
|
2949
|
+
async function readFileContent(fs, filePath) {
|
|
2950
|
+
const outcome = await readBoundedFile(fs, filePath);
|
|
2782
2951
|
return outcomeToContent(outcome, "The path is not a regular file.");
|
|
2783
2952
|
}
|
|
2784
2953
|
var BACKSLASH = "\\";
|
|
@@ -2989,7 +3158,8 @@ function createTreeFileAdapter(options) {
|
|
|
2989
3158
|
buildWriteTree,
|
|
2990
3159
|
comparePlaceholders,
|
|
2991
3160
|
deriveDescriptions,
|
|
2992
|
-
keyMode = "literal-leaf"
|
|
3161
|
+
keyMode = "literal-leaf",
|
|
3162
|
+
fs = nodeAdapterFs
|
|
2993
3163
|
} = options;
|
|
2994
3164
|
return {
|
|
2995
3165
|
format,
|
|
@@ -2998,7 +3168,7 @@ function createTreeFileAdapter(options) {
|
|
|
2998
3168
|
validateMessage: validateMessage ?? (() => true),
|
|
2999
3169
|
...comparePlaceholders !== void 0 ? { comparePlaceholders } : {},
|
|
3000
3170
|
async read(filePath, locale) {
|
|
3001
|
-
const content = await readFileContent(filePath);
|
|
3171
|
+
const content = await readFileContent(fs, filePath);
|
|
3002
3172
|
const namespace = namespaceOf(filePath);
|
|
3003
3173
|
const { entries, excludedLeafPaths } = toEntries(
|
|
3004
3174
|
content,
|
|
@@ -3014,8 +3184,8 @@ function createTreeFileAdapter(options) {
|
|
|
3014
3184
|
return { resource, invalidIcuKeys, excludedLeafPaths };
|
|
3015
3185
|
},
|
|
3016
3186
|
async write(resource, filePath) {
|
|
3017
|
-
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath) : unflattenEntries(resource.entries);
|
|
3018
|
-
await
|
|
3187
|
+
const tree = buildWriteTree ? await buildWriteTree(resource.entries, filePath, fs) : unflattenEntries(resource.entries);
|
|
3188
|
+
await fs.writeFileAtomic(filePath, serialize2(tree));
|
|
3019
3189
|
}
|
|
3020
3190
|
};
|
|
3021
3191
|
}
|
|
@@ -3076,10 +3246,10 @@ function messagesFromEntries(entries) {
|
|
|
3076
3246
|
}
|
|
3077
3247
|
return out;
|
|
3078
3248
|
}
|
|
3079
|
-
async function readDestinationPairs(filePath) {
|
|
3249
|
+
async function readDestinationPairs(filePath, fs) {
|
|
3080
3250
|
let content;
|
|
3081
3251
|
try {
|
|
3082
|
-
content = await readFileContent(filePath);
|
|
3252
|
+
content = await readFileContent(fs, filePath);
|
|
3083
3253
|
} catch (error) {
|
|
3084
3254
|
if (isEnoent(error)) {
|
|
3085
3255
|
return null;
|
|
@@ -3088,9 +3258,9 @@ async function readDestinationPairs(filePath) {
|
|
|
3088
3258
|
}
|
|
3089
3259
|
return [...parseArbObject(content)];
|
|
3090
3260
|
}
|
|
3091
|
-
async function buildArbWriteTree(entries, filePath) {
|
|
3261
|
+
async function buildArbWriteTree(entries, filePath, fs) {
|
|
3092
3262
|
const messages = messagesFromEntries(entries);
|
|
3093
|
-
const pairs = await readDestinationPairs(filePath);
|
|
3263
|
+
const pairs = await readDestinationPairs(filePath, fs);
|
|
3094
3264
|
const out = /* @__PURE__ */ new Map();
|
|
3095
3265
|
const consumed = /* @__PURE__ */ new Set();
|
|
3096
3266
|
for (const [key, value] of pairs ?? []) {
|
|
@@ -3112,8 +3282,9 @@ async function buildArbWriteTree(entries, filePath) {
|
|
|
3112
3282
|
function parseArb(content) {
|
|
3113
3283
|
return assertJsonRecord(stripArbMetadata(parseArbObject(content)));
|
|
3114
3284
|
}
|
|
3115
|
-
function createArbAdapter() {
|
|
3285
|
+
function createArbAdapter(fs = nodeAdapterFs) {
|
|
3116
3286
|
return createTreeFileAdapter({
|
|
3287
|
+
fs,
|
|
3117
3288
|
format: "arb",
|
|
3118
3289
|
extensions: [".arb"],
|
|
3119
3290
|
sniff: sniffJsonObject,
|
|
@@ -3137,6 +3308,38 @@ function createJsonFileAdapter(options) {
|
|
|
3137
3308
|
serialize: serializeJsonTree
|
|
3138
3309
|
});
|
|
3139
3310
|
}
|
|
3311
|
+
var SINGLE_BRACE_PATTERN = /(?<!\{)\{\s*([A-Za-z_][\w$-]*|\d+)\s*\}(?!\})/g;
|
|
3312
|
+
function extractSingleBraceTokens(value) {
|
|
3313
|
+
return scanTokens(value, SINGLE_BRACE_PATTERN, (match) => {
|
|
3314
|
+
const key = match[1];
|
|
3315
|
+
return key !== void 0 ? `{${key}}` : void 0;
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
function fabricatedTokens(sourceValue, targetValue) {
|
|
3319
|
+
const known = new Set(extractSingleBraceTokens(sourceValue));
|
|
3320
|
+
const invented = /* @__PURE__ */ new Set();
|
|
3321
|
+
for (const token of extractSingleBraceTokens(targetValue)) {
|
|
3322
|
+
if (!known.has(token)) {
|
|
3323
|
+
invented.add(token);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
return [...invented].sort();
|
|
3327
|
+
}
|
|
3328
|
+
function createSingleBraceFabricationComparator(extract) {
|
|
3329
|
+
return (sourceValue, targetValue) => {
|
|
3330
|
+
const format = checkPlaceholders(extract(sourceValue), extract(targetValue));
|
|
3331
|
+
const invented = fabricatedTokens(sourceValue, targetValue);
|
|
3332
|
+
if (invented.length === 0) {
|
|
3333
|
+
return format;
|
|
3334
|
+
}
|
|
3335
|
+
return {
|
|
3336
|
+
matches: false,
|
|
3337
|
+
missing: format.missing,
|
|
3338
|
+
extra: [...format.extra, ...invented].sort(),
|
|
3339
|
+
reordered: false
|
|
3340
|
+
};
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3140
3343
|
var DOUBLE_BRACE_PATTERN = /\{\{[^{}]*\}\}/g;
|
|
3141
3344
|
var I18NEXT_PATTERN = /\{\{[^{}]*\}\}|\$t\([^()]*\)/g;
|
|
3142
3345
|
function extractDoubleBracePlaceholders(value) {
|
|
@@ -3162,18 +3365,21 @@ function pluralBaseKey(key) {
|
|
|
3162
3365
|
function makePluralKey(baseKey, category) {
|
|
3163
3366
|
return `${baseKey}_${category}`;
|
|
3164
3367
|
}
|
|
3165
|
-
function createI18nextJsonAdapter() {
|
|
3368
|
+
function createI18nextJsonAdapter(fs = nodeAdapterFs) {
|
|
3166
3369
|
return createJsonFileAdapter({
|
|
3370
|
+
fs,
|
|
3167
3371
|
format: "i18next-json",
|
|
3168
3372
|
extractPlaceholders: extractI18nextPlaceholders,
|
|
3169
3373
|
deriveEntry: (key, value) => ({
|
|
3170
3374
|
placeholders: extractI18nextPlaceholders(value),
|
|
3171
3375
|
isPlural: isPluralKey(key)
|
|
3172
|
-
})
|
|
3376
|
+
}),
|
|
3377
|
+
comparePlaceholders: createSingleBraceFabricationComparator(extractI18nextPlaceholders)
|
|
3173
3378
|
});
|
|
3174
3379
|
}
|
|
3175
|
-
function createNextIntlJsonAdapter() {
|
|
3380
|
+
function createNextIntlJsonAdapter(fs = nodeAdapterFs) {
|
|
3176
3381
|
return createJsonFileAdapter({
|
|
3382
|
+
fs,
|
|
3177
3383
|
format: "next-intl-json",
|
|
3178
3384
|
extractPlaceholders: icuPlaceholders,
|
|
3179
3385
|
deriveEntry: icuDeriveEntry,
|
|
@@ -3214,10 +3420,10 @@ function assertNotMixed(tree) {
|
|
|
3214
3420
|
}
|
|
3215
3421
|
assertNoDottedNestedKey(tree);
|
|
3216
3422
|
}
|
|
3217
|
-
async function detectStyle(filePath) {
|
|
3423
|
+
async function detectStyle(filePath, fs) {
|
|
3218
3424
|
let parsed;
|
|
3219
3425
|
try {
|
|
3220
|
-
const outcome = await
|
|
3426
|
+
const outcome = await readBoundedFile(fs, filePath);
|
|
3221
3427
|
if (outcome.kind !== "ok") {
|
|
3222
3428
|
return "nested";
|
|
3223
3429
|
}
|
|
@@ -3242,18 +3448,20 @@ function buildFlatTree(entries) {
|
|
|
3242
3448
|
}
|
|
3243
3449
|
return out;
|
|
3244
3450
|
}
|
|
3245
|
-
async function buildNgxWriteTree(entries, filePath) {
|
|
3246
|
-
const style = await detectStyle(filePath);
|
|
3451
|
+
async function buildNgxWriteTree(entries, filePath, fs) {
|
|
3452
|
+
const style = await detectStyle(filePath, fs);
|
|
3247
3453
|
return style === "flat" ? buildFlatTree(entries) : unflattenEntries(entries);
|
|
3248
3454
|
}
|
|
3249
|
-
function createNgxTranslateJsonAdapter() {
|
|
3455
|
+
function createNgxTranslateJsonAdapter(fs = nodeAdapterFs) {
|
|
3250
3456
|
return createJsonFileAdapter({
|
|
3457
|
+
fs,
|
|
3251
3458
|
format: "ngx-translate-json",
|
|
3252
3459
|
extractPlaceholders: extractDoubleBracePlaceholders,
|
|
3253
3460
|
deriveEntry: (_key, value) => ({
|
|
3254
3461
|
placeholders: extractDoubleBracePlaceholders(value),
|
|
3255
3462
|
isPlural: false
|
|
3256
3463
|
}),
|
|
3464
|
+
comparePlaceholders: createSingleBraceFabricationComparator(extractDoubleBracePlaceholders),
|
|
3257
3465
|
validateTree: assertNotMixed,
|
|
3258
3466
|
buildWriteTree: buildNgxWriteTree,
|
|
3259
3467
|
keyMode: "path-notation"
|
|
@@ -3275,7 +3483,8 @@ function createFlatFileAdapter(options) {
|
|
|
3275
3483
|
serializeEntries,
|
|
3276
3484
|
extractPlaceholders,
|
|
3277
3485
|
validateMessage,
|
|
3278
|
-
computeInvalidIcuKeys
|
|
3486
|
+
computeInvalidIcuKeys,
|
|
3487
|
+
fs = nodeAdapterFs
|
|
3279
3488
|
} = options;
|
|
3280
3489
|
return {
|
|
3281
3490
|
format,
|
|
@@ -3283,7 +3492,7 @@ function createFlatFileAdapter(options) {
|
|
|
3283
3492
|
extractPlaceholders,
|
|
3284
3493
|
validateMessage: validateMessage ?? (() => true),
|
|
3285
3494
|
async read(filePath, locale) {
|
|
3286
|
-
const content = await readFileContent(filePath);
|
|
3495
|
+
const content = await readFileContent(fs, filePath);
|
|
3287
3496
|
const namespace = namespaceOf(filePath);
|
|
3288
3497
|
const entries = toEntries2(content, namespace, parseEntries);
|
|
3289
3498
|
const resource = { locale, namespace, format, entries };
|
|
@@ -3291,8 +3500,8 @@ function createFlatFileAdapter(options) {
|
|
|
3291
3500
|
return { resource, invalidIcuKeys, excludedLeafPaths: [] };
|
|
3292
3501
|
},
|
|
3293
3502
|
async write(resource, filePath) {
|
|
3294
|
-
const data = await serializeEntries(resource.entries, filePath);
|
|
3295
|
-
await
|
|
3503
|
+
const data = await serializeEntries(resource.entries, filePath, fs);
|
|
3504
|
+
await fs.writeFileAtomic(filePath, data);
|
|
3296
3505
|
}
|
|
3297
3506
|
};
|
|
3298
3507
|
}
|
|
@@ -3300,13 +3509,13 @@ var SUBMESSAGE_TYPES = /* @__PURE__ */ new Set(["plural", "select", "selectordin
|
|
|
3300
3509
|
var ARGUMENT_NAME = /^(?:\d+|[A-Za-z_$][\w$-]*)$/;
|
|
3301
3510
|
function matchingBraces(value) {
|
|
3302
3511
|
const close = new Int32Array(value.length).fill(-1);
|
|
3303
|
-
const
|
|
3512
|
+
const open22 = [];
|
|
3304
3513
|
for (let i = 0; i < value.length; i += 1) {
|
|
3305
3514
|
const char = value[i];
|
|
3306
3515
|
if (char === "{") {
|
|
3307
|
-
|
|
3516
|
+
open22.push(i);
|
|
3308
3517
|
} else if (char === "}") {
|
|
3309
|
-
const start =
|
|
3518
|
+
const start = open22.pop();
|
|
3310
3519
|
if (start !== void 0) {
|
|
3311
3520
|
close[start] = i;
|
|
3312
3521
|
}
|
|
@@ -3326,13 +3535,13 @@ function findNameEnd(value, from, limit) {
|
|
|
3326
3535
|
}
|
|
3327
3536
|
return { comma: -1, hasBrace: false };
|
|
3328
3537
|
}
|
|
3329
|
-
function parseArgumentAt(value,
|
|
3330
|
-
const { comma, hasBrace } = findNameEnd(value,
|
|
3538
|
+
function parseArgumentAt(value, open22, close) {
|
|
3539
|
+
const { comma, hasBrace } = findNameEnd(value, open22 + 1, close);
|
|
3331
3540
|
if (hasBrace) {
|
|
3332
3541
|
return null;
|
|
3333
3542
|
}
|
|
3334
3543
|
const nameEnd = comma === -1 ? close : comma;
|
|
3335
|
-
const name = value.slice(
|
|
3544
|
+
const name = value.slice(open22 + 1, nameEnd).trim();
|
|
3336
3545
|
if (!ARGUMENT_NAME.test(name)) {
|
|
3337
3546
|
return null;
|
|
3338
3547
|
}
|
|
@@ -3599,10 +3808,10 @@ function escapeString(input, isKey) {
|
|
|
3599
3808
|
function formatEntry(key, value) {
|
|
3600
3809
|
return `${escapeString(key, true)}=${escapeString(value, false)}`;
|
|
3601
3810
|
}
|
|
3602
|
-
async function readStructure(filePath) {
|
|
3811
|
+
async function readStructure(filePath, fs) {
|
|
3603
3812
|
let outcome;
|
|
3604
3813
|
try {
|
|
3605
|
-
outcome = await
|
|
3814
|
+
outcome = await readBoundedFile(fs, filePath);
|
|
3606
3815
|
} catch (error) {
|
|
3607
3816
|
if (isEnoent(error)) {
|
|
3608
3817
|
return { items: [], terminator: "\n" };
|
|
@@ -3615,8 +3824,8 @@ async function readStructure(filePath) {
|
|
|
3615
3824
|
terminator: detectLineTerminator(content)
|
|
3616
3825
|
};
|
|
3617
3826
|
}
|
|
3618
|
-
async function serializePropertiesEntries(entries, filePath) {
|
|
3619
|
-
const { items, terminator } = await readStructure(filePath);
|
|
3827
|
+
async function serializePropertiesEntries(entries, filePath, fs) {
|
|
3828
|
+
const { items, terminator } = await readStructure(filePath, fs);
|
|
3620
3829
|
const emitted = /* @__PURE__ */ new Set();
|
|
3621
3830
|
const lines = [];
|
|
3622
3831
|
for (const item of items) {
|
|
@@ -3637,8 +3846,9 @@ async function serializePropertiesEntries(entries, filePath) {
|
|
|
3637
3846
|
}
|
|
3638
3847
|
return lines.length === 0 ? "" : `${lines.join(terminator)}${terminator}`;
|
|
3639
3848
|
}
|
|
3640
|
-
function createPropertiesAdapter() {
|
|
3849
|
+
function createPropertiesAdapter(fs = nodeAdapterFs) {
|
|
3641
3850
|
return createFlatFileAdapter({
|
|
3851
|
+
fs,
|
|
3642
3852
|
format: "properties",
|
|
3643
3853
|
extensions: [".properties"],
|
|
3644
3854
|
parseEntries: parsePropertiesEntries,
|
|
@@ -3695,18 +3905,15 @@ var AdapterRegistry = class {
|
|
|
3695
3905
|
return this.resolveByDetection(filePath, options.sample);
|
|
3696
3906
|
}
|
|
3697
3907
|
};
|
|
3698
|
-
var PLACEHOLDER_PATTERN = /(?<!\{)\{\s*([A-Za-z_][\w$-]*|\d+)\s*\}(?!\})/g;
|
|
3699
3908
|
function extractVueI18nPlaceholders(value) {
|
|
3700
|
-
return
|
|
3701
|
-
const key = match[1];
|
|
3702
|
-
return key !== void 0 ? `{${key}}` : void 0;
|
|
3703
|
-
});
|
|
3909
|
+
return extractSingleBraceTokens(value);
|
|
3704
3910
|
}
|
|
3705
3911
|
function isPluralValue(value) {
|
|
3706
3912
|
return value.includes("|");
|
|
3707
3913
|
}
|
|
3708
|
-
function createVueI18nJsonAdapter() {
|
|
3914
|
+
function createVueI18nJsonAdapter(fs = nodeAdapterFs) {
|
|
3709
3915
|
return createJsonFileAdapter({
|
|
3916
|
+
fs,
|
|
3710
3917
|
format: "vue-i18n-json",
|
|
3711
3918
|
extractPlaceholders: extractVueI18nPlaceholders,
|
|
3712
3919
|
deriveEntry: (_key, value) => ({
|
|
@@ -3853,10 +4060,10 @@ function parseXliffEntries(content, namespace) {
|
|
|
3853
4060
|
}
|
|
3854
4061
|
return out;
|
|
3855
4062
|
}
|
|
3856
|
-
async function readDestination(filePath) {
|
|
4063
|
+
async function readDestination(filePath, fs) {
|
|
3857
4064
|
let outcome;
|
|
3858
4065
|
try {
|
|
3859
|
-
outcome = await
|
|
4066
|
+
outcome = await readBoundedFile(fs, filePath);
|
|
3860
4067
|
} catch {
|
|
3861
4068
|
throw new AdapterError("INVALID_STRUCTURE", "The destination XLIFF file does not exist.");
|
|
3862
4069
|
}
|
|
@@ -3940,8 +4147,8 @@ function setTargetValue(doc, parser, element, value) {
|
|
|
3940
4147
|
element.appendChild(doc.importNode(node, true));
|
|
3941
4148
|
}
|
|
3942
4149
|
}
|
|
3943
|
-
async function serializeXliffEntries(entries, filePath) {
|
|
3944
|
-
const { doc, root } = parseXml(await readDestination(filePath));
|
|
4150
|
+
async function serializeXliffEntries(entries, filePath, fs) {
|
|
4151
|
+
const { doc, root } = parseXml(await readDestination(filePath, fs));
|
|
3945
4152
|
const parser = new DOMParser({ onError: onFatal });
|
|
3946
4153
|
for (const unit of walkUnits(root)) {
|
|
3947
4154
|
const entry = entries.get(unit.key);
|
|
@@ -3959,8 +4166,9 @@ function sniffXliff(sample) {
|
|
|
3959
4166
|
const head = sample.trimStart();
|
|
3960
4167
|
return head.startsWith("<xliff") || head.startsWith("<?xml");
|
|
3961
4168
|
}
|
|
3962
|
-
function createXliffAdapter() {
|
|
4169
|
+
function createXliffAdapter(fs = nodeAdapterFs) {
|
|
3963
4170
|
return createFlatFileAdapter({
|
|
4171
|
+
fs,
|
|
3964
4172
|
format: "xliff",
|
|
3965
4173
|
extensions: [".xlf", ".xliff"],
|
|
3966
4174
|
sniff: sniffXliff,
|
|
@@ -3998,8 +4206,9 @@ function parseYamlObject(content) {
|
|
|
3998
4206
|
assertWithinDepth(parsed, MAX_DEPTH);
|
|
3999
4207
|
return assertJsonRecord(normalizeYamlTree(parsed));
|
|
4000
4208
|
}
|
|
4001
|
-
function createYamlAdapter() {
|
|
4209
|
+
function createYamlAdapter(fs = nodeAdapterFs) {
|
|
4002
4210
|
return createTreeFileAdapter({
|
|
4211
|
+
fs,
|
|
4003
4212
|
format: "yaml",
|
|
4004
4213
|
extensions: [".yml", ".yaml"],
|
|
4005
4214
|
parse: parseYamlObject,
|
|
@@ -4008,16 +4217,41 @@ function createYamlAdapter() {
|
|
|
4008
4217
|
deriveEntry: (_key, value) => ({
|
|
4009
4218
|
placeholders: extractDoubleBracePlaceholders(value),
|
|
4010
4219
|
isPlural: false
|
|
4011
|
-
})
|
|
4220
|
+
}),
|
|
4221
|
+
comparePlaceholders: createSingleBraceFabricationComparator(extractDoubleBracePlaceholders)
|
|
4222
|
+
});
|
|
4223
|
+
}
|
|
4224
|
+
function createDefaultRegistry(fs = nodeAdapterFs) {
|
|
4225
|
+
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));
|
|
4226
|
+
}
|
|
4227
|
+
function enoent(path) {
|
|
4228
|
+
return Object.assign(new Error(`ENOENT: no such file or directory, open '${path}'`), {
|
|
4229
|
+
code: "ENOENT"
|
|
4012
4230
|
});
|
|
4013
4231
|
}
|
|
4014
|
-
function
|
|
4015
|
-
return
|
|
4232
|
+
function toAdapterFs(fs) {
|
|
4233
|
+
return {
|
|
4234
|
+
async readBounded(path, maxBytes) {
|
|
4235
|
+
const outcome = await fs.readFileBounded(path, maxBytes);
|
|
4236
|
+
if (outcome.kind === "missing") {
|
|
4237
|
+
throw enoent(path);
|
|
4238
|
+
}
|
|
4239
|
+
if (outcome.kind === "too-large") {
|
|
4240
|
+
return { kind: "too-large" };
|
|
4241
|
+
}
|
|
4242
|
+
return { kind: "ok", content: outcome.content };
|
|
4243
|
+
},
|
|
4244
|
+
async writeFileAtomic(path, data) {
|
|
4245
|
+
await fs.mkdir?.(dirname(path));
|
|
4246
|
+
await fs.writeFile(path, data);
|
|
4247
|
+
}
|
|
4248
|
+
};
|
|
4016
4249
|
}
|
|
4017
4250
|
|
|
4018
4251
|
// src/selection/select-adapter.ts
|
|
4019
|
-
function selectAdapter(format, registry
|
|
4020
|
-
const
|
|
4252
|
+
function selectAdapter(format, registry, fs) {
|
|
4253
|
+
const resolved = registry ?? createDefaultRegistry(fs === void 0 ? void 0 : toAdapterFs(fs));
|
|
4254
|
+
const resolution = resolved.resolve("", { format });
|
|
4021
4255
|
if (resolution.status === "resolved") {
|
|
4022
4256
|
return resolution.adapter;
|
|
4023
4257
|
}
|
|
@@ -4028,12 +4262,25 @@ function selectAdapter(format, registry = createDefaultRegistry()) {
|
|
|
4028
4262
|
}
|
|
4029
4263
|
|
|
4030
4264
|
// src/flow/read-target.ts
|
|
4265
|
+
function attributeTargetRead(error, locale, path) {
|
|
4266
|
+
if (!(error instanceof AdapterError)) {
|
|
4267
|
+
return error;
|
|
4268
|
+
}
|
|
4269
|
+
return new AdapterError(
|
|
4270
|
+
error.code,
|
|
4271
|
+
`The ${locale} locale file at ${path} could not be read: ${error.message}`
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4031
4274
|
async function readTargetResource(input) {
|
|
4032
4275
|
const path = input.resolver.pathFor(input.locale);
|
|
4033
4276
|
if (!await input.fs.fileExists(path)) {
|
|
4034
4277
|
return { locale: input.locale, namespace: "", format: input.format, entries: /* @__PURE__ */ new Map() };
|
|
4035
4278
|
}
|
|
4036
|
-
|
|
4279
|
+
try {
|
|
4280
|
+
return (await input.adapter.read(path, input.locale)).resource;
|
|
4281
|
+
} catch (error) {
|
|
4282
|
+
throw attributeTargetRead(error, input.locale, path);
|
|
4283
|
+
}
|
|
4037
4284
|
}
|
|
4038
4285
|
|
|
4039
4286
|
// src/flow/select-locales.ts
|
|
@@ -4091,7 +4338,7 @@ async function diffLocales(input, deps = {}) {
|
|
|
4091
4338
|
const config = input.config;
|
|
4092
4339
|
const cwd = input.cwd ?? process.cwd();
|
|
4093
4340
|
const fs = deps.fs ?? defaultFs;
|
|
4094
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4341
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4095
4342
|
const resolver = createLocalePathResolver(cwd, config);
|
|
4096
4343
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
4097
4344
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
@@ -4142,6 +4389,133 @@ async function diff(input, deps = {}) {
|
|
|
4142
4389
|
return { hasPendingChanges: locales.some((entry) => entry.hasPendingChanges), locales };
|
|
4143
4390
|
}
|
|
4144
4391
|
|
|
4392
|
+
// src/flow/doctor.ts
|
|
4393
|
+
var CHECK_TITLES = {
|
|
4394
|
+
config: "Configuration",
|
|
4395
|
+
"format-adapter": "Format adapter",
|
|
4396
|
+
provider: "Provider",
|
|
4397
|
+
"api-key": "API key environment variable",
|
|
4398
|
+
"source-file": "Source locale file"
|
|
4399
|
+
};
|
|
4400
|
+
var CONFIG_DEPENDENT_IDS = [
|
|
4401
|
+
"format-adapter",
|
|
4402
|
+
"provider",
|
|
4403
|
+
"api-key",
|
|
4404
|
+
"source-file"
|
|
4405
|
+
];
|
|
4406
|
+
var SKIPPED_DETAIL = "Not checked: the configuration could not be loaded.";
|
|
4407
|
+
function check2(id, status, detail) {
|
|
4408
|
+
return { id, title: CHECK_TITLES[id], status, detail };
|
|
4409
|
+
}
|
|
4410
|
+
function verdict(id, passed, detail) {
|
|
4411
|
+
return check2(id, passed ? "pass" : "fail", detail);
|
|
4412
|
+
}
|
|
4413
|
+
function toResult(checks) {
|
|
4414
|
+
return { ok: checks.every((entry) => entry.status !== "fail"), checks };
|
|
4415
|
+
}
|
|
4416
|
+
function loadOptionsFor(input, deps) {
|
|
4417
|
+
return {
|
|
4418
|
+
...input.cwd !== void 0 ? { cwd: input.cwd } : {},
|
|
4419
|
+
...input.configPath !== void 0 ? { configPath: input.configPath } : {},
|
|
4420
|
+
...deps.fs !== void 0 ? { fs: deps.fs } : {}
|
|
4421
|
+
};
|
|
4422
|
+
}
|
|
4423
|
+
function isMissingExplicitConfig(error, input) {
|
|
4424
|
+
return input.configPath !== void 0 && error instanceof SdkError && error.code === "CONFIG_NOT_FOUND";
|
|
4425
|
+
}
|
|
4426
|
+
async function loadForDoctor(input, deps) {
|
|
4427
|
+
const load = deps.loadConfig ?? loadConfigWithMeta;
|
|
4428
|
+
try {
|
|
4429
|
+
return { kind: "loaded", loaded: await load(loadOptionsFor(input, deps)) };
|
|
4430
|
+
} catch (error) {
|
|
4431
|
+
if (isMissingExplicitConfig(error, input)) {
|
|
4432
|
+
throw error;
|
|
4433
|
+
}
|
|
4434
|
+
return { kind: "failed", detail: errorMessage(error) };
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
function configDetail(source) {
|
|
4438
|
+
return source.kind === "override" ? "Validated the config supplied in memory." : `Loaded ${source.filepath}.`;
|
|
4439
|
+
}
|
|
4440
|
+
function resolveAdapter(config, deps) {
|
|
4441
|
+
try {
|
|
4442
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4443
|
+
return { kind: "resolved", adapter };
|
|
4444
|
+
} catch (error) {
|
|
4445
|
+
return { kind: "failed", detail: errorMessage(error) };
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
function checkAdapter(config, outcome) {
|
|
4449
|
+
return outcome.kind === "resolved" ? verdict("format-adapter", true, `Format "${config.format}" resolves to an adapter.`) : verdict("format-adapter", false, outcome.detail);
|
|
4450
|
+
}
|
|
4451
|
+
function checkProvider(provider) {
|
|
4452
|
+
return hasProviderFactory(provider.id) ? verdict("provider", true, `Provider "${provider.id}" resolves to a factory.`) : verdict(
|
|
4453
|
+
"provider",
|
|
4454
|
+
false,
|
|
4455
|
+
`No factory is registered for provider "${provider.id}". Supported providers: ${PROVIDER_IDS.join(", ")}.`
|
|
4456
|
+
);
|
|
4457
|
+
}
|
|
4458
|
+
function isEnvVarSet(name) {
|
|
4459
|
+
const value = process.env[name];
|
|
4460
|
+
return value !== void 0 && value.length > 0;
|
|
4461
|
+
}
|
|
4462
|
+
function envVarVerdict(name) {
|
|
4463
|
+
return isEnvVarSet(name) ? verdict("api-key", true, `${name} is set.`) : verdict("api-key", false, `The ${name} environment variable is not set.`);
|
|
4464
|
+
}
|
|
4465
|
+
function checkOpenAiCompatibleKey(apiKeyEnvVar) {
|
|
4466
|
+
if (apiKeyEnvVar === void 0) {
|
|
4467
|
+
return verdict(
|
|
4468
|
+
"api-key",
|
|
4469
|
+
true,
|
|
4470
|
+
`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.`
|
|
4471
|
+
);
|
|
4472
|
+
}
|
|
4473
|
+
return envVarVerdict(apiKeyEnvVar);
|
|
4474
|
+
}
|
|
4475
|
+
function checkApiKey(provider) {
|
|
4476
|
+
return provider.id === "openai-compatible" ? checkOpenAiCompatibleKey(provider.options.apiKeyEnvVar) : envVarVerdict(PROVIDER_ENV[provider.id]);
|
|
4477
|
+
}
|
|
4478
|
+
var NOT_PARSED_DETAIL = "Its contents were not checked, because the configured format resolves to no adapter.";
|
|
4479
|
+
async function existenceOnlyVerdict(sourcePath, fs) {
|
|
4480
|
+
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}.`);
|
|
4481
|
+
}
|
|
4482
|
+
async function parseSourceVerdict(config, resolver, fs, adapter, sourcePath) {
|
|
4483
|
+
const { resource } = await readSourceResource(config, resolver, fs, adapter);
|
|
4484
|
+
const count = resource.entries.size;
|
|
4485
|
+
return verdict(
|
|
4486
|
+
"source-file",
|
|
4487
|
+
true,
|
|
4488
|
+
`Read ${sourcePath} (${count} translatable ${count === 1 ? "key" : "keys"}).`
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
async function checkSourceFile(config, cwd, fs, outcome) {
|
|
4492
|
+
try {
|
|
4493
|
+
const resolver = createLocalePathResolver(cwd, config);
|
|
4494
|
+
const sourcePath = resolver.pathFor(config.sourceLocale);
|
|
4495
|
+
return outcome.kind === "resolved" ? await parseSourceVerdict(config, resolver, fs, outcome.adapter, sourcePath) : await existenceOnlyVerdict(sourcePath, fs);
|
|
4496
|
+
} catch (error) {
|
|
4497
|
+
return verdict("source-file", false, errorMessage(error));
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
async function doctor(input = {}, deps = {}) {
|
|
4501
|
+
const outcome = await loadForDoctor(input, deps);
|
|
4502
|
+
if (outcome.kind === "failed") {
|
|
4503
|
+
return toResult([
|
|
4504
|
+
verdict("config", false, outcome.detail),
|
|
4505
|
+
...CONFIG_DEPENDENT_IDS.map((id) => check2(id, "skipped", SKIPPED_DETAIL))
|
|
4506
|
+
]);
|
|
4507
|
+
}
|
|
4508
|
+
const { config, source } = outcome.loaded;
|
|
4509
|
+
const adapter = resolveAdapter(config, deps);
|
|
4510
|
+
return toResult([
|
|
4511
|
+
verdict("config", true, configDetail(source)),
|
|
4512
|
+
checkAdapter(config, adapter),
|
|
4513
|
+
checkProvider(config.provider),
|
|
4514
|
+
checkApiKey(config.provider),
|
|
4515
|
+
await checkSourceFile(config, input.cwd ?? process.cwd(), deps.fs ?? defaultFs, adapter)
|
|
4516
|
+
]);
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4145
4519
|
// src/cache/fingerprint.ts
|
|
4146
4520
|
function fingerprintModel(provider) {
|
|
4147
4521
|
const options = provider.options;
|
|
@@ -4178,13 +4552,51 @@ function gateCandidateValue(sourceEntry, candidateValue, adapter) {
|
|
|
4178
4552
|
}
|
|
4179
4553
|
return { accepted: true };
|
|
4180
4554
|
}
|
|
4555
|
+
var REMEDY_BY_CODE = {
|
|
4556
|
+
EACCES: "Check the write permissions on the containing directory, then run again.",
|
|
4557
|
+
EPERM: "Check the write permissions on the containing directory, then run again.",
|
|
4558
|
+
EROFS: "That file system is mounted read-only, so nothing can be written there.",
|
|
4559
|
+
ENOSPC: "The device it lives on has no space left.",
|
|
4560
|
+
ENOENT: "Check that the containing directory exists and is reachable, then run again.",
|
|
4561
|
+
EISDIR: "A directory already exists at that path, so no file can take its place."
|
|
4562
|
+
};
|
|
4563
|
+
var DEFAULT_REMEDY = "Check that the containing directory exists and is writable, then run again.";
|
|
4564
|
+
function displayPath(targetPath, cwd) {
|
|
4565
|
+
const relativePath = relative(cwd, targetPath);
|
|
4566
|
+
if (relativePath.length === 0 || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
4567
|
+
return targetPath;
|
|
4568
|
+
}
|
|
4569
|
+
return relativePath.split(sep).join("/");
|
|
4570
|
+
}
|
|
4571
|
+
function fsErrorCode(error) {
|
|
4572
|
+
if (error instanceof Error && "code" in error && typeof error.code === "string") {
|
|
4573
|
+
return error.code;
|
|
4574
|
+
}
|
|
4575
|
+
return void 0;
|
|
4576
|
+
}
|
|
4577
|
+
function targetUnwritableMessage(targetPath, cwd, error) {
|
|
4578
|
+
const code = fsErrorCode(error);
|
|
4579
|
+
const remedy = (code === void 0 ? void 0 : REMEDY_BY_CODE[code]) ?? DEFAULT_REMEDY;
|
|
4580
|
+
const detail = code === void 0 ? "" : ` (${code})`;
|
|
4581
|
+
return `Could not write the locale file ${displayPath(targetPath, cwd)}${detail}. ${remedy}`;
|
|
4582
|
+
}
|
|
4583
|
+
async function writeTargetResource(adapter, resource, targetPath, cwd) {
|
|
4584
|
+
try {
|
|
4585
|
+
await adapter.write(resource, targetPath);
|
|
4586
|
+
} catch (error) {
|
|
4587
|
+
if (error instanceof AdapterError || error instanceof SdkError) {
|
|
4588
|
+
throw error;
|
|
4589
|
+
}
|
|
4590
|
+
throw new SdkError("TARGET_UNWRITABLE", targetUnwritableMessage(targetPath, cwd, error));
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4181
4593
|
|
|
4182
4594
|
// src/flow/edit-entry.ts
|
|
4183
4595
|
async function editEntry(input, deps = {}) {
|
|
4184
4596
|
const config = input.config;
|
|
4185
4597
|
const cwd = input.cwd ?? process.cwd();
|
|
4186
4598
|
const fs = deps.fs ?? defaultFs;
|
|
4187
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4599
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4188
4600
|
const [locale] = selectLocales(config, [input.locale]);
|
|
4189
4601
|
if (locale === void 0) {
|
|
4190
4602
|
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
@@ -4206,9 +4618,11 @@ async function editEntry(input, deps = {}) {
|
|
|
4206
4618
|
const merged = new Map(target.entries);
|
|
4207
4619
|
merged.set(input.key, { ...sourceEntry, value: input.value, namespace: target.namespace });
|
|
4208
4620
|
const path = createLocalePathResolver(cwd, config).pathFor(locale);
|
|
4209
|
-
await
|
|
4621
|
+
await writeTargetResource(
|
|
4622
|
+
adapter,
|
|
4210
4623
|
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
4211
|
-
path
|
|
4624
|
+
path,
|
|
4625
|
+
cwd
|
|
4212
4626
|
);
|
|
4213
4627
|
await updateLockFileLocale(cwd, fs, locale, {
|
|
4214
4628
|
mode: "merge",
|
|
@@ -4259,7 +4673,7 @@ async function keyIntegrity(input, deps = {}) {
|
|
|
4259
4673
|
const config = input.config;
|
|
4260
4674
|
const cwd = input.cwd ?? process.cwd();
|
|
4261
4675
|
const fs = deps.fs ?? defaultFs;
|
|
4262
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4676
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4263
4677
|
const resolver = createLocalePathResolver(cwd, config);
|
|
4264
4678
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
4265
4679
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
@@ -4287,7 +4701,7 @@ async function keyValue(input, deps = {}) {
|
|
|
4287
4701
|
const config = input.config;
|
|
4288
4702
|
const cwd = input.cwd ?? process.cwd();
|
|
4289
4703
|
const fs = deps.fs ?? defaultFs;
|
|
4290
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4704
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4291
4705
|
const [locale] = selectLocales(config, [input.locale]);
|
|
4292
4706
|
if (locale === void 0) {
|
|
4293
4707
|
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
@@ -4312,7 +4726,7 @@ async function keyValue(input, deps = {}) {
|
|
|
4312
4726
|
async function readLocaleFileSnapshot(input, deps = {}) {
|
|
4313
4727
|
const cwd = input.cwd ?? process.cwd();
|
|
4314
4728
|
const fs = deps.fs ?? defaultFs;
|
|
4315
|
-
const adapter = selectAdapter(input.config.format, deps.adapterRegistry);
|
|
4729
|
+
const adapter = selectAdapter(input.config.format, deps.adapterRegistry, deps.fs);
|
|
4316
4730
|
const resource = await readTarget(cwd, input.config, adapter, fs, input.locale);
|
|
4317
4731
|
const hashes = /* @__PURE__ */ new Map();
|
|
4318
4732
|
for (const [key, entry] of resource.entries) {
|
|
@@ -4361,7 +4775,7 @@ async function lockState(input, deps = {}) {
|
|
|
4361
4775
|
return { exists: false };
|
|
4362
4776
|
}
|
|
4363
4777
|
const lock = await readLockFile(path, fs);
|
|
4364
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4778
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4365
4779
|
const source = await readSource(config, cwd, fs, adapter);
|
|
4366
4780
|
const localeStates = await Promise.all(
|
|
4367
4781
|
locales.map(async (locale) => {
|
|
@@ -4405,7 +4819,7 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
4405
4819
|
const config = input.config;
|
|
4406
4820
|
const cwd = input.cwd ?? process.cwd();
|
|
4407
4821
|
const fs = deps.fs ?? defaultFs;
|
|
4408
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
4822
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
4409
4823
|
const [locale] = selectLocales(config, [input.locale]);
|
|
4410
4824
|
if (locale === void 0) {
|
|
4411
4825
|
throw new SdkError("UNKNOWN_LOCALE", `Locale "${input.locale}" could not be resolved.`);
|
|
@@ -4447,9 +4861,11 @@ async function retranslateEntry(input, deps = {}) {
|
|
|
4447
4861
|
const merged = new Map(target.entries);
|
|
4448
4862
|
merged.set(input.key, { ...sourceEntry, value, namespace: target.namespace });
|
|
4449
4863
|
const path = createLocalePathResolver(cwd, config).pathFor(locale);
|
|
4450
|
-
await
|
|
4864
|
+
await writeTargetResource(
|
|
4865
|
+
adapter,
|
|
4451
4866
|
{ locale, namespace: target.namespace, format: config.format, entries: merged },
|
|
4452
|
-
path
|
|
4867
|
+
path,
|
|
4868
|
+
cwd
|
|
4453
4869
|
);
|
|
4454
4870
|
await updateLockFileLocale(cwd, fs, locale, {
|
|
4455
4871
|
mode: "merge",
|
|
@@ -4539,7 +4955,12 @@ function fromParsed(data) {
|
|
|
4539
4955
|
};
|
|
4540
4956
|
}
|
|
4541
4957
|
async function readRunStatusFile(path, fs) {
|
|
4542
|
-
|
|
4958
|
+
let read;
|
|
4959
|
+
try {
|
|
4960
|
+
read = await fs.readFileBounded(path, MAX_RUN_STATUS_FILE_BYTES);
|
|
4961
|
+
} catch {
|
|
4962
|
+
return void 0;
|
|
4963
|
+
}
|
|
4543
4964
|
if (read.kind !== "ok") {
|
|
4544
4965
|
return void 0;
|
|
4545
4966
|
}
|
|
@@ -5155,14 +5576,16 @@ async function runLocale(params) {
|
|
|
5155
5576
|
generated: generation.accepted.length
|
|
5156
5577
|
});
|
|
5157
5578
|
if (writeNeeded) {
|
|
5158
|
-
await
|
|
5579
|
+
await writeTargetResource(
|
|
5580
|
+
params.adapter,
|
|
5159
5581
|
{
|
|
5160
5582
|
locale: params.targetLocale,
|
|
5161
5583
|
namespace: target.namespace,
|
|
5162
5584
|
format: params.format,
|
|
5163
5585
|
entries: merged
|
|
5164
5586
|
},
|
|
5165
|
-
path
|
|
5587
|
+
path,
|
|
5588
|
+
params.cwd
|
|
5166
5589
|
);
|
|
5167
5590
|
}
|
|
5168
5591
|
const pluralNotices = params.generatePlurals ? pluralNoticeFor(params, merged) : sdkNotices;
|
|
@@ -5581,6 +6004,7 @@ async function translate2(input, deps = {}) {
|
|
|
5581
6004
|
const config = input.config;
|
|
5582
6005
|
const cwd = input.cwd ?? process.cwd();
|
|
5583
6006
|
const dryRun = input.dryRun ?? false;
|
|
6007
|
+
const targetLocales = selectLocales(config, input.locales);
|
|
5584
6008
|
const concurrency = resolveRunConcurrency(input.concurrency, dryRun, config);
|
|
5585
6009
|
const prune = input.prune ?? config.prune ?? false;
|
|
5586
6010
|
const generatePlurals = input.generatePlurals ?? config.generatePlurals ?? false;
|
|
@@ -5591,7 +6015,7 @@ async function translate2(input, deps = {}) {
|
|
|
5591
6015
|
config.budgetBehavior ?? DEFAULT_BUDGET_BEHAVIOR
|
|
5592
6016
|
);
|
|
5593
6017
|
const resolver = createLocalePathResolver(cwd, config);
|
|
5594
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
6018
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
5595
6019
|
const provider = dryRun ? void 0 : selectProvider(config.provider, deps.createProvider);
|
|
5596
6020
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
5597
6021
|
const cache = await createRunCacheState(input, config, cwd, dryRun, fs);
|
|
@@ -5612,7 +6036,7 @@ async function translate2(input, deps = {}) {
|
|
|
5612
6036
|
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
5613
6037
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {}
|
|
5614
6038
|
};
|
|
5615
|
-
const summaries = dryRun ? await runAllLocalesDry(context,
|
|
6039
|
+
const summaries = dryRun ? await runAllLocalesDry(context, targetLocales, concurrency) : await runAllLocalesLive(context, targetLocales, concurrency);
|
|
5616
6040
|
input.onProgress?.({ type: "run-finished", localesCompleted: summaries.length });
|
|
5617
6041
|
const locales = withCacheNotices(summaries, cache);
|
|
5618
6042
|
const { succeeded, partial, failed } = partition(locales);
|
|
@@ -6479,7 +6903,7 @@ async function exportWorkbook(input, deps = {}) {
|
|
|
6479
6903
|
const config = input.config;
|
|
6480
6904
|
const cwd = input.cwd ?? process.cwd();
|
|
6481
6905
|
const fs = deps.fs ?? defaultFs;
|
|
6482
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
6906
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
6483
6907
|
const resolver = createLocalePathResolver(cwd, config);
|
|
6484
6908
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
6485
6909
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
@@ -6830,14 +7254,16 @@ async function runSheet(ctx, sheet, lock) {
|
|
|
6830
7254
|
const merged = mergeAccepted(target, accepted);
|
|
6831
7255
|
if (accepted.size > 0) {
|
|
6832
7256
|
const path = ctx.resolver.pathFor(sheet.locale);
|
|
6833
|
-
await
|
|
7257
|
+
await writeTargetResource(
|
|
7258
|
+
ctx.adapter,
|
|
6834
7259
|
{
|
|
6835
7260
|
locale: sheet.locale,
|
|
6836
7261
|
namespace: target.namespace,
|
|
6837
7262
|
format: ctx.config.format,
|
|
6838
7263
|
entries: merged
|
|
6839
7264
|
},
|
|
6840
|
-
path
|
|
7265
|
+
path,
|
|
7266
|
+
ctx.cwd
|
|
6841
7267
|
);
|
|
6842
7268
|
}
|
|
6843
7269
|
return {
|
|
@@ -6851,7 +7277,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6851
7277
|
const cwd = input.cwd ?? process.cwd();
|
|
6852
7278
|
const dryRun = input.dryRun ?? false;
|
|
6853
7279
|
const fs = deps.fs ?? defaultFs;
|
|
6854
|
-
const adapter = selectAdapter(config.format, deps.adapterRegistry);
|
|
7280
|
+
const adapter = selectAdapter(config.format, deps.adapterRegistry, deps.fs);
|
|
6855
7281
|
const resolver = createLocalePathResolver(cwd, config);
|
|
6856
7282
|
const source = await readSourceResource(config, resolver, fs, adapter);
|
|
6857
7283
|
const format = input.format ?? DEFAULT_EXCHANGE_FORMAT;
|
|
@@ -6864,6 +7290,7 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6864
7290
|
const lock = await readLockFile(lockFilePath(cwd), fs);
|
|
6865
7291
|
const ctx = {
|
|
6866
7292
|
config,
|
|
7293
|
+
cwd,
|
|
6867
7294
|
resolver,
|
|
6868
7295
|
adapter,
|
|
6869
7296
|
fs,
|
|
@@ -6894,6 +7321,9 @@ async function importWorkbook(input, deps = {}) {
|
|
|
6894
7321
|
}
|
|
6895
7322
|
summaries.push(summary);
|
|
6896
7323
|
} catch (error) {
|
|
7324
|
+
if (error instanceof SdkError && error.code === "LOCK_FILE_INVALID") {
|
|
7325
|
+
throw error;
|
|
7326
|
+
}
|
|
6897
7327
|
summaries.push(failureSummary(sheet.locale, error));
|
|
6898
7328
|
}
|
|
6899
7329
|
}
|
|
@@ -6953,6 +7383,7 @@ async function watch(input, deps = {}) {
|
|
|
6953
7383
|
const cwd = input.cwd ?? process.cwd();
|
|
6954
7384
|
const debounceMs = input.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
6955
7385
|
const fs = deps.fs ?? defaultFs;
|
|
7386
|
+
selectLocales(input.config, input.locales);
|
|
6956
7387
|
resolveRunConcurrency(input.concurrency, false, input.config);
|
|
6957
7388
|
const resolver = createLocalePathResolver(cwd, input.config);
|
|
6958
7389
|
const sourcePath = resolver.pathFor(input.config.sourceLocale);
|
|
@@ -6966,6 +7397,7 @@ async function watch(input, deps = {}) {
|
|
|
6966
7397
|
const runInput = {
|
|
6967
7398
|
config: input.config,
|
|
6968
7399
|
cwd,
|
|
7400
|
+
...input.locales !== void 0 ? { locales: input.locales } : {},
|
|
6969
7401
|
...input.onLockWait !== void 0 ? { onLockWait: input.onLockWait } : {},
|
|
6970
7402
|
...input.onProgress !== void 0 ? { onProgress: input.onProgress } : {},
|
|
6971
7403
|
...input.lockAcquireTimeoutMs !== void 0 ? { lockAcquireTimeoutMs: input.lockAcquireTimeoutMs } : {},
|
|
@@ -7038,6 +7470,6 @@ async function watch(input, deps = {}) {
|
|
|
7038
7470
|
return { stop };
|
|
7039
7471
|
}
|
|
7040
7472
|
|
|
7041
|
-
export { CACHE_FILE_NAME, DEFAULT_DELIMITED_PATH, DEFAULT_EXCHANGE_FORMAT, DEFAULT_WORKBOOK_PATH, EXCHANGE_FORMATS, LOCK_FILE_NAME, SdkError, check, createLocalePathResolver, defineConfig, diff, diffLocaleSnapshots, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, verbatraConfigSchema, watch };
|
|
7473
|
+
export { CACHE_FILE_NAME, DEFAULT_DELIMITED_PATH, DEFAULT_EXCHANGE_FORMAT, DEFAULT_WORKBOOK_PATH, EXCHANGE_FORMATS, LOCK_FILE_NAME, SdkError, check, createLocalePathResolver, defineConfig, diff, diffLocaleSnapshots, doctor, editEntry, exportWorkbook, importWorkbook, keyIntegrity, keyValue, loadConfig, loadConfigWithMeta, loadLockFile, lockState, readGlossaryFile, readLocaleFileSnapshot, retranslateEntry, runStatus, scaffoldingMetadata, translate2 as translate, updateGlossaryTerm, verbatraConfigSchema, watch };
|
|
7042
7474
|
//# sourceMappingURL=index.js.map
|
|
7043
7475
|
//# sourceMappingURL=index.js.map
|