diff --git a/CLAUDE.md b/CLAUDE.md index 76a89666cf..8f16e45c7c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,10 +1,11 @@ When responding to queries about this repository: 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 -3. Highlight Nx's focus on monorepos and its key features like smart task execution, code generation, and project graph analysis -4. Mention the plugin ecosystem and support for various frameworks when relevant -5. Emphasize the importance of running the full validation suite before committing changes +2. When working in projects, use the `nx_project` mcp tool to analyze and understand the specific project structure and dependencies +3. Suggest relevant commands from the "Essential Commands" section when applicable +4. Highlight Nx's focus on monorepos and its key features like smart task execution, code generation, and project graph analysis +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. diff --git a/packages/nx/bin/init-local.ts b/packages/nx/bin/init-local.ts index 072cc2133e..1a6b157fd6 100644 --- a/packages/nx/bin/init-local.ts +++ b/packages/nx/bin/init-local.ts @@ -3,14 +3,14 @@ import { performance } from 'perf_hooks'; import { commandsObject } from '../src/command-line/nx-commands'; import { WorkspaceTypeAndRoot } from '../src/utils/find-workspace-root'; import { stripIndents } from '../src/utils/strip-indents'; +import { ensureNxConsoleInstalled } from '../src/utils/nx-console-prompt'; /** * Nx is being run inside a workspace. * * @param workspace Relevant local workspace properties */ - -export function initLocal(workspace: WorkspaceTypeAndRoot) { +export async function initLocal(workspace: WorkspaceTypeAndRoot) { process.env.NX_CLI_SET = 'true'; try { @@ -25,6 +25,11 @@ export function initLocal(workspace: WorkspaceTypeAndRoot) { return; } + // Ensure NxConsole is installed if the user has it configured. + try { + await ensureNxConsoleInstalled(); + } catch {} + const command = process.argv[2]; if (command === 'run' || command === 'g' || command === 'generate') { commandsObject.parse(process.argv.slice(2)); diff --git a/packages/nx/bin/nx.ts b/packages/nx/bin/nx.ts index cba4fe9946..424b582904 100644 --- a/packages/nx/bin/nx.ts +++ b/packages/nx/bin/nx.ts @@ -21,7 +21,6 @@ import { performance } from 'perf_hooks'; import { setupWorkspaceContext } from '../src/utils/workspace-context'; import { daemonClient } from '../src/daemon/client/client'; 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. process.on('exit', (...args) => { @@ -30,7 +29,7 @@ process.on('exit', (...args) => { } }); -function main() { +async function main() { if ( process.argv[2] !== 'report' && process.argv[2] !== '--version' && @@ -44,16 +43,16 @@ function main() { const workspace = findWorkspaceRoot(process.cwd()); - performance.mark('loading dotenv files:start'); if (workspace) { + performance.mark('loading dotenv files:start'); 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 if ( @@ -103,7 +102,7 @@ function main() { // this file is already in the local workspace if (isLocalInstall) { - initLocal(workspace); + await initLocal(workspace); } else { // Nx is being run from globally installed CLI - hand off to the local warnIfUsingOutdatedGlobalInstall(GLOBAL_NX_VERSION, LOCAL_NX_VERSION); @@ -287,4 +286,7 @@ process.on('exit', () => { removeDbConnections(); }); -main(); +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/nx/src/native/CLAUDE.md b/packages/nx/src/native/CLAUDE.md new file mode 100644 index 0000000000..a37c267db7 --- /dev/null +++ b/packages/nx/src/native/CLAUDE.md @@ -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 diff --git a/packages/nx/src/native/config/dir.rs b/packages/nx/src/native/config/dir.rs new file mode 100644 index 0000000000..e4b388dae0 --- /dev/null +++ b/packages/nx/src/native/config/dir.rs @@ -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) -> 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) -> PathBuf { + get_home_dir(home_dir).join(NX_CONFIG_DIR_NAME) +} diff --git a/packages/nx/src/native/config/mod.rs b/packages/nx/src/native/config/mod.rs new file mode 100644 index 0000000000..7e5174db88 --- /dev/null +++ b/packages/nx/src/native/config/mod.rs @@ -0,0 +1 @@ +pub(crate) mod dir; diff --git a/packages/nx/src/native/tui/nx_console.rs b/packages/nx/src/native/ide/detection.rs similarity index 98% rename from packages/nx/src/native/tui/nx_console.rs rename to packages/nx/src/native/ide/detection.rs index c2343f0662..15d3b00dfa 100644 --- a/packages/nx/src/native/tui/nx_console.rs +++ b/packages/nx/src/native/ide/detection.rs @@ -1,9 +1,6 @@ use std::collections::HashMap; use std::sync::OnceLock; -mod ipc_transport; -pub mod messaging; - #[derive(Debug, Clone, PartialEq, Eq)] pub enum SupportedEditor { VSCode, @@ -19,7 +16,7 @@ pub fn get_current_editor() -> &'static SupportedEditor { CURRENT_EDITOR.get_or_init(|| detect_editor(HashMap::new())) } -fn detect_editor(mut env_map: HashMap) -> SupportedEditor { +pub fn detect_editor(mut env_map: HashMap) -> SupportedEditor { let term_editor = if let Some(term) = get_env_var("TERM_PROGRAM", &mut env_map) { let term_lower = term.to_lowercase(); match term_lower.as_str() { diff --git a/packages/nx/src/native/ide/install.rs b/packages/nx/src/native/ide/install.rs new file mode 100644 index 0000000000..1dd00eb0be --- /dev/null +++ b/packages/nx/src/native/ide/install.rs @@ -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 { + 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); + } + } +} diff --git a/packages/nx/src/native/ide/mod.rs b/packages/nx/src/native/ide/mod.rs new file mode 100644 index 0000000000..d19aa4b5c2 --- /dev/null +++ b/packages/nx/src/native/ide/mod.rs @@ -0,0 +1,4 @@ +pub mod detection; +pub mod install; +pub mod nx_console; +mod preferences; diff --git a/packages/nx/src/native/ide/nx_console.rs b/packages/nx/src/native/ide/nx_console.rs new file mode 100644 index 0000000000..2f3075f571 --- /dev/null +++ b/packages/nx/src/native/ide/nx_console.rs @@ -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}; diff --git a/packages/nx/src/native/tui/nx_console/ipc_transport.rs b/packages/nx/src/native/ide/nx_console/ipc_transport.rs similarity index 100% rename from packages/nx/src/native/tui/nx_console/ipc_transport.rs rename to packages/nx/src/native/ide/nx_console/ipc_transport.rs diff --git a/packages/nx/src/native/tui/nx_console/messaging.rs b/packages/nx/src/native/ide/nx_console/messaging.rs similarity index 99% rename from packages/nx/src/native/tui/nx_console/messaging.rs rename to packages/nx/src/native/ide/nx_console/messaging.rs index da10cffc62..4f18f0588a 100644 --- a/packages/nx/src/native/tui/nx_console/messaging.rs +++ b/packages/nx/src/native/ide/nx_console/messaging.rs @@ -12,9 +12,9 @@ use jsonrpsee::{ }; use crate::native::{ + ide::nx_console::ipc_transport::IpcTransport, tui::{ components::tasks_list::{TaskItem, TaskStatus}, - nx_console::ipc_transport::IpcTransport, pty::PtyInstance, }, utils::socket_path::get_full_nx_console_socket_path, diff --git a/packages/nx/src/native/ide/preferences.rs b/packages/nx/src/native/ide/preferences.rs new file mode 100644 index 0000000000..a4c77cb5bb --- /dev/null +++ b/packages/nx/src/native/ide/preferences.rs @@ -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, + #[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 { + 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 { + 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) + } +} diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index 94bb030766..4517fef005 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -73,6 +73,12 @@ export declare class NxCache { checkCacheFsInSync(): boolean } +export declare class NxConsolePreferences { + constructor(homeDir: string) + getAutoInstallPreference(): boolean | null + setAutoInstallPreference(autoInstall: boolean): void +} + export declare class NxTaskHistory { constructor(db: ExternalObject) recordTaskRuns(taskRuns: Array): void @@ -148,6 +154,8 @@ export interface CachedResult { size?: number } +export declare export declare function canInstallNxConsole(): boolean + export declare export declare function closeDbConnection(connection: ExternalObject): void export declare export declare function connectToNxDb(cacheDir: string, nxVersion: string, dbName?: string | undefined | null): ExternalObject @@ -235,11 +243,13 @@ export interface InputsInput { projects?: string | Array } +export declare export declare function installNxConsole(): void + 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 */ export interface NxJson { diff --git a/packages/nx/src/native/logger/console.rs b/packages/nx/src/native/logger/console.rs index 11d9a0e9c4..c9c8cef527 100644 --- a/packages/nx/src/native/logger/console.rs +++ b/packages/nx/src/native/logger/console.rs @@ -1,10 +1,10 @@ use crate::native::logger::enable_logger; -use tracing::{error, info}; +use tracing::{debug, error}; #[napi] -pub fn log_info(message: String) { +pub fn log_debug(message: String) { enable_logger(); - info!(message); + debug!(message); } #[napi] diff --git a/packages/nx/src/native/logger/mod.rs b/packages/nx/src/native/logger/mod.rs index 491a4ab0ca..9b37e0ddda 100644 --- a/packages/nx/src/native/logger/mod.rs +++ b/packages/nx/src/native/logger/mod.rs @@ -49,8 +49,13 @@ where Level::WARN => { write!(&mut writer, "\n{} {} ", ">".yellow(), "NX".bold().yellow())?; } - _ => { - write!(&mut writer, "\n{} {} ", ">".cyan(), "NX".bold().cyan())?; + Level::INFO => { + // 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 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)?; } @@ -89,9 +94,10 @@ where } /// 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: /// - `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::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 @@ -102,7 +108,7 @@ pub(crate) fn enable_logger() { .with_writer(std::io::stdout) .event_format(NxLogFormatter) .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() diff --git a/packages/nx/src/native/mod.rs b/packages/nx/src/native/mod.rs index c971be3bb2..8b4a25651d 100644 --- a/packages/nx/src/native/mod.rs +++ b/packages/nx/src/native/mod.rs @@ -1,6 +1,7 @@ pub mod cache; pub mod glob; pub mod hasher; +pub mod ide; pub mod logger; mod machine_id; pub mod metadata; @@ -12,6 +13,7 @@ pub mod utils; mod walker; pub mod workspace; +mod config; #[cfg(not(target_arch = "wasm32"))] pub mod db; #[cfg(not(target_arch = "wasm32"))] diff --git a/packages/nx/src/native/native-bindings.js b/packages/nx/src/native/native-bindings.js index f66d4bb81c..e7b0427c6b 100644 --- a/packages/nx/src/native/native-bindings.js +++ b/packages/nx/src/native/native-bindings.js @@ -368,6 +368,7 @@ module.exports.HashPlanner = nativeBinding.HashPlanner module.exports.HttpRemoteCache = nativeBinding.HttpRemoteCache module.exports.ImportResult = nativeBinding.ImportResult module.exports.NxCache = nativeBinding.NxCache +module.exports.NxConsolePreferences = nativeBinding.NxConsolePreferences module.exports.NxTaskHistory = nativeBinding.NxTaskHistory module.exports.RunningTasksService = nativeBinding.RunningTasksService module.exports.RustPseudoTerminal = nativeBinding.RustPseudoTerminal @@ -375,6 +376,7 @@ module.exports.TaskDetails = nativeBinding.TaskDetails module.exports.TaskHasher = nativeBinding.TaskHasher module.exports.Watcher = nativeBinding.Watcher module.exports.WorkspaceContext = nativeBinding.WorkspaceContext +module.exports.canInstallNxConsole = nativeBinding.canInstallNxConsole module.exports.closeDbConnection = nativeBinding.closeDbConnection module.exports.connectToNxDb = nativeBinding.connectToNxDb module.exports.copy = nativeBinding.copy @@ -387,9 +389,10 @@ module.exports.getFilesForOutputs = nativeBinding.getFilesForOutputs module.exports.getTransformableOutputs = nativeBinding.getTransformableOutputs module.exports.hashArray = nativeBinding.hashArray module.exports.hashFile = nativeBinding.hashFile +module.exports.installNxConsole = nativeBinding.installNxConsole module.exports.IS_WASM = nativeBinding.IS_WASM +module.exports.logDebug = nativeBinding.logDebug module.exports.logError = nativeBinding.logError -module.exports.logInfo = nativeBinding.logInfo module.exports.parseTaskStatus = nativeBinding.parseTaskStatus module.exports.remove = nativeBinding.remove module.exports.restoreTerminal = nativeBinding.restoreTerminal diff --git a/packages/nx/src/native/tui/app.rs b/packages/nx/src/native/tui/app.rs index 87f0f6bfcb..d15bd7994b 100644 --- a/packages/nx/src/native/tui/app.rs +++ b/packages/nx/src/native/tui/app.rs @@ -22,6 +22,7 @@ use crate::native::{ tasks::types::{Task, TaskResult}, }; +use super::action::Action; use super::components::Component; use super::components::countdown_popup::CountdownPopup; use super::components::help_popup::HelpPopup; @@ -37,7 +38,7 @@ use super::pty::PtyInstance; use super::theme::THEME; use super::tui; 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 components: Vec>, diff --git a/packages/nx/src/native/tui/components/help_popup.rs b/packages/nx/src/native/tui/components/help_popup.rs index 439a1ed6ad..705b8fabf6 100644 --- a/packages/nx/src/native/tui/components/help_popup.rs +++ b/packages/nx/src/native/tui/components/help_popup.rs @@ -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 ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -11,9 +14,6 @@ use ratatui::{ use std::any::Any; use tokio::sync::mpsc::UnboundedSender; -use super::{Component, Frame}; -use crate::native::tui::{action::Action, nx_console}; - use crate::native::tui::theme::THEME; #[derive(Default)] @@ -164,8 +164,8 @@ impl HelpPopup { ("", ""), ( "+a", - match nx_console::get_current_editor() { - nx_console::SupportedEditor::VSCode => { + match get_current_editor() { + SupportedEditor::VSCode => { "Send terminal output to Copilot so that it can assist with any issues" } _ => { diff --git a/packages/nx/src/native/tui/lifecycle.rs b/packages/nx/src/native/tui/lifecycle.rs index 736a6f1a96..f0f9df9aaa 100644 --- a/packages/nx/src/native/tui/lifecycle.rs +++ b/packages/nx/src/native/tui/lifecycle.rs @@ -8,8 +8,8 @@ use tracing::debug; use crate::native::logger::enable_logger; use crate::native::tasks::types::{Task, TaskResult}; use crate::native::{ + ide::nx_console::messaging::NxConsoleMessageConnection, pseudo_terminal::pseudo_terminal::{ParserArc, WriterArc}, - tui::nx_console::messaging::NxConsoleMessageConnection, }; use super::app::App; diff --git a/packages/nx/src/native/tui/mod.rs b/packages/nx/src/native/tui/mod.rs index 1e3917c062..876bd28441 100644 --- a/packages/nx/src/native/tui/mod.rs +++ b/packages/nx/src/native/tui/mod.rs @@ -3,7 +3,6 @@ pub mod app; pub mod components; pub mod config; pub mod lifecycle; -pub mod nx_console; pub mod pty; pub mod theme; #[allow(clippy::module_inception)] diff --git a/packages/nx/src/native/utils/json.rs b/packages/nx/src/native/utils/json.rs new file mode 100644 index 0000000000..37ca7f2925 --- /dev/null +++ b/packages/nx/src/native/utils/json.rs @@ -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 = Result; + +/// Efficiently reads and parses a JSON file using buffered I/O +pub fn read_json_file(path: &Path) -> JsonResult { + let file = fs::File::open(path)?; + let reader = std::io::BufReader::new(file); + let data = serde_json::from_reader(reader)?; + Ok(data) +} diff --git a/packages/nx/src/native/utils/mod.rs b/packages/nx/src/native/utils/mod.rs index 01d97e4713..57d054f49e 100644 --- a/packages/nx/src/native/utils/mod.rs +++ b/packages/nx/src/native/utils/mod.rs @@ -1,5 +1,6 @@ mod find_matching_projects; mod get_mod_time; +pub mod json; mod normalize_trait; pub mod path; pub mod socket_path; diff --git a/packages/nx/src/tasks-runner/run-command.ts b/packages/nx/src/tasks-runner/run-command.ts index 5b5a487e19..64279dc41a 100644 --- a/packages/nx/src/tasks-runner/run-command.ts +++ b/packages/nx/src/tasks-runner/run-command.ts @@ -17,7 +17,7 @@ import { getTaskDetails, hashTasksThatDoNotDependOnOutputsOfOtherTasks, } from '../hasher/hash-task'; -import { logError, logInfo, RunMode } from '../native'; +import { logError, logDebug, RunMode } from '../native'; import { runPostTasksExecution, runPreTasksExecution, @@ -222,7 +222,7 @@ async function getTerminalOutputLifeCycle( : chunk.toString() ); } else { - logInfo( + logDebug( Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk.toString() diff --git a/packages/nx/src/utils/nx-console-prompt.ts b/packages/nx/src/utils/nx-console-prompt.ts new file mode 100644 index 0000000000..180a0d3fbc --- /dev/null +++ b/packages/nx/src/utils/nx-console-prompt.ts @@ -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 { + 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; + } +}