unity-hub-cli 0.10.1 → 0.11.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.
Files changed (2) hide show
  1. package/dist/index.js +215 -60
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -621,8 +621,8 @@ var UnityTempDirectoryCleaner = class {
621
621
 
622
622
  // src/presentation/App.tsx
623
623
  import clipboard from "clipboardy";
624
- import { Box as Box5, Text as Text3, useApp, useInput, useStdout as useStdout2 } from "ink";
625
- import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState as useState3 } from "react";
624
+ import { Box as Box6, Text as Text4, useApp, useInput, useStdout as useStdout2 } from "ink";
625
+ import { useCallback, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
626
626
 
627
627
  // src/presentation/components/LayoutManager.tsx
628
628
  import { Box } from "ink";
@@ -980,16 +980,55 @@ var SortPanel = ({ sortPreferences, focusedIndex, width }) => {
980
980
  );
981
981
  };
982
982
 
983
+ // src/presentation/components/VisibilityPanel.tsx
984
+ import { Box as Box5, Text as Text3 } from "ink";
985
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
986
+ var lineForBranch = (on) => `Show branch: ${on ? "ON" : "OFF"}`;
987
+ var lineForPath = (on) => `Show path: ${on ? "ON" : "OFF"}`;
988
+ var VisibilityPanel = ({ visibility, focusedIndex, width }) => {
989
+ const branchLine = lineForBranch(visibility.showBranch);
990
+ const pathLine = lineForPath(visibility.showPath);
991
+ const Item = ({ label, selected }) => {
992
+ const prefix = selected ? "> " : " ";
993
+ return /* @__PURE__ */ jsxs4(Text3, { children: [
994
+ selected ? /* @__PURE__ */ jsx5(Text3, { color: "green", children: prefix }) : prefix,
995
+ label
996
+ ] });
997
+ };
998
+ return /* @__PURE__ */ jsxs4(
999
+ Box5,
1000
+ {
1001
+ flexDirection: "column",
1002
+ borderStyle: "round",
1003
+ borderColor: "green",
1004
+ paddingX: 1,
1005
+ width,
1006
+ children: [
1007
+ /* @__PURE__ */ jsx5(Item, { label: branchLine, selected: focusedIndex === 0 }),
1008
+ /* @__PURE__ */ jsx5(Item, { label: pathLine, selected: focusedIndex === 1 })
1009
+ ]
1010
+ }
1011
+ );
1012
+ };
1013
+
983
1014
  // src/presentation/hooks/useSortPreferences.ts
984
1015
  import { useEffect, useState } from "react";
985
1016
 
986
1017
  // src/infrastructure/config.ts
