feat(misc): enhance IDE integration with Nx Console auto-installation and improved logging (#31462)

## Current Behavior

Currently, IDE integration setup requires manual configuration and lacks
streamlined auto-installation capabilities for Nx Console. The logging
system also needs improvement for better developer experience.

## Expected Behavior

With these changes, the IDE integration provides:
- Prompt for automatic Nx Console installation with user preferences
- Enhanced native logger with proper formatting and levels
- Better development documentation for IDE setup
- Updated documentation links to use the new format

## Related Issue(s)

<\!-- Please link the issue being fixed so it gets closed when this is
merged. -->

This PR implements IDE integration improvements including Nx Console
auto-installation and enhanced logging capabilities.
This commit is contained in:
Jason Jean 2025-06-06 13:15:50 -04:00 committed by GitHub
parent 6613dd29ea
commit 77ff63f356
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 512 additions and 43 deletions

View File

@ -1,10 +1,11 @@
When responding to queries about this repository: When responding to queries about this repository:
1. Use the `nx_workspace` mcp tool for understanding the workspace architecture when appropriate 1. Use the `nx_workspace` mcp tool for understanding the workspace architecture when appropriate
2. Suggest relevant commands from the "Essential Commands" section when applicable 2. When working in projects, use the `nx_project` mcp tool to analyze and understand the specific project structure and dependencies
3. Highlight Nx's focus on monorepos and its key features like smart task execution, code generation, and project graph analysis 3. Suggest relevant commands from the "Essential Commands" section when applicable
4. Mention the plugin ecosystem and support for various frameworks when relevant 4. Highlight Nx's focus on monorepos and its key features like smart task execution, code generation, and project graph analysis
5. Emphasize the importance of running the full validation suite before committing changes 5. Mention the plugin ecosystem and support for various frameworks when relevant
6. Emphasize the importance of running the full validation suite before committing changes
Always strive to provide accurate, helpful responses that align with the best practices and workflows described in this file. Always strive to provide accurate, helpful responses that align with the best practices and workflows described in this file.

View File

@ -3,14 +3,14 @@ import { performance } from 'perf_hooks';
import { commandsObject } from '../src/command-line/nx-commands'; import { commandsObject } from '../src/command-line/nx-commands';
import { WorkspaceTypeAndRoot } from '../src/utils/find-workspace-root'; import { WorkspaceTypeAndRoot } from '../src/utils/find-workspace-root';
import { stripIndents } from '../src/utils/strip-indents'; import { stripIndents } from '../src/utils/strip-indents';
import { ensureNxConsoleInstalled } from '../src/utils/nx-console-prompt';
/** /**
* Nx is being run inside a workspace. * Nx is being run inside a workspace.
* *
* @param workspace Relevant local workspace properties * @param workspace Relevant local workspace properties
*/ */
export async function initLocal(workspace: WorkspaceTypeAndRoot) {
export function initLocal(workspace: WorkspaceTypeAndRoot) {
process.env.NX_CLI_SET = 'true'; process.env.NX_CLI_SET = 'true';
try { try {
@ -25,6 +25,11 @@ export function initLocal(workspace: WorkspaceTypeAndRoot) {
return; return;
} }
// Ensure NxConsole is installed if the user has it configured.
try {
await ensureNxConsoleInstalled();
} catch {}
const command = process.argv[2]; const command = process.argv[2];
if (command === 'run' || command === 'g' || command === 'generate') { if (command === 'run' || command === 'g' || command === 'generate') {
commandsObject.parse(process.argv.slice(2)); commandsObject.parse(process.argv.slice(2));

View File

@ -21,7 +21,6 @@ import { performance } from 'perf_hooks';
import { setupWorkspaceContext } from '../src/utils/workspace-context'; import { setupWorkspaceContext } from '../src/utils/workspace-context';
import { daemonClient } from '../src/daemon/client/client'; import { daemonClient } from '../src/daemon/client/client';
import { removeDbConnections } from '../src/utils/db-connection'; import { removeDbConnections } from '../src/utils/db-connection';
import { signalToCode } from '../src/utils/exit-codes';
// In case Nx Cloud forcibly exits while the TUI is running, ensure the terminal is restored etc. // In case Nx Cloud forcibly exits while the TUI is running, ensure the terminal is restored etc.
process.on('exit', (...args) => { process.on('exit', (...args) => {
@ -30,7 +29,7 @@ process.on('exit', (...args) => {
} }
}); });
function main() { async function main() {
if ( if (
process.argv[2] !== 'report' && process.argv[2] !== 'report' &&
process.argv[2] !== '--version' && process.argv[2] !== '--version' &&
@ -44,16 +43,16 @@ function main() {
const workspace = findWorkspaceRoot(process.cwd()); const workspace = findWorkspaceRoot(process.cwd());
performance.mark('loading dotenv files:start');
if (workspace) { if (workspace) {
performance.mark('loading dotenv files:start');
loadRootEnvFiles(workspace.dir); loadRootEnvFiles(workspace.dir);
performance.mark('loading dotenv files:end');
performance.measure(
'loading dotenv files',
'loading dotenv files:start',
'loading dotenv files:end'
);
} }
performance.mark('loading dotenv files:end');
performance.measure(
'loading dotenv files',
'loading dotenv files:start',
'loading dotenv files:end'
);
// new is a special case because there is no local workspace to load // new is a special case because there is no local workspace to load
if ( if (
@ -103,7 +102,7 @@ function main() {
// this file is already in the local workspace // this file is already in the local workspace
if (isLocalInstall) { if (isLocalInstall) {
initLocal(workspace); await initLocal(workspace);
} else { } else {
// Nx is being run from globally installed CLI - hand off to the local // Nx is being run from globally installed CLI - hand off to the local
warnIfUsingOutdatedGlobalInstall(GLOBAL_NX_VERSION, LOCAL_NX_VERSION); warnIfUsingOutdatedGlobalInstall(GLOBAL_NX_VERSION, LOCAL_NX_VERSION);
@ -287,4 +286,7 @@ process.on('exit', () => {
removeDbConnections(); removeDbConnections();
}); });
main(); main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,51 @@
# Nx Native (Rust Core) Development Guide
This directory contains the core Nx functionality written in Rust, providing high-performance operations for the Nx build system.
## Overview
The native module uses [napi-rs](https://napi.rs/) to create Node.js bindings for Rust code, enabling seamless integration between the TypeScript Nx codebase and performance-critical Rust implementations.
## Building the Native Module
After making changes to any Rust code in this directory, you must rebuild the nx package:
```bash
nx build nx
```
This command:
- Compiles the Rust code
- Generates TypeScript bindings
- Creates the native module for your current platform
## Development Workflow
1. **Make Rust Changes**: Edit `.rs` files in this directory
2. **Build Native Module**: Run `nx build-native nx --configuration local`
3. **Test Changes**: Run tests to verify functionality
4. **Format Code**: Ensure Rust code follows project conventions using `cargo fmt`
## Important Notes
- Always rebuild after Rust changes - TypeScript won't see updates until you rebuild
- The generated TypeScript bindings are in `packages/nx/src/native/index.js` and `.d.ts`
- Performance-critical operations should be implemented here rather than in TypeScript
- Use appropriate error handling - Rust panics will crash the Node.js process
## Testing
Run Rust tests with:
```bash
cargo test
```
Integration tests that verify the TypeScript/Rust boundary should be written in the TypeScript test files.
## Common Issues
- **Module not found errors**: Ensure you've run the build command after changes
- **Type mismatches**: Check that the napi-rs decorators match the TypeScript expectations
- **Performance regressions**: Profile before and after changes to ensure optimizations work as expected

View File

@ -0,0 +1,31 @@
use std::env;
use std::path::{Path, PathBuf};
#[cfg(target_os = "windows")]
const NX_CONFIG_DIR_NAME: &str = ".nx";
#[cfg(not(target_os = "windows"))]
const NX_CONFIG_DIR_NAME: &str = "nx";
/// Get the base configuration directory path.
///
/// - **Windows**: `%USERPROFILE%`
/// - **Unix**: `$XDG_CONFIG_HOME` or `$HOME/.config`
fn get_home_dir(home_dir: impl AsRef<Path>) -> PathBuf {
if cfg!(target_os = "windows") {
home_dir.as_ref().to_path_buf()
} else {
match env::var("XDG_CONFIG_HOME") {
Ok(xdg_home) => PathBuf::from(xdg_home),
Err(_) => home_dir.as_ref().join(".config"),
}
}
}
/// Get the user configuration directory for Nx.
///
/// - **Windows**: `%USERPROFILE%\.nx`
/// - **Unix**: `$XDG_CONFIG_HOME/nx` or `$HOME/.config/nx`
pub(crate) fn get_user_config_dir(home_dir: impl AsRef<Path>) -> PathBuf {
get_home_dir(home_dir).join(NX_CONFIG_DIR_NAME)
}

View File

@ -0,0 +1 @@
pub(crate) mod dir;

View File

@ -1,9 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::OnceLock; use std::sync::OnceLock;
mod ipc_transport;
pub mod messaging;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum SupportedEditor { pub enum SupportedEditor {
VSCode, VSCode,
@ -19,7 +16,7 @@ pub fn get_current_editor() -> &'static SupportedEditor {
CURRENT_EDITOR.get_or_init(|| detect_editor(HashMap::new())) CURRENT_EDITOR.get_or_init(|| detect_editor(HashMap::new()))
} }
fn detect_editor(mut env_map: HashMap<String, String>) -> SupportedEditor { pub fn detect_editor(mut env_map: HashMap<String, String>) -> SupportedEditor {
let term_editor = if let Some(term) = get_env_var("TERM_PROGRAM", &mut env_map) { let term_editor = if let Some(term) = get_env_var("TERM_PROGRAM", &mut env_map) {
let term_lower = term.to_lowercase(); let term_lower = term.to_lowercase();
match term_lower.as_str() { match term_lower.as_str() {

View File

@ -0,0 +1,198 @@
use crate::native::ide::detection::{SupportedEditor, get_current_editor};
use crate::native::logger::enable_logger;
use napi::Error;
use std::process::Command;
use tracing::{debug, info, trace};
const NX_CONSOLE_EXTENSION_ID: &str = "nrwl.angular-console";
fn is_nx_console_installed(command: &str) -> Result<bool, Error> {
debug!(
"Checking if Nx Console extension is installed with: {} --list-extensions",
command
);
let output = match Command::new(command).arg("--list-extensions").output() {
Ok(output) => output,
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => {
debug!(
"Command '{}' not found, cannot check extension status",
command
);
return Ok(false);
}
_ => {
debug!("Failed to execute command: {}", e);
return Err(Error::from_reason(format!(
"Failed to run command '{}': {}",
command, e
)));
}
},
};
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if output.status.success() {
let is_installed = stdout
.lines()
.any(|line| line.trim() == NX_CONSOLE_EXTENSION_ID);
if is_installed {
debug!("Nx Console extension is installed");
} else {
debug!("Nx Console extension is not installed");
}
return Ok(is_installed);
}
// Log the error output for debugging
debug!("Command failed with status: {:?}", output.status);
if !stdout.is_empty() {
trace!("Command stdout: {}", stdout.trim());
}
if !stderr.is_empty() {
trace!("Command stderr: {}", stderr.trim());
}
// Command failed, assume not installed
Ok(false)
}
fn install_extension(command: &str) -> Result<(), Error> {
debug!(
"Attempting to install Nx Console extension with: {} --install-extension {}",
command, NX_CONSOLE_EXTENSION_ID
);
let output = match Command::new(command)
.arg("--install-extension")
.arg(NX_CONSOLE_EXTENSION_ID)
.output()
{
Ok(output) => output,
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => {
debug!(
"Command '{}' not found, skipping extension installation",
command
);
return Ok(());
}
_ => {
debug!("Failed to execute command: {}", e);
return Err(Error::from_reason(format!(
"Failed to run command '{}': {}",
command, e
)));
}
},
};
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if output.status.success() {
if stdout.contains("already installed") {
debug!("Nx Console extension is already installed");
} else {
info!("Successfully installed Nx Console");
}
trace!("Command output: {}", stdout.trim());
return Ok(());
}
// Check for "already installed" message in stdout or stderr
let combined_output = format!("{} {}", stdout, stderr);
if combined_output.contains("already installed") {
debug!("Nx Console extension is already installed");
return Ok(());
}
// Log the error output for debugging
debug!("Command failed with status: {:?}", output.status);
if !stdout.is_empty() {
trace!("Command stdout: {}", stdout.trim());
}
if !stderr.is_empty() {
trace!("Command stderr: {}", stderr.trim());
}
// Command failed but this is OK - we don't want to crash Nx
Ok(())
}
#[napi]
pub fn can_install_nx_console() -> bool {
enable_logger();
if let Some(command) = get_install_command() {
if let Ok(installed) = is_nx_console_installed(command) {
!installed
} else {
false
}
} else {
false
}
}
pub fn get_install_command() -> Option<&'static str> {
// Check if installation should be skipped
let skip_install = std::env::var("NX_SKIP_VSCODE_EXTENSION_INSTALL")
.map(|v| v == "true")
.unwrap_or(false);
if skip_install {
debug!("Nx Console extension installation disabled via NX_SKIP_VSCODE_EXTENSION_INSTALL");
return None;
}
// Use the sophisticated editor detection from nx_console
let current_editor = get_current_editor();
debug!("Detected editor: {:?}", current_editor);
match current_editor {
SupportedEditor::VSCode => {
debug!("Installing Nx Console extension for VS Code");
#[cfg(target_os = "windows")]
{
Some("code.cmd")
}
#[cfg(not(target_os = "windows"))]
{
Some("code")
}
}
SupportedEditor::Windsurf => {
debug!("Installing Nx Console extension for Windsurf");
#[cfg(target_os = "windows")]
{
Some("windsurf.cmd")
}
#[cfg(not(target_os = "windows"))]
{
Some("windsurf")
}
}
editor => {
trace!(
"Unknown editor ({editor:?}) detected, skipping Nx Console extension installation"
);
None
}
}
}
#[napi]
pub fn install_nx_console() {
enable_logger();
if let Some(command) = get_install_command() {
// Try to install the extension
if let Err(e) = install_extension(command) {
debug!("Failed to install Nx Console extension: {}", e);
}
}
}

View File

@ -0,0 +1,4 @@
pub mod detection;
pub mod install;
pub mod nx_console;
mod preferences;

View File

@ -0,0 +1,5 @@
mod ipc_transport;
pub mod messaging;
// Re-export from ide/detection for backward compatibility
pub use crate::native::ide::detection::{SupportedEditor, get_current_editor};

View File

@ -12,9 +12,9 @@ use jsonrpsee::{
}; };
use crate::native::{ use crate::native::{
ide::nx_console::ipc_transport::IpcTransport,
tui::{ tui::{
components::tasks_list::{TaskItem, TaskStatus}, components::tasks_list::{TaskItem, TaskStatus},
nx_console::ipc_transport::IpcTransport,
pty::PtyInstance, pty::PtyInstance,
}, },
utils::socket_path::get_full_nx_console_socket_path, utils::socket_path::get_full_nx_console_socket_path,

View File

@ -0,0 +1,69 @@
use crate::native::config::dir::get_user_config_dir;
use crate::native::utils::json::{JsonResult, read_json_file};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use tracing::debug;
const NX_CONSOLE_PREFERENCES_FILE_NAME: &str = "ide.json";
#[napi]
#[derive(Debug, Serialize, Deserialize)]
pub struct NxConsolePreferences {
auto_install_console: Option<bool>,
#[serde(skip)]
path: PathBuf,
}
#[napi]
impl NxConsolePreferences {
#[napi(constructor)]
pub fn new(home_dir: String) -> Self {
let home_dir = PathBuf::from(home_dir);
let config_dir = get_user_config_dir(home_dir);
Self {
auto_install_console: None,
path: config_dir.join(NX_CONSOLE_PREFERENCES_FILE_NAME),
}
}
#[napi]
pub fn get_auto_install_preference(&mut self) -> Option<bool> {
if let Ok(prefs) = self.load() {
prefs.auto_install_console
} else {
None
}
}
#[napi]
pub fn set_auto_install_preference(&mut self, auto_install: bool) {
self.auto_install_console = Some(auto_install);
if let Err(err) = self.save() {
debug!("Failed to save console preferences: {}", err);
} else {
debug!("Console preferences saved successfully.");
}
}
fn save(&self) -> anyhow::Result<()> {
if let Some(parent) = self.path.parent() {
fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(self)?;
fs::write(&self.path, content)?;
Ok(())
}
fn load(&self) -> JsonResult<NxConsolePreferences> {
let mut prefs: NxConsolePreferences = read_json_file(&self.path)?;
// Set the path field since it's skipped during deserialization
prefs.path = self.path.clone();
debug!("Loaded console preferences: {:?}", prefs);
Ok(prefs)
}
}

View File

@ -73,6 +73,12 @@ export declare class NxCache {
checkCacheFsInSync(): boolean checkCacheFsInSync(): boolean
} }
export declare class NxConsolePreferences {
constructor(homeDir: string)
getAutoInstallPreference(): boolean | null
setAutoInstallPreference(autoInstall: boolean): void
}
export declare class NxTaskHistory { export declare class NxTaskHistory {
constructor(db: ExternalObject<NxDbConnection>) constructor(db: ExternalObject<NxDbConnection>)
recordTaskRuns(taskRuns: Array<TaskRun>): void recordTaskRuns(taskRuns: Array<TaskRun>): void
@ -148,6 +154,8 @@ export interface CachedResult {
size?: number size?: number
} }
export declare export declare function canInstallNxConsole(): boolean
export declare export declare function closeDbConnection(connection: ExternalObject<NxDbConnection>): void export declare export declare function closeDbConnection(connection: ExternalObject<NxDbConnection>): void
export declare export declare function connectToNxDb(cacheDir: string, nxVersion: string, dbName?: string | undefined | null): ExternalObject<NxDbConnection> export declare export declare function connectToNxDb(cacheDir: string, nxVersion: string, dbName?: string | undefined | null): ExternalObject<NxDbConnection>
@ -235,11 +243,13 @@ export interface InputsInput {
projects?: string | Array<string> projects?: string | Array<string>
} }
export declare export declare function installNxConsole(): void
export const IS_WASM: boolean export const IS_WASM: boolean
export declare export declare function logError(message: string): void export declare export declare function logDebug(message: string): void
export declare export declare function logInfo(message: string): void export declare export declare function logError(message: string): void
/** Stripped version of the NxJson interface for use in rust */ /** Stripped version of the NxJson interface for use in rust */
export interface NxJson { export interface NxJson {

View File

@ -1,10 +1,10 @@
use crate::native::logger::enable_logger; use crate::native::logger::enable_logger;
use tracing::{error, info}; use tracing::{debug, error};
#[napi] #[napi]
pub fn log_info(message: String) { pub fn log_debug(message: String) {
enable_logger(); enable_logger();
info!(message); debug!(message);
} }
#[napi] #[napi]

View File

@ -49,8 +49,13 @@ where
Level::WARN => { Level::WARN => {
write!(&mut writer, "\n{} {} ", ">".yellow(), "NX".bold().yellow())?; write!(&mut writer, "\n{} {} ", ">".yellow(), "NX".bold().yellow())?;
} }
_ => { Level::INFO => {
write!(&mut writer, "\n{} {} ", ">".cyan(), "NX".bold().cyan())?; // Match TypeScript logger format: inverse cyan "NX" prefix
write!(&mut writer, "\n{} ", " NX ".on_cyan().black().bold())?;
}
Level::ERROR => {
// Match TypeScript logger format: inverse red "ERROR" prefix
write!(&mut writer, "\n{} ", " ERROR ".on_red().white().bold())?;
} }
} }
@ -80,7 +85,7 @@ where
// Write fields on the event // Write fields on the event
ctx.field_format().format_fields(writer.by_ref(), event)?; ctx.field_format().format_fields(writer.by_ref(), event)?;
if !(matches!(level, Level::TRACE)) && !(matches!(level, Level::DEBUG)) { if matches!(level, Level::INFO | Level::ERROR | Level::WARN) {
writeln!(&mut writer)?; writeln!(&mut writer)?;
} }
@ -89,9 +94,10 @@ where
} }
/// Enable logging for the native module /// Enable logging for the native module
/// You can set log levels and different logs by setting the `NX_NATIVE_LOGGING` environment variable /// By default, info level logs are shown. You can change log levels by setting the `NX_NATIVE_LOGGING` environment variable
/// Examples: /// Examples:
/// - `NX_NATIVE_LOGGING=trace|warn|debug|error|info` - enable all logs for all crates and modules /// - `NX_NATIVE_LOGGING=trace|warn|debug|error|info` - enable all logs for all crates and modules
/// - `NX_NATIVE_LOGGING=off` - disable all logging
/// - `NX_NATIVE_LOGGING=nx=trace` - enable all logs for the `nx` (this) crate /// - `NX_NATIVE_LOGGING=nx=trace` - enable all logs for the `nx` (this) crate
/// - `NX_NATIVE_LOGGING=nx::native::tasks::hashers::hash_project_files=trace` - enable all logs for the `hash_project_files` module /// - `NX_NATIVE_LOGGING=nx::native::tasks::hashers::hash_project_files=trace` - enable all logs for the `hash_project_files` module
/// - `NX_NATIVE_LOGGING=[{project_name=project}]` - enable logs that contain the project in its span /// - `NX_NATIVE_LOGGING=[{project_name=project}]` - enable logs that contain the project in its span
@ -102,7 +108,7 @@ pub(crate) fn enable_logger() {
.with_writer(std::io::stdout) .with_writer(std::io::stdout)
.event_format(NxLogFormatter) .event_format(NxLogFormatter)
.with_filter( .with_filter(
EnvFilter::try_from_env("NX_NATIVE_LOGGING").unwrap_or_else(|_| EnvFilter::new("OFF")), EnvFilter::try_from_env("NX_NATIVE_LOGGING").unwrap_or_else(|_| EnvFilter::new("info")),
); );
let registry = tracing_subscriber::registry() let registry = tracing_subscriber::registry()

View File

@ -1,6 +1,7 @@
pub mod cache; pub mod cache;
pub mod glob; pub mod glob;
pub mod hasher; pub mod hasher;
pub mod ide;
pub mod logger; pub mod logger;
mod machine_id; mod machine_id;
pub mod metadata; pub mod metadata;
@ -12,6 +13,7 @@ pub mod utils;
mod walker; mod walker;
pub mod workspace; pub mod workspace;
mod config;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod db; pub mod db;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]

View File

@ -368,6 +368,7 @@ module.exports.HashPlanner = nativeBinding.HashPlanner
module.exports.HttpRemoteCache = nativeBinding.HttpRemoteCache module.exports.HttpRemoteCache = nativeBinding.HttpRemoteCache
module.exports.ImportResult = nativeBinding.ImportResult module.exports.ImportResult = nativeBinding.ImportResult
module.exports.NxCache = nativeBinding.NxCache module.exports.NxCache = nativeBinding.NxCache
module.exports.NxConsolePreferences = nativeBinding.NxConsolePreferences
module.exports.NxTaskHistory = nativeBinding.NxTaskHistory module.exports.NxTaskHistory = nativeBinding.NxTaskHistory
module.exports.RunningTasksService = nativeBinding.RunningTasksService module.exports.RunningTasksService = nativeBinding.RunningTasksService
module.exports.RustPseudoTerminal = nativeBinding.RustPseudoTerminal module.exports.RustPseudoTerminal = nativeBinding.RustPseudoTerminal
@ -375,6 +376,7 @@ module.exports.TaskDetails = nativeBinding.TaskDetails
module.exports.TaskHasher = nativeBinding.TaskHasher module.exports.TaskHasher = nativeBinding.TaskHasher
module.exports.Watcher = nativeBinding.Watcher module.exports.Watcher = nativeBinding.Watcher
module.exports.WorkspaceContext = nativeBinding.WorkspaceContext module.exports.WorkspaceContext = nativeBinding.WorkspaceContext
module.exports.canInstallNxConsole = nativeBinding.canInstallNxConsole
module.exports.closeDbConnection = nativeBinding.closeDbConnection module.exports.closeDbConnection = nativeBinding.closeDbConnection
module.exports.connectToNxDb = nativeBinding.connectToNxDb module.exports.connectToNxDb = nativeBinding.connectToNxDb
module.exports.copy = nativeBinding.copy module.exports.copy = nativeBinding.copy
@ -387,9 +389,10 @@ module.exports.getFilesForOutputs = nativeBinding.getFilesForOutputs
module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs
module.exports.hashArray = nativeBinding.hashArray module.exports.hashArray = nativeBinding.hashArray
module.exports.hashFile = nativeBinding.hashFile module.exports.hashFile = nativeBinding.hashFile
module.exports.installNxConsole = nativeBinding.installNxConsole
module.exports.IS_WASM = nativeBinding.IS_WASM module.exports.IS_WASM = nativeBinding.IS_WASM
module.exports.logDebug = nativeBinding.logDebug
module.exports.logError = nativeBinding.logError module.exports.logError = nativeBinding.logError
module.exports.logInfo = nativeBinding.logInfo
module.exports.parseTaskStatus = nativeBinding.parseTaskStatus module.exports.parseTaskStatus = nativeBinding.parseTaskStatus
module.exports.remove = nativeBinding.remove module.exports.remove = nativeBinding.remove
module.exports.restoreTerminal = nativeBinding.restoreTerminal module.exports.restoreTerminal = nativeBinding.restoreTerminal

View File

@ -22,6 +22,7 @@ use crate::native::{
tasks::types::{Task, TaskResult}, tasks::types::{Task, TaskResult},
}; };
use super::action::Action;
use super::components::Component; use super::components::Component;
use super::components::countdown_popup::CountdownPopup; use super::components::countdown_popup::CountdownPopup;
use super::components::help_popup::HelpPopup; use super::components::help_popup::HelpPopup;
@ -37,7 +38,7 @@ use super::pty::PtyInstance;
use super::theme::THEME; use super::theme::THEME;
use super::tui; use super::tui;
use super::utils::normalize_newlines; use super::utils::normalize_newlines;
use super::{action::Action, nx_console::messaging::NxConsoleMessageConnection}; use crate::native::ide::nx_console::messaging::NxConsoleMessageConnection;
pub struct App { pub struct App {
pub components: Vec<Box<dyn Component>>, pub components: Vec<Box<dyn Component>>,

View File

@ -1,3 +1,6 @@
use super::{Component, Frame};
use crate::native::ide::detection::{SupportedEditor, get_current_editor};
use crate::native::tui::action::Action;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
@ -11,9 +14,6 @@ use ratatui::{
use std::any::Any; use std::any::Any;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::{Component, Frame};
use crate::native::tui::{action::Action, nx_console};
use crate::native::tui::theme::THEME; use crate::native::tui::theme::THEME;
#[derive(Default)] #[derive(Default)]
@ -164,8 +164,8 @@ impl HelpPopup {
("", ""), ("", ""),
( (
"<ctrl>+a", "<ctrl>+a",
match nx_console::get_current_editor() { match get_current_editor() {
nx_console::SupportedEditor::VSCode => { SupportedEditor::VSCode => {
"Send terminal output to Copilot so that it can assist with any issues" "Send terminal output to Copilot so that it can assist with any issues"
} }
_ => { _ => {

View File

@ -8,8 +8,8 @@ use tracing::debug;
use crate::native::logger::enable_logger; use crate::native::logger::enable_logger;
use crate::native::tasks::types::{Task, TaskResult}; use crate::native::tasks::types::{Task, TaskResult};
use crate::native::{ use crate::native::{
ide::nx_console::messaging::NxConsoleMessageConnection,
pseudo_terminal::pseudo_terminal::{ParserArc, WriterArc}, pseudo_terminal::pseudo_terminal::{ParserArc, WriterArc},
tui::nx_console::messaging::NxConsoleMessageConnection,
}; };
use super::app::App; use super::app::App;

View File

@ -3,7 +3,6 @@ pub mod app;
pub mod components; pub mod components;
pub mod config; pub mod config;
pub mod lifecycle; pub mod lifecycle;
pub mod nx_console;
pub mod pty; pub mod pty;
pub mod theme; pub mod theme;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]

View File

@ -0,0 +1,22 @@
use serde::de::DeserializeOwned;
use std::fs;
use std::path::Path;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum JsonError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON parse error: {0}")]
Parse(#[from] serde_json::Error),
}
pub type JsonResult<T> = Result<T, JsonError>;
/// Efficiently reads and parses a JSON file using buffered I/O
pub fn read_json_file<T: DeserializeOwned>(path: &Path) -> JsonResult<T> {
let file = fs::File::open(path)?;
let reader = std::io::BufReader::new(file);
let data = serde_json::from_reader(reader)?;
Ok(data)
}

View File

@ -1,5 +1,6 @@
mod find_matching_projects; mod find_matching_projects;
mod get_mod_time; mod get_mod_time;
pub mod json;
mod normalize_trait; mod normalize_trait;
pub mod path; pub mod path;
pub mod socket_path; pub mod socket_path;

View File

@ -17,7 +17,7 @@ import {
getTaskDetails, getTaskDetails,
hashTasksThatDoNotDependOnOutputsOfOtherTasks, hashTasksThatDoNotDependOnOutputsOfOtherTasks,
} from '../hasher/hash-task'; } from '../hasher/hash-task';
import { logError, logInfo, RunMode } from '../native'; import { logError, logDebug, RunMode } from '../native';
import { import {
runPostTasksExecution, runPostTasksExecution,
runPreTasksExecution, runPreTasksExecution,
@ -222,7 +222,7 @@ async function getTerminalOutputLifeCycle(
: chunk.toString() : chunk.toString()
); );
} else { } else {
logInfo( logDebug(
Buffer.isBuffer(chunk) Buffer.isBuffer(chunk)
? chunk.toString(encoding) ? chunk.toString(encoding)
: chunk.toString() : chunk.toString()

View File

@ -0,0 +1,61 @@
import { prompt } from 'enquirer';
import { homedir } from 'os';
import { output } from './output';
import {
installNxConsole,
canInstallNxConsole,
NxConsolePreferences,
} from '../native';
export async function ensureNxConsoleInstalled() {
const preferences = new NxConsolePreferences(homedir());
let setting = preferences.getAutoInstallPreference();
const canInstallConsole = canInstallNxConsole();
// Noop
if (!canInstallConsole) {
return;
}
if (typeof setting !== 'boolean') {
setting = await promptForNxConsoleInstallation();
preferences.setAutoInstallPreference(setting);
}
if (setting) {
installNxConsole();
}
}
/**
* Prompts the user whether they want to automatically install the Nx Console extension
* and persists their preference using the NxConsolePreferences struct
*/
async function promptForNxConsoleInstallation(): Promise<boolean> {
try {
output.log({
title:
"Enhance your developer experience with Nx Console, Nx's official editor extension",
bodyLines: [
'- Enable your AI assistant to do more by understanding your workspace',
'- Add IntelliSense for Nx configuration files',
'- Explore your workspace visually',
'- Generate code and execute tasks interactively',
],
});
const { shouldInstallNxConsole } = await prompt<{
shouldInstallNxConsole: boolean;
}>({
type: 'confirm',
name: 'shouldInstallNxConsole',
message: 'Install Nx Console? (you can uninstall anytime)',
initial: true,
});
return shouldInstallNxConsole;
} catch (error) {
return false;
}
}