unity-hub-cli 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/dist/index.js +355 -34
- package/package.json +31 -18
package/README.md
CHANGED
|
@@ -58,14 +58,53 @@ The display includes Git branch (if present), Unity version, project path, and l
|
|
|
58
58
|
## CLI Options
|
|
59
59
|
|
|
60
60
|
- `--no-git-root-name`: Display Unity project titles instead of Git repository root folder names.
|
|
61
|
+
- `--shell-init`: Install shell function for automatic `cd` integration (with confirmation prompt).
|
|
62
|
+
- `--shell-init --dry-run`: Preview the shell function without installing.
|
|
63
|
+
|
|
64
|
+
## Shell Integration
|
|
65
|
+
|
|
66
|
+
You can add a shell function to automatically `cd` to the project directory after opening Unity.
|
|
67
|
+
|
|
68
|
+
### Setup
|
|
69
|
+
|
|
70
|
+
1. Install globally:
|
|
71
|
+
```bash
|
|
72
|
+
npm install -g unity-hub-cli
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. Run the shell init command (auto-detects your shell):
|
|
76
|
+
```bash
|
|
77
|
+
unity-hub-cli --shell-init
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This automatically adds the `unity-hub` function to your shell config file (`.zshrc`, `.bashrc`, `config.fish`, or PowerShell profile).
|
|
81
|
+
|
|
82
|
+
3. Reload your shell:
|
|
83
|
+
```bash
|
|
84
|
+
source ~/.zshrc # or restart your terminal
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Usage
|
|
88
|
+
|
|
89
|
+
Now you can use `unity-hub` to:
|
|
90
|
+
1. Browse and select Unity projects
|
|
91
|
+
2. Press `o` to launch Unity
|
|
92
|
+
3. Your terminal automatically `cd`s to the project directory
|
|
93
|
+
|
|
94
|
+
### Notes
|
|
95
|
+
|
|
96
|
+
- Running `--shell-init` multiple times is safe - it updates the existing function using marker comments
|
|
97
|
+
- The function uses absolute paths detected from your environment
|
|
98
|
+
- **Windows**: Shell integration supports PowerShell only. CMD is not supported because it lacks shell functions required for automatic `cd` after launching Unity
|
|
61
99
|
|
|
62
100
|
## Security
|
|
63
101
|
|
|
64
102
|
This project implements supply chain attack prevention measures:
|
|
65
103
|
|
|
66
104
|
- **ignore-scripts**: Disables automatic script execution during `npm install`
|
|
105
|
+
- **@lavamoat/allow-scripts**: Explicitly controls which packages can run install scripts
|
|
67
106
|
- **Dependabot**: Automated weekly security updates
|
|
68
|
-
- **Security audit CI**: Runs `npm audit
|
|
107
|
+
- **Security audit CI**: Runs `npm audit`, `lockfile-lint`, and OSV-Scanner on every PR
|
|
69
108
|
- **Pinned versions**: All dependencies use exact versions (no `^` or `~`)
|
|
70
109
|
|
|
71
110
|
## License
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
4
8
|
import process3 from "process";
|
|
9
|
+
import { createInterface as createInterface2 } from "readline";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import chalk from "chalk";
|
|
5
12
|
import { render } from "ink";
|
|
6
13
|
|
|
7
14
|
// src/application/usecases.ts
|
|
@@ -778,6 +785,38 @@ var MacUnityHubProjectsReader = class {
|
|
|
778
785
|
};
|
|
779
786
|
await writeFile(HUB_PROJECTS_PATH, JSON.stringify(json, void 0, 2), "utf8");
|
|
780
787
|
}
|
|
788
|
+
async toggleFavorite(projectPath) {
|
|
789
|
+
let content;
|
|
790
|
+
try {
|
|
791
|
+
content = await readFile2(HUB_PROJECTS_PATH, "utf8");
|
|
792
|
+
} catch {
|
|
793
|
+
throw new Error(`Unity Hub project list not found (${HUB_PROJECTS_PATH}).`);
|
|
794
|
+
}
|
|
795
|
+
let json;
|
|
796
|
+
try {
|
|
797
|
+
json = JSON.parse(content);
|
|
798
|
+
} catch {
|
|
799
|
+
throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
|
|
800
|
+
}
|
|
801
|
+
if (!json.data) {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
const projectKey = Object.keys(json.data).find((key) => json.data?.[key]?.path === projectPath);
|
|
805
|
+
if (!projectKey) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
const original = json.data[projectKey];
|
|
809
|
+
if (!original) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
const newFavorite = original.isFavorite !== true;
|
|
813
|
+
json.data[projectKey] = {
|
|
814
|
+
...original,
|
|
815
|
+
isFavorite: newFavorite
|
|
816
|
+
};
|
|
817
|
+
await writeFile(HUB_PROJECTS_PATH, JSON.stringify(json, void 0, 2), "utf8");
|
|
818
|
+
return newFavorite;
|
|
819
|
+
}
|
|
781
820
|
async readCliArgs(projectPath) {
|
|
782
821
|
const infoPath = `${process.env.HOME ?? ""}/Library/Application Support/UnityHub/projectsInfo.json`;
|
|
783
822
|
let content;
|
|
@@ -909,6 +948,38 @@ var WinUnityHubProjectsReader = class {
|
|
|
909
948
|
};
|
|
910
949
|
await writeFile2(HUB_PROJECTS_PATH2, JSON.stringify(json, void 0, 2), "utf8");
|
|
911
950
|
}
|
|
951
|
+
async toggleFavorite(projectPath) {
|
|
952
|
+
let content;
|
|
953
|
+
try {
|
|
954
|
+
content = await readFile3(HUB_PROJECTS_PATH2, "utf8");
|
|
955
|
+
} catch {
|
|
956
|
+
throw new Error(`Unity Hub project list not found (${HUB_PROJECTS_PATH2}).`);
|
|
957
|
+
}
|
|
958
|
+
let json;
|
|
959
|
+
try {
|
|
960
|
+
json = JSON.parse(content);
|
|
961
|
+
} catch {
|
|
962
|
+
throw new Error("Unable to read the Unity Hub project list (permissions/format error).");
|
|
963
|
+
}
|
|
964
|
+
if (!json.data) {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
const projectKey = Object.keys(json.data).find((key) => json.data?.[key]?.path === projectPath);
|
|
968
|
+
if (!projectKey) {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
const original = json.data[projectKey];
|
|
972
|
+
if (!original) {
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
const newFavorite = original.isFavorite !== true;
|
|
976
|
+
json.data[projectKey] = {
|
|
977
|
+
...original,
|
|
978
|
+
isFavorite: newFavorite
|
|
979
|
+
};
|
|
980
|
+
await writeFile2(HUB_PROJECTS_PATH2, JSON.stringify(json, void 0, 2), "utf8");
|
|
981
|
+
return newFavorite;
|
|
982
|
+
}
|
|
912
983
|
async readCliArgs(projectPath) {
|
|
913
984
|
const infoPath = join6(HUB_DIR, "projectsInfo.json");
|
|
914
985
|
let content;
|
|
@@ -1332,7 +1403,7 @@ var WinUnityProcessTerminator = class {
|
|
|
1332
1403
|
],
|
|
1333
1404
|
{ env: getMsysDisabledEnv() }
|
|
1334
1405
|
);
|
|
1335
|
-
} catch
|
|
1406
|
+
} catch {
|
|
1336
1407
|
if (!ensureProcessAlive2(unityProcess.pid)) {
|
|
1337
1408
|
return { terminated: true, stage: "sigterm" };
|
|
1338
1409
|
}
|
|
@@ -1358,7 +1429,7 @@ var WinUnityProcessTerminator = class {
|
|
|
1358
1429
|
],
|
|
1359
1430
|
{ env: getMsysDisabledEnv() }
|
|
1360
1431
|
);
|
|
1361
|
-
} catch
|
|
1432
|
+
} catch {
|
|
1362
1433
|
if (!ensureProcessAlive2(unityProcess.pid)) {
|
|
1363
1434
|
return { terminated: true, stage: "sigkill" };
|
|
1364
1435
|
}
|
|
@@ -1667,7 +1738,8 @@ var ProjectList = ({
|
|
|
1667
1738
|
const rowIndex = startIndex + offset;
|
|
1668
1739
|
const isSelected = rowIndex === selectedIndex;
|
|
1669
1740
|
const selectionBar = isSelected ? "\u2503" : " ";
|
|
1670
|
-
const
|
|
1741
|
+
const baseName = formatProjectName(project.title, repository, useGitRootName);
|
|
1742
|
+
const projectName = project.favorite ? `\u2B51 ${baseName}` : baseName;
|
|
1671
1743
|
const versionLabel = `(${project.version.value})`;
|
|
1672
1744
|
const updatedText = formatUpdatedText(project.lastModified);
|
|
1673
1745
|
const pathLine = shortenHomePath(project.path);
|
|
@@ -1987,7 +2059,7 @@ var extractRootFolder2 = (repository) => {
|
|
|
1987
2059
|
return base || void 0;
|
|
1988
2060
|
};
|
|
1989
2061
|
var minimumVisibleProjectCount = 4;
|
|
1990
|
-
var defaultHintMessage = `j/k Select \xB7 [o]pen [O]+Editor [i]de [q]uit [r]efresh [c]opy [s]ort [v]isibility \xB7 ^C Exit`;
|
|
2062
|
+
var defaultHintMessage = `j/k Select \xB7 [o]pen [O]+Editor [i]de [q]uit [r]efresh [c]opy [f]av [s]ort [v]isibility \xB7 ^C Exit`;
|
|
1991
2063
|
var getCopyTargetPath = (view) => {
|
|
1992
2064
|
const root = view.repository?.root;
|
|
1993
2065
|
return root && root.length > 0 ? root : view.project.path;
|
|
@@ -1999,7 +2071,10 @@ var App = ({
|
|
|
1999
2071
|
onLaunchEditorOnly,
|
|
2000
2072
|
onTerminate,
|
|
2001
2073
|
onRefresh,
|
|
2002
|
-
|
|
2074
|
+
onToggleFavorite,
|
|
2075
|
+
useGitRootName = true,
|
|
2076
|
+
outputPathOnExit = false,
|
|
2077
|
+
onSetExitPath
|
|
2003
2078
|
}) => {
|
|
2004
2079
|
const { exit } = useApp();
|
|
2005
2080
|
const { stdout } = useStdout2();
|
|
@@ -2196,20 +2271,14 @@ var App = ({
|
|
|
2196
2271
|
return;
|
|
2197
2272
|
}
|
|
2198
2273
|
const { project } = projectView;
|
|
2199
|
-
|
|
2200
|
-
const cdTarget = getCopyTargetPath(projectView);
|
|
2201
|
-
const command = buildCdCommand(cdTarget);
|
|
2202
|
-
clipboard.writeSync(command);
|
|
2203
|
-
} catch (error) {
|
|
2204
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2205
|
-
setHint(`Failed to copy: ${message}`);
|
|
2206
|
-
setTimeout(() => {
|
|
2207
|
-
setHint(defaultHintMessage);
|
|
2208
|
-
}, 3e3);
|
|
2209
|
-
return;
|
|
2210
|
-
}
|
|
2274
|
+
const cdTarget = getCopyTargetPath(projectView);
|
|
2211
2275
|
try {
|
|
2212
2276
|
await onLaunch(project);
|
|
2277
|
+
if (outputPathOnExit) {
|
|
2278
|
+
onSetExitPath?.(cdTarget);
|
|
2279
|
+
exit();
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2213
2282
|
setLaunchedProjects((previous) => {
|
|
2214
2283
|
const next = new Set(previous);
|
|
2215
2284
|
next.add(project.id);
|
|
@@ -2229,6 +2298,11 @@ var App = ({
|
|
|
2229
2298
|
}, 3e3);
|
|
2230
2299
|
} catch (error) {
|
|
2231
2300
|
if (error instanceof LaunchCancelledError) {
|
|
2301
|
+
if (outputPathOnExit) {
|
|
2302
|
+
onSetExitPath?.(cdTarget);
|
|
2303
|
+
exit();
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2232
2306
|
setHint("Launch cancelled");
|
|
2233
2307
|
setTimeout(() => {
|
|
2234
2308
|
setHint(defaultHintMessage);
|
|
@@ -2241,7 +2315,7 @@ var App = ({
|
|
|
2241
2315
|
setHint(defaultHintMessage);
|
|
2242
2316
|
}, 3e3);
|
|
2243
2317
|
}
|
|
2244
|
-
}, [index, onLaunch, sortedProjects]);
|
|
2318
|
+
}, [exit, index, onLaunch, onSetExitPath, outputPathOnExit, sortedProjects]);
|
|
2245
2319
|
const launchSelectedWithEditor = useCallback(async () => {
|
|
2246
2320
|
if (!onLaunchWithEditor) {
|
|
2247
2321
|
setHint("Launch with editor not available");
|
|
@@ -2259,20 +2333,14 @@ var App = ({
|
|
|
2259
2333
|
return;
|
|
2260
2334
|
}
|
|
2261
2335
|
const { project } = projectView;
|
|
2262
|
-
|
|
2263
|
-
const cdTarget = getCopyTargetPath(projectView);
|
|
2264
|
-
const command = buildCdCommand(cdTarget);
|
|
2265
|
-
clipboard.writeSync(command);
|
|
2266
|
-
} catch (error) {
|
|
2267
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2268
|
-
setHint(`Failed to copy: ${message}`);
|
|
2269
|
-
setTimeout(() => {
|
|
2270
|
-
setHint(defaultHintMessage);
|
|
2271
|
-
}, 3e3);
|
|
2272
|
-
return;
|
|
2273
|
-
}
|
|
2336
|
+
const cdTarget = getCopyTargetPath(projectView);
|
|
2274
2337
|
try {
|
|
2275
2338
|
const result = await onLaunchWithEditor(project);
|
|
2339
|
+
if (outputPathOnExit) {
|
|
2340
|
+
onSetExitPath?.(cdTarget);
|
|
2341
|
+
exit();
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2276
2344
|
setLaunchedProjects((previous) => {
|
|
2277
2345
|
const next = new Set(previous);
|
|
2278
2346
|
next.add(project.id);
|
|
@@ -2292,6 +2360,11 @@ var App = ({
|
|
|
2292
2360
|
}, 3e3);
|
|
2293
2361
|
} catch (error) {
|
|
2294
2362
|
if (error instanceof LaunchCancelledError) {
|
|
2363
|
+
if (outputPathOnExit) {
|
|
2364
|
+
onSetExitPath?.(cdTarget);
|
|
2365
|
+
exit();
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2295
2368
|
setHint("Launch cancelled");
|
|
2296
2369
|
setTimeout(() => {
|
|
2297
2370
|
setHint(defaultHintMessage);
|
|
@@ -2304,7 +2377,7 @@ var App = ({
|
|
|
2304
2377
|
setHint(defaultHintMessage);
|
|
2305
2378
|
}, 3e3);
|
|
2306
2379
|
}
|
|
2307
|
-
}, [index, onLaunchWithEditor, sortedProjects]);
|
|
2380
|
+
}, [exit, index, onLaunchWithEditor, onSetExitPath, outputPathOnExit, sortedProjects]);
|
|
2308
2381
|
const launchEditorOnly = useCallback(async () => {
|
|
2309
2382
|
if (!onLaunchEditorOnly) {
|
|
2310
2383
|
setHint("Launch editor only not available");
|
|
@@ -2437,6 +2510,43 @@ var App = ({
|
|
|
2437
2510
|
setIsRefreshing(false);
|
|
2438
2511
|
}
|
|
2439
2512
|
}, [isRefreshing, onRefresh, sortedProjects]);
|
|
2513
|
+
const toggleFavoriteSelected = useCallback(async () => {
|
|
2514
|
+
if (!onToggleFavorite) {
|
|
2515
|
+
setHint("Toggle favorite not available");
|
|
2516
|
+
setTimeout(() => {
|
|
2517
|
+
setHint(defaultHintMessage);
|
|
2518
|
+
}, 2e3);
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const projectView = sortedProjects[index];
|
|
2522
|
+
if (!projectView) {
|
|
2523
|
+
setHint("No project to toggle favorite");
|
|
2524
|
+
setTimeout(() => {
|
|
2525
|
+
setHint(defaultHintMessage);
|
|
2526
|
+
}, 2e3);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
const { project } = projectView;
|
|
2530
|
+
try {
|
|
2531
|
+
const newFavorite = await onToggleFavorite(project);
|
|
2532
|
+
setProjectViews(
|
|
2533
|
+
(prev) => prev.map(
|
|
2534
|
+
(pv) => pv.project.id === project.id ? { ...pv, project: { ...pv.project, favorite: newFavorite } } : pv
|
|
2535
|
+
)
|
|
2536
|
+
);
|
|
2537
|
+
const status = newFavorite ? "added to" : "removed from";
|
|
2538
|
+
setHint(`${project.title} ${status} favorites`);
|
|
2539
|
+
setTimeout(() => {
|
|
2540
|
+
setHint(defaultHintMessage);
|
|
2541
|
+
}, 2e3);
|
|
2542
|
+
} catch (error) {
|
|
2543
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2544
|
+
setHint(`Failed to toggle favorite: ${message}`);
|
|
2545
|
+
setTimeout(() => {
|
|
2546
|
+
setHint(defaultHintMessage);
|
|
2547
|
+
}, 3e3);
|
|
2548
|
+
}
|
|
2549
|
+
}, [index, onToggleFavorite, sortedProjects]);
|
|
2440
2550
|
useInput((input, key) => {
|
|
2441
2551
|
if (isSortMenuOpen) {
|
|
2442
2552
|
if (key.escape || input === "\x1B") {
|
|
@@ -2557,6 +2667,10 @@ var App = ({
|
|
|
2557
2667
|
}
|
|
2558
2668
|
if (input === "c") {
|
|
2559
2669
|
copyProjectPath();
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
if (input === "f" || input === "F") {
|
|
2673
|
+
void toggleFavoriteSelected();
|
|
2560
2674
|
}
|
|
2561
2675
|
});
|
|
2562
2676
|
const { startIndex, visibleProjects } = useMemo2(() => {
|
|
@@ -2634,7 +2748,196 @@ var App = ({
|
|
|
2634
2748
|
|
|
2635
2749
|
// src/index.tsx
|
|
2636
2750
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2751
|
+
var SHELL_INIT_MARKER_START = "# >>> unity-hub-cli >>>";
|
|
2752
|
+
var SHELL_INIT_MARKER_END = "# <<< unity-hub-cli <<<";
|
|
2753
|
+
var getVersion = () => {
|
|
2754
|
+
const currentDir = dirname2(fileURLToPath(import.meta.url));
|
|
2755
|
+
const packageJsonPath = join9(currentDir, "..", "package.json");
|
|
2756
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
2757
|
+
return packageJson.version;
|
|
2758
|
+
};
|
|
2759
|
+
var getShellConfigPath = () => {
|
|
2760
|
+
const shell = process3.env["SHELL"] ?? "";
|
|
2761
|
+
const home = homedir();
|
|
2762
|
+
if (shell.includes("zsh")) {
|
|
2763
|
+
return join9(home, ".zshrc");
|
|
2764
|
+
}
|
|
2765
|
+
if (shell.includes("bash")) {
|
|
2766
|
+
const bashrcPath = join9(home, ".bashrc");
|
|
2767
|
+
const profilePath = join9(home, ".bash_profile");
|
|
2768
|
+
return existsSync3(bashrcPath) ? bashrcPath : profilePath;
|
|
2769
|
+
}
|
|
2770
|
+
if (shell.includes("fish")) {
|
|
2771
|
+
return join9(home, ".config", "fish", "config.fish");
|
|
2772
|
+
}
|
|
2773
|
+
if (process3.platform === "win32") {
|
|
2774
|
+
return join9(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
2775
|
+
}
|
|
2776
|
+
return void 0;
|
|
2777
|
+
};
|
|
2778
|
+
var getShellInitScriptWithMarkers = () => {
|
|
2779
|
+
const script = getShellInitScript();
|
|
2780
|
+
return `${SHELL_INIT_MARKER_START}
|
|
2781
|
+
${script}
|
|
2782
|
+
${SHELL_INIT_MARKER_END}`;
|
|
2783
|
+
};
|
|
2784
|
+
var askConfirmation = (question) => {
|
|
2785
|
+
const rl = createInterface2({
|
|
2786
|
+
input: process3.stdin,
|
|
2787
|
+
output: process3.stdout
|
|
2788
|
+
});
|
|
2789
|
+
return new Promise((resolve4) => {
|
|
2790
|
+
rl.question(question, (answer) => {
|
|
2791
|
+
rl.close();
|
|
2792
|
+
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
2793
|
+
});
|
|
2794
|
+
});
|
|
2795
|
+
};
|
|
2796
|
+
var previewShellInit = () => {
|
|
2797
|
+
const configPath = getShellConfigPath();
|
|
2798
|
+
console.log("=== Shell Integration Preview ===\n");
|
|
2799
|
+
console.log(`Target file: ${configPath ?? "Unknown (unsupported shell)"}
|
|
2800
|
+
`);
|
|
2801
|
+
console.log("Content to be added:\n");
|
|
2802
|
+
console.log(getShellInitScriptWithMarkers());
|
|
2803
|
+
};
|
|
2804
|
+
var installShellInit = () => {
|
|
2805
|
+
const configPath = getShellConfigPath();
|
|
2806
|
+
if (!configPath) {
|
|
2807
|
+
return { success: false, message: "Unsupported shell. Please copy the function from the README into your shell config manually." };
|
|
2808
|
+
}
|
|
2809
|
+
const scriptWithMarkers = getShellInitScriptWithMarkers();
|
|
2810
|
+
const markerPattern = new RegExp(
|
|
2811
|
+
`${SHELL_INIT_MARKER_START}[\\s\\S]*?${SHELL_INIT_MARKER_END}`,
|
|
2812
|
+
"g"
|
|
2813
|
+
);
|
|
2814
|
+
let content = "";
|
|
2815
|
+
if (existsSync3(configPath)) {
|
|
2816
|
+
content = readFileSync(configPath, "utf-8");
|
|
2817
|
+
}
|
|
2818
|
+
const existingMatch = content.match(markerPattern);
|
|
2819
|
+
if (existingMatch && existingMatch[0] === scriptWithMarkers) {
|
|
2820
|
+
return { success: true, message: `Shell integration is already up to date in ${configPath}` };
|
|
2821
|
+
}
|
|
2822
|
+
let newContent;
|
|
2823
|
+
let action;
|
|
2824
|
+
if (existingMatch) {
|
|
2825
|
+
newContent = content.replace(markerPattern, scriptWithMarkers);
|
|
2826
|
+
action = "updated";
|
|
2827
|
+
} else {
|
|
2828
|
+
newContent = content.trimEnd() + "\n\n" + scriptWithMarkers + "\n";
|
|
2829
|
+
action = "installed";
|
|
2830
|
+
}
|
|
2831
|
+
mkdirSync(dirname2(configPath), { recursive: true });
|
|
2832
|
+
writeFileSync(configPath, newContent, "utf-8");
|
|
2833
|
+
return { success: true, message: `Shell integration ${action} in ${configPath}` };
|
|
2834
|
+
};
|
|
2835
|
+
var getNodePath = () => {
|
|
2836
|
+
const isWindows = process3.platform === "win32";
|
|
2837
|
+
const command = isWindows ? "where node" : "which node";
|
|
2838
|
+
try {
|
|
2839
|
+
const result = execSync(command, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
2840
|
+
if (result && existsSync3(result)) {
|
|
2841
|
+
return result;
|
|
2842
|
+
}
|
|
2843
|
+
} catch {
|
|
2844
|
+
}
|
|
2845
|
+
return "node";
|
|
2846
|
+
};
|
|
2847
|
+
var getUnityHubCliPath = () => {
|
|
2848
|
+
const isWindows = process3.platform === "win32";
|
|
2849
|
+
try {
|
|
2850
|
+
const prefix = execSync("npm config get prefix", { encoding: "utf-8" }).trim();
|
|
2851
|
+
const binDir = isWindows ? prefix : `${prefix}/bin`;
|
|
2852
|
+
const cliPath = isWindows ? `${binDir}/unity-hub-cli.cmd` : `${binDir}/unity-hub-cli`;
|
|
2853
|
+
if (existsSync3(cliPath)) {
|
|
2854
|
+
return cliPath;
|
|
2855
|
+
}
|
|
2856
|
+
} catch {
|
|
2857
|
+
}
|
|
2858
|
+
return "unity-hub-cli";
|
|
2859
|
+
};
|
|
2860
|
+
var getShellInitScript = () => {
|
|
2861
|
+
const shell = process3.env["SHELL"] ?? "";
|
|
2862
|
+
const isWindows = process3.platform === "win32";
|
|
2863
|
+
const nodePath = getNodePath();
|
|
2864
|
+
const cliPath = getUnityHubCliPath();
|
|
2865
|
+
if (shell.includes("fish")) {
|
|
2866
|
+
return `function unity-hub
|
|
2867
|
+
set -l tmpfile (mktemp)
|
|
2868
|
+
${nodePath} ${cliPath} --output-path-on-exit > $tmpfile
|
|
2869
|
+
set -l dir (cat $tmpfile)
|
|
2870
|
+
rm -f $tmpfile
|
|
2871
|
+
if test -n "$dir"
|
|
2872
|
+
cd $dir
|
|
2873
|
+
end
|
|
2874
|
+
end`;
|
|
2875
|
+
}
|
|
2876
|
+
if (shell.includes("bash") || shell.includes("zsh")) {
|
|
2877
|
+
return `unity-hub() {
|
|
2878
|
+
local tmpfile=$(mktemp)
|
|
2879
|
+
${nodePath} ${cliPath} --output-path-on-exit >| "$tmpfile"
|
|
2880
|
+
local dir=$(cat "$tmpfile")
|
|
2881
|
+
rm -f "$tmpfile"
|
|
2882
|
+
if [ -n "$dir" ]; then
|
|
2883
|
+
cd "$dir"
|
|
2884
|
+
fi
|
|
2885
|
+
}`;
|
|
2886
|
+
}
|
|
2887
|
+
if (isWindows) {
|
|
2888
|
+
return `function unity-hub {
|
|
2889
|
+
$tmpfile = [System.IO.Path]::GetTempFileName()
|
|
2890
|
+
& "${cliPath}" --output-path-on-exit > $tmpfile
|
|
2891
|
+
$dir = Get-Content $tmpfile
|
|
2892
|
+
Remove-Item $tmpfile
|
|
2893
|
+
if ($dir) {
|
|
2894
|
+
Set-Location $dir
|
|
2895
|
+
}
|
|
2896
|
+
}`;
|
|
2897
|
+
}
|
|
2898
|
+
return `unity-hub() {
|
|
2899
|
+
local tmpfile=$(mktemp)
|
|
2900
|
+
${nodePath} ${cliPath} --output-path-on-exit >| "$tmpfile"
|
|
2901
|
+
local dir=$(cat "$tmpfile")
|
|
2902
|
+
rm -f "$tmpfile"
|
|
2903
|
+
if [ -n "$dir" ]; then
|
|
2904
|
+
cd "$dir"
|
|
2905
|
+
fi
|
|
2906
|
+
}`;
|
|
2907
|
+
};
|
|
2637
2908
|
var bootstrap = async () => {
|
|
2909
|
+
const args = process3.argv.slice(2);
|
|
2910
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
2911
|
+
console.log(getVersion());
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
if (args.includes("--shell-init")) {
|
|
2915
|
+
const isDryRun = args.includes("--dry-run");
|
|
2916
|
+
if (isDryRun) {
|
|
2917
|
+
previewShellInit();
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
const configPath = getShellConfigPath();
|
|
2921
|
+
if (!configPath) {
|
|
2922
|
+
console.log("Unsupported shell. Please copy the function from the README into your shell config manually.");
|
|
2923
|
+
process3.exitCode = 1;
|
|
2924
|
+
return;
|
|
2925
|
+
}
|
|
2926
|
+
console.log(`This will install the unity-hub function to: ${configPath}
|
|
2927
|
+
`);
|
|
2928
|
+
previewShellInit();
|
|
2929
|
+
console.log("");
|
|
2930
|
+
const confirmed = await askConfirmation("Proceed with installation? (y/n): ");
|
|
2931
|
+
if (!confirmed) {
|
|
2932
|
+
console.log("Installation cancelled.");
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
const result = installShellInit();
|
|
2936
|
+
console.log(result.message);
|
|
2937
|
+
process3.exitCode = result.success ? 0 : 1;
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
const outputPathOnExit = args.includes("--output-path-on-exit");
|
|
2638
2941
|
const isWindows = process3.platform === "win32";
|
|
2639
2942
|
const unityHubReader = isWindows ? new WinUnityHubProjectsReader() : new MacUnityHubProjectsReader();
|
|
2640
2943
|
const gitRepositoryInfoReader = new GitRepositoryInfoReader();
|
|
@@ -2694,8 +2997,13 @@ var bootstrap = async () => {
|
|
|
2694
2997
|
process3.exitCode = 1;
|
|
2695
2998
|
return;
|
|
2696
2999
|
}
|
|
3000
|
+
if (outputPathOnExit && process3.stderr.isTTY) {
|
|
3001
|
+
chalk.level = 3;
|
|
3002
|
+
}
|
|
2697
3003
|
const theme = await detectTerminalTheme();
|
|
3004
|
+
let lastOpenedPath;
|
|
2698
3005
|
const projects = await listProjectsUseCase.execute();
|
|
3006
|
+
const renderOptions = outputPathOnExit ? { stdout: process3.stderr, stdin: process3.stdin } : void 0;
|
|
2699
3007
|
const { waitUntilExit } = render(
|
|
2700
3008
|
/* @__PURE__ */ jsx7(ThemeProvider, { theme, children: /* @__PURE__ */ jsx7(
|
|
2701
3009
|
App,
|
|
@@ -2706,12 +3014,25 @@ var bootstrap = async () => {
|
|
|
2706
3014
|
onLaunchEditorOnly: (project) => launchEditorOnlyUseCase.execute(project),
|
|
2707
3015
|
onTerminate: (project) => terminateProjectUseCase.execute(project),
|
|
2708
3016
|
onRefresh: () => listProjectsUseCase.execute(),
|
|
2709
|
-
|
|
3017
|
+
onToggleFavorite: (project) => unityHubReader.toggleFavorite(project.path),
|
|
3018
|
+
useGitRootName,
|
|
3019
|
+
outputPathOnExit,
|
|
3020
|
+
onSetExitPath: (path) => {
|
|
3021
|
+
lastOpenedPath = path;
|
|
3022
|
+
}
|
|
2710
3023
|
}
|
|
2711
|
-
) })
|
|
3024
|
+
) }),
|
|
3025
|
+
renderOptions
|
|
2712
3026
|
);
|
|
2713
3027
|
await waitUntilExit();
|
|
2714
|
-
|
|
3028
|
+
if (outputPathOnExit) {
|
|
3029
|
+
process3.stderr.write("\x1B[2J\x1B[3J\x1B[H");
|
|
3030
|
+
if (lastOpenedPath) {
|
|
3031
|
+
process3.stdout.write(lastOpenedPath);
|
|
3032
|
+
}
|
|
3033
|
+
} else {
|
|
3034
|
+
process3.stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
3035
|
+
}
|
|
2715
3036
|
} catch (error) {
|
|
2716
3037
|
const message = error instanceof Error ? error.message : String(error);
|
|
2717
3038
|
console.error(message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unity-hub-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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": {
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
"dev": "tsx src/index.ts",
|
|
9
9
|
"build": "tsup",
|
|
10
10
|
"start": "node dist/index.js",
|
|
11
|
-
"lint": "eslint .
|
|
12
|
-
"lint:fix": "eslint . --
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix",
|
|
13
13
|
"format": "prettier --write .",
|
|
14
|
-
"typecheck": "tsc -noEmit"
|
|
14
|
+
"typecheck": "tsc -noEmit",
|
|
15
|
+
"allow-scripts": "allow-scripts"
|
|
15
16
|
},
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
@@ -36,26 +37,38 @@
|
|
|
36
37
|
"dist"
|
|
37
38
|
],
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
40
|
+
"chalk": "5.6.2",
|
|
41
|
+
"clipboardy": "5.0.1",
|
|
42
|
+
"ink": "6.5.1",
|
|
43
|
+
"react": "19.2.1"
|
|
42
44
|
},
|
|
43
45
|
"engines": {
|
|
44
46
|
"node": ">=18"
|
|
45
47
|
},
|
|
48
|
+
"lavamoat": {
|
|
49
|
+
"allowScripts": {
|
|
50
|
+
"eslint-import-resolver-typescript>unrs-resolver": false,
|
|
51
|
+
"tsup>bundle-require>esbuild": false,
|
|
52
|
+
"tsup>esbuild": false,
|
|
53
|
+
"tsx>esbuild": false
|
|
54
|
+
}
|
|
55
|
+
},
|
|
46
56
|
"devDependencies": {
|
|
47
|
-
"@
|
|
48
|
-
"@
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
51
|
-
"eslint": "8.
|
|
52
|
-
"eslint
|
|
53
|
-
"eslint
|
|
57
|
+
"@eslint/js": "^9.39.1",
|
|
58
|
+
"@lavamoat/allow-scripts": "3.4.1",
|
|
59
|
+
"@types/node": "24.10.1",
|
|
60
|
+
"@types/react": "19.2.7",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "8.48.1",
|
|
62
|
+
"@typescript-eslint/parser": "8.48.1",
|
|
63
|
+
"eslint": "9.39.1",
|
|
64
|
+
"eslint-config-prettier": "10.1.8",
|
|
65
|
+
"eslint-import-resolver-typescript": "4.4.4",
|
|
54
66
|
"eslint-plugin-import": "2.32.0",
|
|
55
|
-
"prettier": "3.
|
|
56
|
-
"tsup": "8.5.
|
|
57
|
-
"tsx": "4.
|
|
67
|
+
"prettier": "3.7.4",
|
|
68
|
+
"tsup": "8.5.1",
|
|
69
|
+
"tsx": "4.21.0",
|
|
58
70
|
"typescript": "5.9.3",
|
|
59
|
-
"
|
|
71
|
+
"typescript-eslint": "^8.48.1",
|
|
72
|
+
"vitest": "4.0.15"
|
|
60
73
|
}
|
|
61
74
|
}
|