987
1018
  import { mkdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
988
- var defaultPreferences = {
1019
+ var defaultSortPreferences = {
989
1020
  favoritesFirst: true,
990
1021
  primary: "updated",
991
1022
  direction: "desc"
992
1023
  };
1024
+ var defaultVisibilityPreferences = {
1025
+ showBranch: true,
1026
+ showPath: true
1027
+ };
1028
+ var defaultAppConfig = {
1029
+ sort: defaultSortPreferences,
1030
+ visibility: defaultVisibilityPreferences
1031
+ };
993
1032
  var getConfigDir = () => {
994
1033
  const home = process.env.HOME ?? "";
995
1034
  return `${home}/Library/Application Support/UnityHubCli`;
@@ -997,35 +1036,71 @@ var getConfigDir = () => {
997
1036
  var getConfigPath = () => `${getConfigDir()}/config.json`;
998
1037
  var isValidPrimary = (value) => value === "updated" || value === "name";
999
1038
  var isValidDirection = (value) => value === "asc" || value === "desc";
1000
- var sanitizePreferences = (input) => {
1039
+ var sanitizeSort = (input) => {
1001
1040
  if (!input || typeof input !== "object") {
1002
- return defaultPreferences;
1041
+ return defaultSortPreferences;
1003
1042
  }
1004
1043
  const record = input;
1005
- const favoritesFirst = typeof record.favoritesFirst === "boolean" ? record.favoritesFirst : defaultPreferences.favoritesFirst;
1006
- const primary = isValidPrimary(record.primary) ? record.primary : defaultPreferences.primary;
1007
- const direction = isValidDirection(record.direction) ? record.direction : defaultPreferences.direction;
1044
+ const favoritesFirst = typeof record.favoritesFirst === "boolean" ? record.favoritesFirst : defaultSortPreferences.favoritesFirst;
1045
+ const primary = isValidPrimary(record.primary) ? record.primary : defaultSortPreferences.primary;
1046
+ const direction = isValidDirection(record.direction) ? record.direction : defaultSortPreferences.direction;
1008
1047
  return { favoritesFirst, primary, direction };
1009
1048
  };
1010
- var readSortPreferences = async () => {
1049
+ var sanitizeVisibility = (input) => {
1050
+ if (!input || typeof input !== "object") {
1051
+ return defaultVisibilityPreferences;
1052
+ }
1053
+ const record = input;
1054
+ const showBranch = typeof record.showBranch === "boolean" ? record.showBranch : defaultVisibilityPreferences.showBranch;
1055
+ const showPath = typeof record.showPath === "boolean" ? record.showPath : defaultVisibilityPreferences.showPath;
1056
+ return { showBranch, showPath };
1057
+ };
1058
+ var sanitizeAppConfig = (input) => {
1059
+ if (!input || typeof input !== "object") {
1060
+ return defaultAppConfig;
1061
+ }
1062
+ const record = input;
1063
+ const sort = sanitizeSort(record.sort);
1064
+ const visibility = sanitizeVisibility(record.visibility);
1065
+ return { sort, visibility };
1066
+ };
1067
+ var readAppConfig = async () => {
1011
1068
  try {
1012
1069
  const content = await readFile3(getConfigPath(), "utf8");
1013
1070
  const json = JSON.parse(content);
1014
- return sanitizePreferences(json);
1071
+ return sanitizeAppConfig(json);
1015
1072
  } catch {
1016
- return defaultPreferences;
1073
+ return defaultAppConfig;
1017
1074
  }
1018
1075
  };
1019
- var writeSortPreferences = async (prefs) => {
1076
+ var writeAppConfig = async (config) => {
1020
1077
  try {
1021
1078
  await mkdir(getConfigDir(), { recursive: true });
1022
1079
  } catch {
1023
1080
  }
1024
- const sanitized = sanitizePreferences(prefs);
1025
- const json = JSON.stringify(sanitized, void 0, 2);
1081
+ const json = JSON.stringify(sanitizeAppConfig(config), void 0, 2);
1026
1082
  await writeFile2(getConfigPath(), json, "utf8");
1027
1083
  };
1028
- var getDefaultSortPreferences = () => defaultPreferences;
1084
+ var readSortPreferences = async () => {
1085
+ const config = await readAppConfig();
1086
+ return config.sort;
1087
+ };
1088
+ var writeSortPreferences = async (prefs) => {
1089
+ const current = await readAppConfig();
1090
+ const next = { ...current, sort: sanitizeSort(prefs) };
1091
+ await writeAppConfig(next);
1092
+ };
1093
+ var getDefaultSortPreferences = () => defaultSortPreferences;
1094
+ var readVisibilityPreferences = async () => {
1095
+ const config = await readAppConfig();
1096
+ return config.visibility;
1097
+ };
1098
+ var writeVisibilityPreferences = async (prefs) => {
1099
+ const current = await readAppConfig();
1100
+ const next = { ...current, visibility: sanitizeVisibility(prefs) };
1101
+ await writeAppConfig(next);
1102
+ };
1103
+ var getDefaultVisibilityPreferences = () => defaultVisibilityPreferences;
1029
1104
 
1030
1105
  // src/presentation/hooks/useSortPreferences.ts
1031
1106
  var useSortPreferences = () => {
@@ -1050,8 +1125,33 @@ var useSortPreferences = () => {
1050
1125
  return { sortPreferences, setSortPreferences, isLoaded };
1051
1126
  };
1052
1127
 
1128
+ // src/presentation/hooks/useVisibilityPreferences.ts
1129
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
1130
+ var useVisibilityPreferences = () => {
1131
+ const [visibilityPreferences, setVisibilityPreferences] = useState2(getDefaultVisibilityPreferences());
1132
+ const [isLoaded, setIsLoaded] = useState2(false);
1133
+ const writeQueueRef = useRef(Promise.resolve());
1134
+ useEffect2(() => {
1135
+ void (async () => {
1136
+ try {
1137
+ const prefs = await readVisibilityPreferences();
1138
+ setVisibilityPreferences(prefs);
1139
+ } finally {
1140
+ setIsLoaded(true);
1141
+ }
1142
+ })();
1143
+ }, []);
1144
+ useEffect2(() => {
1145
+ if (!isLoaded) {
1146
+ return;
1147
+ }
1148
+ writeQueueRef.current = writeQueueRef.current.catch(() => void 0).then(() => writeVisibilityPreferences(visibilityPreferences));
1149
+ }, [isLoaded, visibilityPreferences]);
1150
+ return { visibilityPreferences, setVisibilityPreferences, isLoaded };
1151
+ };
1152
+
1053
1153
  // src/presentation/hooks/useVisibleCount.ts
1054
- import { useEffect as useEffect2, useState as useState2 } from "react";
1154
+ import { useEffect as useEffect3, useState as useState3 } from "react";
1055
1155
  var useVisibleCount = (stdout, linesPerProject, panelVisible, panelHeight, minimumVisibleProjectCount2) => {
1056
1156
  const compute = () => {
1057
1157
  if (!stdout || typeof stdout.columns !== "number" || typeof stdout.rows !== "number") {
@@ -1065,8 +1165,8 @@ var useVisibleCount = (stdout, linesPerProject, panelVisible, panelHeight, minim
1065
1165
  const calculatedCount = Math.max(1, Math.floor(availableRows / rowsPerProject));
1066
1166
  return calculatedCount;
1067
1167
  };
1068
- const [visibleCount, setVisibleCount] = useState2(compute);
1069
- useEffect2(() => {
1168
+ const [visibleCount, setVisibleCount] = useState3(compute);
1169
+ useEffect3(() => {
1070
1170
  const updateVisible = () => setVisibleCount(compute());
1071
1171
  updateVisible();
1072
1172
  stdout?.on("resize", updateVisible);
@@ -1078,7 +1178,7 @@ var useVisibleCount = (stdout, linesPerProject, panelVisible, panelHeight, minim
1078
1178
  };
1079
1179
 
1080
1180
  // src/presentation/App.tsx
1081
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1181
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1082
1182
  var extractRootFolder2 = (repository) => {
1083
1183
  if (!repository?.root) {
1084
1184
  return void 0;
@@ -1090,7 +1190,7 @@ var extractRootFolder2 = (repository) => {
1090
1190
  return segments[segments.length - 1];
1091
1191
  };
1092
1192
  var minimumVisibleProjectCount = 4;
1093
- var defaultHintMessage = "Select: j/k \xB7 Open: o \xB7 Quit: q \xB7 Refresh: r \xB7 CopyPath: c \xB7 Sort: s \xB7 Close: ctrl + c";
1193
+ var defaultHintMessage = "Select: j/k \xB7 Open: o \xB7 Quit: q \xB7 Refresh: r \xB7 CopyPath: c \xB7 Sort: s \xB7 Visibility: v \xB7 Close: ctrl + c";
1094
1194
  var getCopyTargetPath = (view) => {
1095
1195
  const root = view.repository?.root;
1096
1196
  return root && root.length > 0 ? root : view.project.path;
@@ -1100,24 +1200,29 @@ var App = ({
1100
1200
  onLaunch,
1101
1201
  onTerminate,
1102
1202
  onRefresh,
1103
- useGitRootName = true,
1104
- showBranch = true,
1105
- showPath = true
1203
+ useGitRootName = true
1106
1204
  }) => {
1107
1205
  const { exit } = useApp();
1108
1206
  const { stdout } = useStdout2();
1109
- const [projectViews, setProjectViews] = useState3(projects);
1110
- const [isSortMenuOpen, setIsSortMenuOpen] = useState3(false);
1207
+ const [projectViews, setProjectViews] = useState4(projects);
1208
+ const [isSortMenuOpen, setIsSortMenuOpen] = useState4(false);
1209
+ const [isVisibilityMenuOpen, setIsVisibilityMenuOpen] = useState4(false);
1210
+ const { visibilityPreferences, setVisibilityPreferences } = useVisibilityPreferences();
1211
+ const showBranch = visibilityPreferences.showBranch;
1212
+ const showPath = visibilityPreferences.showPath;
1213
+ const sortPanelHeight = 6;
1214
+ const visibilityPanelHeight = 5;
1111
1215
  const linesPerProject = (showBranch ? 1 : 0) + (showPath ? 1 : 0) + 2;
1112
- const panelHeight = 6;
1113
- const visibleCount = useVisibleCount(stdout, linesPerProject, isSortMenuOpen, panelHeight, minimumVisibleProjectCount);
1114
- const [index, setIndex] = useState3(0);
1115
- const [hint, setHint] = useState3(defaultHintMessage);
1116
- const [windowStart, setWindowStart] = useState3(0);
1117
- const [releasedProjects, setReleasedProjects] = useState3(/* @__PURE__ */ new Set());
1118
- const [launchedProjects, setLaunchedProjects] = useState3(/* @__PURE__ */ new Set());
1119
- const [isRefreshing, setIsRefreshing] = useState3(false);
1120
- const [sortMenuIndex, setSortMenuIndex] = useState3(0);
1216
+ const isAnyMenuOpen = isSortMenuOpen || isVisibilityMenuOpen;
1217
+ const panelHeight = isVisibilityMenuOpen ? visibilityPanelHeight : sortPanelHeight;
1218
+ const visibleCount = useVisibleCount(stdout, linesPerProject, isAnyMenuOpen, panelHeight, minimumVisibleProjectCount);
1219
+ const [index, setIndex] = useState4(0);
1220
+ const [hint, setHint] = useState4(defaultHintMessage);
1221
+ const [windowStart, setWindowStart] = useState4(0);
1222
+ const [releasedProjects, setReleasedProjects] = useState4(/* @__PURE__ */ new Set());
1223
+ const [launchedProjects, setLaunchedProjects] = useState4(/* @__PURE__ */ new Set());
1224
+ const [isRefreshing, setIsRefreshing] = useState4(false);
1225
+ const [sortMenuIndex, setSortMenuIndex] = useState4(0);
1121
1226
  const { sortPreferences, setSortPreferences } = useSortPreferences();
1122
1227
  const sortedProjects = useMemo2(() => {
1123
1228
  const fallbackTime = 0;
@@ -1171,7 +1276,7 @@ var App = ({
1171
1276
  return tieBreaker(a).localeCompare(tieBreaker(b));
1172
1277
  });
1173
1278
  }, [projectViews, sortPreferences]);
1174
- useEffect3(() => {
1279
+ useEffect4(() => {
1175
1280
  const handleSigint = () => {
1176
1281
  exit();
1177
1282
  };
@@ -1224,7 +1329,7 @@ var App = ({
1224
1329
  },
1225
1330
  [index, limit, sortedProjects.length]
1226
1331
  );
1227
- useEffect3(() => {
1332
+ useEffect4(() => {
1228
1333
  setWindowStart((prevStart) => {
1229
1334
  if (sortedProjects.length <= limit) {
1230
1335
  return prevStart === 0 ? prevStart : 0;
@@ -1367,7 +1472,7 @@ var App = ({
1367
1472
  }, 3e3);
1368
1473
  }
1369
1474
  }, [index, onTerminate, sortedProjects]);
1370
- useEffect3(() => {
1475
+ useEffect4(() => {
1371
1476
  setProjectViews(projects);
1372
1477
  setReleasedProjects(/* @__PURE__ */ new Set());
1373
1478
  setLaunchedProjects(/* @__PURE__ */ new Set());
@@ -1463,8 +1568,49 @@ var App = ({
1463
1568
  }
1464
1569
  return;
1465
1570
  }
1571
+ if (isVisibilityMenuOpen) {
1572
+ if (key.escape || input === "\x1B") {
1573
+ setIsVisibilityMenuOpen(false);
1574
+ return;
1575
+ }
1576
+ if (input === "j") {
1577
+ setSortMenuIndex((prev) => {
1578
+ const last = 1;
1579
+ const next = prev + 1;
1580
+ return next > last ? 0 : next;
1581
+ });
1582
+ return;
1583
+ }
1584
+ if (input === "k") {
1585
+ setSortMenuIndex((prev) => {
1586
+ const last = 1;
1587
+ const next = prev - 1;
1588
+ return next < 0 ? last : next;
1589
+ });
1590
+ return;
1591
+ }
1592
+ const toggleCurrent = () => {
1593
+ if (sortMenuIndex === 0) {
1594
+ setVisibilityPreferences((prev) => ({ ...prev, showBranch: !prev.showBranch }));
1595
+ return;
1596
+ }
1597
+ setVisibilityPreferences((prev) => ({ ...prev, showPath: !prev.showPath }));
1598
+ };
1599
+ if (input === " ") {
1600
+ toggleCurrent();
1601
+ }
1602
+ return;
1603
+ }
1466
1604
  if (input === "S" || input === "s") {
1605
+ setIsVisibilityMenuOpen(false);
1467
1606
  setIsSortMenuOpen(true);
1607
+ setSortMenuIndex(0);
1608
+ return;
1609
+ }
1610
+ if (input === "v" || input === "V") {
1611
+ setIsSortMenuOpen(false);
1612
+ setIsVisibilityMenuOpen(true);
1613
+ setSortMenuIndex(0);
1468
1614
  return;
1469
1615
  }
1470
1616
  if (input === "j" || key.downArrow) {
@@ -1504,19 +1650,19 @@ var App = ({
1504
1650
  visibleProjects: sortedProjects.slice(clampedStart, end)
1505
1651
  };
1506
1652
  }, [limit, sortedProjects, windowStart]);
1507
- return /* @__PURE__ */ jsx5(
1653
+ return /* @__PURE__ */ jsx6(
1508
1654
  LayoutManager,
1509
1655
  {
1510
1656
  layoutMode: getLayoutMode(),
1511
- panelVisible: isSortMenuOpen,
1512
- list: /* @__PURE__ */ jsx5(
1513
- Box5,
1657
+ panelVisible: isAnyMenuOpen,
1658
+ list: /* @__PURE__ */ jsx6(
1659
+ Box6,
1514
1660
  {
1515
1661
  flexDirection: "column",
1516
1662
  borderStyle: "round",
1517
1663
  borderColor: "green",
1518
1664
  width: typeof stdout?.columns === "number" ? stdout.columns : void 0,
1519
- children: sortedProjects.length === 0 ? /* @__PURE__ */ jsx5(Text3, { children: "No Unity Hub projects were found." }) : /* @__PURE__ */ jsx5(
1665
+ children: sortedProjects.length === 0 ? /* @__PURE__ */ jsx6(Text4, { children: "No Unity Hub projects were found." }) : /* @__PURE__ */ jsx6(
1520
1666
  ProjectList,
1521
1667
  {
1522
1668
  visibleProjects,
@@ -1533,24 +1679,37 @@ var App = ({
1533
1679
  )
1534
1680
  }
1535
1681
  ),
1536
- panel: /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", width: typeof stdout?.columns === "number" ? stdout.columns : void 0, children: [
1537
- /* @__PURE__ */ jsx5(Text3, { children: "Sort Settings" }),
1538
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
1539
- SortPanel,
1540
- {
1541
- sortPreferences,
1542
- focusedIndex: sortMenuIndex,
1543
- width: typeof stdout?.columns === "number" ? stdout.columns : void 0
1544
- }
1545
- ) })
1682
+ panel: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", width: typeof stdout?.columns === "number" ? stdout.columns : void 0, children: [
1683
+ isSortMenuOpen && /* @__PURE__ */ jsxs5(Fragment, { children: [
1684
+ /* @__PURE__ */ jsx6(Text4, { children: "Sort Settings" }),
1685
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
1686
+ SortPanel,
1687
+ {
1688
+ sortPreferences,
1689
+ focusedIndex: sortMenuIndex,
1690
+ width: typeof stdout?.columns === "number" ? stdout.columns : void 0
1691
+ }
1692
+ ) })
1693
+ ] }),
1694
+ isVisibilityMenuOpen && /* @__PURE__ */ jsxs5(Fragment, { children: [
1695
+ /* @__PURE__ */ jsx6(Text4, { children: "Visibility Settings" }),
1696
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
1697
+ VisibilityPanel,
1698
+ {
1699
+ visibility: visibilityPreferences,
1700
+ focusedIndex: sortMenuIndex,
1701
+ width: typeof stdout?.columns === "number" ? stdout.columns : void 0
1702
+ }
1703
+ ) })
1704
+ ] })
1546
1705
  ] }),
1547
- statusBar: isSortMenuOpen ? /* @__PURE__ */ jsx5(Text3, { wrap: "truncate", children: "Select: j/k, Toggle: Space, Back: Esc" }) : /* @__PURE__ */ jsx5(Text3, { wrap: "truncate", children: hint })
1706
+ statusBar: isAnyMenuOpen ? /* @__PURE__ */ jsx6(Text4, { wrap: "truncate", children: "Select: j/k, Toggle: Space, Back: Esc" }) : /* @__PURE__ */ jsx6(Text4, { wrap: "truncate", children: hint })
1548
1707
  }
1549
1708
  );
1550
1709
  };
1551
1710
 
1552
1711
  // src/index.tsx
1553
- import { jsx as jsx6 } from "react/jsx-runtime";
1712
+ import { jsx as jsx7 } from "react/jsx-runtime";
1554
1713
  var bootstrap = async () => {
1555
1714
  const unityHubReader = new UnityHubProjectsReader();
1556
1715
  const gitRepositoryInfoReader = new GitRepositoryInfoReader();
@@ -1581,21 +1740,17 @@ var bootstrap = async () => {
1581
1740
  unityTempDirectoryCleaner
1582
1741
  );
1583
1742
  const useGitRootName = !process2.argv.includes("--no-git-root-name");
1584
- const showBranch = !process2.argv.includes("--hide-branch");
1585
- const showPath = !process2.argv.includes("--hide-path");
1586
1743
  try {
1587
1744
  const projects = await listProjectsUseCase.execute();
1588
1745
  const { waitUntilExit } = render(
1589
- /* @__PURE__ */ jsx6(
1746
+ /* @__PURE__ */ jsx7(
1590
1747
  App,
1591
1748
  {
1592
1749
  projects,
1593
1750
  onLaunch: (project) => launchProjectUseCase.execute(project),
1594
1751
  onTerminate: (project) => terminateProjectUseCase.execute(project),
1595
1752
  onRefresh: () => listProjectsUseCase.execute(),
1596
- useGitRootName,
1597
- showBranch,
1598
- showPath
1753
+ useGitRootName
1599
1754
  }
1600
1755
  )
1601
1756
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unity-hub-cli",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "A CLI tool that reads Unity Hub's projects and launches Unity Editor with an interactive TUI",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {