@vocoder/cli 0.1.16 → 0.1.17
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/LICENSE +21 -0
- package/dist/bin.mjs +176 -175
- package/dist/bin.mjs.map +1 -1
- package/dist/chunk-3QBORM6T.mjs +261 -0
- package/dist/chunk-3QBORM6T.mjs.map +1 -0
- package/dist/lib.d.mts +54 -109
- package/dist/lib.mjs +1 -1
- package/package.json +14 -20
- package/dist/chunk-KPIT5ETY.mjs +0 -547
- package/dist/chunk-KPIT5ETY.mjs.map +0 -1
package/dist/bin.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
detectLocalEcosystem,
|
|
6
6
|
getPackagesToInstall,
|
|
7
7
|
getSetupSnippets
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3QBORM6T.mjs";
|
|
9
9
|
|
|
10
10
|
// src/bin.ts
|
|
11
11
|
import { Command } from "commander";
|
|
@@ -420,7 +420,7 @@ var VocoderAPI = class {
|
|
|
420
420
|
organizationName: data.organizationName,
|
|
421
421
|
sourceLocale: data.sourceLocale,
|
|
422
422
|
targetLocales: data.targetLocales,
|
|
423
|
-
|
|
423
|
+
targetBranches: data.targetBranches ?? ["main"],
|
|
424
424
|
primaryBranch: data.primaryBranch,
|
|
425
425
|
syncPolicy: {
|
|
426
426
|
blockingBranches: data.syncPolicy?.blockingBranches ?? ["main", "master"],
|
|
@@ -480,7 +480,8 @@ var VocoderAPI = class {
|
|
|
480
480
|
...typeof options?.requestedMaxWaitMs === "number" ? { requestedMaxWaitMs: options.requestedMaxWaitMs } : {},
|
|
481
481
|
...options?.clientRunId ? { clientRunId: options.clientRunId } : {},
|
|
482
482
|
...repoIdentity?.repoCanonical ? { repoCanonical: repoIdentity.repoCanonical } : {},
|
|
483
|
-
...repoIdentity?.
|
|
483
|
+
...repoIdentity?.repoAppDir !== void 0 ? { repoAppDir: repoIdentity.repoAppDir } : {},
|
|
484
|
+
...repoIdentity?.commitSha ? { commitSha: repoIdentity.commitSha } : {}
|
|
484
485
|
})
|
|
485
486
|
}, "Translation submission failed");
|
|
486
487
|
}
|
|
@@ -700,6 +701,25 @@ var VocoderAPI = class {
|
|
|
700
701
|
const result = payload;
|
|
701
702
|
return result.projects;
|
|
702
703
|
}
|
|
704
|
+
async regenerateProjectApiKey(userToken, projectId) {
|
|
705
|
+
const response = await fetch(`${this.apiUrl}/api/cli/project/regenerate-key`, {
|
|
706
|
+
method: "POST",
|
|
707
|
+
headers: {
|
|
708
|
+
"Content-Type": "application/json",
|
|
709
|
+
Authorization: `Bearer ${userToken}`
|
|
710
|
+
},
|
|
711
|
+
body: JSON.stringify({ projectId })
|
|
712
|
+
});
|
|
713
|
+
const payload = await readPayload(response);
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
throw new VocoderAPIError({
|
|
716
|
+
message: extractErrorMessage(payload, `Failed to regenerate API key (${response.status})`),
|
|
717
|
+
status: response.status,
|
|
718
|
+
payload
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return payload;
|
|
722
|
+
}
|
|
703
723
|
// ── CLI GitHub endpoints ──────────────────────────────────────────────────────
|
|
704
724
|
async startCliGitHubInstall(userToken, params) {
|
|
705
725
|
const response = await fetch(`${this.apiUrl}/api/cli/github/install/start`, {
|
|
@@ -842,7 +862,7 @@ var VocoderAPI = class {
|
|
|
842
862
|
headers: { "Content-Type": "application/json" },
|
|
843
863
|
body: JSON.stringify({
|
|
844
864
|
repo: params.repoCanonical,
|
|
845
|
-
|
|
865
|
+
appDir: params.appDir
|
|
846
866
|
})
|
|
847
867
|
});
|
|
848
868
|
if (!response.ok) {
|
|
@@ -886,6 +906,15 @@ import { config as loadEnv } from "dotenv";
|
|
|
886
906
|
// src/utils/git-identity.ts
|
|
887
907
|
import { execSync } from "child_process";
|
|
888
908
|
import { relative, resolve } from "path";
|
|
909
|
+
var SHA_REGEX = /^[0-9a-f]{40}$/i;
|
|
910
|
+
function detectCommitSha() {
|
|
911
|
+
if (process.env.VOCODER_COMMIT_SHA && SHA_REGEX.test(process.env.VOCODER_COMMIT_SHA)) {
|
|
912
|
+
return process.env.VOCODER_COMMIT_SHA;
|
|
913
|
+
}
|
|
914
|
+
const knownSha = process.env.GITHUB_SHA || process.env.VERCEL_GIT_COMMIT_SHA || process.env.CI_COMMIT_SHA || process.env.BITBUCKET_COMMIT || process.env.CIRCLE_SHA1 || process.env.RENDER_GIT_COMMIT;
|
|
915
|
+
if (knownSha && SHA_REGEX.test(knownSha)) return knownSha;
|
|
916
|
+
return safeExec("git rev-parse HEAD");
|
|
917
|
+
}
|
|
889
918
|
function safeExec(command) {
|
|
890
919
|
try {
|
|
891
920
|
const output = execSync(command, {
|
|
@@ -956,16 +985,16 @@ function resolveGitRepositoryIdentity() {
|
|
|
956
985
|
}
|
|
957
986
|
const repositoryRoot = safeExec("git rev-parse --show-toplevel");
|
|
958
987
|
const currentDirectory = process.cwd();
|
|
959
|
-
let
|
|
988
|
+
let repoAppDir = "";
|
|
960
989
|
if (repositoryRoot) {
|
|
961
990
|
const relativePath = relative(resolve(repositoryRoot), resolve(currentDirectory)).replace(/\\/g, "/").trim();
|
|
962
991
|
if (relativePath && relativePath !== "." && !relativePath.startsWith("..")) {
|
|
963
|
-
|
|
992
|
+
repoAppDir = relativePath;
|
|
964
993
|
}
|
|
965
994
|
}
|
|
966
995
|
return {
|
|
967
996
|
repoCanonical: toCanonical(parsed.host, parsed.ownerRepoPath),
|
|
968
|
-
|
|
997
|
+
repoAppDir
|
|
969
998
|
};
|
|
970
999
|
}
|
|
971
1000
|
function resolveGitContext() {
|
|
@@ -1249,7 +1278,7 @@ function filterItems(items, query) {
|
|
|
1249
1278
|
const lower = query.toLowerCase();
|
|
1250
1279
|
return items.filter((i) => i.value.toLowerCase().includes(lower));
|
|
1251
1280
|
}
|
|
1252
|
-
function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional = false) {
|
|
1281
|
+
function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional = false, excludedPatterns = /* @__PURE__ */ new Set()) {
|
|
1253
1282
|
const lines = [];
|
|
1254
1283
|
const end = Math.min(filtered.length, scrollOffset + MAX_VISIBLE2);
|
|
1255
1284
|
for (let i = scrollOffset; i < end; i++) {
|
|
@@ -1265,7 +1294,7 @@ function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatt
|
|
|
1265
1294
|
const allItems = [...filtered];
|
|
1266
1295
|
const isNewPattern = trimmed.length > 0 && !allItems.some((i) => i.value === trimmed) && !customPatterns.includes(trimmed);
|
|
1267
1296
|
if (isNewPattern) {
|
|
1268
|
-
const err = validateBranchPattern(trimmed);
|
|
1297
|
+
const err = validateBranchPattern(trimmed) ?? (excludedPatterns.has(trimmed) ? "Already used for automatic translation" : null);
|
|
1269
1298
|
const icon = addCursor ? grn2("\u25FB") : dim2("\u25FB");
|
|
1270
1299
|
const label = err ? `${ylw2("+")} ${dim2(`"${trimmed}" \u2014 ${err}`)}` : `${grn2("+")} Add "${trimmed}" as branch pattern`;
|
|
1271
1300
|
lines.push(`${cyan2(S_BAR2)} ${icon} ${label}`);
|
|
@@ -1284,6 +1313,7 @@ function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatt
|
|
|
1284
1313
|
async function filterableBranchSelect(params) {
|
|
1285
1314
|
const { message, branches, defaultBranch } = params;
|
|
1286
1315
|
const optional = params.optional ?? false;
|
|
1316
|
+
const excludedSet = new Set(params.excludedPatterns ?? []);
|
|
1287
1317
|
let filter = "";
|
|
1288
1318
|
let cursor = 0;
|
|
1289
1319
|
let scrollOffset = 0;
|
|
@@ -1331,7 +1361,7 @@ ${symbol2(this.state)} ${message}
|
|
|
1331
1361
|
return [
|
|
1332
1362
|
hdr.trimEnd(),
|
|
1333
1363
|
`${ylw2(S_BAR2)} ${dim2("/")} ${hint}`,
|
|
1334
|
-
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional),
|
|
1364
|
+
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional, excludedSet),
|
|
1335
1365
|
`${ylw2(S_BAR_END2)} ${ylw2(this.error)}`,
|
|
1336
1366
|
""
|
|
1337
1367
|
].join("\n");
|
|
@@ -1339,7 +1369,7 @@ ${symbol2(this.state)} ${message}
|
|
|
1339
1369
|
return [
|
|
1340
1370
|
hdr.trimEnd(),
|
|
1341
1371
|
`${cyan2(S_BAR2)} ${dim2("/")} ${hint}`,
|
|
1342
|
-
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional),
|
|
1372
|
+
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional, excludedSet),
|
|
1343
1373
|
`${cyan2(S_BAR_END2)}`,
|
|
1344
1374
|
""
|
|
1345
1375
|
].join("\n");
|
|
@@ -1380,7 +1410,7 @@ ${symbol2(this.state)} ${message}
|
|
|
1380
1410
|
case "space":
|
|
1381
1411
|
if (addCursor) {
|
|
1382
1412
|
const t = filter.trim();
|
|
1383
|
-
const err = validateBranchPattern(t);
|
|
1413
|
+
const err = validateBranchPattern(t) ?? (excludedSet.has(t) ? "Already used for automatic translation" : null);
|
|
1384
1414
|
if (!err) {
|
|
1385
1415
|
customPatterns.push(t);
|
|
1386
1416
|
selected.add(t);
|
|
@@ -1441,10 +1471,10 @@ async function runProjectCreate(params) {
|
|
|
1441
1471
|
}
|
|
1442
1472
|
const languageOptions = buildLanguageOptions(rawLocales);
|
|
1443
1473
|
const localeOptions = buildLocaleOptions(rawLocales);
|
|
1444
|
-
let
|
|
1445
|
-
if (params.
|
|
1446
|
-
|
|
1447
|
-
p3.log.success(`App directory: ${chalk4.bold(
|
|
1474
|
+
let appDir;
|
|
1475
|
+
if (params.defaultAppDir) {
|
|
1476
|
+
appDir = params.defaultAppDir;
|
|
1477
|
+
p3.log.success(`App directory: ${chalk4.bold(appDir)}`);
|
|
1448
1478
|
} else {
|
|
1449
1479
|
const rawScope = await p3.text({
|
|
1450
1480
|
message: "App directory (leave blank for the entire repo)",
|
|
@@ -1458,7 +1488,7 @@ async function runProjectCreate(params) {
|
|
|
1458
1488
|
}
|
|
1459
1489
|
});
|
|
1460
1490
|
if (p3.isCancel(rawScope)) return null;
|
|
1461
|
-
|
|
1491
|
+
appDir = (rawScope ?? "").trim();
|
|
1462
1492
|
}
|
|
1463
1493
|
const sourceLocale = await searchSelectLocale(
|
|
1464
1494
|
languageOptions,
|
|
@@ -1482,7 +1512,7 @@ async function runProjectCreate(params) {
|
|
|
1482
1512
|
let initial = initialBranches;
|
|
1483
1513
|
while (pushBranches.length === 0) {
|
|
1484
1514
|
const result = await filterableBranchSelect({
|
|
1485
|
-
message: "
|
|
1515
|
+
message: "Which branches should trigger translations?",
|
|
1486
1516
|
branches: detected.branches,
|
|
1487
1517
|
defaultBranch: detected.defaultBranch,
|
|
1488
1518
|
initialValues: initial
|
|
@@ -1496,59 +1526,15 @@ async function runProjectCreate(params) {
|
|
|
1496
1526
|
}
|
|
1497
1527
|
}
|
|
1498
1528
|
}
|
|
1499
|
-
const
|
|
1500
|
-
message: "Translate on pull requests \u2014 which branches? (optional)",
|
|
1501
|
-
branches: detected.branches,
|
|
1502
|
-
defaultBranch: detected.defaultBranch,
|
|
1503
|
-
initialValues: [],
|
|
1504
|
-
optional: true
|
|
1505
|
-
});
|
|
1506
|
-
if (prResult === null) return null;
|
|
1507
|
-
const prBranches = prResult;
|
|
1508
|
-
const autoSet = /* @__PURE__ */ new Set([...pushBranches, ...prBranches]);
|
|
1509
|
-
const manualResult = await filterableBranchSelect({
|
|
1510
|
-
message: `Manual-only branches \u2014 translate via \`vocoder sync\` only (optional)`,
|
|
1511
|
-
branches: detected.branches.filter((b) => !autoSet.has(b)),
|
|
1512
|
-
defaultBranch: detected.defaultBranch,
|
|
1513
|
-
initialValues: [],
|
|
1514
|
-
optional: true
|
|
1515
|
-
});
|
|
1516
|
-
if (manualResult === null) return null;
|
|
1517
|
-
const manualBranches = manualResult.filter((b) => {
|
|
1518
|
-
if (autoSet.has(b)) {
|
|
1519
|
-
p3.log.warn(`"${b}" is already configured for automatic translation \u2014 skipping from manual.`);
|
|
1520
|
-
return false;
|
|
1521
|
-
}
|
|
1522
|
-
return true;
|
|
1523
|
-
});
|
|
1524
|
-
if (pushBranches.length === 0 && prBranches.length === 0 && manualBranches.length === 0) {
|
|
1525
|
-
p3.log.error("At least one branch must be configured.");
|
|
1526
|
-
return null;
|
|
1527
|
-
}
|
|
1528
|
-
const triggerMap = /* @__PURE__ */ new Map();
|
|
1529
|
-
for (const b of pushBranches) {
|
|
1530
|
-
if (!triggerMap.has(b)) triggerMap.set(b, /* @__PURE__ */ new Set());
|
|
1531
|
-
triggerMap.get(b).add("push");
|
|
1532
|
-
}
|
|
1533
|
-
for (const b of prBranches) {
|
|
1534
|
-
if (!triggerMap.has(b)) triggerMap.set(b, /* @__PURE__ */ new Set());
|
|
1535
|
-
triggerMap.get(b).add("pull_request");
|
|
1536
|
-
}
|
|
1537
|
-
for (const b of manualBranches) {
|
|
1538
|
-
triggerMap.set(b, /* @__PURE__ */ new Set(["manual"]));
|
|
1539
|
-
}
|
|
1540
|
-
const branchTriggers = Array.from(triggerMap.entries()).map(([pattern, triggers]) => ({
|
|
1541
|
-
pattern,
|
|
1542
|
-
triggers: Array.from(triggers)
|
|
1543
|
-
}));
|
|
1529
|
+
const targetBranches = pushBranches;
|
|
1544
1530
|
try {
|
|
1545
1531
|
const result = await api.createProject(userToken, {
|
|
1546
1532
|
organizationId,
|
|
1547
1533
|
name: projectName,
|
|
1548
1534
|
sourceLocale,
|
|
1549
1535
|
targetLocales,
|
|
1550
|
-
|
|
1551
|
-
|
|
1536
|
+
targetBranches,
|
|
1537
|
+
appDirs: appDir ? [appDir] : [],
|
|
1552
1538
|
repoCanonical
|
|
1553
1539
|
});
|
|
1554
1540
|
p3.log.success(`Project ${chalk4.bold(result.projectName)} created!`);
|
|
@@ -1561,7 +1547,7 @@ async function runProjectCreate(params) {
|
|
|
1561
1547
|
}
|
|
1562
1548
|
async function runProjectAppCreate(params) {
|
|
1563
1549
|
const { api, userToken, projectId, projectName, repoCanonical } = params;
|
|
1564
|
-
const existingScopes = new Set(params.existingApps.map((a) => a.
|
|
1550
|
+
const existingScopes = new Set(params.existingApps.map((a) => a.appDir));
|
|
1565
1551
|
let rawLocales;
|
|
1566
1552
|
try {
|
|
1567
1553
|
rawLocales = await api.listLocales(userToken);
|
|
@@ -1571,20 +1557,20 @@ async function runProjectAppCreate(params) {
|
|
|
1571
1557
|
}
|
|
1572
1558
|
const languageOptions = buildLanguageOptions(rawLocales);
|
|
1573
1559
|
const localeOptions = buildLocaleOptions(rawLocales);
|
|
1574
|
-
let
|
|
1575
|
-
if (params.
|
|
1576
|
-
|
|
1577
|
-
p3.log.success(`App directory: ${chalk4.bold(
|
|
1560
|
+
let appDir;
|
|
1561
|
+
if (params.defaultAppDir && !existingScopes.has(params.defaultAppDir)) {
|
|
1562
|
+
appDir = params.defaultAppDir;
|
|
1563
|
+
p3.log.success(`App directory: ${chalk4.bold(appDir)}`);
|
|
1578
1564
|
} else {
|
|
1579
1565
|
if (params.existingApps.length > 0) {
|
|
1580
|
-
const configuredList = params.existingApps.map((a) => chalk4.dim(a.
|
|
1566
|
+
const configuredList = params.existingApps.map((a) => chalk4.dim(a.appDir || "(entire repo)")).join(", ");
|
|
1581
1567
|
p3.log.info(`Already configured: ${configuredList}`);
|
|
1582
1568
|
}
|
|
1583
1569
|
const hasWholeRepoApp = existingScopes.has("");
|
|
1584
1570
|
const rawScope = await p3.text({
|
|
1585
1571
|
message: "App directory for this new app",
|
|
1586
1572
|
placeholder: "e.g. apps/backend",
|
|
1587
|
-
initialValue: params.
|
|
1573
|
+
initialValue: params.defaultAppDir ?? "",
|
|
1588
1574
|
validate(value) {
|
|
1589
1575
|
const v = value.trim();
|
|
1590
1576
|
if (!v && hasWholeRepoApp) return "This project already covers the entire repo.";
|
|
@@ -1595,7 +1581,7 @@ async function runProjectAppCreate(params) {
|
|
|
1595
1581
|
}
|
|
1596
1582
|
});
|
|
1597
1583
|
if (p3.isCancel(rawScope)) return null;
|
|
1598
|
-
|
|
1584
|
+
appDir = (rawScope ?? "").trim();
|
|
1599
1585
|
}
|
|
1600
1586
|
const sourceLocale = await searchSelectLocale(
|
|
1601
1587
|
languageOptions,
|
|
@@ -1618,7 +1604,7 @@ async function runProjectAppCreate(params) {
|
|
|
1618
1604
|
let initial = [detectedApp.defaultBranch];
|
|
1619
1605
|
while (appPushBranches.length === 0) {
|
|
1620
1606
|
const result = await filterableBranchSelect({
|
|
1621
|
-
message: "
|
|
1607
|
+
message: "Which branches should trigger translations?",
|
|
1622
1608
|
branches: detectedApp.branches,
|
|
1623
1609
|
defaultBranch: detectedApp.defaultBranch,
|
|
1624
1610
|
initialValues: initial
|
|
@@ -1632,64 +1618,25 @@ async function runProjectAppCreate(params) {
|
|
|
1632
1618
|
}
|
|
1633
1619
|
}
|
|
1634
1620
|
}
|
|
1635
|
-
const
|
|
1636
|
-
message: "Translate on pull requests \u2014 which branches? (optional)",
|
|
1637
|
-
branches: detectedApp.branches,
|
|
1638
|
-
defaultBranch: detectedApp.defaultBranch,
|
|
1639
|
-
initialValues: [],
|
|
1640
|
-
optional: true
|
|
1641
|
-
});
|
|
1642
|
-
if (appPrResult === null) return null;
|
|
1643
|
-
const appAutoSet = /* @__PURE__ */ new Set([...appPushBranches, ...appPrResult]);
|
|
1644
|
-
const appManualResult = await filterableBranchSelect({
|
|
1645
|
-
message: "Manual-only branches (optional)",
|
|
1646
|
-
branches: detectedApp.branches.filter((b) => !appAutoSet.has(b)),
|
|
1647
|
-
defaultBranch: detectedApp.defaultBranch,
|
|
1648
|
-
initialValues: [],
|
|
1649
|
-
optional: true
|
|
1650
|
-
});
|
|
1651
|
-
if (appManualResult === null) return null;
|
|
1652
|
-
const appManualBranches = appManualResult.filter((b) => {
|
|
1653
|
-
if (appAutoSet.has(b)) {
|
|
1654
|
-
p3.log.warn(`"${b}" is already configured for automatic translation \u2014 skipping from manual.`);
|
|
1655
|
-
return false;
|
|
1656
|
-
}
|
|
1657
|
-
return true;
|
|
1658
|
-
});
|
|
1659
|
-
const appTriggerMap = /* @__PURE__ */ new Map();
|
|
1660
|
-
for (const b of appPushBranches) {
|
|
1661
|
-
if (!appTriggerMap.has(b)) appTriggerMap.set(b, /* @__PURE__ */ new Set());
|
|
1662
|
-
appTriggerMap.get(b).add("push");
|
|
1663
|
-
}
|
|
1664
|
-
for (const b of appPrResult) {
|
|
1665
|
-
if (!appTriggerMap.has(b)) appTriggerMap.set(b, /* @__PURE__ */ new Set());
|
|
1666
|
-
appTriggerMap.get(b).add("pull_request");
|
|
1667
|
-
}
|
|
1668
|
-
for (const b of appManualBranches) {
|
|
1669
|
-
appTriggerMap.set(b, /* @__PURE__ */ new Set(["manual"]));
|
|
1670
|
-
}
|
|
1671
|
-
const branchTriggers = Array.from(appTriggerMap.entries()).map(([pattern, triggers]) => ({
|
|
1672
|
-
pattern,
|
|
1673
|
-
triggers: Array.from(triggers)
|
|
1674
|
-
}));
|
|
1621
|
+
const targetBranches = appPushBranches;
|
|
1675
1622
|
try {
|
|
1676
1623
|
const result = await api.createProjectApp(userToken, {
|
|
1677
1624
|
projectId,
|
|
1678
|
-
|
|
1625
|
+
appDir,
|
|
1679
1626
|
sourceLocale,
|
|
1680
1627
|
targetLocales,
|
|
1681
|
-
|
|
1628
|
+
targetBranches,
|
|
1682
1629
|
repoCanonical: repoCanonical ?? ""
|
|
1683
1630
|
});
|
|
1684
|
-
p3.log.success(`App ${chalk4.bold(
|
|
1631
|
+
p3.log.success(`App ${chalk4.bold(appDir)} added to ${chalk4.bold(projectName)}!`);
|
|
1685
1632
|
return {
|
|
1686
1633
|
projectId: result.projectId,
|
|
1687
1634
|
projectName: result.projectName,
|
|
1688
1635
|
apiKey: result.apiKey,
|
|
1689
|
-
|
|
1636
|
+
appDir: result.appDir,
|
|
1690
1637
|
sourceLocale,
|
|
1691
1638
|
targetLocales,
|
|
1692
|
-
|
|
1639
|
+
targetBranches
|
|
1693
1640
|
};
|
|
1694
1641
|
} catch (error) {
|
|
1695
1642
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1799,9 +1746,7 @@ function printPlanLimitMessage(apiUrl, message) {
|
|
|
1799
1746
|
p5.log.info(`Manage subscription: ${getSubscriptionSettingsUrl(apiUrl)}`);
|
|
1800
1747
|
}
|
|
1801
1748
|
function runScaffold(params) {
|
|
1802
|
-
const {
|
|
1803
|
-
p5.log.info(`Project: ${chalk6.bold(projectName)}`);
|
|
1804
|
-
p5.log.info(`Workspace: ${chalk6.bold(organizationName)}`);
|
|
1749
|
+
const { sourceLocale, targetBranches } = params;
|
|
1805
1750
|
const detection = detectLocalEcosystem();
|
|
1806
1751
|
if (detection.ecosystem) {
|
|
1807
1752
|
const frameworkLabel = detection.framework ?? detection.ecosystem;
|
|
@@ -1828,7 +1773,7 @@ function runScaffold(params) {
|
|
|
1828
1773
|
framework: detection.framework,
|
|
1829
1774
|
ecosystem: detection.ecosystem,
|
|
1830
1775
|
sourceLocale,
|
|
1831
|
-
|
|
1776
|
+
targetBranches
|
|
1832
1777
|
});
|
|
1833
1778
|
let stepNum = 1;
|
|
1834
1779
|
if (snippets.pluginStep) {
|
|
@@ -2037,7 +1982,7 @@ async function runAuthFlow(api, options, reauth = false, repoCanonical) {
|
|
|
2037
1982
|
}
|
|
2038
1983
|
async function init(options = {}) {
|
|
2039
1984
|
const apiUrl = options.apiUrl || process.env.VOCODER_API_URL || "https://vocoder.app";
|
|
2040
|
-
p5.intro("Vocoder Setup");
|
|
1985
|
+
p5.intro(chalk6.bold("Vocoder Setup"));
|
|
2041
1986
|
try {
|
|
2042
1987
|
const gitContext = resolveGitContext();
|
|
2043
1988
|
const identity = gitContext.identity;
|
|
@@ -2053,28 +1998,43 @@ async function init(options = {}) {
|
|
|
2053
1998
|
const anonApi = new VocoderAPI({ apiUrl, apiKey: "" });
|
|
2054
1999
|
const lookup = await anonApi.lookupProjectByRepo({
|
|
2055
2000
|
repoCanonical: identity.repoCanonical,
|
|
2056
|
-
|
|
2001
|
+
appDir: identity.repoAppDir
|
|
2057
2002
|
});
|
|
2058
2003
|
if (lookup.exactMatch) {
|
|
2059
2004
|
const { exactMatch } = lookup;
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
branchTriggers: exactMatch.branchTriggers ?? [{ pattern: "main", triggers: ["push"] }]
|
|
2005
|
+
p5.log.success(`Project: ${chalk6.bold(exactMatch.projectName)}`);
|
|
2006
|
+
p5.log.info(`Branches: ${chalk6.cyan((exactMatch.targetBranches ?? ["main"]).join(", "))}`);
|
|
2007
|
+
const needsKey = await p5.confirm({
|
|
2008
|
+
message: "Need to regenerate your API key?"
|
|
2065
2009
|
});
|
|
2010
|
+
if (!p5.isCancel(needsKey) && needsKey) {
|
|
2011
|
+
const anonApi2 = new VocoderAPI({ apiUrl, apiKey: "" });
|
|
2012
|
+
const authResult = await runAuthFlow(
|
|
2013
|
+
anonApi2,
|
|
2014
|
+
options,
|
|
2015
|
+
/* reauth */
|
|
2016
|
+
true
|
|
2017
|
+
);
|
|
2018
|
+
if (!authResult) return 1;
|
|
2019
|
+
const spinner4 = p5.spinner();
|
|
2020
|
+
spinner4.start("Generating new API key...");
|
|
2021
|
+
try {
|
|
2022
|
+
const { apiKey } = await anonApi2.regenerateProjectApiKey(authResult.token, exactMatch.projectId);
|
|
2023
|
+
spinner4.stop("New API key generated");
|
|
2024
|
+
printMcpSetup(apiKey);
|
|
2025
|
+
} catch {
|
|
2026
|
+
spinner4.stop("Failed to generate key");
|
|
2027
|
+
p5.log.error("Could not generate API key. Try again or generate one from the dashboard.");
|
|
2028
|
+
return 1;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2066
2031
|
p5.outro("Vocoder is already set up for this repository.");
|
|
2067
2032
|
return 0;
|
|
2068
2033
|
}
|
|
2069
2034
|
if (lookup.hasWholeRepoApp) {
|
|
2070
|
-
const wholeRepo = lookup.existingApps.find((a) => a.
|
|
2035
|
+
const wholeRepo = lookup.existingApps.find((a) => a.appDir === "");
|
|
2071
2036
|
if (wholeRepo) {
|
|
2072
|
-
|
|
2073
|
-
projectName: wholeRepo.projectName,
|
|
2074
|
-
organizationName: wholeRepo.organizationName,
|
|
2075
|
-
sourceLocale: "en",
|
|
2076
|
-
branchTriggers: [{ pattern: "main", triggers: ["push"] }]
|
|
2077
|
-
});
|
|
2037
|
+
p5.log.success(`Project: ${chalk6.bold(wholeRepo.projectName)}`);
|
|
2078
2038
|
p5.outro("Vocoder is already set up for this repository.");
|
|
2079
2039
|
return 0;
|
|
2080
2040
|
}
|
|
@@ -2090,7 +2050,6 @@ async function init(options = {}) {
|
|
|
2090
2050
|
let userEmail;
|
|
2091
2051
|
let userName;
|
|
2092
2052
|
let authOrganizationId;
|
|
2093
|
-
let authDiscoveryReady = false;
|
|
2094
2053
|
const stored = readAuthData();
|
|
2095
2054
|
if (stored && stored.apiUrl === apiUrl) {
|
|
2096
2055
|
const verified = await verifyStoredToken(api, stored.token);
|
|
@@ -2118,7 +2077,6 @@ async function init(options = {}) {
|
|
|
2118
2077
|
userEmail = authResult.email;
|
|
2119
2078
|
userName = authResult.name;
|
|
2120
2079
|
authOrganizationId = authResult.organizationId;
|
|
2121
|
-
authDiscoveryReady = authResult.discoveryReady ?? false;
|
|
2122
2080
|
writeAuthData({
|
|
2123
2081
|
token: userToken,
|
|
2124
2082
|
apiUrl,
|
|
@@ -2360,7 +2318,7 @@ async function init(options = {}) {
|
|
|
2360
2318
|
if (repoProjectId && repoProjectName && existingAppsForRepo.length > 0) {
|
|
2361
2319
|
p5.log.info(
|
|
2362
2320
|
`${chalk6.bold(repoProjectName)} is already set up for this repo.
|
|
2363
|
-
Configured apps: ${existingAppsForRepo.map((a) => chalk6.cyan(a.
|
|
2321
|
+
Configured apps: ${existingAppsForRepo.map((a) => chalk6.cyan(a.appDir || "(entire repo)")).join(", ")}`
|
|
2364
2322
|
);
|
|
2365
2323
|
const appResult = await runProjectAppCreate({
|
|
2366
2324
|
api,
|
|
@@ -2369,7 +2327,7 @@ async function init(options = {}) {
|
|
|
2369
2327
|
projectName: repoProjectName,
|
|
2370
2328
|
organizationName: selectedWorkspaceName,
|
|
2371
2329
|
repoCanonical: identity?.repoCanonical,
|
|
2372
|
-
|
|
2330
|
+
defaultAppDir: identity?.repoAppDir,
|
|
2373
2331
|
existingApps: existingAppsForRepo
|
|
2374
2332
|
});
|
|
2375
2333
|
if (!appResult) {
|
|
@@ -2377,10 +2335,8 @@ async function init(options = {}) {
|
|
|
2377
2335
|
return 1;
|
|
2378
2336
|
}
|
|
2379
2337
|
runScaffold({
|
|
2380
|
-
projectName: appResult.projectName,
|
|
2381
|
-
organizationName: selectedWorkspaceName,
|
|
2382
2338
|
sourceLocale: appResult.sourceLocale,
|
|
2383
|
-
|
|
2339
|
+
targetBranches: appResult.targetBranches
|
|
2384
2340
|
});
|
|
2385
2341
|
p5.outro("You're all set.");
|
|
2386
2342
|
return 0;
|
|
@@ -2439,7 +2395,7 @@ async function init(options = {}) {
|
|
|
2439
2395
|
projectName: chosen.name,
|
|
2440
2396
|
organizationName: selectedWorkspaceName,
|
|
2441
2397
|
repoCanonical: identity?.repoCanonical,
|
|
2442
|
-
|
|
2398
|
+
defaultAppDir: identity?.repoAppDir,
|
|
2443
2399
|
existingApps: []
|
|
2444
2400
|
});
|
|
2445
2401
|
if (!appResult) {
|
|
@@ -2447,10 +2403,8 @@ async function init(options = {}) {
|
|
|
2447
2403
|
return 1;
|
|
2448
2404
|
}
|
|
2449
2405
|
runScaffold({
|
|
2450
|
-
projectName: appResult.projectName,
|
|
2451
|
-
organizationName: selectedWorkspaceName,
|
|
2452
2406
|
sourceLocale: appResult.sourceLocale,
|
|
2453
|
-
|
|
2407
|
+
targetBranches: appResult.targetBranches
|
|
2454
2408
|
});
|
|
2455
2409
|
p5.outro("You're all set.");
|
|
2456
2410
|
return 0;
|
|
@@ -2465,7 +2419,7 @@ async function init(options = {}) {
|
|
|
2465
2419
|
defaultSourceLocale: "en",
|
|
2466
2420
|
repoCanonical: identity?.repoCanonical,
|
|
2467
2421
|
defaultBranches: ["main"],
|
|
2468
|
-
|
|
2422
|
+
defaultAppDir: identity?.repoAppDir
|
|
2469
2423
|
});
|
|
2470
2424
|
if (!projectResult) {
|
|
2471
2425
|
p5.log.error("Project creation failed. Run `vocoder init` again.");
|
|
@@ -2484,10 +2438,8 @@ Translations won't run automatically until you grant access.
|
|
|
2484
2438
|
);
|
|
2485
2439
|
}
|
|
2486
2440
|
runScaffold({
|
|
2487
|
-
projectName: projectResult.projectName,
|
|
2488
|
-
organizationName: selectedWorkspaceName,
|
|
2489
2441
|
sourceLocale: projectResult.sourceLocale,
|
|
2490
|
-
|
|
2442
|
+
targetBranches: projectResult.targetBranches
|
|
2491
2443
|
});
|
|
2492
2444
|
printMcpSetup(projectResult.apiKey);
|
|
2493
2445
|
p5.outro("You're all set.");
|
|
@@ -2606,8 +2558,11 @@ function validateLocalConfig(config) {
|
|
|
2606
2558
|
if (!config.apiKey || config.apiKey.length === 0) {
|
|
2607
2559
|
throw new Error("VOCODER_API_KEY is required. Set it in your .env file.");
|
|
2608
2560
|
}
|
|
2609
|
-
if (!config.apiKey.startsWith("
|
|
2610
|
-
|
|
2561
|
+
if (!config.apiKey.startsWith("vcp_")) {
|
|
2562
|
+
if (config.apiKey.startsWith("vco_") || config.apiKey.startsWith("vcu_")) {
|
|
2563
|
+
throw new Error("VOCODER_API_KEY must be a project-scoped key (starts with vcp_). Got an org or user key.");
|
|
2564
|
+
}
|
|
2565
|
+
throw new Error("Invalid API key format. Expected a project API key starting with vcp_.");
|
|
2611
2566
|
}
|
|
2612
2567
|
if (!config.apiUrl || !config.apiUrl.startsWith("http")) {
|
|
2613
2568
|
throw new Error("Invalid API URL");
|
|
@@ -2615,7 +2570,7 @@ function validateLocalConfig(config) {
|
|
|
2615
2570
|
}
|
|
2616
2571
|
async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
2617
2572
|
const configSources = {
|
|
2618
|
-
|
|
2573
|
+
includePattern: "default",
|
|
2619
2574
|
excludePattern: "default",
|
|
2620
2575
|
apiKey: "environment",
|
|
2621
2576
|
apiUrl: "default",
|
|
@@ -2624,29 +2579,53 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2624
2579
|
noFallback: "default"
|
|
2625
2580
|
};
|
|
2626
2581
|
const defaults = {
|
|
2627
|
-
|
|
2628
|
-
excludePattern: [
|
|
2582
|
+
includePattern: ["**/*.{tsx,jsx,ts,js}"],
|
|
2583
|
+
excludePattern: [
|
|
2584
|
+
"**/node_modules/**",
|
|
2585
|
+
"**/.next/**",
|
|
2586
|
+
"**/.nuxt/**",
|
|
2587
|
+
"**/.svelte-kit/**",
|
|
2588
|
+
"**/.output/**",
|
|
2589
|
+
"**/dist/**",
|
|
2590
|
+
"**/build/**",
|
|
2591
|
+
"**/out/**",
|
|
2592
|
+
"**/.vite/**",
|
|
2593
|
+
"**/.turbo/**",
|
|
2594
|
+
"**/coverage/**",
|
|
2595
|
+
"**/.cache/**",
|
|
2596
|
+
"**/*.min.js",
|
|
2597
|
+
"**/*.min.ts",
|
|
2598
|
+
"**/__generated__/**",
|
|
2599
|
+
"**/*.test.*",
|
|
2600
|
+
"**/*.spec.*",
|
|
2601
|
+
"**/*.stories.*",
|
|
2602
|
+
"**/__tests__/**"
|
|
2603
|
+
],
|
|
2629
2604
|
apiUrl: "https://vocoder.app"
|
|
2630
2605
|
};
|
|
2631
|
-
const envExtractionPattern = process.env.
|
|
2606
|
+
const envExtractionPattern = process.env.VOCODER_INCLUDE_PATTERN;
|
|
2607
|
+
const envExcludePattern = process.env.VOCODER_EXCLUDE_PATTERN;
|
|
2632
2608
|
const envApiUrl = process.env.VOCODER_API_URL;
|
|
2633
2609
|
const envSyncMode = process.env.VOCODER_SYNC_MODE;
|
|
2634
2610
|
const envSyncMaxWaitMs = process.env.VOCODER_SYNC_MAX_WAIT_MS;
|
|
2635
2611
|
const envSyncNoFallback = process.env.VOCODER_SYNC_NO_FALLBACK;
|
|
2636
|
-
let
|
|
2612
|
+
let includePattern;
|
|
2637
2613
|
if (cliOptions.include && cliOptions.include.length > 0) {
|
|
2638
|
-
|
|
2639
|
-
configSources.
|
|
2614
|
+
includePattern = cliOptions.include;
|
|
2615
|
+
configSources.includePattern = "CLI flag";
|
|
2640
2616
|
} else if (envExtractionPattern) {
|
|
2641
|
-
|
|
2642
|
-
configSources.
|
|
2617
|
+
includePattern = [envExtractionPattern];
|
|
2618
|
+
configSources.includePattern = "environment";
|
|
2643
2619
|
} else {
|
|
2644
|
-
|
|
2620
|
+
includePattern = defaults.includePattern;
|
|
2645
2621
|
}
|
|
2646
2622
|
let excludePattern;
|
|
2647
2623
|
if (cliOptions.exclude && cliOptions.exclude.length > 0) {
|
|
2648
2624
|
excludePattern = cliOptions.exclude;
|
|
2649
2625
|
configSources.excludePattern = "CLI flag";
|
|
2626
|
+
} else if (envExcludePattern) {
|
|
2627
|
+
excludePattern = envExcludePattern.split(",").map((p9) => p9.trim()).filter(Boolean);
|
|
2628
|
+
configSources.excludePattern = "environment";
|
|
2650
2629
|
} else {
|
|
2651
2630
|
excludePattern = defaults.excludePattern;
|
|
2652
2631
|
}
|
|
@@ -2692,7 +2671,7 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2692
2671
|
}
|
|
2693
2672
|
if (verbose) {
|
|
2694
2673
|
console.log(chalk7.dim("\n Configuration sources:"));
|
|
2695
|
-
console.log(chalk7.dim(` Include patterns: ${configSources.
|
|
2674
|
+
console.log(chalk7.dim(` Include patterns: ${configSources.includePattern}`));
|
|
2696
2675
|
if (excludePattern.length > 0) {
|
|
2697
2676
|
console.log(chalk7.dim(` Exclude patterns: ${configSources.excludePattern}`));
|
|
2698
2677
|
}
|
|
@@ -2707,7 +2686,7 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2707
2686
|
`));
|
|
2708
2687
|
}
|
|
2709
2688
|
return {
|
|
2710
|
-
|
|
2689
|
+
includePattern,
|
|
2711
2690
|
excludePattern,
|
|
2712
2691
|
apiKey,
|
|
2713
2692
|
apiUrl,
|
|
@@ -2721,6 +2700,20 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2721
2700
|
// src/commands/sync.ts
|
|
2722
2701
|
import chalk8 from "chalk";
|
|
2723
2702
|
import { join as join2 } from "path";
|
|
2703
|
+
function computeStringsHash(texts) {
|
|
2704
|
+
const sorted = [...texts].sort();
|
|
2705
|
+
return createHash("sha256").update(sorted.join("\0")).digest("hex").slice(0, 16);
|
|
2706
|
+
}
|
|
2707
|
+
function readCachedStringsHash(projectRoot, branch) {
|
|
2708
|
+
const filePath = getCacheFilePath(projectRoot, branch);
|
|
2709
|
+
if (!existsSync(filePath)) return null;
|
|
2710
|
+
try {
|
|
2711
|
+
const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
2712
|
+
if (isRecord(raw) && typeof raw.stringsHash === "string") return raw.stringsHash;
|
|
2713
|
+
} catch {
|
|
2714
|
+
}
|
|
2715
|
+
return null;
|
|
2716
|
+
}
|
|
2724
2717
|
function isRecord(value) {
|
|
2725
2718
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2726
2719
|
}
|
|
@@ -2765,10 +2758,8 @@ function parseTranslations(value) {
|
|
|
2765
2758
|
return Object.keys(translations).length > 0 ? translations : null;
|
|
2766
2759
|
}
|
|
2767
2760
|
function getCacheFilePath(projectRoot, branch) {
|
|
2768
|
-
const slug = branch.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40);
|
|
2769
2761
|
const branchHash = createHash("sha1").update(branch).digest("hex").slice(0, 12);
|
|
2770
|
-
|
|
2771
|
-
return join2(projectRoot, "node_modules", ".vocoder", "cache", "sync", filename);
|
|
2762
|
+
return join2(projectRoot, "node_modules", ".vocoder", "cache", "sync", `${branchHash}.json`);
|
|
2772
2763
|
}
|
|
2773
2764
|
function readLocalSnapshotCache(params) {
|
|
2774
2765
|
const candidateBranches = params.branch === "main" ? ["main"] : [params.branch, "main"];
|
|
@@ -2813,6 +2804,7 @@ function writeLocalSnapshotCache(params) {
|
|
|
2813
2804
|
sourceLocale: params.sourceLocale,
|
|
2814
2805
|
targetLocales: params.targetLocales,
|
|
2815
2806
|
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2807
|
+
...params.stringsHash ? { stringsHash: params.stringsHash } : {},
|
|
2816
2808
|
...params.snapshotBatchId ? { snapshotBatchId: params.snapshotBatchId } : {},
|
|
2817
2809
|
...params.completedAt ? { completedAt: params.completedAt } : {},
|
|
2818
2810
|
...params.localeMetadata ? { localeMetadata: params.localeMetadata } : {},
|
|
@@ -2994,7 +2986,7 @@ async function sync(options = {}) {
|
|
|
2994
2986
|
const config = {
|
|
2995
2987
|
...localConfig,
|
|
2996
2988
|
...apiConfig,
|
|
2997
|
-
|
|
2989
|
+
includePattern: mergedConfig.includePattern,
|
|
2998
2990
|
excludePattern: mergedConfig.excludePattern,
|
|
2999
2991
|
timeout: waitTimeoutMs
|
|
3000
2992
|
};
|
|
@@ -3008,11 +3000,11 @@ async function sync(options = {}) {
|
|
|
3008
3000
|
p7.outro("");
|
|
3009
3001
|
return 0;
|
|
3010
3002
|
}
|
|
3011
|
-
const patternsDisplay = Array.isArray(config.
|
|
3003
|
+
const patternsDisplay = Array.isArray(config.includePattern) ? config.includePattern.join(", ") : config.includePattern;
|
|
3012
3004
|
spinner4.start(`Extracting strings from ${patternsDisplay}`);
|
|
3013
3005
|
const extractor = new StringExtractor();
|
|
3014
3006
|
const extractedStrings = await extractor.extractFromProject(
|
|
3015
|
-
config.
|
|
3007
|
+
config.includePattern,
|
|
3016
3008
|
projectRoot,
|
|
3017
3009
|
config.excludePattern
|
|
3018
3010
|
);
|
|
@@ -3053,6 +3045,7 @@ async function sync(options = {}) {
|
|
|
3053
3045
|
"Could not detect git remote origin. Sync will continue without repo metadata."
|
|
3054
3046
|
);
|
|
3055
3047
|
}
|
|
3048
|
+
const commitSha = detectCommitSha() ?? void 0;
|
|
3056
3049
|
const stringEntries = buildStringEntries(extractedStrings);
|
|
3057
3050
|
const sourceStrings = stringEntries.map((entry) => entry.text);
|
|
3058
3051
|
if (options.verbose && stringEntries.length !== extractedStrings.length) {
|
|
@@ -3060,6 +3053,15 @@ async function sync(options = {}) {
|
|
|
3060
3053
|
`Deduped ${extractedStrings.length} extracted entries into ${stringEntries.length} unique source strings`
|
|
3061
3054
|
);
|
|
3062
3055
|
}
|
|
3056
|
+
const currentHash = computeStringsHash(sourceStrings);
|
|
3057
|
+
if (!options.force) {
|
|
3058
|
+
const cachedHash = readCachedStringsHash(projectRoot, branch);
|
|
3059
|
+
if (cachedHash && cachedHash === currentHash) {
|
|
3060
|
+
const duration2 = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
3061
|
+
p7.outro(`Up to date (${duration2}s)`);
|
|
3062
|
+
return 0;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3063
3065
|
spinner4.start("Submitting strings to Vocoder API");
|
|
3064
3066
|
const batchResponse = await api.submitTranslation(
|
|
3065
3067
|
branch,
|
|
@@ -3070,7 +3072,7 @@ async function sync(options = {}) {
|
|
|
3070
3072
|
requestedMaxWaitMs: waitTimeoutMs,
|
|
3071
3073
|
clientRunId: randomUUID()
|
|
3072
3074
|
},
|
|
3073
|
-
repoIdentity
|
|
3075
|
+
repoIdentity ? { ...repoIdentity, commitSha } : { commitSha }
|
|
3074
3076
|
);
|
|
3075
3077
|
spinner4.stop(`Submitted to API - Batch ${chalk8.cyan(batchResponse.batchId)}`);
|
|
3076
3078
|
const effectiveMode = batchResponse.effectiveMode ?? resolveEffectiveModeFromPolicy({
|
|
@@ -3204,6 +3206,7 @@ async function sync(options = {}) {
|
|
|
3204
3206
|
targetLocales: config.targetLocales,
|
|
3205
3207
|
translations: finalTranslations,
|
|
3206
3208
|
localeMetadata: artifacts.localeMetadata,
|
|
3209
|
+
stringsHash: currentHash,
|
|
3207
3210
|
snapshotBatchId: artifacts.snapshotBatchId ?? (artifacts.source === "fresh" ? batchResponse.batchId : batchResponse.latestCompletedBatchId),
|
|
3208
3211
|
completedAt: artifacts.completedAt ?? (artifacts.source === "fresh" ? (/* @__PURE__ */ new Date()).toISOString() : null)
|
|
3209
3212
|
});
|
|
@@ -3224,8 +3227,6 @@ async function sync(options = {}) {
|
|
|
3224
3227
|
}
|
|
3225
3228
|
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
3226
3229
|
p7.outro(`Sync complete! (${duration}s)`);
|
|
3227
|
-
p7.log.info("Translations will be injected at build time by @vocoder/unplugin.");
|
|
3228
|
-
p7.log.info("Just use <VocoderProvider> and <T> \u2014 no manual imports needed.");
|
|
3229
3230
|
return 0;
|
|
3230
3231
|
} catch (error) {
|
|
3231
3232
|
spinner4.stop();
|