@vocoder/cli 0.1.14 → 0.1.16

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/dist/bin.mjs CHANGED
@@ -1249,7 +1249,7 @@ function filterItems(items, query) {
1249
1249
  const lower = query.toLowerCase();
1250
1250
  return items.filter((i) => i.value.toLowerCase().includes(lower));
1251
1251
  }
1252
- function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor) {
1252
+ function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional = false) {
1253
1253
  const lines = [];
1254
1254
  const end = Math.min(filtered.length, scrollOffset + MAX_VISIBLE2);
1255
1255
  for (let i = scrollOffset; i < end; i++) {
@@ -1274,11 +1274,16 @@ function buildList2(filtered, cursor, scrollOffset, selected, filter, customPatt
1274
1274
  }
1275
1275
  const hidden = filtered.length - (end - scrollOffset);
1276
1276
  if (hidden > 0) lines.push(dim2(`${S_BAR2} ${hidden} more`));
1277
- if (selected.size > 0) lines.push(dim2(`${S_BAR2} ${selected.size} selected \u2014 Enter to confirm`));
1277
+ if (selected.size > 0) {
1278
+ lines.push(dim2(`${S_BAR2} ${selected.size} selected \u2014 Enter to confirm`));
1279
+ } else if (optional) {
1280
+ lines.push(dim2(`${S_BAR2} Enter to skip`));
1281
+ }
1278
1282
  return lines.join("\n");
1279
1283
  }
1280
1284
  async function filterableBranchSelect(params) {
1281
1285
  const { message, branches, defaultBranch } = params;
1286
+ const optional = params.optional ?? false;
1282
1287
  let filter = "";
1283
1288
  let cursor = 0;
1284
1289
  let scrollOffset = 0;
@@ -1305,7 +1310,7 @@ async function filterableBranchSelect(params) {
1305
1310
  const prompt = new Prompt2(
1306
1311
  {
1307
1312
  validate() {
1308
- if (selected.size === 0) return "At least one branch is required.";
1313
+ if (!optional && selected.size === 0) return "At least one branch is required.";
1309
1314
  return void 0;
1310
1315
  },
1311
1316
  render() {
@@ -1326,7 +1331,7 @@ ${symbol2(this.state)} ${message}
1326
1331
  return [
1327
1332
  hdr.trimEnd(),
1328
1333
  `${ylw2(S_BAR2)} ${dim2("/")} ${hint}`,
1329
- buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor),
1334
+ buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional),
1330
1335
  `${ylw2(S_BAR_END2)} ${ylw2(this.error)}`,
1331
1336
  ""
1332
1337
  ].join("\n");
@@ -1334,7 +1339,7 @@ ${symbol2(this.state)} ${message}
1334
1339
  return [
1335
1340
  hdr.trimEnd(),
1336
1341
  `${cyan2(S_BAR2)} ${dim2("/")} ${hint}`,
1337
- buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor),
1342
+ buildList2(filtered, cursor, scrollOffset, selected, filter, customPatterns, addCursor, optional),
1338
1343
  `${cyan2(S_BAR_END2)}`,
1339
1344
  ""
1340
1345
  ].join("\n");
@@ -1472,12 +1477,12 @@ async function runProjectCreate(params) {
1472
1477
  }
1473
1478
  const detected = detectGitBranches();
1474
1479
  const initialBranches = params.defaultBranches?.length ? params.defaultBranches : [detected.defaultBranch];
1475
- let selectedBranches = [];
1480
+ let pushBranches = [];
1476
1481
  {
1477
1482
  let initial = initialBranches;
1478
- while (selectedBranches.length === 0) {
1483
+ while (pushBranches.length === 0) {
1479
1484
  const result = await filterableBranchSelect({
1480
- message: "Target branches",
1485
+ message: "Translate on push \u2014 which branches?",
1481
1486
  branches: detected.branches,
1482
1487
  defaultBranch: detected.defaultBranch,
1483
1488
  initialValues: initial
@@ -1487,24 +1492,54 @@ async function runProjectCreate(params) {
1487
1492
  p3.log.warn("At least one branch is required. Please select at least one.");
1488
1493
  initial = [detected.defaultBranch];
1489
1494
  } else {
1490
- selectedBranches = result;
1495
+ pushBranches = result;
1491
1496
  }
1492
1497
  }
1493
1498
  }
1494
- const triggerChoice = await p3.select({
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
- ]
1499
+ const prResult = await filterableBranchSelect({
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;
1502
1523
  });
1503
- if (p3.isCancel(triggerChoice)) return null;
1504
- const triggersForBranch = triggerChoice === "push_and_pr" ? ["push", "pull_request"] : [triggerChoice];
1505
- const branchTriggers = selectedBranches.map((pattern) => ({
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]) => ({
1506
1541
  pattern,
1507
- triggers: triggersForBranch
1542
+ triggers: Array.from(triggers)
1508
1543
  }));
1509
1544
  try {
1510
1545
  const result = await api.createProject(userToken, {
@@ -1577,40 +1612,65 @@ async function runProjectAppCreate(params) {
1577
1612
  if (targetLocales.length === 0) {
1578
1613
  p3.log.warn("No target languages selected \u2014 you can add them later from the dashboard.");
1579
1614
  }
1580
- const triggerChoice = await p3.select({
1581
- message: "When should translations run?",
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 = [];
1615
+ const detectedApp = detectGitBranches();
1616
+ let appPushBranches = [];
1593
1617
  {
1594
- let initial = [detected.defaultBranch];
1595
- while (selectedBranches.length === 0) {
1618
+ let initial = [detectedApp.defaultBranch];
1619
+ while (appPushBranches.length === 0) {
1596
1620
  const result = await filterableBranchSelect({
1597
- message: "Target branches",
1598
- branches: detected.branches,
1599
- defaultBranch: detected.defaultBranch,
1621
+ message: "Translate on push \u2014 which branches?",
1622
+ branches: detectedApp.branches,
1623
+ defaultBranch: detectedApp.defaultBranch,
1600
1624
  initialValues: initial
1601
1625
  });
1602
1626
  if (result === null) return null;
1603
1627
  if (result.length === 0) {
1604
1628
  p3.log.warn("At least one branch is required.");
1605
- initial = [detected.defaultBranch];
1629
+ initial = [detectedApp.defaultBranch];
1606
1630
  } else {
1607
- selectedBranches = result;
1631
+ appPushBranches = result;
1608
1632
  }
1609
1633
  }
1610
1634
  }
1611
- const branchTriggers = selectedBranches.map((pattern) => ({
1635
+ const appPrResult = await filterableBranchSelect({
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]) => ({
1612
1672
  pattern,
1613
- triggers: triggersForBranch
1673
+ triggers: Array.from(triggers)
1614
1674
  }));
1615
1675
  try {
1616
1676
  const result = await api.createProjectApp(userToken, {
@@ -2332,20 +2392,68 @@ async function init(options = {}) {
2332
2392
  p5.log.warn(
2333
2393
  `Project limit reached \u2014 ${ws.projectCount}/${ws.maxProjects} on your ${chalk6.bold(ws.planId)} plan.`
2334
2394
  );
2395
+ const hasRepoContext = !!identity?.repoCanonical;
2396
+ const options2 = [];
2397
+ if (hasRepoContext) {
2398
+ options2.push({
2399
+ value: "connect",
2400
+ label: "Connect this repo to an existing project"
2401
+ });
2402
+ }
2403
+ options2.push({ value: "upgrade", label: "Upgrade plan" });
2404
+ options2.push({ value: "cancel", label: "Cancel" });
2335
2405
  const limitAction = await p5.select({
2336
2406
  message: "What would you like to do?",
2337
- options: [
2338
- { value: "upgrade", label: "Upgrade plan" },
2339
- { value: "cancel", label: "Cancel" }
2340
- ]
2407
+ options: options2
2341
2408
  });
2342
2409
  if (p5.isCancel(limitAction) || limitAction === "cancel") {
2343
2410
  p5.cancel("Setup cancelled.");
2344
2411
  return 1;
2345
2412
  }
2346
- await tryOpenBrowser2(`${apiUrl}${SUBSCRIPTION_SETTINGS_PATH}`);
2347
- p5.cancel("Upgrade your plan in the browser, then re-run `vocoder init`.");
2348
- return 1;
2413
+ if (limitAction === "upgrade") {
2414
+ await tryOpenBrowser2(`${apiUrl}${SUBSCRIPTION_SETTINGS_PATH}`);
2415
+ p5.cancel("Upgrade your plan in the browser, then re-run `vocoder init`.");
2416
+ return 1;
2417
+ }
2418
+ const existingProjects = await api.listProjects(userToken, selectedWorkspaceId);
2419
+ if (existingProjects.length === 0) {
2420
+ p5.log.error("No projects found in this workspace.");
2421
+ return 1;
2422
+ }
2423
+ const chosenId = await p5.select({
2424
+ message: "Which project should this repo be connected to?",
2425
+ options: existingProjects.map((proj) => ({
2426
+ value: proj.id,
2427
+ label: proj.name
2428
+ }))
2429
+ });
2430
+ if (p5.isCancel(chosenId)) {
2431
+ p5.cancel("Setup cancelled.");
2432
+ return 1;
2433
+ }
2434
+ const chosen = existingProjects.find((proj) => proj.id === chosenId);
2435
+ const appResult = await runProjectAppCreate({
2436
+ api,
2437
+ userToken,
2438
+ projectId: chosen.id,
2439
+ projectName: chosen.name,
2440
+ organizationName: selectedWorkspaceName,
2441
+ repoCanonical: identity?.repoCanonical,
2442
+ defaultScopePath: identity?.repoScopePath,
2443
+ existingApps: []
2444
+ });
2445
+ if (!appResult) {
2446
+ p5.log.error("Setup failed. Run `vocoder init` again.");
2447
+ return 1;
2448
+ }
2449
+ runScaffold({
2450
+ projectName: appResult.projectName,
2451
+ organizationName: selectedWorkspaceName,
2452
+ sourceLocale: appResult.sourceLocale,
2453
+ branchTriggers: appResult.branchTriggers
2454
+ });
2455
+ p5.outro("You're all set.");
2456
+ return 0;
2349
2457
  }
2350
2458
  } catch {
2351
2459
  }