@vocoder/cli 0.1.15 → 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 +194 -133
- 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) {
|
|
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}`);
|
|
@@ -1274,11 +1303,17 @@ function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatt
|
|
|
1274
1303
|
}
|
|
1275
1304
|
const hidden = filtered.length - (end - scrollOffset);
|
|
1276
1305
|
if (hidden > 0) lines.push(dim2(`${S_BAR2} ${hidden} more`));
|
|
1277
|
-
if (selected.size > 0)
|
|
1306
|
+
if (selected.size > 0) {
|
|
1307
|
+
lines.push(dim2(`${S_BAR2} ${selected.size} selected \u2014 Enter to confirm`));
|
|
1308
|
+
} else if (optional) {
|
|
1309
|
+
lines.push(dim2(`${S_BAR2} Enter to skip`));
|
|
1310
|
+
}
|
|
1278
1311
|
return lines.join("\n");
|
|
1279
1312
|
}
|
|
1280
1313
|
async function filterableBranchSelect(params) {
|
|
1281
1314
|
const { message, branches, defaultBranch } = params;
|
|
1315
|
+
const optional = params.optional ?? false;
|
|
1316
|
+
const excludedSet = new Set(params.excludedPatterns ?? []);
|
|
1282
1317
|
let filter = "";
|
|
1283
1318
|
let cursor = 0;
|
|
1284
1319
|
let scrollOffset = 0;
|
|
@@ -1305,7 +1340,7 @@ async function filterableBranchSelect(params) {
|
|
|
1305
1340
|
const prompt = new Prompt2(
|
|
1306
1341
|
{
|
|
1307
1342
|
validate() {
|
|
1308
|
-
if (selected.size === 0) return "At least one branch is required.";
|
|
1343
|
+
if (!optional && selected.size === 0) return "At least one branch is required.";
|
|
1309
1344
|
return void 0;
|
|
1310
1345
|
},
|
|
1311
1346
|
render() {
|
|
@@ -1326,7 +1361,7 @@ ${symbol2(this.state)} ${message}
|
|
|
1326
1361
|
return [
|
|
1327
1362
|
hdr.trimEnd(),
|
|
1328
1363
|
`${ylw2(S_BAR2)} ${dim2("/")} ${hint}`,
|
|
1329
|
-
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor),
|
|
1364
|
+
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional, excludedSet),
|
|
1330
1365
|
`${ylw2(S_BAR_END2)} ${ylw2(this.error)}`,
|
|
1331
1366
|
""
|
|
1332
1367
|
].join("\n");
|
|
@@ -1334,7 +1369,7 @@ ${symbol2(this.state)} ${message}
|
|
|
1334
1369
|
return [
|
|
1335
1370
|
hdr.trimEnd(),
|
|
1336
1371
|
`${cyan2(S_BAR2)} ${dim2("/")} ${hint}`,
|
|
1337
|
-
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor),
|
|
1372
|
+
buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional, excludedSet),
|
|
1338
1373
|
`${cyan2(S_BAR_END2)}`,
|
|
1339
1374
|
""
|
|
1340
1375
|
].join("\n");
|
|
@@ -1375,7 +1410,7 @@ ${symbol2(this.state)} ${message}
|
|
|
1375
1410
|
case "space":
|
|
1376
1411
|
if (addCursor) {
|
|
1377
1412
|
const t = filter.trim();
|
|
1378
|
-
const err = validateBranchPattern(t);
|
|
1413
|
+
const err = validateBranchPattern(t) ?? (excludedSet.has(t) ? "Already used for automatic translation" : null);
|
|
1379
1414
|
if (!err) {
|
|
1380
1415
|
customPatterns.push(t);
|
|
1381
1416
|
selected.add(t);
|
|
@@ -1436,10 +1471,10 @@ async function runProjectCreate(params) {
|
|
|
1436
1471
|
}
|
|
1437
1472
|
const languageOptions = buildLanguageOptions(rawLocales);
|
|
1438
1473
|
const localeOptions = buildLocaleOptions(rawLocales);
|
|
1439
|
-
let
|
|
1440
|
-
if (params.
|
|
1441
|
-
|
|
1442
|
-
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)}`);
|
|
1443
1478
|
} else {
|
|
1444
1479
|
const rawScope = await p3.text({
|
|
1445
1480
|
message: "App directory (leave blank for the entire repo)",
|
|
@@ -1453,7 +1488,7 @@ async function runProjectCreate(params) {
|
|
|
1453
1488
|
}
|
|
1454
1489
|
});
|
|
1455
1490
|
if (p3.isCancel(rawScope)) return null;
|
|
1456
|
-
|
|
1491
|
+
appDir = (rawScope ?? "").trim();
|
|
1457
1492
|
}
|
|
1458
1493
|
const sourceLocale = await searchSelectLocale(
|
|
1459
1494
|
languageOptions,
|
|
@@ -1472,12 +1507,12 @@ async function runProjectCreate(params) {
|
|
|
1472
1507
|
}
|
|
1473
1508
|
const detected = detectGitBranches();
|
|
1474
1509
|
const initialBranches = params.defaultBranches?.length ? params.defaultBranches : [detected.defaultBranch];
|
|
1475
|
-
let
|
|
1510
|
+
let pushBranches = [];
|
|
1476
1511
|
{
|
|
1477
1512
|
let initial = initialBranches;
|
|
1478
|
-
while (
|
|
1513
|
+
while (pushBranches.length === 0) {
|
|
1479
1514
|
const result = await filterableBranchSelect({
|
|
1480
|
-
message: "
|
|
1515
|
+
message: "Which branches should trigger translations?",
|
|
1481
1516
|
branches: detected.branches,
|
|
1482
1517
|
defaultBranch: detected.defaultBranch,
|
|
1483
1518
|
initialValues: initial
|
|
@@ -1487,33 +1522,19 @@ async function runProjectCreate(params) {
|
|
|
1487
1522
|
p3.log.warn("At least one branch is required. Please select at least one.");
|
|
1488
1523
|
initial = [detected.defaultBranch];
|
|
1489
1524
|
} else {
|
|
1490
|
-
|
|
1525
|
+
pushBranches = result;
|
|
1491
1526
|
}
|
|
1492
1527
|
}
|
|
1493
1528
|
}
|
|
1494
|
-
const
|
|
1495
|
-
message: "When should translations run?",
|
|
1496
|
-
options: [
|
|
1497
|
-
{ value: "push", label: "On push to target branches" },
|
|
1498
|
-
{ value: "pull_request", label: "On pull requests" },
|
|
1499
|
-
{ value: "push_and_pr", label: "On push and pull requests" },
|
|
1500
|
-
{ value: "manual", label: "Manual only", hint: "use vocoder sync or trigger from dashboard" }
|
|
1501
|
-
]
|
|
1502
|
-
});
|
|
1503
|
-
if (p3.isCancel(triggerChoice)) return null;
|
|
1504
|
-
const triggersForBranch = triggerChoice === "push_and_pr" ? ["push", "pull_request"] : [triggerChoice];
|
|
1505
|
-
const branchTriggers = selectedBranches.map((pattern) => ({
|
|
1506
|
-
pattern,
|
|
1507
|
-
triggers: triggersForBranch
|
|
1508
|
-
}));
|
|
1529
|
+
const targetBranches = pushBranches;
|
|
1509
1530
|
try {
|
|
1510
1531
|
const result = await api.createProject(userToken, {
|
|
1511
1532
|
organizationId,
|
|
1512
1533
|
name: projectName,
|
|
1513
1534
|
sourceLocale,
|
|
1514
1535
|
targetLocales,
|
|
1515
|
-
|
|
1516
|
-
|
|
1536
|
+
targetBranches,
|
|
1537
|
+
appDirs: appDir ? [appDir] : [],
|
|
1517
1538
|
repoCanonical
|
|
1518
1539
|
});
|
|
1519
1540
|
p3.log.success(`Project ${chalk4.bold(result.projectName)} created!`);
|
|
@@ -1526,7 +1547,7 @@ async function runProjectCreate(params) {
|
|
|
1526
1547
|
}
|
|
1527
1548
|
async function runProjectAppCreate(params) {
|
|
1528
1549
|
const { api, userToken, projectId, projectName, repoCanonical } = params;
|
|
1529
|
-
const existingScopes = new Set(params.existingApps.map((a) => a.
|
|
1550
|
+
const existingScopes = new Set(params.existingApps.map((a) => a.appDir));
|
|
1530
1551
|
let rawLocales;
|
|
1531
1552
|
try {
|
|
1532
1553
|
rawLocales = await api.listLocales(userToken);
|
|
@@ -1536,20 +1557,20 @@ async function runProjectAppCreate(params) {
|
|
|
1536
1557
|
}
|
|
1537
1558
|
const languageOptions = buildLanguageOptions(rawLocales);
|
|
1538
1559
|
const localeOptions = buildLocaleOptions(rawLocales);
|
|
1539
|
-
let
|
|
1540
|
-
if (params.
|
|
1541
|
-
|
|
1542
|
-
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)}`);
|
|
1543
1564
|
} else {
|
|
1544
1565
|
if (params.existingApps.length > 0) {
|
|
1545
|
-
const configuredList = params.existingApps.map((a) => chalk4.dim(a.
|
|
1566
|
+
const configuredList = params.existingApps.map((a) => chalk4.dim(a.appDir || "(entire repo)")).join(", ");
|
|
1546
1567
|
p3.log.info(`Already configured: ${configuredList}`);
|
|
1547
1568
|
}
|
|
1548
1569
|
const hasWholeRepoApp = existingScopes.has("");
|
|
1549
1570
|
const rawScope = await p3.text({
|
|
1550
1571
|
message: "App directory for this new app",
|
|
1551
1572
|
placeholder: "e.g. apps/backend",
|
|
1552
|
-
initialValue: params.
|
|
1573
|
+
initialValue: params.defaultAppDir ?? "",
|
|
1553
1574
|
validate(value) {
|
|
1554
1575
|
const v = value.trim();
|
|
1555
1576
|
if (!v && hasWholeRepoApp) return "This project already covers the entire repo.";
|
|
@@ -1560,7 +1581,7 @@ async function runProjectAppCreate(params) {
|
|
|
1560
1581
|
}
|
|
1561
1582
|
});
|
|
1562
1583
|
if (p3.isCancel(rawScope)) return null;
|
|
1563
|
-
|
|
1584
|
+
appDir = (rawScope ?? "").trim();
|
|
1564
1585
|
}
|
|
1565
1586
|
const sourceLocale = await searchSelectLocale(
|
|
1566
1587
|
languageOptions,
|
|
@@ -1577,59 +1598,45 @@ async function runProjectAppCreate(params) {
|
|
|
1577
1598
|
if (targetLocales.length === 0) {
|
|
1578
1599
|
p3.log.warn("No target languages selected \u2014 you can add them later from the dashboard.");
|
|
1579
1600
|
}
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
options: [
|
|
1583
|
-
{ value: "push", label: "On push to target branches" },
|
|
1584
|
-
{ value: "pull_request", label: "On pull requests" },
|
|
1585
|
-
{ value: "push_and_pr", label: "On push and pull requests" },
|
|
1586
|
-
{ value: "manual", label: "Manual only", hint: "use vocoder sync or trigger from dashboard" }
|
|
1587
|
-
]
|
|
1588
|
-
});
|
|
1589
|
-
if (p3.isCancel(triggerChoice)) return null;
|
|
1590
|
-
const triggersForBranch = triggerChoice === "push_and_pr" ? ["push", "pull_request"] : [triggerChoice];
|
|
1591
|
-
const detected = detectGitBranches();
|
|
1592
|
-
let selectedBranches = [];
|
|
1601
|
+
const detectedApp = detectGitBranches();
|
|
1602
|
+
let appPushBranches = [];
|
|
1593
1603
|
{
|
|
1594
|
-
let initial = [
|
|
1595
|
-
while (
|
|
1604
|
+
let initial = [detectedApp.defaultBranch];
|
|
1605
|
+
while (appPushBranches.length === 0) {
|
|
1596
1606
|
const result = await filterableBranchSelect({
|
|
1597
|
-
message: "
|
|
1598
|
-
branches:
|
|
1599
|
-
defaultBranch:
|
|
1607
|
+
message: "Which branches should trigger translations?",
|
|
1608
|
+
branches: detectedApp.branches,
|
|
1609
|
+
defaultBranch: detectedApp.defaultBranch,
|
|
1600
1610
|
initialValues: initial
|
|
1601
1611
|
});
|
|
1602
1612
|
if (result === null) return null;
|
|
1603
1613
|
if (result.length === 0) {
|
|
1604
1614
|
p3.log.warn("At least one branch is required.");
|
|
1605
|
-
initial = [
|
|
1615
|
+
initial = [detectedApp.defaultBranch];
|
|
1606
1616
|
} else {
|
|
1607
|
-
|
|
1617
|
+
appPushBranches = result;
|
|
1608
1618
|
}
|
|
1609
1619
|
}
|
|
1610
1620
|
}
|
|
1611
|
-
const
|
|
1612
|
-
pattern,
|
|
1613
|
-
triggers: triggersForBranch
|
|
1614
|
-
}));
|
|
1621
|
+
const targetBranches = appPushBranches;
|
|
1615
1622
|
try {
|
|
1616
1623
|
const result = await api.createProjectApp(userToken, {
|
|
1617
1624
|
projectId,
|
|
1618
|
-
|
|
1625
|
+
appDir,
|
|
1619
1626
|
sourceLocale,
|
|
1620
1627
|
targetLocales,
|
|
1621
|
-
|
|
1628
|
+
targetBranches,
|
|
1622
1629
|
repoCanonical: repoCanonical ?? ""
|
|
1623
1630
|
});
|
|
1624
|
-
p3.log.success(`App ${chalk4.bold(
|
|
1631
|
+
p3.log.success(`App ${chalk4.bold(appDir)} added to ${chalk4.bold(projectName)}!`);
|
|
1625
1632
|
return {
|
|
1626
1633
|
projectId: result.projectId,
|
|
1627
1634
|
projectName: result.projectName,
|
|
1628
1635
|
apiKey: result.apiKey,
|
|
1629
|
-
|
|
1636
|
+
appDir: result.appDir,
|
|
1630
1637
|
sourceLocale,
|
|
1631
1638
|
targetLocales,
|
|
1632
|
-
|
|
1639
|
+
targetBranches
|
|
1633
1640
|
};
|
|
1634
1641
|
} catch (error) {
|
|
1635
1642
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1739,9 +1746,7 @@ function printPlanLimitMessage(apiUrl, message) {
|
|
|
1739
1746
|
p5.log.info(`Manage subscription: ${getSubscriptionSettingsUrl(apiUrl)}`);
|
|
1740
1747
|
}
|
|
1741
1748
|
function runScaffold(params) {
|
|
1742
|
-
const {
|
|
1743
|
-
p5.log.info(`Project: ${chalk6.bold(projectName)}`);
|
|
1744
|
-
p5.log.info(`Workspace: ${chalk6.bold(organizationName)}`);
|
|
1749
|
+
const { sourceLocale, targetBranches } = params;
|
|
1745
1750
|
const detection = detectLocalEcosystem();
|
|
1746
1751
|
if (detection.ecosystem) {
|
|
1747
1752
|
const frameworkLabel = detection.framework ?? detection.ecosystem;
|
|
@@ -1768,7 +1773,7 @@ function runScaffold(params) {
|
|
|
1768
1773
|
framework: detection.framework,
|
|
1769
1774
|
ecosystem: detection.ecosystem,
|
|
1770
1775
|
sourceLocale,
|
|
1771
|
-
|
|
1776
|
+
targetBranches
|
|
1772
1777
|
});
|
|
1773
1778
|
let stepNum = 1;
|
|
1774
1779
|
if (snippets.pluginStep) {
|
|
@@ -1977,7 +1982,7 @@ async function runAuthFlow(api, options, reauth = false, repoCanonical) {
|
|
|
1977
1982
|
}
|
|
1978
1983
|
async function init(options = {}) {
|
|
1979
1984
|
const apiUrl = options.apiUrl || process.env.VOCODER_API_URL || "https://vocoder.app";
|
|
1980
|
-
p5.intro("Vocoder Setup");
|
|
1985
|
+
p5.intro(chalk6.bold("Vocoder Setup"));
|
|
1981
1986
|
try {
|
|
1982
1987
|
const gitContext = resolveGitContext();
|
|
1983
1988
|
const identity = gitContext.identity;
|
|
@@ -1993,28 +1998,43 @@ async function init(options = {}) {
|
|
|
1993
1998
|
const anonApi = new VocoderAPI({ apiUrl, apiKey: "" });
|
|
1994
1999
|
const lookup = await anonApi.lookupProjectByRepo({
|
|
1995
2000
|
repoCanonical: identity.repoCanonical,
|
|
1996
|
-
|
|
2001
|
+
appDir: identity.repoAppDir
|
|
1997
2002
|
});
|
|
1998
2003
|
if (lookup.exactMatch) {
|
|
1999
2004
|
const { exactMatch } = lookup;
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
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?"
|
|
2005
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
|
+
}
|
|
2006
2031
|
p5.outro("Vocoder is already set up for this repository.");
|
|
2007
2032
|
return 0;
|
|
2008
2033
|
}
|
|
2009
2034
|
if (lookup.hasWholeRepoApp) {
|
|
2010
|
-
const wholeRepo = lookup.existingApps.find((a) => a.
|
|
2035
|
+
const wholeRepo = lookup.existingApps.find((a) => a.appDir === "");
|
|
2011
2036
|
if (wholeRepo) {
|
|
2012
|
-
|
|
2013
|
-
projectName: wholeRepo.projectName,
|
|
2014
|
-
organizationName: wholeRepo.organizationName,
|
|
2015
|
-
sourceLocale: "en",
|
|
2016
|
-
branchTriggers: [{ pattern: "main", triggers: ["push"] }]
|
|
2017
|
-
});
|
|
2037
|
+
p5.log.success(`Project: ${chalk6.bold(wholeRepo.projectName)}`);
|
|
2018
2038
|
p5.outro("Vocoder is already set up for this repository.");
|
|
2019
2039
|
return 0;
|
|
2020
2040
|
}
|
|
@@ -2030,7 +2050,6 @@ async function init(options = {}) {
|
|
|
2030
2050
|
let userEmail;
|
|
2031
2051
|
let userName;
|
|
2032
2052
|
let authOrganizationId;
|
|
2033
|
-
let authDiscoveryReady = false;
|
|
2034
2053
|
const stored = readAuthData();
|
|
2035
2054
|
if (stored && stored.apiUrl === apiUrl) {
|
|
2036
2055
|
const verified = await verifyStoredToken(api, stored.token);
|
|
@@ -2058,7 +2077,6 @@ async function init(options = {}) {
|
|
|
2058
2077
|
userEmail = authResult.email;
|
|
2059
2078
|
userName = authResult.name;
|
|
2060
2079
|
authOrganizationId = authResult.organizationId;
|
|
2061
|
-
authDiscoveryReady = authResult.discoveryReady ?? false;
|
|
2062
2080
|
writeAuthData({
|
|
2063
2081
|
token: userToken,
|
|
2064
2082
|
apiUrl,
|
|
@@ -2300,7 +2318,7 @@ async function init(options = {}) {
|
|
|
2300
2318
|
if (repoProjectId && repoProjectName && existingAppsForRepo.length > 0) {
|
|
2301
2319
|
p5.log.info(
|
|
2302
2320
|
`${chalk6.bold(repoProjectName)} is already set up for this repo.
|
|
2303
|
-
Configured apps: ${existingAppsForRepo.map((a) => chalk6.cyan(a.
|
|
2321
|
+
Configured apps: ${existingAppsForRepo.map((a) => chalk6.cyan(a.appDir || "(entire repo)")).join(", ")}`
|
|
2304
2322
|
);
|
|
2305
2323
|
const appResult = await runProjectAppCreate({
|
|
2306
2324
|
api,
|
|
@@ -2309,7 +2327,7 @@ async function init(options = {}) {
|
|
|
2309
2327
|
projectName: repoProjectName,
|
|
2310
2328
|
organizationName: selectedWorkspaceName,
|
|
2311
2329
|
repoCanonical: identity?.repoCanonical,
|
|
2312
|
-
|
|
2330
|
+
defaultAppDir: identity?.repoAppDir,
|
|
2313
2331
|
existingApps: existingAppsForRepo
|
|
2314
2332
|
});
|
|
2315
2333
|
if (!appResult) {
|
|
@@ -2317,10 +2335,8 @@ async function init(options = {}) {
|
|
|
2317
2335
|
return 1;
|
|
2318
2336
|
}
|
|
2319
2337
|
runScaffold({
|
|
2320
|
-
projectName: appResult.projectName,
|
|
2321
|
-
organizationName: selectedWorkspaceName,
|
|
2322
2338
|
sourceLocale: appResult.sourceLocale,
|
|
2323
|
-
|
|
2339
|
+
targetBranches: appResult.targetBranches
|
|
2324
2340
|
});
|
|
2325
2341
|
p5.outro("You're all set.");
|
|
2326
2342
|
return 0;
|
|
@@ -2379,7 +2395,7 @@ async function init(options = {}) {
|
|
|
2379
2395
|
projectName: chosen.name,
|
|
2380
2396
|
organizationName: selectedWorkspaceName,
|
|
2381
2397
|
repoCanonical: identity?.repoCanonical,
|
|
2382
|
-
|
|
2398
|
+
defaultAppDir: identity?.repoAppDir,
|
|
2383
2399
|
existingApps: []
|
|
2384
2400
|
});
|
|
2385
2401
|
if (!appResult) {
|
|
@@ -2387,10 +2403,8 @@ async function init(options = {}) {
|
|
|
2387
2403
|
return 1;
|
|
2388
2404
|
}
|
|
2389
2405
|
runScaffold({
|
|
2390
|
-
projectName: appResult.projectName,
|
|
2391
|
-
organizationName: selectedWorkspaceName,
|
|
2392
2406
|
sourceLocale: appResult.sourceLocale,
|
|
2393
|
-
|
|
2407
|
+
targetBranches: appResult.targetBranches
|
|
2394
2408
|
});
|
|
2395
2409
|
p5.outro("You're all set.");
|
|
2396
2410
|
return 0;
|
|
@@ -2405,7 +2419,7 @@ async function init(options = {}) {
|
|
|
2405
2419
|
defaultSourceLocale: "en",
|
|
2406
2420
|
repoCanonical: identity?.repoCanonical,
|
|
2407
2421
|
defaultBranches: ["main"],
|
|
2408
|
-
|
|
2422
|
+
defaultAppDir: identity?.repoAppDir
|
|
2409
2423
|
});
|
|
2410
2424
|
if (!projectResult) {
|
|
2411
2425
|
p5.log.error("Project creation failed. Run `vocoder init` again.");
|
|
@@ -2424,10 +2438,8 @@ Translations won't run automatically until you grant access.
|
|
|
2424
2438
|
);
|
|
2425
2439
|
}
|
|
2426
2440
|
runScaffold({
|
|
2427
|
-
projectName: projectResult.projectName,
|
|
2428
|
-
organizationName: selectedWorkspaceName,
|
|
2429
2441
|
sourceLocale: projectResult.sourceLocale,
|
|
2430
|
-
|
|
2442
|
+
targetBranches: projectResult.targetBranches
|
|
2431
2443
|
});
|
|
2432
2444
|
printMcpSetup(projectResult.apiKey);
|
|
2433
2445
|
p5.outro("You're all set.");
|
|
@@ -2546,8 +2558,11 @@ function validateLocalConfig(config) {
|
|
|
2546
2558
|
if (!config.apiKey || config.apiKey.length === 0) {
|
|
2547
2559
|
throw new Error("VOCODER_API_KEY is required. Set it in your .env file.");
|
|
2548
2560
|
}
|
|
2549
|
-
if (!config.apiKey.startsWith("
|
|
2550
|
-
|
|
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_.");
|
|
2551
2566
|
}
|
|
2552
2567
|
if (!config.apiUrl || !config.apiUrl.startsWith("http")) {
|
|
2553
2568
|
throw new Error("Invalid API URL");
|
|
@@ -2555,7 +2570,7 @@ function validateLocalConfig(config) {
|
|
|
2555
2570
|
}
|
|
2556
2571
|
async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
2557
2572
|
const configSources = {
|
|
2558
|
-
|
|
2573
|
+
includePattern: "default",
|
|
2559
2574
|
excludePattern: "default",
|
|
2560
2575
|
apiKey: "environment",
|
|
2561
2576
|
apiUrl: "default",
|
|
@@ -2564,29 +2579,53 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2564
2579
|
noFallback: "default"
|
|
2565
2580
|
};
|
|
2566
2581
|
const defaults = {
|
|
2567
|
-
|
|
2568
|
-
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
|
+
],
|
|
2569
2604
|
apiUrl: "https://vocoder.app"
|
|
2570
2605
|
};
|
|
2571
|
-
const envExtractionPattern = process.env.
|
|
2606
|
+
const envExtractionPattern = process.env.VOCODER_INCLUDE_PATTERN;
|
|
2607
|
+
const envExcludePattern = process.env.VOCODER_EXCLUDE_PATTERN;
|
|
2572
2608
|
const envApiUrl = process.env.VOCODER_API_URL;
|
|
2573
2609
|
const envSyncMode = process.env.VOCODER_SYNC_MODE;
|
|
2574
2610
|
const envSyncMaxWaitMs = process.env.VOCODER_SYNC_MAX_WAIT_MS;
|
|
2575
2611
|
const envSyncNoFallback = process.env.VOCODER_SYNC_NO_FALLBACK;
|
|
2576
|
-
let
|
|
2612
|
+
let includePattern;
|
|
2577
2613
|
if (cliOptions.include && cliOptions.include.length > 0) {
|
|
2578
|
-
|
|
2579
|
-
configSources.
|
|
2614
|
+
includePattern = cliOptions.include;
|
|
2615
|
+
configSources.includePattern = "CLI flag";
|
|
2580
2616
|
} else if (envExtractionPattern) {
|
|
2581
|
-
|
|
2582
|
-
configSources.
|
|
2617
|
+
includePattern = [envExtractionPattern];
|
|
2618
|
+
configSources.includePattern = "environment";
|
|
2583
2619
|
} else {
|
|
2584
|
-
|
|
2620
|
+
includePattern = defaults.includePattern;
|
|
2585
2621
|
}
|
|
2586
2622
|
let excludePattern;
|
|
2587
2623
|
if (cliOptions.exclude && cliOptions.exclude.length > 0) {
|
|
2588
2624
|
excludePattern = cliOptions.exclude;
|
|
2589
2625
|
configSources.excludePattern = "CLI flag";
|
|
2626
|
+
} else if (envExcludePattern) {
|
|
2627
|
+
excludePattern = envExcludePattern.split(",").map((p9) => p9.trim()).filter(Boolean);
|
|
2628
|
+
configSources.excludePattern = "environment";
|
|
2590
2629
|
} else {
|
|
2591
2630
|
excludePattern = defaults.excludePattern;
|
|
2592
2631
|
}
|
|
@@ -2632,7 +2671,7 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2632
2671
|
}
|
|
2633
2672
|
if (verbose) {
|
|
2634
2673
|
console.log(chalk7.dim("\n Configuration sources:"));
|
|
2635
|
-
console.log(chalk7.dim(` Include patterns: ${configSources.
|
|
2674
|
+
console.log(chalk7.dim(` Include patterns: ${configSources.includePattern}`));
|
|
2636
2675
|
if (excludePattern.length > 0) {
|
|
2637
2676
|
console.log(chalk7.dim(` Exclude patterns: ${configSources.excludePattern}`));
|
|
2638
2677
|
}
|
|
@@ -2647,7 +2686,7 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2647
2686
|
`));
|
|
2648
2687
|
}
|
|
2649
2688
|
return {
|
|
2650
|
-
|
|
2689
|
+
includePattern,
|
|
2651
2690
|
excludePattern,
|
|
2652
2691
|
apiKey,
|
|
2653
2692
|
apiUrl,
|
|
@@ -2661,6 +2700,20 @@ async function getMergedConfig(cliOptions, verbose = false, _startDir) {
|
|
|
2661
2700
|
// src/commands/sync.ts
|
|
2662
2701
|
import chalk8 from "chalk";
|
|
2663
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
|
+
}
|
|
2664
2717
|
function isRecord(value) {
|
|
2665
2718
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2666
2719
|
}
|
|
@@ -2705,10 +2758,8 @@ function parseTranslations(value) {
|
|
|
2705
2758
|
return Object.keys(translations).length > 0 ? translations : null;
|
|
2706
2759
|
}
|
|
2707
2760
|
function getCacheFilePath(projectRoot, branch) {
|
|
2708
|
-
const slug = branch.replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40);
|
|
2709
2761
|
const branchHash = createHash("sha1").update(branch).digest("hex").slice(0, 12);
|
|
2710
|
-
|
|
2711
|
-
return join2(projectRoot, "node_modules", ".vocoder", "cache", "sync", filename);
|
|
2762
|
+
return join2(projectRoot, "node_modules", ".vocoder", "cache", "sync", `${branchHash}.json`);
|
|
2712
2763
|
}
|
|
2713
2764
|
function readLocalSnapshotCache(params) {
|
|
2714
2765
|
const candidateBranches = params.branch === "main" ? ["main"] : [params.branch, "main"];
|
|
@@ -2753,6 +2804,7 @@ function writeLocalSnapshotCache(params) {
|
|
|
2753
2804
|
sourceLocale: params.sourceLocale,
|
|
2754
2805
|
targetLocales: params.targetLocales,
|
|
2755
2806
|
savedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2807
|
+
...params.stringsHash ? { stringsHash: params.stringsHash } : {},
|
|
2756
2808
|
...params.snapshotBatchId ? { snapshotBatchId: params.snapshotBatchId } : {},
|
|
2757
2809
|
...params.completedAt ? { completedAt: params.completedAt } : {},
|
|
2758
2810
|
...params.localeMetadata ? { localeMetadata: params.localeMetadata } : {},
|
|
@@ -2934,7 +2986,7 @@ async function sync(options = {}) {
|
|
|
2934
2986
|
const config = {
|
|
2935
2987
|
...localConfig,
|
|
2936
2988
|
...apiConfig,
|
|
2937
|
-
|
|
2989
|
+
includePattern: mergedConfig.includePattern,
|
|
2938
2990
|
excludePattern: mergedConfig.excludePattern,
|
|
2939
2991
|
timeout: waitTimeoutMs
|
|
2940
2992
|
};
|
|
@@ -2948,11 +3000,11 @@ async function sync(options = {}) {
|
|
|
2948
3000
|
p7.outro("");
|
|
2949
3001
|
return 0;
|
|
2950
3002
|
}
|
|
2951
|
-
const patternsDisplay = Array.isArray(config.
|
|
3003
|
+
const patternsDisplay = Array.isArray(config.includePattern) ? config.includePattern.join(", ") : config.includePattern;
|
|
2952
3004
|
spinner4.start(`Extracting strings from ${patternsDisplay}`);
|
|
2953
3005
|
const extractor = new StringExtractor();
|
|
2954
3006
|
const extractedStrings = await extractor.extractFromProject(
|
|
2955
|
-
config.
|
|
3007
|
+
config.includePattern,
|
|
2956
3008
|
projectRoot,
|
|
2957
3009
|
config.excludePattern
|
|
2958
3010
|
);
|
|
@@ -2993,6 +3045,7 @@ async function sync(options = {}) {
|
|
|
2993
3045
|
"Could not detect git remote origin. Sync will continue without repo metadata."
|
|
2994
3046
|
);
|
|
2995
3047
|
}
|
|
3048
|
+
const commitSha = detectCommitSha() ?? void 0;
|
|
2996
3049
|
const stringEntries = buildStringEntries(extractedStrings);
|
|
2997
3050
|
const sourceStrings = stringEntries.map((entry) => entry.text);
|
|
2998
3051
|
if (options.verbose && stringEntries.length !== extractedStrings.length) {
|
|
@@ -3000,6 +3053,15 @@ async function sync(options = {}) {
|
|
|
3000
3053
|
`Deduped ${extractedStrings.length} extracted entries into ${stringEntries.length} unique source strings`
|
|
3001
3054
|
);
|
|
3002
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
|
+
}
|
|
3003
3065
|
spinner4.start("Submitting strings to Vocoder API");
|
|
3004
3066
|
const batchResponse = await api.submitTranslation(
|
|
3005
3067
|
branch,
|
|
@@ -3010,7 +3072,7 @@ async function sync(options = {}) {
|
|
|
3010
3072
|
requestedMaxWaitMs: waitTimeoutMs,
|
|
3011
3073
|
clientRunId: randomUUID()
|
|
3012
3074
|
},
|
|
3013
|
-
repoIdentity
|
|
3075
|
+
repoIdentity ? { ...repoIdentity, commitSha } : { commitSha }
|
|
3014
3076
|
);
|
|
3015
3077
|
spinner4.stop(`Submitted to API - Batch ${chalk8.cyan(batchResponse.batchId)}`);
|
|
3016
3078
|
const effectiveMode = batchResponse.effectiveMode ?? resolveEffectiveModeFromPolicy({
|
|
@@ -3144,6 +3206,7 @@ async function sync(options = {}) {
|
|
|
3144
3206
|
targetLocales: config.targetLocales,
|
|
3145
3207
|
translations: finalTranslations,
|
|
3146
3208
|
localeMetadata: artifacts.localeMetadata,
|
|
3209
|
+
stringsHash: currentHash,
|
|
3147
3210
|
snapshotBatchId: artifacts.snapshotBatchId ?? (artifacts.source === "fresh" ? batchResponse.batchId : batchResponse.latestCompletedBatchId),
|
|
3148
3211
|
completedAt: artifacts.completedAt ?? (artifacts.source === "fresh" ? (/* @__PURE__ */ new Date()).toISOString() : null)
|
|
3149
3212
|
});
|
|
@@ -3164,8 +3227,6 @@ async function sync(options = {}) {
|
|
|
3164
3227
|
}
|
|
3165
3228
|
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
3166
3229
|
p7.outro(`Sync complete! (${duration}s)`);
|
|
3167
|
-
p7.log.info("Translations will be injected at build time by @vocoder/unplugin.");
|
|
3168
|
-
p7.log.info("Just use <VocoderProvider> and <T> \u2014 no manual imports needed.");
|
|
3169
3230
|
return 0;
|
|
3170
3231
|
} catch (error) {
|
|
3171
3232
|
spinner4.stop();
|