@ts47andres/exeggutor 1.1.3 → 1.1.5
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 +201 -201
- package/README.md +230 -230
- package/bin/exeggutor.js +217 -217
- package/package.json +63 -63
- package/packages/backend/bin/FolderPicker.exe +0 -0
- package/packages/backend/git-wrapper/git +60 -60
- package/packages/backend/native/FolderPicker.cs +139 -139
- package/packages/backend/package.json +25 -25
- package/packages/backend/scripts/compile-picker.js +16 -0
- package/packages/backend/scripts/git-guard.ps1 +48 -48
- package/packages/backend/src/gitWorktree.ts +320 -320
- package/packages/backend/src/index.ts +554 -554
- package/packages/backend/src/ptyManager.ts +414 -414
- package/packages/backend/src/tailscale.ts +138 -138
- package/packages/backend/src/workspaceDb.ts +151 -151
- package/packages/frontend/dist/index.html +15 -15
- package/packages/frontend/package.json +29 -29
- package/src/autostart.js +162 -162
- package/src/cli.js +613 -613
- package/src/server-manager.js +139 -139
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
REAL_GIT=$(command -v git 2>/dev/null)
|
|
3
|
-
if [ -z "$REAL_GIT" ]; then
|
|
4
|
-
REAL_GIT=$(which git 2>/dev/null)
|
|
5
|
-
fi
|
|
6
|
-
if [ -z "$REAL_GIT" ]; then
|
|
7
|
-
echo "ERROR: git not found" >&2
|
|
8
|
-
exit 1
|
|
9
|
-
fi
|
|
10
|
-
|
|
11
|
-
is_branch_delete() {
|
|
12
|
-
local args=("$@")
|
|
13
|
-
if [ ${#args[@]} -lt 3 ]; then
|
|
14
|
-
return 1
|
|
15
|
-
fi
|
|
16
|
-
if [ "${args[0]}" != "branch" ]; then
|
|
17
|
-
return 1
|
|
18
|
-
fi
|
|
19
|
-
local i
|
|
20
|
-
for ((i=1; i<${#args[@]}-1; i++)); do
|
|
21
|
-
local flag="${args[$i]}"
|
|
22
|
-
if [ "$flag" = "-d" ] || [ "$flag" = "-D" ] || [ "$flag" = "--delete" ]; then
|
|
23
|
-
local next_idx=$((i+1))
|
|
24
|
-
local next="${args[$next_idx]}"
|
|
25
|
-
if [ "${next:0:1}" != "-" ]; then
|
|
26
|
-
echo "$next"
|
|
27
|
-
return 0
|
|
28
|
-
fi
|
|
29
|
-
fi
|
|
30
|
-
done
|
|
31
|
-
return 1
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
BRANCH=$(is_branch_delete "$@")
|
|
35
|
-
if [ $? -eq 0 ] && [ -n "$BRANCH" ]; then
|
|
36
|
-
CONFIG_PATH="$HOME/.exeggutor.json"
|
|
37
|
-
PORT=17492
|
|
38
|
-
TOKEN=""
|
|
39
|
-
if [ -f "$CONFIG_PATH" ]; then
|
|
40
|
-
CONF_PORT=$(grep -o '"backendPort"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_PATH" | grep -o '[0-9]*')
|
|
41
|
-
if [ -n "$CONF_PORT" ]; then
|
|
42
|
-
PORT=$CONF_PORT
|
|
43
|
-
fi
|
|
44
|
-
CONF_TOKEN=$(grep -o '"authToken"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_PATH" | head -n 1 | cut -d '"' -f 4)
|
|
45
|
-
if [ -n "$CONF_TOKEN" ]; then
|
|
46
|
-
TOKEN=$CONF_TOKEN
|
|
47
|
-
fi
|
|
48
|
-
fi
|
|
49
|
-
RESPONSE=$(curl -sf -H "Authorization: Bearer $TOKEN" "http://localhost:$PORT/api/branches/in-use?name=$BRANCH" 2>/dev/null)
|
|
50
|
-
if [ $? -ne 0 ]; then
|
|
51
|
-
echo "ERROR: Cannot verify branch status. Exeggutor backend must be running. Branch deletion blocked for safety." >&2
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
if echo "$RESPONSE" | grep -q '"inUse":true'; then
|
|
55
|
-
echo "ERROR: Branch '$BRANCH' is in use by an active Exeggutor terminal. Switch the tab to another branch or close it first." >&2
|
|
56
|
-
exit 1
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
exec "$REAL_GIT" "$@"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
REAL_GIT=$(command -v git 2>/dev/null)
|
|
3
|
+
if [ -z "$REAL_GIT" ]; then
|
|
4
|
+
REAL_GIT=$(which git 2>/dev/null)
|
|
5
|
+
fi
|
|
6
|
+
if [ -z "$REAL_GIT" ]; then
|
|
7
|
+
echo "ERROR: git not found" >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
is_branch_delete() {
|
|
12
|
+
local args=("$@")
|
|
13
|
+
if [ ${#args[@]} -lt 3 ]; then
|
|
14
|
+
return 1
|
|
15
|
+
fi
|
|
16
|
+
if [ "${args[0]}" != "branch" ]; then
|
|
17
|
+
return 1
|
|
18
|
+
fi
|
|
19
|
+
local i
|
|
20
|
+
for ((i=1; i<${#args[@]}-1; i++)); do
|
|
21
|
+
local flag="${args[$i]}"
|
|
22
|
+
if [ "$flag" = "-d" ] || [ "$flag" = "-D" ] || [ "$flag" = "--delete" ]; then
|
|
23
|
+
local next_idx=$((i+1))
|
|
24
|
+
local next="${args[$next_idx]}"
|
|
25
|
+
if [ "${next:0:1}" != "-" ]; then
|
|
26
|
+
echo "$next"
|
|
27
|
+
return 0
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
done
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
BRANCH=$(is_branch_delete "$@")
|
|
35
|
+
if [ $? -eq 0 ] && [ -n "$BRANCH" ]; then
|
|
36
|
+
CONFIG_PATH="$HOME/.exeggutor.json"
|
|
37
|
+
PORT=17492
|
|
38
|
+
TOKEN=""
|
|
39
|
+
if [ -f "$CONFIG_PATH" ]; then
|
|
40
|
+
CONF_PORT=$(grep -o '"backendPort"[[:space:]]*:[[:space:]]*[0-9]*' "$CONFIG_PATH" | grep -o '[0-9]*')
|
|
41
|
+
if [ -n "$CONF_PORT" ]; then
|
|
42
|
+
PORT=$CONF_PORT
|
|
43
|
+
fi
|
|
44
|
+
CONF_TOKEN=$(grep -o '"authToken"[[:space:]]*:[[:space:]]*"[^"]*"' "$CONFIG_PATH" | head -n 1 | cut -d '"' -f 4)
|
|
45
|
+
if [ -n "$CONF_TOKEN" ]; then
|
|
46
|
+
TOKEN=$CONF_TOKEN
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
RESPONSE=$(curl -sf -H "Authorization: Bearer $TOKEN" "http://localhost:$PORT/api/branches/in-use?name=$BRANCH" 2>/dev/null)
|
|
50
|
+
if [ $? -ne 0 ]; then
|
|
51
|
+
echo "ERROR: Cannot verify branch status. Exeggutor backend must be running. Branch deletion blocked for safety." >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
if echo "$RESPONSE" | grep -q '"inUse":true'; then
|
|
55
|
+
echo "ERROR: Branch '$BRANCH' is in use by an active Exeggutor terminal. Switch the tab to another branch or close it first." >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
exec "$REAL_GIT" "$@"
|
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
using System;
|
|
2
|
-
using System.Runtime.InteropServices;
|
|
3
|
-
using System.Threading;
|
|
4
|
-
|
|
5
|
-
// Native folder picker using IFileOpenDialog COM interface with FOS_PICKFOLDERS.
|
|
6
|
-
// Shows the modern Windows folder selection dialog available since Vista.
|
|
7
|
-
// Runs on its own STA thread to ensure proper COM apartment initialization.
|
|
8
|
-
// Exit codes:
|
|
9
|
-
// 0 = Success, selected path printed to stdout.
|
|
10
|
-
// 1 = Error (message printed to stderr).
|
|
11
|
-
// 2 = User cancelled (no output).
|
|
12
|
-
|
|
13
|
-
[ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
|
|
14
|
-
class FileOpenDialogRCW { }
|
|
15
|
-
|
|
16
|
-
[ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960")]
|
|
17
|
-
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
18
|
-
interface IFileOpenDialog
|
|
19
|
-
{
|
|
20
|
-
[PreserveSig] int Show(IntPtr parent); // IModalWindow
|
|
21
|
-
[PreserveSig] int SetFileTypes();
|
|
22
|
-
[PreserveSig] int SetFileTypeIndex(int iFileType);
|
|
23
|
-
[PreserveSig] int GetFileTypeIndex(out int piFileType);
|
|
24
|
-
[PreserveSig] int Advise();
|
|
25
|
-
[PreserveSig] int Unadvise();
|
|
26
|
-
[PreserveSig] int SetOptions(uint fos);
|
|
27
|
-
[PreserveSig] int GetOptions(out uint pfos);
|
|
28
|
-
[PreserveSig] int SetDefaultFolder([MarshalAs(UnmanagedType.Interface)] IShellItem psi);
|
|
29
|
-
[PreserveSig] int SetFolder([MarshalAs(UnmanagedType.Interface)] IShellItem psi);
|
|
30
|
-
[PreserveSig] int GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
31
|
-
[PreserveSig] int GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
32
|
-
[PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
33
|
-
[PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
|
|
34
|
-
[PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
|
|
35
|
-
[PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
|
|
36
|
-
[PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
|
|
37
|
-
[PreserveSig] int GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
38
|
-
[PreserveSig] int AddPlace([MarshalAs(UnmanagedType.Interface)] IShellItem psi, int alignment);
|
|
39
|
-
[PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
|
|
40
|
-
[PreserveSig] int Close(int hr);
|
|
41
|
-
[PreserveSig] int SetClientGuid();
|
|
42
|
-
[PreserveSig] int ClearClientData();
|
|
43
|
-
[PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter);
|
|
44
|
-
[PreserveSig] int GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum);
|
|
45
|
-
[PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
|
|
49
|
-
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
50
|
-
interface IShellItem
|
|
51
|
-
{
|
|
52
|
-
[PreserveSig] int BindToHandler();
|
|
53
|
-
[PreserveSig] int GetParent();
|
|
54
|
-
[PreserveSig] int GetDisplayName(uint sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
|
55
|
-
[PreserveSig] int GetAttributes();
|
|
56
|
-
[PreserveSig] int Compare();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
[ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b")]
|
|
60
|
-
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
61
|
-
interface IShellItemArray
|
|
62
|
-
{
|
|
63
|
-
[PreserveSig] int BindToHandler();
|
|
64
|
-
[PreserveSig] int GetPropertyStore();
|
|
65
|
-
[PreserveSig] int GetPropertyDescriptionList();
|
|
66
|
-
[PreserveSig] int GetAttributes();
|
|
67
|
-
[PreserveSig] int GetCount(out int pdwNumItems);
|
|
68
|
-
[PreserveSig] int GetItemAt(int dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
69
|
-
[PreserveSig] int EnumItems();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
class FolderPicker
|
|
73
|
-
{
|
|
74
|
-
[DllImport("user32.dll")]
|
|
75
|
-
static extern IntPtr GetForegroundWindow();
|
|
76
|
-
|
|
77
|
-
const uint FOS_PICKFOLDERS = 0x20;
|
|
78
|
-
const uint SIGDN_FILESYSPATH = 0x80058000;
|
|
79
|
-
const int ERROR_CANCELLED = unchecked((int)0x800704C7);
|
|
80
|
-
|
|
81
|
-
// Entry point; creates the folder picker COM dialog on an STA thread then exits with the result code.
|
|
82
|
-
static void Main()
|
|
83
|
-
{
|
|
84
|
-
string selectedPath = null; // The folder path selected by the user, or null on cancel/error.
|
|
85
|
-
Exception caughtError = null; // Any exception caught during dialog display.
|
|
86
|
-
|
|
87
|
-
Thread staThread = new Thread(() => // STA thread required by COM for the folder picker dialog.
|
|
88
|
-
{
|
|
89
|
-
try
|
|
90
|
-
{
|
|
91
|
-
IFileOpenDialog dialog = (IFileOpenDialog)new FileOpenDialogRCW(); // The IFileOpenDialog COM interface instance.
|
|
92
|
-
|
|
93
|
-
dialog.SetOptions(FOS_PICKFOLDERS);
|
|
94
|
-
dialog.SetTitle("Select Workspace Folder");
|
|
95
|
-
|
|
96
|
-
IntPtr parentHwnd = GetForegroundWindow(); // Handle of the browser window that triggered the dialog.
|
|
97
|
-
int hr = dialog.Show(parentHwnd); // HRESULT from the dialog; 0 = OK, ERROR_CANCELLED = user cancelled.
|
|
98
|
-
if (hr == ERROR_CANCELLED)
|
|
99
|
-
{
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (hr != 0)
|
|
103
|
-
{
|
|
104
|
-
Marshal.ThrowExceptionForHR(hr);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
IShellItem result; // The IShellItem representing the selected folder.
|
|
108
|
-
dialog.GetResult(out result);
|
|
109
|
-
|
|
110
|
-
string path; // The filesystem path string extracted from the shell item.
|
|
111
|
-
result.GetDisplayName(SIGDN_FILESYSPATH, out path);
|
|
112
|
-
selectedPath = path;
|
|
113
|
-
}
|
|
114
|
-
catch (Exception ex)
|
|
115
|
-
{
|
|
116
|
-
caughtError = ex;
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
staThread.SetApartmentState(ApartmentState.STA);
|
|
121
|
-
staThread.Start();
|
|
122
|
-
staThread.Join();
|
|
123
|
-
|
|
124
|
-
if (caughtError != null)
|
|
125
|
-
{
|
|
126
|
-
Console.Error.Write("ERROR: " + caughtError.Message);
|
|
127
|
-
Environment.Exit(1);
|
|
128
|
-
}
|
|
129
|
-
if (selectedPath != null)
|
|
130
|
-
{
|
|
131
|
-
Console.Write(selectedPath);
|
|
132
|
-
Environment.Exit(0);
|
|
133
|
-
}
|
|
134
|
-
else
|
|
135
|
-
{
|
|
136
|
-
Environment.Exit(2);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
using System;
|
|
2
|
+
using System.Runtime.InteropServices;
|
|
3
|
+
using System.Threading;
|
|
4
|
+
|
|
5
|
+
// Native folder picker using IFileOpenDialog COM interface with FOS_PICKFOLDERS.
|
|
6
|
+
// Shows the modern Windows folder selection dialog available since Vista.
|
|
7
|
+
// Runs on its own STA thread to ensure proper COM apartment initialization.
|
|
8
|
+
// Exit codes:
|
|
9
|
+
// 0 = Success, selected path printed to stdout.
|
|
10
|
+
// 1 = Error (message printed to stderr).
|
|
11
|
+
// 2 = User cancelled (no output).
|
|
12
|
+
|
|
13
|
+
[ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
|
|
14
|
+
class FileOpenDialogRCW { }
|
|
15
|
+
|
|
16
|
+
[ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960")]
|
|
17
|
+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
18
|
+
interface IFileOpenDialog
|
|
19
|
+
{
|
|
20
|
+
[PreserveSig] int Show(IntPtr parent); // IModalWindow
|
|
21
|
+
[PreserveSig] int SetFileTypes();
|
|
22
|
+
[PreserveSig] int SetFileTypeIndex(int iFileType);
|
|
23
|
+
[PreserveSig] int GetFileTypeIndex(out int piFileType);
|
|
24
|
+
[PreserveSig] int Advise();
|
|
25
|
+
[PreserveSig] int Unadvise();
|
|
26
|
+
[PreserveSig] int SetOptions(uint fos);
|
|
27
|
+
[PreserveSig] int GetOptions(out uint pfos);
|
|
28
|
+
[PreserveSig] int SetDefaultFolder([MarshalAs(UnmanagedType.Interface)] IShellItem psi);
|
|
29
|
+
[PreserveSig] int SetFolder([MarshalAs(UnmanagedType.Interface)] IShellItem psi);
|
|
30
|
+
[PreserveSig] int GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
31
|
+
[PreserveSig] int GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
32
|
+
[PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
33
|
+
[PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
|
|
34
|
+
[PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
|
|
35
|
+
[PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
|
|
36
|
+
[PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
|
|
37
|
+
[PreserveSig] int GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
38
|
+
[PreserveSig] int AddPlace([MarshalAs(UnmanagedType.Interface)] IShellItem psi, int alignment);
|
|
39
|
+
[PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
|
|
40
|
+
[PreserveSig] int Close(int hr);
|
|
41
|
+
[PreserveSig] int SetClientGuid();
|
|
42
|
+
[PreserveSig] int ClearClientData();
|
|
43
|
+
[PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter);
|
|
44
|
+
[PreserveSig] int GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum);
|
|
45
|
+
[PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
|
|
49
|
+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
50
|
+
interface IShellItem
|
|
51
|
+
{
|
|
52
|
+
[PreserveSig] int BindToHandler();
|
|
53
|
+
[PreserveSig] int GetParent();
|
|
54
|
+
[PreserveSig] int GetDisplayName(uint sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
|
55
|
+
[PreserveSig] int GetAttributes();
|
|
56
|
+
[PreserveSig] int Compare();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b")]
|
|
60
|
+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
61
|
+
interface IShellItemArray
|
|
62
|
+
{
|
|
63
|
+
[PreserveSig] int BindToHandler();
|
|
64
|
+
[PreserveSig] int GetPropertyStore();
|
|
65
|
+
[PreserveSig] int GetPropertyDescriptionList();
|
|
66
|
+
[PreserveSig] int GetAttributes();
|
|
67
|
+
[PreserveSig] int GetCount(out int pdwNumItems);
|
|
68
|
+
[PreserveSig] int GetItemAt(int dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
|
|
69
|
+
[PreserveSig] int EnumItems();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class FolderPicker
|
|
73
|
+
{
|
|
74
|
+
[DllImport("user32.dll")]
|
|
75
|
+
static extern IntPtr GetForegroundWindow();
|
|
76
|
+
|
|
77
|
+
const uint FOS_PICKFOLDERS = 0x20;
|
|
78
|
+
const uint SIGDN_FILESYSPATH = 0x80058000;
|
|
79
|
+
const int ERROR_CANCELLED = unchecked((int)0x800704C7);
|
|
80
|
+
|
|
81
|
+
// Entry point; creates the folder picker COM dialog on an STA thread then exits with the result code.
|
|
82
|
+
static void Main()
|
|
83
|
+
{
|
|
84
|
+
string selectedPath = null; // The folder path selected by the user, or null on cancel/error.
|
|
85
|
+
Exception caughtError = null; // Any exception caught during dialog display.
|
|
86
|
+
|
|
87
|
+
Thread staThread = new Thread(() => // STA thread required by COM for the folder picker dialog.
|
|
88
|
+
{
|
|
89
|
+
try
|
|
90
|
+
{
|
|
91
|
+
IFileOpenDialog dialog = (IFileOpenDialog)new FileOpenDialogRCW(); // The IFileOpenDialog COM interface instance.
|
|
92
|
+
|
|
93
|
+
dialog.SetOptions(FOS_PICKFOLDERS);
|
|
94
|
+
dialog.SetTitle("Select Workspace Folder");
|
|
95
|
+
|
|
96
|
+
IntPtr parentHwnd = GetForegroundWindow(); // Handle of the browser window that triggered the dialog.
|
|
97
|
+
int hr = dialog.Show(parentHwnd); // HRESULT from the dialog; 0 = OK, ERROR_CANCELLED = user cancelled.
|
|
98
|
+
if (hr == ERROR_CANCELLED)
|
|
99
|
+
{
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (hr != 0)
|
|
103
|
+
{
|
|
104
|
+
Marshal.ThrowExceptionForHR(hr);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
IShellItem result; // The IShellItem representing the selected folder.
|
|
108
|
+
dialog.GetResult(out result);
|
|
109
|
+
|
|
110
|
+
string path; // The filesystem path string extracted from the shell item.
|
|
111
|
+
result.GetDisplayName(SIGDN_FILESYSPATH, out path);
|
|
112
|
+
selectedPath = path;
|
|
113
|
+
}
|
|
114
|
+
catch (Exception ex)
|
|
115
|
+
{
|
|
116
|
+
caughtError = ex;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
staThread.SetApartmentState(ApartmentState.STA);
|
|
121
|
+
staThread.Start();
|
|
122
|
+
staThread.Join();
|
|
123
|
+
|
|
124
|
+
if (caughtError != null)
|
|
125
|
+
{
|
|
126
|
+
Console.Error.Write("ERROR: " + caughtError.Message);
|
|
127
|
+
Environment.Exit(1);
|
|
128
|
+
}
|
|
129
|
+
if (selectedPath != null)
|
|
130
|
+
{
|
|
131
|
+
Console.Write(selectedPath);
|
|
132
|
+
Environment.Exit(0);
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
{
|
|
136
|
+
Environment.Exit(2);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "backend",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"start": "node dist/index.js",
|
|
10
|
-
"compile-picker": "node scripts/compile-picker.js",
|
|
11
|
-
"postinstall": "node scripts/compile-picker.js
|
|
12
|
-
},
|
|
13
|
-
"dependencies": {
|
|
14
|
-
"@fastify/cors": "^9.0.1",
|
|
15
|
-
"@fastify/static": "^7.0.4",
|
|
16
|
-
"@fastify/websocket": "^10.0.1",
|
|
17
|
-
"fastify": "^4.28.1",
|
|
18
|
-
"node-pty": "^1.1.0-beta21"
|
|
19
|
-
},
|
|
20
|
-
"devDependencies": {
|
|
21
|
-
"@types/node": "^20.14.9",
|
|
22
|
-
"ts-node-dev": "^2.0.0",
|
|
23
|
-
"typescript": "^5.5.2"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"compile-picker": "node scripts/compile-picker.js",
|
|
11
|
+
"postinstall": "node scripts/compile-picker.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@fastify/cors": "^9.0.1",
|
|
15
|
+
"@fastify/static": "^7.0.4",
|
|
16
|
+
"@fastify/websocket": "^10.0.1",
|
|
17
|
+
"fastify": "^4.28.1",
|
|
18
|
+
"node-pty": "^1.1.0-beta21"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.14.9",
|
|
22
|
+
"ts-node-dev": "^2.0.0",
|
|
23
|
+
"typescript": "^5.5.2"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -2,6 +2,22 @@ const { execFileSync } = require('child_process');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
|
|
5
|
+
// On macOS, ensure the node-pty spawn helper has execute permissions.
|
|
6
|
+
if (process.platform === 'darwin') {
|
|
7
|
+
const glob = require('glob');
|
|
8
|
+
const helpers = glob.sync('node_modules/node-pty/prebuilds/darwin-*/spawn-helper', {
|
|
9
|
+
cwd: path.join(__dirname, '..', '..', '..'),
|
|
10
|
+
});
|
|
11
|
+
for (const h of helpers) {
|
|
12
|
+
try {
|
|
13
|
+
fs.chmodSync(h, 0o755);
|
|
14
|
+
} catch {
|
|
15
|
+
// non-critical
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
// FolderPicker is Windows-only; skip silently on other platforms.
|
|
6
22
|
if (process.platform !== 'win32') {
|
|
7
23
|
process.exit(0);
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
function global:git {
|
|
2
|
-
param(
|
|
3
|
-
[Parameter(ValueFromRemainingArguments = $true)]
|
|
4
|
-
[string[]]$UserArgs
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
$isBranchDelete = $false
|
|
8
|
-
$targetBranch = $null
|
|
9
|
-
|
|
10
|
-
if ($UserArgs.Count -ge 3 -and $UserArgs[0] -eq 'branch') {
|
|
11
|
-
for ($i = 1; $i -lt $UserArgs.Count - 1; $i++) {
|
|
12
|
-
$arg = $UserArgs[$i]
|
|
13
|
-
if ($arg -eq '-d' -or $arg -eq '-D' -or $arg -eq '--delete') {
|
|
14
|
-
$nextIdx = $i + 1
|
|
15
|
-
if ($nextIdx -lt $UserArgs.Count -and $UserArgs[$nextIdx] -notlike '-*') {
|
|
16
|
-
$isBranchDelete = $true
|
|
17
|
-
$targetBranch = $UserArgs[$nextIdx]
|
|
18
|
-
break
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if ($isBranchDelete -and $targetBranch) {
|
|
25
|
-
try {
|
|
26
|
-
$configPath = Join-Path $env:USERPROFILE ".exeggutor.json"
|
|
27
|
-
$port = 17492
|
|
28
|
-
$token = ""
|
|
29
|
-
if (Test-Path $configPath) {
|
|
30
|
-
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
|
31
|
-
if ($config.backendPort) { $port = $config.backendPort }
|
|
32
|
-
if ($config.authToken) { $token = $config.authToken }
|
|
33
|
-
}
|
|
34
|
-
$headers = @{ Authorization = "Bearer $token" }
|
|
35
|
-
$response = Invoke-RestMethod -Uri "http://localhost:$port/api/branches/in-use?name=$targetBranch" -Headers $headers -ErrorAction Stop
|
|
36
|
-
if ($response.inUse -eq $true) {
|
|
37
|
-
Write-Error "Branch '$targetBranch' is in use by an active Exeggutor terminal. Switch the tab to another branch or close it first."
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
Write-Error "Cannot verify branch status. Exeggutor backend must be running. Branch deletion blocked for safety."
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
& "git.exe" @UserArgs
|
|
48
|
-
}
|
|
1
|
+
function global:git {
|
|
2
|
+
param(
|
|
3
|
+
[Parameter(ValueFromRemainingArguments = $true)]
|
|
4
|
+
[string[]]$UserArgs
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$isBranchDelete = $false
|
|
8
|
+
$targetBranch = $null
|
|
9
|
+
|
|
10
|
+
if ($UserArgs.Count -ge 3 -and $UserArgs[0] -eq 'branch') {
|
|
11
|
+
for ($i = 1; $i -lt $UserArgs.Count - 1; $i++) {
|
|
12
|
+
$arg = $UserArgs[$i]
|
|
13
|
+
if ($arg -eq '-d' -or $arg -eq '-D' -or $arg -eq '--delete') {
|
|
14
|
+
$nextIdx = $i + 1
|
|
15
|
+
if ($nextIdx -lt $UserArgs.Count -and $UserArgs[$nextIdx] -notlike '-*') {
|
|
16
|
+
$isBranchDelete = $true
|
|
17
|
+
$targetBranch = $UserArgs[$nextIdx]
|
|
18
|
+
break
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if ($isBranchDelete -and $targetBranch) {
|
|
25
|
+
try {
|
|
26
|
+
$configPath = Join-Path $env:USERPROFILE ".exeggutor.json"
|
|
27
|
+
$port = 17492
|
|
28
|
+
$token = ""
|
|
29
|
+
if (Test-Path $configPath) {
|
|
30
|
+
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
|
31
|
+
if ($config.backendPort) { $port = $config.backendPort }
|
|
32
|
+
if ($config.authToken) { $token = $config.authToken }
|
|
33
|
+
}
|
|
34
|
+
$headers = @{ Authorization = "Bearer $token" }
|
|
35
|
+
$response = Invoke-RestMethod -Uri "http://localhost:$port/api/branches/in-use?name=$targetBranch" -Headers $headers -ErrorAction Stop
|
|
36
|
+
if ($response.inUse -eq $true) {
|
|
37
|
+
Write-Error "Branch '$targetBranch' is in use by an active Exeggutor terminal. Switch the tab to another branch or close it first."
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
Write-Error "Cannot verify branch status. Exeggutor backend must be running. Branch deletion blocked for safety."
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
& "git.exe" @UserArgs
|
|
48
|
+
}
|