diff --git a/docs/API-Reference/command/Menus.md b/docs/API-Reference/command/Menus.md
index a7d0967e5..3e6ea9c4f 100644
--- a/docs/API-Reference/command/Menus.md
+++ b/docs/API-Reference/command/Menus.md
@@ -16,17 +16,7 @@ const Menus = brackets.getModule("command/Menus")
### new MenuItem(id, command, [options])
-MenuItem represents a single menu item that executes a Command or a menu divider. MenuItems
-may have a sub-menu. A MenuItem may correspond to an HTML-based
-menu item or a native menu item if Brackets is running in a native application shell
-
-Since MenuItems may have a native implementation clients should create MenuItems through
-addMenuItem() and should NOT construct a MenuItem object directly.
-Clients should also not access HTML content of a menu directly and instead use
-the MenuItem API to query and modify menus items.
-
-MenuItems are views on to Command objects so modify the underlying Command to modify the
-name, enabled, and checked state of a MenuItem. The MenuItem will update automatically
+MenuItem represents a single menu item that executes a Command or a menu divider. MenuItems
may have a sub-menu. A MenuItem may correspond to an HTML-based
menu item or a native menu item if Brackets is running in a native application shell
Since MenuItems may have a native implementation clients should create MenuItems through
addMenuItem() and should NOT construct a MenuItem object directly.
Clients should also not access HTML content of a menu directly and instead use
the MenuItem API to query and modify menus items.
MenuItems are views on to Command objects so modify the underlying Command to modify the
name, enabled, and checked state of a MenuItem. The MenuItem will update automatically
| Param | Type | Description |
@@ -66,13 +56,7 @@ Returns the parent Menu for this MenuItem
### new Menu(id)
-Menu represents a top-level menu in the menu bar. A Menu may correspond to an HTML-based
-menu or a native menu if Brackets is running in a native application shell.
-
-Since menus may have a native implementation clients should create Menus through
-addMenu() and should NOT construct a Menu object directly.
-Clients should also not access HTML content of a menu directly and instead use
-the Menu API to query and modify menus.
+Menu represents a top-level menu in the menu bar. A Menu may correspond to an HTML-based
menu or a native menu if Brackets is running in a native application shell.
Since menus may have a native implementation clients should create Menus through
addMenu() and should NOT construct a Menu object directly.
Clients should also not access HTML content of a menu directly and instead use
the Menu API to query and modify menus.
| Param | Type |
@@ -82,8 +66,7 @@ the Menu API to query and modify menus.
### menu.removeMenuItem(command)
-Removes the specified menu item from this Menu. Key bindings are unaffected; use KeyBindingManager
-directly to remove key bindings if desired.
+Removes the specified menu item from this Menu. Key bindings are unaffected; use KeyBindingManager
directly to remove key bindings if desired.
**Kind**: instance method of [Menu
](#Menu)
@@ -105,17 +88,7 @@ Removes the specified menu divider from this Menu.
### menu.addMenuItem(command, [keyBindings], [position], [relativeID], [options]) ⇒ [MenuItem
](#MenuItem)
-Adds a new menu item with the specified id and display text. The insertion position is
-specified via the relativeID and position arguments which describe a position
-relative to another MenuItem or MenuGroup. It is preferred that plug-ins
-insert new MenuItems relative to a menu section rather than a specific
-MenuItem (see Menu Section Constants).
-
-TODO: Sub-menus are not yet supported, but when they are implemented this API will
-allow adding new MenuItems to sub-menus as well.
-
-Note, keyBindings are bound to Command objects not MenuItems. The provided keyBindings
- will be bound to the supplied Command object rather than the MenuItem.
+Adds a new menu item with the specified id and display text. The insertion position is
specified via the relativeID and position arguments which describe a position
relative to another MenuItem or MenuGroup. It is preferred that plug-ins
insert new MenuItems relative to a menu section rather than a specific
MenuItem (see Menu Section Constants).
TODO: Sub-menus are not yet supported, but when they are implemented this API will
allow adding new MenuItems to sub-menus as well.
Note, keyBindings are bound to Command objects not MenuItems. The provided keyBindings
will be bound to the supplied Command object rather than the MenuItem.
**Kind**: instance method of [Menu
](#Menu)
**Returns**: [MenuItem
](#MenuItem) - the newly created MenuItem
@@ -145,18 +118,7 @@ Inserts divider item in menu.
### menu.addSubMenu(name, id, position, relativeID) ⇒ [Menu
](#Menu)
-Creates a new submenu and a menuItem and adds the menuItem of the submenu
-to the menu and returns the submenu.
-
-A submenu will have the same structure of a menu with a additional field
-parentMenuItem which has the reference of the submenu's parent menuItem.
-A submenu will raise the following events:
-- beforeSubMenuOpen
-- beforeSubMenuClose
-
-Note, This function will create only a context submenu.
-
-TODO: Make this function work for Menus
+Creates a new submenu and a menuItem and adds the menuItem of the submenu
to the menu and returns the submenu.
A submenu will have the same structure of a menu with a additional field
parentMenuItem which has the reference of the submenu's parent menuItem.
A submenu will raise the following events:
- beforeSubMenuOpen
- beforeSubMenuClose
Note, This function will create only a context submenu.
TODO: Make this function work for Menus
**Kind**: instance method of [Menu
](#Menu)
**Returns**: [Menu
](#Menu) - the newly created submenu
@@ -171,11 +133,7 @@ TODO: Make this function work for Menus
### menu.removeSubMenu(subMenuID)
-Removes the specified submenu from this Menu.
-
-Note, this function will only remove context submenus
-
-TODO: Make this function work for Menus
+Removes the specified submenu from this Menu.
Note, this function will only remove context submenus
TODO: Make this function work for Menus
**Kind**: instance method of [Menu
](#Menu)
@@ -214,30 +172,12 @@ Closes the submenu if the menu has a submenu open.
### new ContextMenu()
-Represents a context menu that can open at a specific location in the UI.
-
-Clients should not create this object directly and should instead use registerContextMenu()
-to create new ContextMenu objects.
-
-Context menus in brackets may be HTML-based or native so clients should not reach into
-the HTML and should instead manipulate ContextMenus through the API.
-
-Events:
-- beforeContextMenuOpen
-- beforeContextMenuClose
+Represents a context menu that can open at a specific location in the UI.
Clients should not create this object directly and should instead use registerContextMenu()
to create new ContextMenu objects.
Context menus in brackets may be HTML-based or native so clients should not reach into
the HTML and should instead manipulate ContextMenus through the API.
Events:
- beforeContextMenuOpen
- beforeContextMenuClose
### contextMenu.open(mouseOrLocation)
-Displays the ContextMenu at the specified location and dispatches the
-"beforeContextMenuOpen" event or "beforeSubMenuOpen" event (for submenus).
-The menu location may be adjusted to prevent clipping by the browser window.
-All other menus and ContextMenus will be closed before a new menu
-will be closed before a new menu is shown (if the new menu is not
-a submenu).
-
-In case of submenus, the parentMenu of the submenu will not be closed when the
-sub menu is open.
+Displays the ContextMenu at the specified location and dispatches the
"beforeContextMenuOpen" event or "beforeSubMenuOpen" event (for submenus).
The menu location may be adjusted to prevent clipping by the browser window.
All other menus and ContextMenus will be closed before a new menu
will be closed before a new menu is shown (if the new menu is not
a submenu).
In case of submenus, the parentMenu of the submenu will not be closed when the
sub menu is open.
**Kind**: instance method of [ContextMenu
](#ContextMenu)
@@ -260,8 +200,7 @@ Detect if current context menu is already open
### contextMenu.removeMenuItem(command)
-Removes the specified menu item from this Menu. Key bindings are unaffected; use KeyBindingManager
-directly to remove key bindings if desired.
+Removes the specified menu item from this Menu. Key bindings are unaffected; use KeyBindingManager
directly to remove key bindings if desired.
**Kind**: instance method of [ContextMenu
](#ContextMenu)
@@ -283,17 +222,7 @@ Removes the specified menu divider from this Menu.
### contextMenu.addMenuItem(command, [keyBindings], [position], [relativeID], [options]) ⇒ [MenuItem
](#MenuItem)
-Adds a new menu item with the specified id and display text. The insertion position is
-specified via the relativeID and position arguments which describe a position
-relative to another MenuItem or MenuGroup. It is preferred that plug-ins
-insert new MenuItems relative to a menu section rather than a specific
-MenuItem (see Menu Section Constants).
-
-TODO: Sub-menus are not yet supported, but when they are implemented this API will
-allow adding new MenuItems to sub-menus as well.
-
-Note, keyBindings are bound to Command objects not MenuItems. The provided keyBindings
- will be bound to the supplied Command object rather than the MenuItem.
+Adds a new menu item with the specified id and display text. The insertion position is
specified via the relativeID and position arguments which describe a position
relative to another MenuItem or MenuGroup. It is preferred that plug-ins
insert new MenuItems relative to a menu section rather than a specific
MenuItem (see Menu Section Constants).
TODO: Sub-menus are not yet supported, but when they are implemented this API will
allow adding new MenuItems to sub-menus as well.
Note, keyBindings are bound to Command objects not MenuItems. The provided keyBindings
will be bound to the supplied Command object rather than the MenuItem.
**Kind**: instance method of [ContextMenu
](#ContextMenu)
**Returns**: [MenuItem
](#MenuItem) - the newly created MenuItem
@@ -323,18 +252,7 @@ Inserts divider item in menu.
### contextMenu.addSubMenu(name, id, position, relativeID) ⇒ [Menu
](#Menu)
-Creates a new submenu and a menuItem and adds the menuItem of the submenu
-to the menu and returns the submenu.
-
-A submenu will have the same structure of a menu with a additional field
-parentMenuItem which has the reference of the submenu's parent menuItem.
-A submenu will raise the following events:
-- beforeSubMenuOpen
-- beforeSubMenuClose
-
-Note, This function will create only a context submenu.
-
-TODO: Make this function work for Menus
+Creates a new submenu and a menuItem and adds the menuItem of the submenu
to the menu and returns the submenu.
A submenu will have the same structure of a menu with a additional field
parentMenuItem which has the reference of the submenu's parent menuItem.
A submenu will raise the following events:
- beforeSubMenuOpen
- beforeSubMenuClose
Note, This function will create only a context submenu.
TODO: Make this function work for Menus
**Kind**: instance method of [ContextMenu
](#ContextMenu)
**Returns**: [Menu
](#Menu) - the newly created submenu
@@ -349,11 +267,7 @@ TODO: Make this function work for Menus
### contextMenu.removeSubMenu(subMenuID)
-Removes the specified submenu from this Menu.
-
-Note, this function will only remove context submenus
-
-TODO: Make this function work for Menus
+Removes the specified submenu from this Menu.
Note, this function will only remove context submenus
TODO: Make this function work for Menus
**Kind**: instance method of [ContextMenu
](#ContextMenu)
@@ -370,9 +284,7 @@ Closes the submenu if the menu has a submenu open.
### ContextMenu.assignContextMenuToSelector()
-Associate a context menu to a DOM element.
-This static function take care of registering event handlers for the click event
-listener and passing the right "position" object to the Context#open method
+Associate a context menu to a DOM element.
This static function take care of registering event handlers for the click event
listener and passing the right "position" object to the Context#open method
**Kind**: static method of [ContextMenu
](#ContextMenu)
@@ -419,15 +331,7 @@ Brackets Context Menu Constants
## MenuSection : enum
-Brackets Application Menu Section Constants
-It is preferred that plug-ins specify the location of new MenuItems
-in terms of a menu section rather than a specific MenuItem. This provides
-looser coupling to Bracket's internal MenuItems and makes menu organization
-more semantic.
-Use these constants as the "relativeID" parameter when calling addMenuItem() and
-specify a position of FIRST_IN_SECTION or LAST_IN_SECTION.
-
-Menu sections are denoted by dividers or the beginning/end of a menu
+Brackets Application Menu Section Constants
It is preferred that plug-ins specify the location of new MenuItems
in terms of a menu section rather than a specific MenuItem. This provides
looser coupling to Bracket's internal MenuItems and makes menu organization
more semantic.
Use these constants as the "relativeID" parameter when calling addMenuItem() and
specify a position of FIRST_IN_SECTION or LAST_IN_SECTION.
Menu sections are denoted by dividers or the beginning/end of a menu
**Kind**: global enum
**Properties**
@@ -461,9 +365,7 @@ Menu sections are denoted by dividers or the beginning/end of a menu
## BEFORE : enum
-Insertion position constants
-Used by addMenu(), addMenuItem(), and addSubMenu() to
-specify the relative position of a newly created menu object
+Insertion position constants
Used by addMenu(), addMenuItem(), and addSubMenu() to
specify the relative position of a newly created menu object
**Kind**: global enum
@@ -563,24 +465,7 @@ Removes a top-level menu from the application menu bar which may be native or HT
## registerContextMenu(id) ⇒ [ContextMenu
](#ContextMenu)
-Registers new context menu with Brackets.
-Extensions should generally use the predefined context menus built into Brackets. Use this
-API to add a new context menu to UI that is specific to an extension.
-
-After registering a new context menu clients should:
- - use addMenuItem() to add items to the context menu
- - call open() to show the context menu.
- For example:
- ```js
- $("#my_ID").contextmenu(function (e) {
- if (e.which === 3) {
- my_cmenu.open(e);
- }
- });
- ```
-To make menu items be contextual to things like selection, listen for the "beforeContextMenuOpen"
-to make changes to Command objects before the context menu is shown. MenuItems are views of
-Commands, which control a MenuItem's name, enabled state, and checked state.
+Registers new context menu with Brackets.
Extensions should generally use the predefined context menus built into Brackets. Use this
API to add a new context menu to UI that is specific to an extension.
After registering a new context menu clients should:
- use addMenuItem() to add items to the context menu
- call open() to show the context menu.
For example:
```js
$("#my_ID").contextmenu(function (e) {
if (e.which === 3) {
my_cmenu.open(e);
}
});
```
To make menu items be contextual to things like selection, listen for the "beforeContextMenuOpen"
to make changes to Command objects before the context menu is shown. MenuItems are views of
Commands, which control a MenuItem's name, enabled state, and checked state.
**Kind**: global function
**Returns**: [ContextMenu
](#ContextMenu) - the newly created context menu
@@ -613,3 +498,21 @@ Event triggered before a sub-menu opens.
Event triggered before a sub-menu closes.
**Kind**: event emitted
+
+
+## "EVENT_MENU_ADDED"
+Event triggered when a menu or menu is added
+
+**Kind**: event emitted
+
+
+## "EVENT_SUB_MENU_ADDED"
+Event triggered when a menu or submenu is added
+
+**Kind**: event emitted
+
+
+## "EVENT_MENU_ITEM_ADDED"
+Event triggered when a menu item is added
+
+**Kind**: event emitted
diff --git a/docs/API-Reference/utils/LocalizationUtils.md b/docs/API-Reference/utils/LocalizationUtils.md
index efe3950c2..eba404e95 100644
--- a/docs/API-Reference/utils/LocalizationUtils.md
+++ b/docs/API-Reference/utils/LocalizationUtils.md
@@ -6,8 +6,7 @@ const LocalizationUtils = brackets.getModule("utils/LocalizationUtils")
## getLocalizedLabel(locale) ⇒ string
-Converts a language code to its written name, if possible.
-If not possible, the language code is simply returned.
+Converts a language code to its written name, if possible.
If not possible, the language code is simply returned.
**Kind**: global function
**Returns**: string
- The language's name or the given language code
@@ -44,15 +43,12 @@ Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the d
| --- | --- | --- |
| [date] | Date
| The date to compare with the current date and time. If not given, defaults to now. |
| [lang] | string
| Optional language code to use for formatting (e.g., 'en', 'fr'). If not provided, defaults to the application locale or 'en'. |
-| [fromDate] | string
| Optional date to use instead of now to compute the relative dateTime from. |
+| [fromDate] | Date
| Optional date to use instead of now to compute the relative dateTime from. |
## dateTimeFromNowFriendly(date, [lang]) ⇒ string
-Returns an intelligent date string.
-- For dates within the last 30 days or the future: relative time (e.g., "2 days ago", "in 3 hours").
-- For dates earlier this year: formatted date (e.g., "Jan 5").
-- For dates not in the current year: formatted date with year (e.g., "Jan 5, 2023").
+Returns an intelligent date string.
- For dates within the last 30 days or the future: relative time (e.g., "2 days ago", "in 3 hours").
- For dates earlier this year: formatted date (e.g., "Jan 5").
- For dates not in the current year: formatted date with year (e.g., "Jan 5, 2023").
**Kind**: global function
**Returns**: string
- - An intelligently formatted date string.
diff --git a/docs/API-Reference/widgets/PopUpManager.md b/docs/API-Reference/widgets/PopUpManager.md
index 1f9ce816f..01621c697 100644
--- a/docs/API-Reference/widgets/PopUpManager.md
+++ b/docs/API-Reference/widgets/PopUpManager.md
@@ -28,8 +28,7 @@ Add Esc key handling for a popup DOM element.
## removePopUp($popUp)
-Remove Esc key handling for a pop-up. Removes the pop-up from the DOM
-if the pop-up is currently visible and was not originally attached.
+Remove Esc key handling for a pop-up. Removes the pop-up from the DOM
if the pop-up is currently visible and was not originally attached.
**Kind**: global function
@@ -37,12 +36,34 @@ if the pop-up is currently visible and was not originally attached.
| --- | --- |
| $popUp | jQuery
|
+
+
+## \_filterDropdown($popup, searchString)
+hides all elements in popup that doesn't match the given search string, also shows the search bar in popup
+
+**Kind**: global function
+
+| Param |
+| --- |
+| $popup |
+| searchString |
+
+
+
+## selectNextItem(direction, $popUp)
+Selects the next or previous item in the popup.
+
+**Kind**: global function
+
+| Param | Type | Description |
+| --- | --- | --- |
+| direction | number
| +1 for next, -1 for prev |
+| $popUp | | |
+
## listenToContextMenu(contextMenu)
-Context menus are also created in AppInit.htmlReady(), so they may not
-yet have been created when we get our AppInit.htmlReady() callback, so
-we provide this method to tell us when to start listening for their events
+Context menus are also created in AppInit.htmlReady(), so they may not
yet have been created when we get our AppInit.htmlReady() callback, so
we provide this method to tell us when to start listening for their events
**Kind**: global function
diff --git a/package-lock.json b/package-lock.json
index a9ed2cccb..313203939 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "phoenix",
- "version": "3.11.0-0",
+ "version": "4.0.0-0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "phoenix",
- "version": "3.11.0-0",
+ "version": "4.0.0-0",
"dependencies": {
"@bugsnag/js": "^7.18.0",
"@floating-ui/dom": "^0.5.4",
diff --git a/src-node/git/cli.js b/src-node/git/cli.js
new file mode 100644
index 000000000..b45df98b7
--- /dev/null
+++ b/src-node/git/cli.js
@@ -0,0 +1,163 @@
+const gitNodeConnector = global.createNodeConnector("phcode-git-core", exports);
+
+const GIT_PROGRESS_EVENT = "git_progress";
+
+let ChildProcess = require("child_process"),
+ crossSpawn = require('cross-spawn'),
+ ProcessUtils = require("./processUtils"),
+ processMap = {},
+ resolvedPaths = {};
+
+function fixEOL(str) {
+ if (str[str.length - 1] === "\n") {
+ str = str.slice(0, -1);
+ }
+ return str;
+}
+
+// handler with ChildProcess.exec
+// this won't handle cases where process outputs a large string
+function execute(directory, command, args, opts, callback) {
+ // execute commands have to be escaped, spawn does this automatically and will fail if cmd is escaped
+ if (command[0] !== "\"" || command[command.length - 1] !== "\"") {
+ command = "\"" + command + "\"";
+ }
+ // http://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
+ const toExec = command + " " + args.join(" ");
+ processMap[opts.cliId] = ChildProcess.exec(toExec, {
+ cwd: directory,
+ maxBuffer: 20 * 1024 * 1024
+ }, function (err, stdout, stderr) {
+ delete processMap[opts.cliId];
+ callback(err ? fixEOL(stderr) : undefined, err ? undefined : fixEOL(stdout));
+ });
+}
+
+// handler with cross-spawn
+function join(arr) {
+ let result, index = 0, length;
+ length = arr.reduce(function (l, b) {
+ return l + b.length;
+ }, 0);
+ result = new Buffer(length);
+ arr.forEach(function (b) {
+ b.copy(result, index);
+ index += b.length;
+ });
+ return fixEOL(result.toString("utf8"));
+}
+
+function spawn(directory, command, args, opts, callback) {
+ // https://github.com/creationix/node-git
+ const child = crossSpawn(command, args, {
+ cwd: directory
+ });
+ child.on("error", function (err) {
+ callback(err.stack, undefined);
+ });
+
+ processMap[opts.cliId] = child;
+
+ let exitCode, stdout = [], stderr = [];
+ child.stdout.addListener("data", function (text) {
+ stdout[stdout.length] = text;
+ });
+ child.stderr.addListener("data", function (text) {
+ // Git writes its informational messages (such as Successfully rebased
+ // and updated) to stderr. This behavior is intentional because Git uses
+ // stderr for all output that is not a direct result of the command (e.g.,
+ // status updates, progress, or errors).
+ if (opts.watchProgress) {
+ gitNodeConnector.triggerPeer(GIT_PROGRESS_EVENT, {
+ cliId: opts.cliId,
+ time: (new Date()).getTime(),
+ data: fixEOL(text.toString("utf8"))
+ });
+ }
+ stderr[stderr.length] = text;
+ });
+ child.addListener("exit", function (code) {
+ exitCode = code;
+ });
+ child.addListener("close", function () {
+ delete processMap[opts.cliId];
+ callback(exitCode > 0 ? join(stderr) : undefined,
+ exitCode > 0 ? undefined : join(stdout));
+ });
+ child.stdin.end();
+}
+
+function doIfExists(method, directory, command, args, opts, callback) {
+ // do not call executableExists if we already know it exists
+ if (resolvedPaths[command]) {
+ return method(directory, resolvedPaths[command], args, opts, callback);
+ }
+
+ ProcessUtils.executableExists(command, function (err, exists, resolvedPath) {
+ if (exists) {
+ resolvedPaths[command] = resolvedPath;
+ return method(directory, resolvedPath, args, opts, callback);
+ } else {
+ callback("ProcessUtils can't resolve the path requested: " + command);
+ }
+ });
+}
+
+function executeIfExists({directory, command, args, opts}) {
+ return new Promise(function (resolve, reject) {
+ doIfExists(execute, directory, command, args, opts, (err, stdout)=>{
+ if(err){
+ reject(err);
+ } else {
+ resolve(stdout);
+ }
+ });
+ });
+}
+
+function spawnIfExists({directory, command, args, opts}) {
+ return new Promise(function (resolve, reject) {
+ doIfExists(spawn, directory, command, args, opts, (err, stdout)=>{
+ if(err){
+ reject(err);
+ } else {
+ resolve(stdout);
+ }
+ });
+ });
+}
+
+function kill(cliId) {
+ return new Promise(function (resolve, reject) {
+ const process = processMap[cliId];
+ if (!process) {
+ reject("Couldn't find process to kill with ID:" + cliId);
+ }
+ delete processMap[cliId];
+ resolve(""); // at this point we resolve anyways as we cant do anything after deleting the object
+ ProcessUtils.getChildrenOfPid(process.pid, function (err, children) {
+ // kill also parent process
+ children.push(process.pid);
+ children.forEach(function (pid) {
+ ProcessUtils.killSingleProcess(pid);
+ });
+ });
+ });
+}
+
+function which({command}) {
+ return new Promise(function (resolve, reject) {
+ ProcessUtils.executableExists(command, function (err, exists, resolvedPath) {
+ if (exists) {
+ resolve(resolvedPath);
+ } else {
+ reject("ProcessUtils can't resolve the path requested: " + command);
+ }
+ });
+ });
+}
+
+exports.execute = executeIfExists;
+exports.spawn = spawnIfExists;
+exports.kill = kill;
+exports.which = which;
diff --git a/src-node/git/processUtils-test.js b/src-node/git/processUtils-test.js
new file mode 100644
index 000000000..84e6bac5e
--- /dev/null
+++ b/src-node/git/processUtils-test.js
@@ -0,0 +1,58 @@
+const ProcessUtils = require("./processUtils");
+
+/*
+var pid = 5064;
+ProcessUtils.getChildrenOfPid(pid, function (err, children) {
+ console.log(children);
+ children.push(pid);
+ children.forEach(function (pid) {
+ ProcessUtils.killSingleProcess(pid);
+ });
+});
+*/
+
+[
+ "git",
+ "C:\\Program Files (x86)\\Git\\cmd\\git.exe",
+ "C:/Program Files (x86)/Git/cmd/git.exe",
+ "C:/Program Files (x86)/Git/cmd/git2.exe",
+ "C:/Program Files (x86)/Git/cmd/",
+ "C:/Program Files (x86)/Git/cmd",
+ "C:\\Program Files (x86)\\Git\\Git Bash.vbs"
+].forEach(function (path) {
+
+ ProcessUtils.executableExists(path, function (err, exists, resolvedPath) {
+ console.log("ProcessUtils.executableExists for: " + path);
+ console.log(" - exists: " + exists);
+ console.log(" - resolvedPath: " + resolvedPath);
+ });
+
+});
+
+/*
+ProcessUtils.executableExists("git", function (err, result, resolvedPath) {
+ console.log("git");
+ console.log(result);
+});
+ProcessUtils.executableExists("C:\\Program Files (x86)\\Git\\cmd\\git.exe", function (err, result) {
+ console.log("result for C:\\Program Files (x86)\\Git\\cmd\\git.exe");
+ console.log(result);
+});
+ProcessUtils.executableExists("C:/Program Files (x86)/Git/cmd/git.exe", function (err, result) {
+ console.log("result for C:/Program Files (x86)/Git/cmd/git.exe");
+ console.log(result);
+});
+
+ProcessUtils.executableExists("C:/Program Files (x86)/Git/cmd/git2.exe", function (err, result) {
+ console.log("result for C:/Program Files (x86)/Git/cmd/git2.exe");
+ console.log(result);
+});
+ProcessUtils.executableExists("C:/Program Files (x86)/Git/cmd/", function (err, result) {
+ console.log("result for C:/Program Files (x86)/Git/cmd/");
+ console.log(result);
+});
+ProcessUtils.executableExists("C:/Program Files (x86)/Git/cmd", function (err, result) {
+ console.log("result for C:/Program Files (x86)/Git/cmd");
+ console.log(result);
+});
+*/
diff --git a/src-node/git/processUtils.js b/src-node/git/processUtils.js
new file mode 100644
index 000000000..e2f3dee2b
--- /dev/null
+++ b/src-node/git/processUtils.js
@@ -0,0 +1,114 @@
+var exec = require("child_process").exec,
+ fs = require("fs"),
+ Path = require("path"),
+ which = require("which");
+
+var isWin = /^win/.test(process.platform);
+var noop = function () {};
+
+function fixEOL(str) {
+ if (str[str.length - 1] === "\n") {
+ str = str.slice(0, -1);
+ }
+ return str;
+}
+
+function findChildren(arr, pid) {
+ var result = [];
+ arr.forEach(function (obj) {
+ if (obj.parentprocessid == pid) {
+ // add children pid first
+ result = result.concat(findChildren(arr, obj.processid));
+ result.push(obj.processid);
+ }
+ });
+ return result;
+}
+
+function killSingleProcess(pid, callback) {
+ callback = callback || noop;
+ pid = pid.toString();
+
+ if (isWin) {
+ // "taskkill /F /PID 827"
+ exec("taskkill /F /PID " + pid, function (err, stdout, stderr) {
+ callback(err ? fixEOL(stderr) : undefined, err ? undefined : fixEOL(stdout));
+ });
+ } else {
+ // "kill -9 2563"
+ exec("kill -9 " + pid, function (err, stdout, stderr) {
+ callback(err ? fixEOL(stderr) : undefined, err ? undefined : fixEOL(stdout));
+ });
+ }
+}
+
+function getChildrenOfPid(pid, callback) {
+ callback = callback || noop;
+ pid = pid.toString();
+
+ if (isWin) {
+ exec("wmic process get parentprocessid,processid", function (err, stdout, stderr) {
+ if (err) {
+ return callback(fixEOL(stderr));
+ }
+ stdout = fixEOL(stdout);
+
+ var map = stdout.split("\n").map(function (line) {
+ var parts = line.trim().split(/\s+/);
+ var o = {};
+ o.processid = parts.pop();
+ o.parentprocessid = parts.pop();
+ return o;
+ });
+
+ callback(null, findChildren(map, pid));
+ });
+ } else {
+ exec("ps -A -o ppid,pid", function (err, stdout, stderr) {
+ if (err) {
+ return callback(fixEOL(stderr));
+ }
+ stdout = fixEOL(stdout);
+
+ var map = stdout.split("\n").map(function (line) {
+ var parts = line.trim().split(/\s+/);
+ var o = {};
+ o.processid = parts.pop();
+ o.parentprocessid = parts.pop();
+ return o;
+ });
+
+ callback(null, findChildren(map, pid));
+ });
+ }
+}
+
+function executableExists(filename, dir, callback) {
+ if (typeof dir === "function") {
+ callback = dir;
+ dir = "";
+ }
+
+ which(filename, function (err, path) {
+ if (err) {
+ return callback(err, false);
+ }
+
+ path = Path.normalize(path);
+
+ fs.stat(path, function (err, stats) {
+ if (err) {
+ return callback(err, false);
+ }
+
+ var exists = stats.isFile();
+ if (!exists) { path = undefined; }
+
+ return callback(null, exists, path);
+ });
+ });
+}
+
+exports.getChildrenOfPid = getChildrenOfPid;
+exports.killSingleProcess = killSingleProcess;
+exports.executableExists = executableExists;
diff --git a/src-node/index.js b/src-node/index.js
index ec1cc4723..f28f4a2c0 100644
--- a/src-node/index.js
+++ b/src-node/index.js
@@ -68,6 +68,7 @@ const NodeConnector = require("./node-connector");
const LivePreview = require("./live-preview");
require("./test-connection");
require("./utils");
+require("./git/cli");
function randomNonce(byteLength) {
const randomBuffer = new Uint8Array(byteLength);
crypto.getRandomValues(randomBuffer);
@@ -269,4 +270,4 @@ server.listen(0, localhostOnly, () => {
savedConsoleLog(`Phoenix node connector url is ws://localhost:${port}${PHOENIX_NODE_URL}`);
savedConsoleLog(`Phoenix live preview comm url is ws://localhost:${port}${PHOENIX_LIVE_PREVIEW_COMM_URL}`);
serverPortResolve(port);
-});
\ No newline at end of file
+});
diff --git a/src-node/package-lock.json b/src-node/package-lock.json
index c6a9f080e..f0fe857dd 100644
--- a/src-node/package-lock.json
+++ b/src-node/package-lock.json
@@ -1,19 +1,21 @@
{
"name": "@phcode/node-core",
- "version": "3.11.0-0",
+ "version": "4.0.0-0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@phcode/node-core",
- "version": "3.11.0-0",
+ "version": "4.0.0-0",
"license": "GNU-AGPL3.0",
"dependencies": {
"@phcode/fs": "^3.0.1",
+ "cross-spawn": "^7.0.6",
"lmdb": "^2.9.2",
"mime-types": "^2.1.35",
"npm": "10.1.0",
"open": "^10.1.0",
+ "which": "^2.0.1",
"ws": "^8.17.1"
},
"engines": {
@@ -246,6 +248,19 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/default-browser": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
@@ -417,6 +432,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
"node_modules/lmdb": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.9.2.tgz",
@@ -3607,6 +3627,14 @@
"resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz",
"integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A=="
},
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -3640,6 +3668,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -3656,6 +3703,20 @@
"resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz",
"integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw=="
},
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
diff --git a/src-node/package.json b/src-node/package.json
index 9144d5311..1d130e45e 100644
--- a/src-node/package.json
+++ b/src-node/package.json
@@ -24,6 +24,8 @@
"npm": "10.1.0",
"ws": "^8.17.1",
"lmdb": "^2.9.2",
- "mime-types": "^2.1.35"
+ "mime-types": "^2.1.35",
+ "cross-spawn": "^7.0.6",
+ "which": "^2.0.1"
}
-}
\ No newline at end of file
+}