added --status

This commit is contained in:
MasterGordon 2025-10-23 18:01:27 +02:00
parent 9e5c5d38e5
commit 74db516798
4 changed files with 369 additions and 7 deletions

2
.gitignore vendored
View File

@ -5,6 +5,8 @@ node_modules
out
dist
*.tgz
csharpierd
csharpierd.exe
# code coverage
coverage

204
README.md
View File

@ -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 <filename> < 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

147
index.ts
View File

@ -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 <filename> < 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<void> {
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<void> {
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 <filename> < input.cs");
console.error("Usage: csharpierd <filename> < input.cs");
console.error("Try 'csharpierd --help' for more information");
process.exit(1);
}

View File

@ -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"
}
}