Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
529a403c4b | |||
ece16b75e2 | |||
1516185c4d | |||
1a782f0768 | |||
ae4148c82f |
20
changelog.md
20
changelog.md
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-08-28 - 3.1.0 - feat(daemon)
|
||||||
|
Reorganize and refactor core into client/daemon/shared modules; add IPC protocol and tests
|
||||||
|
|
||||||
|
- Reorganized core code: split daemon and client logic into ts/daemon and ts/client directories
|
||||||
|
- Moved process management into ProcessManager, ProcessMonitor and ProcessWrapper under ts/daemon
|
||||||
|
- Added a dedicated IPC client and service manager under ts/client (tspm.ipcclient, tspm.servicemanager)
|
||||||
|
- Introduced shared protocol and error handling: ts/shared/protocol/ipc.types.ts, protocol.version.ts and ts/shared/common/utils.errorhandler.ts
|
||||||
|
- Updated CLI to import Logger from shared/common utils and updated related helpers
|
||||||
|
- Added daemon entrypoint at ts/daemon/index.ts and reorganized daemon startup/shutdown/heartbeat handling
|
||||||
|
- Added test assets (test/testassets/simple-test.ts, simple-script2.ts) and expanded test files under test/
|
||||||
|
- Removed legacy top-level class files (classes.*) in favor of the new structured layout
|
||||||
|
|
||||||
|
## 2025-08-28 - 3.0.2 - fix(daemon)
|
||||||
|
Ensure TSPM runtime dir exists and improve daemon startup/debug output
|
||||||
|
|
||||||
|
- Create ~/.tspm directory before starting the daemon to avoid missing-directory errors
|
||||||
|
- Start daemon child process with stdio inherited when TSPM_DEBUG=true to surface startup errors during debugging
|
||||||
|
- Add warning and troubleshooting guidance when daemon process starts but does not respond (suggest checking socket file and using TSPM_DEBUG)
|
||||||
|
- Bump package version to 3.0.1
|
||||||
|
|
||||||
## 2025-08-28 - 3.0.0 - BREAKING CHANGE(daemon)
|
## 2025-08-28 - 3.0.0 - BREAKING CHANGE(daemon)
|
||||||
Refactor daemon and service management: remove IPC auto-spawn, add TspmServiceManager, tighten IPC/client/CLI behavior and tests
|
Refactor daemon and service management: remove IPC auto-spawn, add TspmServiceManager, tighten IPC/client/CLI behavior and tests
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/tspm",
|
"name": "@git.zone/tspm",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "a no fuzz process manager",
|
"description": "a no fuzz process manager",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
271
readme.plan.md
271
readme.plan.md
@@ -1,56 +1,249 @@
|
|||||||
# TSPM SmartDaemon Service Management Refactor
|
# TSPM Architecture Refactoring Plan
|
||||||
|
|
||||||
## Problem
|
## Current Problems
|
||||||
|
The current architecture has several issues that make the codebase confusing:
|
||||||
|
|
||||||
Currently TSPM auto-spawns the daemon as a detached child process, which is improper daemon management. It should use smartdaemon for all lifecycle management and never spawn processes directly.
|
1. **Flat structure confusion**: All classes are mixed together in the `ts/` directory with a `classes.` prefix naming convention
|
||||||
|
2. **Unclear boundaries**: It's hard to tell what code runs in the daemon vs the client
|
||||||
|
3. **Misleading naming**: The `Tspm` class is actually the core ProcessManager, not the overall system
|
||||||
|
4. **Coupling risk**: Client code could accidentally import daemon internals, bloating bundles
|
||||||
|
5. **No architectural enforcement**: Nothing prevents cross-boundary imports
|
||||||
|
|
||||||
## Solution
|
## Goal
|
||||||
|
Refactor into a clean 3-folder architecture (daemon/client/shared) with proper separation of concerns and enforced boundaries.
|
||||||
|
|
||||||
Refactor to use SmartDaemon for proper systemd service integration.
|
## Key Insights from Architecture Review
|
||||||
|
|
||||||
## Implementation Tasks
|
### Why This Separation Makes Sense
|
||||||
|
After discussion with GPT-5, we identified that:
|
||||||
|
|
||||||
### Phase 1: Remove Auto-Spawn Behavior
|
1. **ProcessManager/Monitor/Wrapper are daemon-only**: These classes actually spawn and manage processes. Clients never need them - they only communicate via IPC.
|
||||||
|
|
||||||
- [x] Remove spawn import from ts/classes.ipcclient.ts
|
2. **The client is just an IPC bridge**: The client (CLI and library users) only needs to send messages to the daemon and receive responses. It should never directly manage processes.
|
||||||
- [x] Delete startDaemon() method from IpcClient
|
|
||||||
- [x] Update connect() to throw error when daemon not running
|
|
||||||
- [x] Remove auto-reconnect logic from request() method
|
|
||||||
|
|
||||||
### Phase 2: Create Service Manager
|
3. **Shared should be minimal**: Only the IPC protocol types and pure utilities should be shared. No Node.js APIs, no file system access.
|
||||||
|
|
||||||
- [x] Create new file ts/classes.servicemanager.ts
|
4. **Protocol is the contract**: The IPC types are the only coupling between client and daemon. This allows independent evolution.
|
||||||
- [x] Implement TspmServiceManager class
|
|
||||||
- [x] Add getOrCreateService() method
|
|
||||||
- [x] Add enableService() method
|
|
||||||
- [x] Add disableService() method
|
|
||||||
- [x] Add getServiceStatus() method
|
|
||||||
|
|
||||||
### Phase 3: Update CLI Commands
|
## Architecture Overview
|
||||||
|
|
||||||
- [x] Add 'enable' command to CLI
|
### Folder Structure
|
||||||
- [x] Add 'disable' command to CLI
|
- **ts/daemon/** - Process orchestration (runs in daemon process only)
|
||||||
- [x] Update 'daemon start' to work without systemd
|
- Contains all process management logic
|
||||||
- [x] Add 'daemon start-service' internal command for systemd
|
- Spawns and monitors actual system processes
|
||||||
- [x] Update all commands to handle missing daemon gracefully
|
- Manages configuration and state
|
||||||
- [x] Add proper error messages with hints
|
- Never imported by client code
|
||||||
|
|
||||||
### Phase 4: Update Documentation
|
- **ts/client/** - IPC communication (runs in CLI/client process)
|
||||||
|
- Only knows how to talk to the daemon via IPC
|
||||||
|
- Lightweight - no process management logic
|
||||||
|
- What library users import when they use TSPM
|
||||||
|
- Can work in any Node.js environment (or potentially browser)
|
||||||
|
|
||||||
- [x] Update help text in CLI
|
- **ts/shared/** - Minimal shared contract (protocol & pure utilities)
|
||||||
- [ ] Update command descriptions
|
- **protocol/** - IPC request/response types, error codes, version
|
||||||
- [x] Add service management section
|
- **common/** - Pure utilities with no environment dependencies
|
||||||
|
- No fs, net, child_process, or Node-specific APIs
|
||||||
|
- Keep as small as possible to minimize coupling
|
||||||
|
|
||||||
### Phase 5: Testing
|
## File Organization Rationale
|
||||||
|
|
||||||
- [x] Test enable command
|
### What Goes in Daemon
|
||||||
- [x] Test disable command
|
These files are daemon-only because they actually manage processes:
|
||||||
- [x] Test daemon commands
|
- `processmanager.ts` (was Tspm) - Core process orchestration logic
|
||||||
- [x] Test error handling when daemon not running
|
- `processmonitor.ts` - Monitors memory and restarts processes
|
||||||
- [x] Build and verify TypeScript compilation
|
- `processwrapper.ts` - Wraps child processes with logging
|
||||||
|
- `tspm.config.ts` - Persists process configurations to disk
|
||||||
|
- `tspm.daemon.ts` - Wires everything together, handles IPC requests
|
||||||
|
|
||||||
## Migration Notes
|
### What Goes in Client
|
||||||
|
These files are client-only because they just communicate:
|
||||||
|
- `tspm.ipcclient.ts` - Sends requests to daemon via Unix socket
|
||||||
|
- `tspm.servicemanager.ts` - Manages systemd service (delegates to smartdaemon)
|
||||||
|
- CLI files - Command-line interface that uses the IPC client
|
||||||
|
|
||||||
- Users will need to run `tspm enable` once after update
|
### What Goes in Shared
|
||||||
- Existing daemon instances will stop working
|
Only the absolute minimum needed by both:
|
||||||
- Documentation needs updating to explain new behavior
|
- `protocol/ipc.types.ts` - Request/response type definitions
|
||||||
|
- `protocol/error.codes.ts` - Standardized error codes
|
||||||
|
- `common/utils.errorhandler.ts` - If it's pure (no I/O)
|
||||||
|
- Parts of `paths.ts` - Constants like socket path (not OS-specific resolution)
|
||||||
|
- Plugin interfaces only (not loading logic)
|
||||||
|
|
||||||
|
### Critical Design Decisions
|
||||||
|
|
||||||
|
1. **Rename Tspm to ProcessManager**: The class name should reflect what it does
|
||||||
|
2. **No process management in shared**: ProcessManager, ProcessMonitor, ProcessWrapper are daemon-only
|
||||||
|
3. **Protocol versioning**: Add version to allow client/daemon compatibility
|
||||||
|
4. **Enforce boundaries**: Use TypeScript project references to prevent violations
|
||||||
|
5. **Control exports**: Package.json exports map ensures library users can't import daemon code
|
||||||
|
|
||||||
|
## Detailed Task List
|
||||||
|
|
||||||
|
### Phase 1: Create New Structure
|
||||||
|
- [ ] Create directory `ts/daemon/`
|
||||||
|
- [ ] Create directory `ts/client/`
|
||||||
|
- [ ] Create directory `ts/shared/`
|
||||||
|
- [ ] Create directory `ts/shared/protocol/`
|
||||||
|
- [ ] Create directory `ts/shared/common/`
|
||||||
|
|
||||||
|
### Phase 2: Move Daemon Files
|
||||||
|
- [ ] Move `ts/daemon.ts` → `ts/daemon/index.ts`
|
||||||
|
- [ ] Move `ts/classes.daemon.ts` → `ts/daemon/tspm.daemon.ts`
|
||||||
|
- [ ] Move `ts/classes.tspm.ts` → `ts/daemon/processmanager.ts`
|
||||||
|
- [ ] Move `ts/classes.processmonitor.ts` → `ts/daemon/processmonitor.ts`
|
||||||
|
- [ ] Move `ts/classes.processwrapper.ts` → `ts/daemon/processwrapper.ts`
|
||||||
|
- [ ] Move `ts/classes.config.ts` → `ts/daemon/tspm.config.ts`
|
||||||
|
|
||||||
|
### Phase 3: Move Client Files
|
||||||
|
- [ ] Move `ts/classes.ipcclient.ts` → `ts/client/tspm.ipcclient.ts`
|
||||||
|
- [ ] Move `ts/classes.servicemanager.ts` → `ts/client/tspm.servicemanager.ts`
|
||||||
|
- [ ] Create `ts/client/index.ts` barrel export file
|
||||||
|
|
||||||
|
### Phase 4: Move Shared Files
|
||||||
|
- [ ] Move `ts/ipc.types.ts` → `ts/shared/protocol/ipc.types.ts`
|
||||||
|
- [ ] Create `ts/shared/protocol/protocol.version.ts` with version constant
|
||||||
|
- [ ] Create `ts/shared/protocol/error.codes.ts` with standardized error codes
|
||||||
|
- [ ] Move `ts/utils.errorhandler.ts` → `ts/shared/common/utils.errorhandler.ts`
|
||||||
|
- [ ] Analyze `ts/paths.ts` - split into constants (shared) vs resolvers (daemon)
|
||||||
|
- [ ] Move/split `ts/plugins.ts` - interfaces to shared, loaders to daemon
|
||||||
|
|
||||||
|
### Phase 5: Rename Classes
|
||||||
|
- [ ] In `processmanager.ts`: Rename class `Tspm` → `ProcessManager`
|
||||||
|
- [ ] Update all references to `Tspm` class to use `ProcessManager`
|
||||||
|
- [ ] Update constructor in `tspm.daemon.ts` to use `ProcessManager`
|
||||||
|
|
||||||
|
### Phase 6: Update Imports - Daemon Files
|
||||||
|
- [ ] Update imports in `ts/daemon/index.ts`
|
||||||
|
- [ ] Update imports in `ts/daemon/tspm.daemon.ts`
|
||||||
|
- [ ] Change `'./classes.tspm.js'` → `'./processmanager.js'`
|
||||||
|
- [ ] Change `'./paths.js'` → appropriate shared/daemon path
|
||||||
|
- [ ] Change `'./ipc.types.js'` → `'../shared/protocol/ipc.types.js'`
|
||||||
|
- [ ] Update imports in `ts/daemon/processmanager.ts`
|
||||||
|
- [ ] Change `'./classes.processmonitor.js'` → `'./processmonitor.js'`
|
||||||
|
- [ ] Change `'./classes.processwrapper.js'` → `'./processwrapper.js'`
|
||||||
|
- [ ] Change `'./classes.config.js'` → `'./tspm.config.js'`
|
||||||
|
- [ ] Change `'./utils.errorhandler.js'` → `'../shared/common/utils.errorhandler.js'`
|
||||||
|
- [ ] Update imports in `ts/daemon/processmonitor.ts`
|
||||||
|
- [ ] Change `'./classes.processwrapper.js'` → `'./processwrapper.js'`
|
||||||
|
- [ ] Update imports in `ts/daemon/processwrapper.ts`
|
||||||
|
- [ ] Update imports in `ts/daemon/tspm.config.ts`
|
||||||
|
|
||||||
|
### Phase 7: Update Imports - Client Files
|
||||||
|
- [ ] Update imports in `ts/client/tspm.ipcclient.ts`
|
||||||
|
- [ ] Change `'./paths.js'` → appropriate shared/daemon path
|
||||||
|
- [ ] Change `'./ipc.types.js'` → `'../shared/protocol/ipc.types.js'`
|
||||||
|
- [ ] Update imports in `ts/client/tspm.servicemanager.ts`
|
||||||
|
- [ ] Change `'./paths.js'` → appropriate shared/daemon path
|
||||||
|
- [ ] Create exports in `ts/client/index.ts`
|
||||||
|
- [ ] Export TspmIpcClient
|
||||||
|
- [ ] Export TspmServiceManager
|
||||||
|
|
||||||
|
### Phase 8: Update Imports - CLI Files
|
||||||
|
- [ ] Update imports in `ts/cli/index.ts`
|
||||||
|
- [ ] Change `'../classes.ipcclient.js'` → `'../client/tspm.ipcclient.js'`
|
||||||
|
- [ ] Update imports in `ts/cli/commands/service/enable.ts`
|
||||||
|
- [ ] Change `'../../../classes.servicemanager.js'` → `'../../../client/tspm.servicemanager.js'`
|
||||||
|
- [ ] Update imports in `ts/cli/commands/service/disable.ts`
|
||||||
|
- [ ] Change `'../../../classes.servicemanager.js'` → `'../../../client/tspm.servicemanager.js'`
|
||||||
|
- [ ] Update imports in `ts/cli/commands/daemon/index.ts`
|
||||||
|
- [ ] Change `'../../../classes.daemon.js'` → `'../../../daemon/tspm.daemon.js'`
|
||||||
|
- [ ] Change `'../../../classes.ipcclient.js'` → `'../../../client/tspm.ipcclient.js'`
|
||||||
|
- [ ] Update imports in `ts/cli/commands/process/*.ts` files
|
||||||
|
- [ ] Change all `'../../../classes.ipcclient.js'` → `'../../../client/tspm.ipcclient.js'`
|
||||||
|
- [ ] Change all `'../../../classes.tspm.js'` → `'../../../shared/protocol/ipc.types.js'` (for types)
|
||||||
|
- [ ] Update imports in `ts/cli/registration/index.ts`
|
||||||
|
- [ ] Change `'../../classes.ipcclient.js'` → `'../../client/tspm.ipcclient.js'`
|
||||||
|
|
||||||
|
### Phase 9: Update Main Exports
|
||||||
|
- [ ] Update `ts/index.ts`
|
||||||
|
- [ ] Remove `export * from './classes.tspm.js'`
|
||||||
|
- [ ] Remove `export * from './classes.processmonitor.js'`
|
||||||
|
- [ ] Remove `export * from './classes.processwrapper.js'`
|
||||||
|
- [ ] Remove `export * from './classes.daemon.js'`
|
||||||
|
- [ ] Remove `export * from './classes.ipcclient.js'`
|
||||||
|
- [ ] Remove `export * from './classes.servicemanager.js'`
|
||||||
|
- [ ] Add `export * from './client/index.js'`
|
||||||
|
- [ ] Add `export * from './shared/protocol/ipc.types.js'`
|
||||||
|
- [ ] Add `export { startDaemon } from './daemon/index.js'`
|
||||||
|
|
||||||
|
### Phase 10: Update Package.json
|
||||||
|
- [ ] Add exports map to package.json:
|
||||||
|
```json
|
||||||
|
"exports": {
|
||||||
|
".": "./dist_ts/client/index.js",
|
||||||
|
"./client": "./dist_ts/client/index.js",
|
||||||
|
"./daemon": "./dist_ts/daemon/index.js",
|
||||||
|
"./protocol": "./dist_ts/shared/protocol/ipc.types.js"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 11: TypeScript Configuration
|
||||||
|
- [ ] Create `tsconfig.base.json` with common settings
|
||||||
|
- [ ] Create `tsconfig.shared.json` for shared code
|
||||||
|
- [ ] Create `tsconfig.client.json` with reference to shared
|
||||||
|
- [ ] Create `tsconfig.daemon.json` with reference to shared
|
||||||
|
- [ ] Update main `tsconfig.json` to use references
|
||||||
|
|
||||||
|
### Phase 12: Testing
|
||||||
|
- [ ] Run `pnpm run build` and fix any compilation errors
|
||||||
|
- [ ] Test daemon startup: `./cli.js daemon start`
|
||||||
|
- [ ] Test process management: `./cli.js start "echo test"`
|
||||||
|
- [ ] Test client commands: `./cli.js list`
|
||||||
|
- [ ] Run existing tests: `pnpm test`
|
||||||
|
- [ ] Update test imports if needed
|
||||||
|
|
||||||
|
### Phase 13: Documentation
|
||||||
|
- [ ] Update README.md if needed
|
||||||
|
- [ ] Document the new architecture in a comment at top of ts/index.ts
|
||||||
|
- [ ] Add comments explaining the separation in each index.ts file
|
||||||
|
|
||||||
|
### Phase 14: Cleanup
|
||||||
|
- [ ] Delete empty directories from old structure
|
||||||
|
- [ ] Verify no broken imports remain
|
||||||
|
- [ ] Run linter and fix any issues
|
||||||
|
- [ ] Commit with message: "refactor(architecture): reorganize into daemon/client/shared structure"
|
||||||
|
|
||||||
|
## Benefits After Completion
|
||||||
|
|
||||||
|
### Immediate Benefits
|
||||||
|
- **Clear separation**: Instantly obvious what runs where (daemon vs client)
|
||||||
|
- **Smaller client bundles**: Client code won't accidentally include ProcessMonitor, ProcessWrapper, etc.
|
||||||
|
- **Better testing**: Can test client and daemon independently
|
||||||
|
- **Cleaner imports**: No more confusing `classes.` prefix on everything
|
||||||
|
|
||||||
|
### Architecture Benefits
|
||||||
|
- **Enforced boundaries**: TypeScript project references prevent cross-imports
|
||||||
|
- **Protocol as contract**: Client and daemon can evolve independently
|
||||||
|
- **Version compatibility**: Protocol versioning allows client/daemon version skew
|
||||||
|
- **Security**: Internal daemon errors don't leak to clients over IPC
|
||||||
|
|
||||||
|
### Future Benefits
|
||||||
|
- **Browser support**: Clean client could potentially work in browser
|
||||||
|
- **Embedded mode**: Could add option to run ProcessManager in-process
|
||||||
|
- **Plugin system**: Clear boundary for plugin interfaces vs implementation
|
||||||
|
- **Multi-language clients**: Other languages only need to implement IPC protocol
|
||||||
|
|
||||||
|
## Implementation Safeguards (from GPT-5 Review)
|
||||||
|
|
||||||
|
### Boundary Enforcement
|
||||||
|
- **TypeScript project references**: Separate tsconfig files prevent illegal imports
|
||||||
|
- **ESLint rules**: Use `import/no-restricted-paths` to catch violations
|
||||||
|
- **Package.json exports**: Control what external consumers can import
|
||||||
|
|
||||||
|
### Keep Shared Minimal
|
||||||
|
- **No Node.js APIs**: No fs, net, child_process in shared
|
||||||
|
- **No environment access**: No process.env, no OS-specific code
|
||||||
|
- **Pure functions only**: Shared utilities must be environment-agnostic
|
||||||
|
- **Protocol-focused**: Mainly type definitions and constants
|
||||||
|
|
||||||
|
### Prevent Environment Bleed
|
||||||
|
- **Split paths.ts**: Constants (shared) vs OS-specific resolution (daemon)
|
||||||
|
- **Plugin interfaces only**: Loading/discovery stays in daemon
|
||||||
|
- **No dynamic imports**: Keep shared statically analyzable
|
||||||
|
|
||||||
|
### Future-Proofing
|
||||||
|
- **Protocol versioning**: Add version field for compatibility
|
||||||
|
- **Error codes**: Standardized errors instead of string messages
|
||||||
|
- **Capability negotiation**: Client can query daemon capabilities
|
||||||
|
- **Subpath exports**: Different entry points for different use cases
|
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tspm',
|
name: '@git.zone/tspm',
|
||||||
version: '3.0.0',
|
version: '3.1.0',
|
||||||
description: 'a no fuzz process manager'
|
description: 'a no fuzz process manager'
|
||||||
}
|
}
|
||||||
|
@@ -37,9 +37,10 @@ export function registerDaemonCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Start daemon as a detached background process
|
// Start daemon as a detached background process
|
||||||
|
// Use 'inherit' for stdio to see any startup errors when debugging
|
||||||
const daemonProcess = spawn(process.execPath, [daemonScript], {
|
const daemonProcess = spawn(process.execPath, [daemonScript], {
|
||||||
detached: true,
|
detached: true,
|
||||||
stdio: 'ignore',
|
stdio: process.env.TSPM_DEBUG === 'true' ? 'inherit' : 'ignore',
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
TSPM_DAEMON_MODE: 'true',
|
TSPM_DAEMON_MODE: 'true',
|
||||||
@@ -62,6 +63,13 @@ export function registerDaemonCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
'\nNote: This daemon will run until you stop it or logout.',
|
'\nNote: This daemon will run until you stop it or logout.',
|
||||||
);
|
);
|
||||||
console.log('For automatic startup, use "tspm enable" instead.');
|
console.log('For automatic startup, use "tspm enable" instead.');
|
||||||
|
} else {
|
||||||
|
console.warn('\n⚠️ Warning: Daemon process started but is not responding.');
|
||||||
|
console.log('The daemon may have crashed on startup.');
|
||||||
|
console.log('\nTo debug, try:');
|
||||||
|
console.log(' TSPM_DEBUG=true tspm daemon start');
|
||||||
|
console.log('\nOr check if the socket file exists:');
|
||||||
|
console.log(` ls -la ~/.tspm/tspm.sock`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect from the daemon after starting
|
// Disconnect from the daemon after starting
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../../../plugins.js';
|
import * as plugins from '../../../plugins.js';
|
||||||
import { tspmIpcClient } from '../../../classes.ipcclient.js';
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
import type { CliArguments } from '../../types.js';
|
import type { CliArguments } from '../../types.js';
|
||||||
import { registerIpcCommand } from '../../registration/index.js';
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../../../plugins.js';
|
import * as plugins from '../../../plugins.js';
|
||||||
import { tspmIpcClient } from '../../../classes.ipcclient.js';
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
import type { CliArguments } from '../../types.js';
|
import type { CliArguments } from '../../types.js';
|
||||||
import { registerIpcCommand } from '../../registration/index.js';
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
import { pad } from '../../helpers/formatting.js';
|
import { pad } from '../../helpers/formatting.js';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../../../plugins.js';
|
import * as plugins from '../../../plugins.js';
|
||||||
import { tspmIpcClient } from '../../../classes.ipcclient.js';
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
import type { CliArguments } from '../../types.js';
|
import type { CliArguments } from '../../types.js';
|
||||||
import { registerIpcCommand } from '../../registration/index.js';
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../../../plugins.js';
|
import * as plugins from '../../../plugins.js';
|
||||||
import { tspmIpcClient } from '../../../classes.ipcclient.js';
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
import type { IProcessConfig } from '../../../classes.tspm.js';
|
import type { IProcessConfig } from '../../../classes.tspm.js';
|
||||||
import type { CliArguments } from '../../types.js';
|
import type { CliArguments } from '../../types.js';
|
||||||
import { parseMemoryString, formatMemory } from '../../helpers/memory.js';
|
import { parseMemoryString, formatMemory } from '../../helpers/memory.js';
|
||||||
@@ -10,10 +10,16 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
smartcli,
|
smartcli,
|
||||||
'start',
|
'start',
|
||||||
async (argvArg: CliArguments) => {
|
async (argvArg: CliArguments) => {
|
||||||
const script = argvArg._[1];
|
// Get all arguments after 'start' command
|
||||||
if (!script) {
|
const commandArgs = argvArg._.slice(1);
|
||||||
console.error('Error: Please provide a script to run');
|
if (commandArgs.length === 0) {
|
||||||
console.log('Usage: tspm start <script> [options]');
|
console.error('Error: Please provide a command to run');
|
||||||
|
console.log('Usage: tspm start <command> [options]');
|
||||||
|
console.log('\nExamples:');
|
||||||
|
console.log(' tspm start "npm run dev"');
|
||||||
|
console.log(' tspm start pnpm start');
|
||||||
|
console.log(' tspm start node server.js');
|
||||||
|
console.log(' tspm start script.ts');
|
||||||
console.log('\nOptions:');
|
console.log('\nOptions:');
|
||||||
console.log(' --name <name> Name for the process');
|
console.log(' --name <name> Name for the process');
|
||||||
console.log(
|
console.log(
|
||||||
@@ -28,16 +34,24 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Join all command parts to form the full command
|
||||||
|
const script = commandArgs.join(' ');
|
||||||
|
|
||||||
const memoryLimit = argvArg.memory
|
const memoryLimit = argvArg.memory
|
||||||
? parseMemoryString(argvArg.memory)
|
? parseMemoryString(argvArg.memory)
|
||||||
: 512 * 1024 * 1024;
|
: 512 * 1024 * 1024;
|
||||||
const projectDir = argvArg.cwd || process.cwd();
|
const projectDir = argvArg.cwd || process.cwd();
|
||||||
|
|
||||||
// Direct .ts support via tsx (bundled with TSPM)
|
// Parse the command to determine if we need to handle .ts files
|
||||||
let actualCommand = script;
|
let actualCommand: string;
|
||||||
let commandArgs: string[] | undefined = undefined;
|
let processArgs: string[] | undefined = undefined;
|
||||||
|
|
||||||
if (script.endsWith('.ts')) {
|
// Split the script to check if it's a single .ts file or a full command
|
||||||
|
const scriptParts = script.split(' ');
|
||||||
|
const firstPart = scriptParts[0];
|
||||||
|
|
||||||
|
// Check if this is a direct .ts file execution (single argument ending in .ts)
|
||||||
|
if (scriptParts.length === 1 && firstPart.endsWith('.ts')) {
|
||||||
try {
|
try {
|
||||||
const tsxPath = await (async () => {
|
const tsxPath = await (async () => {
|
||||||
const { createRequire } = await import('module');
|
const { createRequire } = await import('module');
|
||||||
@@ -45,15 +59,20 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
return require.resolve('tsx/dist/cli.mjs');
|
return require.resolve('tsx/dist/cli.mjs');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const scriptPath = plugins.path.isAbsolute(script)
|
const scriptPath = plugins.path.isAbsolute(firstPart)
|
||||||
? script
|
? firstPart
|
||||||
: plugins.path.join(projectDir, script);
|
: plugins.path.join(projectDir, firstPart);
|
||||||
actualCommand = tsxPath;
|
actualCommand = tsxPath;
|
||||||
commandArgs = [scriptPath];
|
processArgs = [scriptPath];
|
||||||
} catch {
|
} catch {
|
||||||
actualCommand = 'tsx';
|
actualCommand = 'tsx';
|
||||||
commandArgs = [script];
|
processArgs = [firstPart];
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// For multi-word commands, use the entire script as the command
|
||||||
|
// This handles cases like "pnpm start", "npm run dev", etc.
|
||||||
|
actualCommand = script;
|
||||||
|
processArgs = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = argvArg.name || script;
|
const name = argvArg.name || script;
|
||||||
@@ -69,7 +88,7 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
id: name.replace(/[^a-zA-Z0-9-_]/g, '_'),
|
id: name.replace(/[^a-zA-Z0-9-_]/g, '_'),
|
||||||
name,
|
name,
|
||||||
command: actualCommand,
|
command: actualCommand,
|
||||||
args: commandArgs,
|
args: processArgs,
|
||||||
projectDir,
|
projectDir,
|
||||||
memoryLimitBytes: memoryLimit,
|
memoryLimitBytes: memoryLimit,
|
||||||
autorestart,
|
autorestart,
|
||||||
@@ -79,7 +98,7 @@ export function registerStartCommand(smartcli: plugins.smartcli.Smartcli) {
|
|||||||
|
|
||||||
console.log(`Starting process: ${name}`);
|
console.log(`Starting process: ${name}`);
|
||||||
console.log(
|
console.log(
|
||||||
` Command: ${script}${script.endsWith('.ts') ? ' (via tsx)' : ''}`,
|
` Command: ${script}${scriptParts.length === 1 && firstPart.endsWith('.ts') ? ' (via tsx)' : ''}`,
|
||||||
);
|
);
|
||||||
console.log(` Directory: ${projectDir}`);
|
console.log(` Directory: ${projectDir}`);
|
||||||
console.log(` Memory limit: ${formatMemory(memoryLimit)}`);
|
console.log(` Memory limit: ${formatMemory(memoryLimit)}`);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../../../plugins.js';
|
import * as plugins from '../../../plugins.js';
|
||||||
import { tspmIpcClient } from '../../../classes.ipcclient.js';
|
import { tspmIpcClient } from '../../../client/tspm.ipcclient.js';
|
||||||
import type { CliArguments } from '../../types.js';
|
import type { CliArguments } from '../../types.js';
|
||||||
import { registerIpcCommand } from '../../registration/index.js';
|
import { registerIpcCommand } from '../../registration/index.js';
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from '../paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { Logger, LogLevel } from '../utils.errorhandler.js';
|
import { Logger, LogLevel } from '../shared/common/utils.errorhandler.js';
|
||||||
|
|
||||||
// Import command registration functions
|
// Import command registration functions
|
||||||
import { registerDefaultCommand } from './commands/default.js';
|
import { registerDefaultCommand } from './commands/default.js';
|
||||||
|
8
ts/client/index.ts
Normal file
8
ts/client/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Client-side exports for TSPM
|
||||||
|
* These are the only components that client applications should use
|
||||||
|
* They only communicate with the daemon via IPC, never directly manage processes
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './tspm.ipcclient.js';
|
||||||
|
export * from './tspm.servicemanager.js';
|
@@ -1,11 +1,11 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from '../paths.js';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IpcMethodMap,
|
IpcMethodMap,
|
||||||
RequestForMethod,
|
RequestForMethod,
|
||||||
ResponseForMethod,
|
ResponseForMethod,
|
||||||
} from './ipc.types.js';
|
} from '../shared/protocol/ipc.types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPC client for communicating with the TSPM daemon
|
* IPC client for communicating with the TSPM daemon
|
@@ -1,5 +1,5 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from '../paths.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages TSPM daemon as a systemd service via smartdaemon
|
* Manages TSPM daemon as a systemd service via smartdaemon
|
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { startDaemon } from './classes.daemon.js';
|
import { startDaemon } from './tspm.daemon.js';
|
||||||
|
|
||||||
// Start the daemon
|
// Start the daemon
|
||||||
startDaemon().catch((error) => {
|
startDaemon().catch((error) => {
|
@@ -1,19 +1,19 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as paths from './paths.js';
|
import * as paths from '../paths.js';
|
||||||
import {
|
import {
|
||||||
ProcessMonitor,
|
ProcessMonitor,
|
||||||
type IMonitorConfig,
|
type IMonitorConfig,
|
||||||
} from './classes.processmonitor.js';
|
} from './processmonitor.js';
|
||||||
import { type IProcessLog } from './classes.processwrapper.js';
|
import { type IProcessLog } from './processwrapper.js';
|
||||||
import { TspmConfig } from './classes.config.js';
|
import { TspmConfig } from './tspm.config.js';
|
||||||
import {
|
import {
|
||||||
Logger,
|
Logger,
|
||||||
ProcessError,
|
ProcessError,
|
||||||
ConfigError,
|
ConfigError,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
handleError,
|
handleError,
|
||||||
} from './utils.errorhandler.js';
|
} from '../shared/common/utils.errorhandler.js';
|
||||||
|
|
||||||
export interface IProcessConfig extends IMonitorConfig {
|
export interface IProcessConfig extends IMonitorConfig {
|
||||||
id: string; // Unique identifier for the process
|
id: string; // Unique identifier for the process
|
||||||
@@ -32,7 +32,7 @@ export interface IProcessInfo {
|
|||||||
restarts: number;
|
restarts: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tspm extends EventEmitter {
|
export class ProcessManager extends EventEmitter {
|
||||||
public processes: Map<string, ProcessMonitor> = new Map();
|
public processes: Map<string, ProcessMonitor> = new Map();
|
||||||
public processConfigs: Map<string, IProcessConfig> = new Map();
|
public processConfigs: Map<string, IProcessConfig> = new Map();
|
||||||
public processInfo: Map<string, IProcessInfo> = new Map();
|
public processInfo: Map<string, IProcessInfo> = new Map();
|
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { ProcessWrapper, type IProcessLog } from './classes.processwrapper.js';
|
import { ProcessWrapper, type IProcessLog } from './processwrapper.js';
|
||||||
import { Logger, ProcessError, handleError } from './utils.errorhandler.js';
|
import { Logger, ProcessError, handleError } from '../shared/common/utils.errorhandler.js';
|
||||||
|
|
||||||
export interface IMonitorConfig {
|
export interface IMonitorConfig {
|
||||||
name?: string; // Optional name to identify the instance
|
name?: string; // Optional name to identify the instance
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { Logger, ProcessError, handleError } from './utils.errorhandler.js';
|
import { Logger, ProcessError, handleError } from '../shared/common/utils.errorhandler.js';
|
||||||
|
|
||||||
export interface IProcessWrapperOptions {
|
export interface IProcessWrapperOptions {
|
||||||
command: string;
|
command: string;
|
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
export class TspmConfig {
|
export class TspmConfig {
|
||||||
public npmextraInstance = new plugins.npmextra.KeyValueStore({
|
public npmextraInstance = new plugins.npmextra.KeyValueStore({
|
@@ -1,19 +1,19 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from './paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { Tspm } from './classes.tspm.js';
|
import { ProcessManager } from './processmanager.js';
|
||||||
import type {
|
import type {
|
||||||
IpcMethodMap,
|
IpcMethodMap,
|
||||||
RequestForMethod,
|
RequestForMethod,
|
||||||
ResponseForMethod,
|
ResponseForMethod,
|
||||||
DaemonStatusResponse,
|
DaemonStatusResponse,
|
||||||
HeartbeatResponse,
|
HeartbeatResponse,
|
||||||
} from './ipc.types.js';
|
} from '../shared/protocol/ipc.types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central daemon server that manages all TSPM processes
|
* Central daemon server that manages all TSPM processes
|
||||||
*/
|
*/
|
||||||
export class TspmDaemon {
|
export class TspmDaemon {
|
||||||
private tspmInstance: Tspm;
|
private tspmInstance: ProcessManager;
|
||||||
private ipcServer: plugins.smartipc.IpcServer;
|
private ipcServer: plugins.smartipc.IpcServer;
|
||||||
private startTime: number;
|
private startTime: number;
|
||||||
private isShuttingDown: boolean = false;
|
private isShuttingDown: boolean = false;
|
||||||
@@ -22,7 +22,7 @@ export class TspmDaemon {
|
|||||||
private daemonPidFile: string;
|
private daemonPidFile: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tspmInstance = new Tspm();
|
this.tspmInstance = new ProcessManager();
|
||||||
this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
|
this.socketPath = plugins.path.join(paths.tspmDir, 'tspm.sock');
|
||||||
this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
|
this.daemonPidFile = plugins.path.join(paths.tspmDir, 'daemon.pid');
|
||||||
this.startTime = Date.now();
|
this.startTime = Date.now();
|
||||||
@@ -34,6 +34,10 @@ export class TspmDaemon {
|
|||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
console.log('Starting TSPM daemon...');
|
console.log('Starting TSPM daemon...');
|
||||||
|
|
||||||
|
// Ensure the TSPM directory exists
|
||||||
|
const fs = await import('fs/promises');
|
||||||
|
await fs.mkdir(paths.tspmDir, { recursive: true });
|
||||||
|
|
||||||
// Check if another daemon is already running
|
// Check if another daemon is already running
|
||||||
if (await this.isDaemonRunning()) {
|
if (await this.isDaemonRunning()) {
|
||||||
throw new Error('Another TSPM daemon instance is already running');
|
throw new Error('Another TSPM daemon instance is already running');
|
26
ts/shared/protocol/error.codes.ts
Normal file
26
ts/shared/protocol/error.codes.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Standardized error codes for IPC communication
|
||||||
|
* These are used instead of string messages for better error handling
|
||||||
|
*/
|
||||||
|
export enum ErrorCode {
|
||||||
|
// General errors
|
||||||
|
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
|
||||||
|
INVALID_REQUEST = 'INVALID_REQUEST',
|
||||||
|
|
||||||
|
// Process errors
|
||||||
|
PROCESS_NOT_FOUND = 'PROCESS_NOT_FOUND',
|
||||||
|
PROCESS_ALREADY_EXISTS = 'PROCESS_ALREADY_EXISTS',
|
||||||
|
PROCESS_START_FAILED = 'PROCESS_START_FAILED',
|
||||||
|
PROCESS_STOP_FAILED = 'PROCESS_STOP_FAILED',
|
||||||
|
|
||||||
|
// Daemon errors
|
||||||
|
DAEMON_NOT_RUNNING = 'DAEMON_NOT_RUNNING',
|
||||||
|
DAEMON_ALREADY_RUNNING = 'DAEMON_ALREADY_RUNNING',
|
||||||
|
|
||||||
|
// Memory errors
|
||||||
|
MEMORY_LIMIT_EXCEEDED = 'MEMORY_LIMIT_EXCEEDED',
|
||||||
|
|
||||||
|
// Config errors
|
||||||
|
CONFIG_INVALID = 'CONFIG_INVALID',
|
||||||
|
CONFIG_SAVE_FAILED = 'CONFIG_SAVE_FAILED',
|
||||||
|
}
|
5
ts/shared/protocol/protocol.version.ts
Normal file
5
ts/shared/protocol/protocol.version.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Protocol version for client-daemon communication
|
||||||
|
* This allows for version compatibility checks between client and daemon
|
||||||
|
*/
|
||||||
|
export const PROTOCOL_VERSION = '1.0.0';
|
Reference in New Issue
Block a user