From 74db516798ddd0dfc93e0980b86ccdf086380d65 Mon Sep 17 00:00:00 2001 From: MasterGordon Date: Thu, 23 Oct 2025 18:01:27 +0200 Subject: [PATCH] added --status --- .gitignore | 2 + README.md | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++- index.ts | 147 ++++++++++++++++++++++++++++++++++++- package.json | 23 +++++- 4 files changed, 369 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index a14702c..f0b3429 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ node_modules out dist *.tgz +csharpierd +csharpierd.exe # code coverage coverage diff --git a/README.md b/README.md index f6fff9f..2d7118a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,211 @@ # csharpierd -To install dependencies: +A persistent CSharpier formatting daemon with automatic server management and idle timeout. + +## Features + +- Starts CSharpier server in background on first use +- Reuses existing server for subsequent formatting requests +- Automatically shuts down after 1 hour of inactivity +- Thread-safe with file locking mechanism +- Auto-recovery if server crashes + +## Requirements + +- [Bun](https://bun.sh) runtime (>= 1.0.0) +- [CSharpier](https://csharpier.com/) installed globally or locally + +## Installation + +### Global Installation + +```bash +# Bun +bun install -g csharpierd + +# npm +npm install -g csharpierd + +# Yarn +yarn global add csharpierd + +# pnpm +pnpm install -g csharpierd +``` + +### Local Development ```bash bun install ``` -To run: +## Usage + +### Command Line Options ```bash -bun run index.ts +csharpierd < input.cs # Format C# code from stdin +csharpierd --status # Show server status +csharpierd --stop # Stop the background server +csharpierd --help # Show help message ``` -This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. +### As Global Command + +After global installation: + +```bash +# Format a C# file +csharpierd Program.cs < Program.cs + +# Or using cat +cat MyFile.cs | csharpierd MyFile.cs + +# Output formatted code to a new file +csharpierd MyFile.cs < MyFile.cs > MyFile.formatted.cs + +# Check server status +csharpierd --status + +# Stop the background server +csharpierd --stop + +# Show help +csharpierd --help +``` + +### Local Development + +```bash +# Format a file +bun index.ts Program.cs < Program.cs + +# Check server status +bun index.ts --status + +# Stop the server +bun index.ts --stop + +# Show help +bun index.ts --help +``` + +### Server Management + +#### Check Server Status + +The `--status` flag shows detailed information about the server including: + +- Running state (RUNNING, STARTING, STOPPED, or NOT RUNNING) +- Process ID and port +- Last access time +- Idle time with color-coded warnings (green < 75% timeout, yellow >= 75%, red >= 100%) +- Configuration details + +```bash +csharpierd --status +``` + +#### Stopping the Server + +The server will automatically shut down after 1 hour of inactivity, but you can manually stop it: + +```bash +csharpierd --stop +``` + +## Building + +You can compile the TypeScript code to a standalone binary: + +```bash +bun run build +``` + +This creates a `csharpierd` binary in the current directory that can be distributed without requiring Bun to be installed. The binary is self-contained and includes all dependencies. + +```bash +# Run the compiled binary +./csharpierd Program.cs < Program.cs +``` + +## Editor Integration + +### Neovim with conform.nvim + +[conform.nvim](https://github.com/stevearc/conform.nvim) is a popular formatter plugin for Neovim. Here's how to configure it to use `csharpierd`: + +#### Basic Configuration + +```lua +require("conform").setup({ + formatters_by_ft = { + cs = { "csharpierd" }, + }, + formatters = { + csharpierd = { + command = "csharpierd", + args = { "$FILENAME" }, + stdin = true, + }, + }, +}) +``` + +#### With Lazy.nvim + +```lua +{ + "stevearc/conform.nvim", + event = { "BufWritePre" }, + cmd = { "ConformInfo" }, + opts = { + formatters_by_ft = { + cs = { "csharpierd" }, + }, + formatters = { + csharpierd = { + command = "csharpierd", + args = { "$FILENAME" }, + stdin = true, + }, + }, + format_on_save = { + timeout_ms = 5000, + lsp_fallback = true, + }, + }, +} +``` + +### Benefits of using csharpierd with conform.nvim + +- **Fast formatting**: Reuses the CSharpier server process, avoiding startup overhead +- **Automatic server management**: Server starts on first use and stops after 1 hour of inactivity + +## How It Works + +1. **First Call**: Starts `dotnet csharpier server --server-port 78912` in the background +2. **Subsequent Calls**: Reuses the existing server process +3. **Idle Timeout**: Server automatically shuts down after 1 hour of inactivity +4. **State Management**: Server state (PID, port, last access time) stored in `/tmp/csharpierd-state.json` +5. **Concurrency**: Lock file prevents race conditions when multiple instances run simultaneously + +## Server Details + +- **Port**: 78912 (hardcoded) +- **State File**: `/tmp/csharpierd-state.json` +- **Lock File**: `/tmp/csharpierd.lock` +- **Idle Timeout**: 1 hour (3600000ms) + +## Publishing + +To publish this package to npm: + +```bash +bun publish +``` + +## License + +MIT diff --git a/index.ts b/index.ts index 98fd218..bf63488 100755 --- a/index.ts +++ b/index.ts @@ -5,6 +5,18 @@ const LOCK_FILE = "/tmp/csharpierd.lock"; const SERVER_PORT = 18912; const IDLE_TIMEOUT_MS = 60 * 60 * 1000; // 1 hour +// Color utilities using Bun.color +const RESET = "\x1b[0m"; +const BOLD = "\x1b[1m"; + +const colorize = (text: string, color: string): string => { + return `${Bun.color(color, "ansi")}${text}${RESET}`; +}; + +const bold = (text: string): string => { + return `${BOLD}${text}${RESET}`; +}; + interface ServerState { pid: number; port: number; @@ -221,11 +233,142 @@ async function formatCode( } } +// Show help message +function showHelp(): void { + console.log(`csharpierd - CSharpier formatting daemon + +Usage: + csharpierd < input.cs Format C# code from stdin + csharpierd --status Show server status + csharpierd --stop Stop the background server + csharpierd --help Show this help message + +Description: + A persistent CSharpier formatting daemon that starts a background server + on first use and reuses it for subsequent formatting requests. The server + automatically shuts down after 1 hour of inactivity. + +Examples: + # Format a C# file + csharpierd Program.cs < Program.cs + + # Format and save to a new file + csharpierd MyFile.cs < MyFile.cs > MyFile.formatted.cs + + # Using cat + cat Program.cs | csharpierd Program.cs + + # Check server status + csharpierd --status + + # Stop the background server + csharpierd --stop + +Server Details: + Port: ${SERVER_PORT} + State File: ${STATE_FILE} + Lock File: ${LOCK_FILE} + Idle Timeout: ${IDLE_TIMEOUT_MS / 1000 / 60} minutes +`); +} + +// Stop the server +async function stopServer(): Promise { + const state = await loadState(); + + if (!state) { + console.error("No server is currently running"); + return; + } + + console.error(`Stopping CSharpier server (PID ${state.pid})...`); + await killServer(state.pid); + await Bun.$`rm -f ${STATE_FILE} ${LOCK_FILE}`.quiet(); + console.error("Server stopped successfully"); +} + +// Show server status +async function showStatus(): Promise { + const state = await loadState(); + + console.log(bold("\nCSharpier Server Status")); + console.log("═".repeat(50)); + + if (!state) { + console.log(colorize("Status:", "cyan"), colorize("NOT RUNNING", "red")); + console.log("\nNo server is currently active."); + console.log("The server will start automatically on the first format request."); + return; + } + + // Check if process is actually running + const isRunning = await isProcessRunning(state.pid); + const isResponsive = isRunning ? await isServerResponsive(state.port) : false; + + if (isRunning && isResponsive) { + console.log(colorize("Status:", "cyan"), colorize("RUNNING", "green")); + } else if (isRunning && !isResponsive) { + console.log(colorize("Status:", "cyan"), colorize("STARTING", "yellow")); + } else { + console.log(colorize("Status:", "cyan"), colorize("STOPPED", "red")); + } + + console.log(colorize("PID:", "cyan"), state.pid); + console.log(colorize("Port:", "cyan"), state.port); + + // Calculate and display uptime + const now = Date.now(); + const lastAccess = new Date(state.lastAccess); + const idleTime = now - state.lastAccess; + const idleMinutes = Math.floor(idleTime / 1000 / 60); + const idleSeconds = Math.floor((idleTime / 1000) % 60); + + console.log(colorize("Last Access:", "cyan"), lastAccess.toLocaleString()); + + const idleTimeStr = `${idleMinutes}m ${idleSeconds}s`; + const timeoutMinutes = IDLE_TIMEOUT_MS / 1000 / 60; + + if (idleMinutes >= timeoutMinutes) { + console.log(colorize("Idle Time:", "cyan"), colorize(idleTimeStr, "red"), "(will shutdown)"); + } else if (idleMinutes >= timeoutMinutes * 0.75) { + console.log(colorize("Idle Time:", "cyan"), colorize(idleTimeStr, "yellow"), `(${timeoutMinutes - idleMinutes}m until timeout)`); + } else { + console.log(colorize("Idle Time:", "cyan"), colorize(idleTimeStr, "green")); + } + + console.log(colorize("State File:", "cyan"), STATE_FILE); + console.log(colorize("Lock File:", "cyan"), LOCK_FILE); + console.log(colorize("Idle Timeout:", "cyan"), `${timeoutMinutes} minutes`); + console.log(""); +} + // Main async function main() { - const fileName = process.argv[2]; + const arg = process.argv[2]; + + // Handle --help flag + if (arg === "--help" || arg === "-h") { + showHelp(); + process.exit(0); + } + + // Handle --status flag + if (arg === "--status") { + await showStatus(); + process.exit(0); + } + + // Handle --stop flag + if (arg === "--stop") { + await stopServer(); + process.exit(0); + } + + // Normal formatting mode + const fileName = arg; if (!fileName) { - console.error("Usage: bun index.ts < input.cs"); + console.error("Usage: csharpierd < input.cs"); + console.error("Try 'csharpierd --help' for more information"); process.exit(1); } diff --git a/package.json b/package.json index c962be7..ad7644d 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,33 @@ { "name": "csharpierd", + "version": "1.0.0", + "description": "A persistent CSharpier formatting daemon with automatic server management and idle timeout", "module": "index.ts", "type": "module", - "private": true, + "bin": { + "csharpierd": "./index.ts" + }, + "scripts": { + "format": "bun index.ts", + "build": "bun build --compile --minify ./index.ts --outfile csharpierd" + }, + "keywords": [ + "csharpier", + "formatter", + "csharp", + "dotnet", + "daemon", + "persistent" + ], + "author": "", + "license": "MIT", "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" + }, + "engines": { + "bun": ">=1.0.0" } }