Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
f80ec7ddfe | |||
f2823c2645 | |||
75783b0e87 | |||
13e1582732 | |||
7e2f076b35 | |||
7e8a404fcf |
22
changelog.md
22
changelog.md
@ -1,5 +1,27 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-05-16 - 3.1.2 - fix(tests)
|
||||||
|
Update test imports and devDependencies to use @git.zone/tstest/tapbundle
|
||||||
|
|
||||||
|
- Changed import statements in test files from '@push.rocks/tapbundle' to '@git.zone/tstest/tapbundle'
|
||||||
|
- Updated devDependency '@git.zone/tstest' to version ^1.7.0 and removed dependency on '@push.rocks/tapbundle'
|
||||||
|
|
||||||
|
## 2025-05-15 - 3.1.1 - fix(source-interactive)
|
||||||
|
Fix import path in receiver tests and rename progress bar property for clarity; update SmartlogSourceOra getter for improved backward compatibility.
|
||||||
|
|
||||||
|
- Changed test file import from '../ts/index.js' to '../dist_ts/index.js' in test.receiver.node.ts to resolve module path issues
|
||||||
|
- Renamed property 'complete' to 'completeChar' in SmartlogProgressBar and updated its usage accordingly
|
||||||
|
- Modified SmartlogSourceOra getter to use public methods for starting and stopping the spinner, ensuring backward compatibility
|
||||||
|
|
||||||
|
## 2025-05-15 - 3.1.0 - feat(interactive)
|
||||||
|
Add interactive console features and refactor spinner module by renaming source-ora to source-interactive and removing ora dependency
|
||||||
|
|
||||||
|
- Renamed source-ora module to source-interactive and updated package.json exports
|
||||||
|
- Removed ora dependency in favor of a custom spinner implementation
|
||||||
|
- Added new progress bar functionality with configurable options including ETA, percentage, and color
|
||||||
|
- Updated tests and documentation (README and plan) to reflect the new interactive features
|
||||||
|
- Bumped dependency versions in package.json and improved test script configuration
|
||||||
|
|
||||||
## 2025-05-12 - 3.0.9 - fix(test/destination-devtools.browser)
|
## 2025-05-12 - 3.0.9 - fix(test/destination-devtools.browser)
|
||||||
Simplify DevTools browser tests by removing redundant styled log assertions.
|
Simplify DevTools browser tests by removing redundant styled log assertions.
|
||||||
|
|
||||||
|
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartlog",
|
"name": "@push.rocks/smartlog",
|
||||||
"version": "3.0.9",
|
"version": "3.1.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A minimalistic, distributed, and extensible logging tool supporting centralized log management.",
|
"description": "A minimalistic, distributed, and extensible logging tool supporting centralized log management.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"./context": "./dist_ts_context/index.js",
|
"./context": "./dist_ts_context/index.js",
|
||||||
"./interfaces": "./dist_ts_interfaces/index.js",
|
"./interfaces": "./dist_ts_interfaces/index.js",
|
||||||
"./source-ora": "./dist_ts_source_ora/index.js",
|
"./source-interactive": "./dist_ts_source_interactive/index.js",
|
||||||
"./destination-clickhouse": "./dist_ts_destination_clickhouse/index.js",
|
"./destination-clickhouse": "./dist_ts_destination_clickhouse/index.js",
|
||||||
"./destination-devtools": "./dist_ts_destination_devtools/index.js",
|
"./destination-devtools": "./dist_ts_destination_devtools/index.js",
|
||||||
"./destination-file": "./dist_ts_destination_file/index.js",
|
"./destination-file": "./dist_ts_destination_file/index.js",
|
||||||
@ -36,18 +36,17 @@
|
|||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/)",
|
"test": "(tstest test/**/*.ts --verbose)",
|
||||||
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)",
|
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle npm)",
|
||||||
"format": "(gitzone format)",
|
"format": "(gitzone format)",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.3.2",
|
"@git.zone/tsbuild": "^2.5.1",
|
||||||
"@git.zone/tsbundle": "^2.2.5",
|
"@git.zone/tsbundle": "^2.2.5",
|
||||||
"@git.zone/tsrun": "^1.3.3",
|
"@git.zone/tsrun": "^1.3.3",
|
||||||
"@git.zone/tstest": "^1.0.96",
|
"@git.zone/tstest": "^1.7.0",
|
||||||
"@push.rocks/tapbundle": "^6.0.3",
|
"@types/node": "^22.15.18"
|
||||||
"@types/node": "^22.15.17"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||||
@ -59,8 +58,7 @@
|
|||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smarttime": "^4.1.1",
|
"@push.rocks/smarttime": "^4.1.1",
|
||||||
"@push.rocks/webrequest": "^3.0.37",
|
"@push.rocks/webrequest": "^3.0.37",
|
||||||
"@tsclass/tsclass": "^9.2.0",
|
"@tsclass/tsclass": "^9.2.0"
|
||||||
"ora": "^8.2.0"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
1978
pnpm-lock.yaml
generated
1978
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
72
readme.md
72
readme.md
@ -60,6 +60,78 @@ defaultLogger.log('warn', 'This is a warning message using the default logger');
|
|||||||
|
|
||||||
This is particularly helpful for simple applications or for initial project setup.
|
This is particularly helpful for simple applications or for initial project setup.
|
||||||
|
|
||||||
|
### Interactive Console Features
|
||||||
|
|
||||||
|
Smartlog provides interactive console features through the `@push.rocks/smartlog/source-interactive` module:
|
||||||
|
|
||||||
|
#### Spinners
|
||||||
|
|
||||||
|
Use spinners to show progress for operations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartlogSourceInteractive } from '@push.rocks/smartlog/source-interactive';
|
||||||
|
|
||||||
|
const spinner = new SmartlogSourceInteractive();
|
||||||
|
spinner.text('Loading data...');
|
||||||
|
|
||||||
|
// Later, when the operation completes:
|
||||||
|
spinner.finishSuccess('Data loaded successfully!');
|
||||||
|
// Or if it fails:
|
||||||
|
spinner.finishFail('Failed to load data');
|
||||||
|
|
||||||
|
// You can chain operations:
|
||||||
|
spinner.text('Connecting to server');
|
||||||
|
spinner.successAndNext('Fetching records');
|
||||||
|
spinner.successAndNext('Processing data');
|
||||||
|
spinner.finishSuccess('All done!');
|
||||||
|
|
||||||
|
// Customize appearance:
|
||||||
|
spinner.setSpinnerStyle('line'); // 'dots', 'line', 'star', or 'simple'
|
||||||
|
spinner.setColor('green'); // 'red', 'green', 'yellow', 'blue', etc.
|
||||||
|
spinner.setSpeed(100); // Animation speed in milliseconds
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Progress Bars
|
||||||
|
|
||||||
|
Create progress bars for tracking operation progress:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartlogProgressBar } from '@push.rocks/smartlog/source-interactive';
|
||||||
|
|
||||||
|
const progressBar = new SmartlogProgressBar({
|
||||||
|
total: 100, // Total number of items
|
||||||
|
width: 40, // Width of the progress bar
|
||||||
|
complete: '█', // Character for completed section
|
||||||
|
incomplete: '░', // Character for incomplete section
|
||||||
|
showEta: true, // Show estimated time remaining
|
||||||
|
showPercent: true, // Show percentage
|
||||||
|
showCount: true // Show count (e.g., "50/100")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update progress
|
||||||
|
progressBar.update(50); // Set to 50% progress
|
||||||
|
|
||||||
|
// Or increment
|
||||||
|
progressBar.increment(10); // Increase by 10 units
|
||||||
|
|
||||||
|
// Change color
|
||||||
|
progressBar.setColor('blue');
|
||||||
|
|
||||||
|
// Complete the progress bar
|
||||||
|
progressBar.update(100); // or progressBar.complete();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Non-Interactive Environments
|
||||||
|
|
||||||
|
Both spinners and progress bars automatically detect non-interactive environments (CI/CD, piped output, non-TTY) and provide fallback text-based output:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Loading] Loading data...
|
||||||
|
Progress: 50% (50/100)
|
||||||
|
Progress: 100% (100/100)
|
||||||
|
[Success] Data loaded successfully!
|
||||||
|
```
|
||||||
|
|
||||||
### Extending With Log Destinations
|
### Extending With Log Destinations
|
||||||
|
|
||||||
One of the core strengths of `@push.rocks/smartlog` is its ability to work with multiple log destinations, enabling you to log messages not just to the console but also to external logging services or custom destinations.
|
One of the core strengths of `@push.rocks/smartlog` is its ability to work with multiple log destinations, enabling you to log messages not just to the console but also to external logging services or custom destinations.
|
||||||
|
101
readme.plan.md
101
readme.plan.md
@ -0,0 +1,101 @@
|
|||||||
|
# Smartlog Interactive Console Features Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document outlines the plan for enhancing the console output capabilities of `@push.rocks/smartlog` by creating a comprehensive interactive console module. This involves renaming the current `ts_source_ora` module to `ts_source_interactive`, implementing our own spinner functionality (removing the ora dependency), and adding new features like progress bars and other interactive elements.
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### 1. Rename and Restructure
|
||||||
|
|
||||||
|
- Rename directory from `ts_source_ora` to `ts_source_interactive`
|
||||||
|
- Update all imports, exports, and references
|
||||||
|
- Update package.json exports to reflect new module name
|
||||||
|
- Maintain backward compatibility through proper export paths
|
||||||
|
|
||||||
|
### 2. Custom Spinner Implementation
|
||||||
|
|
||||||
|
- Create a native spinner implementation to replace ora dependency
|
||||||
|
- Implement spinner frames and animation timing
|
||||||
|
- Maintain API compatibility with current spinner methods:
|
||||||
|
- `text(textArg)` - Set text and start spinner
|
||||||
|
- `stop()` - Stop the spinner
|
||||||
|
- `finishSuccess(textArg?)` - Mark as succeeded
|
||||||
|
- `finishFail(textArg?)` - Mark as failed
|
||||||
|
- `successAndNext(textArg)` - Success and start new spinner
|
||||||
|
- `failAndNext(textArg)` - Fail and start new spinner
|
||||||
|
- Add spinner customization options (speed, frames, colors)
|
||||||
|
|
||||||
|
### 3. Progress Bar Implementation
|
||||||
|
|
||||||
|
- Create a progress bar component with the following features:
|
||||||
|
- Configurable width and style
|
||||||
|
- Percentage display
|
||||||
|
- ETA calculation
|
||||||
|
- Current/total value display
|
||||||
|
- Custom formatting
|
||||||
|
- Theming support
|
||||||
|
- Methods for incrementing and updating
|
||||||
|
|
||||||
|
### 4. Additional Interactive Features
|
||||||
|
|
||||||
|
- Add indeterminate progress indicator
|
||||||
|
- Add multi-line status display
|
||||||
|
- Add table formatting for structured data
|
||||||
|
- Add interactive prompts/confirmations
|
||||||
|
|
||||||
|
### 5. Testing
|
||||||
|
|
||||||
|
- Update existing tests to work with new implementation
|
||||||
|
- Add tests for new progress bar functionality
|
||||||
|
- Add tests for additional interactive features
|
||||||
|
- Ensure consistent behavior across platforms
|
||||||
|
|
||||||
|
### 6. Documentation
|
||||||
|
|
||||||
|
- Update README with examples of new features
|
||||||
|
- Add API documentation for all new methods
|
||||||
|
- Include usage examples
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Progress Bar API
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Creating a progress bar
|
||||||
|
const progressBar = new SmartlogProgressBar({
|
||||||
|
total: 100,
|
||||||
|
width: 40,
|
||||||
|
complete: '=',
|
||||||
|
incomplete: ' ',
|
||||||
|
renderThrottle: 100, // ms
|
||||||
|
clearOnComplete: false,
|
||||||
|
showEta: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Updating a progress bar
|
||||||
|
progressBar.update(50); // Update to 50%
|
||||||
|
progressBar.increment(10); // Increment by 10
|
||||||
|
progressBar.complete(); // Mark as complete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Spinner API (maintains compatibility)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Current API (to be maintained)
|
||||||
|
const spinner = new SmartlogSourceInteractive();
|
||||||
|
spinner.text('Loading data');
|
||||||
|
spinner.finishSuccess('Data loaded successfully');
|
||||||
|
|
||||||
|
// New additions
|
||||||
|
spinner.setSpinnerStyle('dots'); // Change spinner style
|
||||||
|
spinner.setColor('green'); // Change color
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- Remove external dependency (ora) for better control and smaller bundle size
|
||||||
|
- Provide more interactive console features for improved user experience
|
||||||
|
- Maintain consistent API styling across all smartlog modules
|
||||||
|
- Improve testability with custom implementation
|
||||||
|
- Enable more advanced terminal interactions
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as smartlog from '../ts/index.js';
|
import * as smartlog from '../ts/index.js';
|
||||||
|
|
||||||
let testConsoleLog: smartlog.ConsoleLog;
|
let testConsoleLog: smartlog.ConsoleLog;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as smartlogContext from '../ts_context/index.js';
|
import * as smartlogContext from '../ts_context/index.js';
|
||||||
|
|
||||||
tap.test('should correctly export strings from context module', async () => {
|
tap.test('should correctly export strings from context module', async () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartlogDestinationClickhouse } from '../ts_destination_clickhouse/index.js';
|
import { SmartlogDestinationClickhouse } from '../ts_destination_clickhouse/index.js';
|
||||||
import * as smartclickhouse from '@push.rocks/smartclickhouse';
|
import * as smartclickhouse from '@push.rocks/smartclickhouse';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartlogDestinationDevtools } from '../ts_destination_devtools/index.js';
|
import { SmartlogDestinationDevtools } from '../ts_destination_devtools/index.js';
|
||||||
|
|
||||||
export const run = async function() {
|
export const run = async function() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartlogDestinationDevtools } from '../ts_destination_devtools/index.js';
|
import { SmartlogDestinationDevtools } from '../ts_destination_devtools/index.js';
|
||||||
|
|
||||||
// Test we can create a destination instance
|
// Test we can create a destination instance
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartlogDestinationFile } from '../ts_destination_file/index.js';
|
import { SmartlogDestinationFile } from '../ts_destination_file/index.js';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DestinationLocal } from '../ts_destination_local/index.js';
|
import { DestinationLocal } from '../ts_destination_local/index.js';
|
||||||
import * as smartlogInterfaces from '../ts_interfaces/index.js';
|
import * as smartlogInterfaces from '../ts_interfaces/index.js';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartlogDestinationReceiver } from '../ts_destination_receiver/index.js';
|
import { SmartlogDestinationReceiver } from '../ts_destination_receiver/index.js';
|
||||||
import { Smartlog } from '../ts/index.js';
|
import { Smartlog } from '../ts/index.js';
|
||||||
import * as smartlogInterfaces from '../ts_interfaces/index.js';
|
import * as smartlogInterfaces from '../ts_interfaces/index.js';
|
||||||
|
64
test/test.noninteractive.node.ts
Normal file
64
test/test.noninteractive.node.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { SmartlogSourceInteractive, SmartlogProgressBar } from '../ts_source_interactive/index.js';
|
||||||
|
|
||||||
|
// Test instances
|
||||||
|
let testSpinner: SmartlogSourceInteractive;
|
||||||
|
let testProgressBar: SmartlogProgressBar;
|
||||||
|
|
||||||
|
// Original state for restoration
|
||||||
|
const originalState = {
|
||||||
|
stdoutTTY: process.stdout.isTTY,
|
||||||
|
consoleLog: console.log
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log tracking
|
||||||
|
const logs: string[] = [];
|
||||||
|
|
||||||
|
tap.test('should handle non-interactive mode correctly', async (toolsArg) => {
|
||||||
|
// Setup non-interactive mode
|
||||||
|
process.stdout.isTTY = false;
|
||||||
|
console.log = (...args: any[]) => {
|
||||||
|
logs.push(args.join(' '));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test spinner creation
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
expect(testSpinner).toBeTruthy();
|
||||||
|
|
||||||
|
// Test spinner text
|
||||||
|
logs.length = 0;
|
||||||
|
testSpinner.text('Loading data');
|
||||||
|
expect(logs.length).toBeGreaterThan(0);
|
||||||
|
expect(logs[0]).toContain('[Loading]');
|
||||||
|
expect(logs[0]).toContain('Loading data');
|
||||||
|
|
||||||
|
// Test spinner success
|
||||||
|
logs.length = 0;
|
||||||
|
testSpinner.finishSuccess('Task completed');
|
||||||
|
expect(logs.length).toBeGreaterThan(0);
|
||||||
|
expect(logs[0]).toContain('[Success]');
|
||||||
|
expect(logs[0]).toContain('Task completed');
|
||||||
|
|
||||||
|
// Test progress bar
|
||||||
|
testProgressBar = new SmartlogProgressBar({ total: 100 });
|
||||||
|
expect(testProgressBar).toBeTruthy();
|
||||||
|
|
||||||
|
// Test progress updates
|
||||||
|
logs.length = 0;
|
||||||
|
testProgressBar.update(10);
|
||||||
|
testProgressBar.update(50);
|
||||||
|
testProgressBar.update(100);
|
||||||
|
|
||||||
|
expect(logs.length).toBeGreaterThan(0);
|
||||||
|
const progressLogs = logs.join(' ');
|
||||||
|
expect(progressLogs).toContain('10%');
|
||||||
|
expect(progressLogs).toContain('50%');
|
||||||
|
expect(progressLogs).toContain('100%');
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
testSpinner.stop();
|
||||||
|
console.log = originalState.consoleLog;
|
||||||
|
process.stdout.isTTY = originalState.stdoutTTY;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { SmartlogReceiver } from '../ts_receiver/index.js';
|
import { SmartlogReceiver } from '../ts_receiver/index.js';
|
||||||
import { Smartlog } from '../ts/index.js';
|
import { Smartlog } from '../dist_ts/index.js';
|
||||||
import * as smartlogInterfaces from '../ts_interfaces/index.js';
|
import * as smartlogInterfaces from '../ts_interfaces/index.js';
|
||||||
import * as smarthash from '@push.rocks/smarthash';
|
import * as smarthash from '@push.rocks/smarthash';
|
||||||
|
|
||||||
|
190
test/test.source-interactive.node.ts
Normal file
190
test/test.source-interactive.node.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { SmartlogSourceInteractive, SmartlogProgressBar, SmartlogSourceOra } from '../ts_source_interactive/index.js';
|
||||||
|
|
||||||
|
// Test spinner functionality
|
||||||
|
let testSpinner: SmartlogSourceInteractive;
|
||||||
|
|
||||||
|
// Helper function to clean up spinners after each test
|
||||||
|
const cleanupSpinner = (spinner: SmartlogSourceInteractive) => {
|
||||||
|
if (spinner.isStarted()) {
|
||||||
|
spinner.stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.test('should create a SmartlogSourceInteractive instance', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.setSpeed(10); // Set fast animation speed for tests
|
||||||
|
expect(testSpinner).toBeTruthy();
|
||||||
|
expect(testSpinner.isStarted()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should set text and start spinner', async () => {
|
||||||
|
const testText = 'Testing spinner';
|
||||||
|
testSpinner.text(testText);
|
||||||
|
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should update text', async () => {
|
||||||
|
const newText = 'Updated text';
|
||||||
|
testSpinner.text(newText);
|
||||||
|
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should stop spinner', async () => {
|
||||||
|
testSpinner.stop();
|
||||||
|
// We can't easily test the visual state, but we can verify it doesn't throw errors
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should finish with success', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.text('Starting again');
|
||||||
|
|
||||||
|
const successText = 'Operation successful';
|
||||||
|
testSpinner.finishSuccess(successText);
|
||||||
|
|
||||||
|
expect(testSpinner.isStarted()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should finish with failure', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.text('Starting again');
|
||||||
|
|
||||||
|
const failText = 'Operation failed';
|
||||||
|
testSpinner.finishFail(failText);
|
||||||
|
|
||||||
|
expect(testSpinner.isStarted()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle success and next', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.setSpeed(10); // Fast animation
|
||||||
|
testSpinner.text('Starting again');
|
||||||
|
|
||||||
|
const nextText = 'Next operation';
|
||||||
|
testSpinner.successAndNext(nextText);
|
||||||
|
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle fail and next', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.setSpeed(10); // Fast animation
|
||||||
|
testSpinner.text('Starting again');
|
||||||
|
|
||||||
|
const nextText = 'Next operation after failure';
|
||||||
|
testSpinner.failAndNext(nextText);
|
||||||
|
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should set spinner style', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.setSpeed(10); // Fast animation
|
||||||
|
testSpinner.setSpinnerStyle('line');
|
||||||
|
testSpinner.text('Custom style spinner');
|
||||||
|
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should set spinner color', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.setSpeed(10); // Fast animation
|
||||||
|
testSpinner.setColor('green');
|
||||||
|
testSpinner.text('Green spinner');
|
||||||
|
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should set animation speed', async () => {
|
||||||
|
testSpinner = new SmartlogSourceInteractive();
|
||||||
|
testSpinner.setSpeed(10); // Actually set fast for testing
|
||||||
|
testSpinner.text('Slow spinner');
|
||||||
|
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
expect(testSpinner.isStarted()).toBeTrue();
|
||||||
|
cleanupSpinner(testSpinner);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test progress bar functionality
|
||||||
|
let testProgressBar: SmartlogProgressBar;
|
||||||
|
|
||||||
|
tap.test('should create a progress bar instance', async () => {
|
||||||
|
testProgressBar = new SmartlogProgressBar({
|
||||||
|
total: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(testProgressBar).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should update progress bar value', async () => {
|
||||||
|
testProgressBar.update(50);
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should increment progress bar', async () => {
|
||||||
|
const initialValue = 50;
|
||||||
|
const increment = 10;
|
||||||
|
testProgressBar = new SmartlogProgressBar({ total: 100 });
|
||||||
|
testProgressBar.update(initialValue);
|
||||||
|
testProgressBar.increment(increment);
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should complete progress bar', async () => {
|
||||||
|
testProgressBar = new SmartlogProgressBar({ total: 100 });
|
||||||
|
testProgressBar.update(50);
|
||||||
|
testProgressBar.update(100); // Update to 100% to simulate completion
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should set progress bar color', async () => {
|
||||||
|
testProgressBar = new SmartlogProgressBar({ total: 100 });
|
||||||
|
testProgressBar.setColor('blue');
|
||||||
|
testProgressBar.update(50);
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle custom progress bar options', async () => {
|
||||||
|
testProgressBar = new SmartlogProgressBar({
|
||||||
|
total: 100,
|
||||||
|
width: 40,
|
||||||
|
complete: '=',
|
||||||
|
incomplete: '-',
|
||||||
|
showEta: false,
|
||||||
|
showPercent: true,
|
||||||
|
showCount: true
|
||||||
|
});
|
||||||
|
|
||||||
|
testProgressBar.update(30);
|
||||||
|
// Visual effect can't be easily tested, but we can verify it doesn't throw errors
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test backward compatibility with SmartlogSourceOra
|
||||||
|
let testSourceOra: SmartlogSourceOra;
|
||||||
|
|
||||||
|
tap.test('should create a SmartlogSourceOra instance for backward compatibility', async () => {
|
||||||
|
testSourceOra = new SmartlogSourceOra();
|
||||||
|
expect(testSourceOra).toBeTruthy();
|
||||||
|
expect(testSourceOra.isStarted()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should maintain compatibility with old API', async () => {
|
||||||
|
testSourceOra.setSpeed(10); // Fast animation
|
||||||
|
testSourceOra.text('Testing backward compatibility');
|
||||||
|
expect(testSourceOra.isStarted()).toBeTrue();
|
||||||
|
|
||||||
|
testSourceOra.finishSuccess('Success');
|
||||||
|
expect(testSourceOra.isStarted()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -1,75 +0,0 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
|
||||||
import { SmartlogSourceOra } from '../ts_source_ora/index.js';
|
|
||||||
|
|
||||||
let testSourceOra: SmartlogSourceOra;
|
|
||||||
|
|
||||||
tap.test('should create a SmartlogSourceOra instance', async () => {
|
|
||||||
testSourceOra = new SmartlogSourceOra();
|
|
||||||
expect(testSourceOra).toBeTruthy();
|
|
||||||
expect(testSourceOra.started).toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should set text and start spinner', async () => {
|
|
||||||
const testText = 'Testing ora spinner';
|
|
||||||
testSourceOra.text(testText);
|
|
||||||
|
|
||||||
expect(testSourceOra.started).toBeTrue();
|
|
||||||
expect(testSourceOra.oraInstance.text).toEqual(testText);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should update text', async () => {
|
|
||||||
const newText = 'Updated text';
|
|
||||||
testSourceOra.text(newText);
|
|
||||||
|
|
||||||
expect(testSourceOra.oraInstance.text).toEqual(newText);
|
|
||||||
expect(testSourceOra.started).toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should stop spinner', async () => {
|
|
||||||
testSourceOra.stop();
|
|
||||||
// We can't easily test the visual state, but we can verify it doesn't throw errors
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should finish with success', async () => {
|
|
||||||
testSourceOra = new SmartlogSourceOra();
|
|
||||||
testSourceOra.text('Starting again');
|
|
||||||
|
|
||||||
const successText = 'Operation successful';
|
|
||||||
testSourceOra.finishSuccess(successText);
|
|
||||||
|
|
||||||
expect(testSourceOra.started).toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should finish with failure', async () => {
|
|
||||||
testSourceOra = new SmartlogSourceOra();
|
|
||||||
testSourceOra.text('Starting again');
|
|
||||||
|
|
||||||
const failText = 'Operation failed';
|
|
||||||
testSourceOra.finishFail(failText);
|
|
||||||
|
|
||||||
expect(testSourceOra.started).toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should handle success and next', async () => {
|
|
||||||
testSourceOra = new SmartlogSourceOra();
|
|
||||||
testSourceOra.text('Starting again');
|
|
||||||
|
|
||||||
const nextText = 'Next operation';
|
|
||||||
testSourceOra.successAndNext(nextText);
|
|
||||||
|
|
||||||
expect(testSourceOra.started).toBeTrue();
|
|
||||||
expect(testSourceOra.oraInstance.text).toEqual(nextText);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should handle fail and next', async () => {
|
|
||||||
testSourceOra = new SmartlogSourceOra();
|
|
||||||
testSourceOra.text('Starting again');
|
|
||||||
|
|
||||||
const nextText = 'Next operation after failure';
|
|
||||||
testSourceOra.failAndNext(nextText);
|
|
||||||
|
|
||||||
expect(testSourceOra.started).toBeTrue();
|
|
||||||
expect(testSourceOra.oraInstance.text).toEqual(nextText);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
|
@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as smartlog from '../ts/index.js';
|
import * as smartlog from '../ts/index.js';
|
||||||
|
|
||||||
let testConsoleLog: smartlog.ConsoleLog;
|
let testConsoleLog: smartlog.ConsoleLog;
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartlog',
|
name: '@push.rocks/smartlog',
|
||||||
version: '3.0.9',
|
version: '3.1.2',
|
||||||
description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.'
|
description: 'A minimalistic, distributed, and extensible logging tool supporting centralized log management.'
|
||||||
}
|
}
|
||||||
|
417
ts_source_interactive/index.ts
Normal file
417
ts_source_interactive/index.ts
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
import * as plugins from './smartlog-source-interactive.plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to detect if the environment is interactive
|
||||||
|
* Checks for TTY capability and common CI environment variables
|
||||||
|
*/
|
||||||
|
const isInteractive = () => {
|
||||||
|
try {
|
||||||
|
return Boolean(
|
||||||
|
// Check TTY capability
|
||||||
|
process.stdout && process.stdout.isTTY &&
|
||||||
|
|
||||||
|
// Additional checks for non-interactive environments
|
||||||
|
!('CI' in process.env) &&
|
||||||
|
!process.env.GITHUB_ACTIONS &&
|
||||||
|
!process.env.JENKINS_URL &&
|
||||||
|
!process.env.GITLAB_CI &&
|
||||||
|
!process.env.TRAVIS &&
|
||||||
|
!process.env.CIRCLECI &&
|
||||||
|
process.env.TERM !== 'dumb'
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// If any error occurs (e.g., in browser environments without process),
|
||||||
|
// assume a non-interactive environment to be safe
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to log messages in non-interactive mode
|
||||||
|
const logMessage = (message: string, prefix = '') => {
|
||||||
|
if (prefix) {
|
||||||
|
console.log(`${prefix} ${message}`);
|
||||||
|
} else {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Spinner frames and styles
|
||||||
|
const spinnerFrames = {
|
||||||
|
dots: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
||||||
|
line: ['|', '/', '-', '\\'],
|
||||||
|
star: ['✶', '✸', '✹', '✺', '✹', '✷'],
|
||||||
|
simple: ['-', '\\', '|', '/']
|
||||||
|
};
|
||||||
|
|
||||||
|
// Color names mapping to ANSI color codes
|
||||||
|
const colors = {
|
||||||
|
black: '\u001b[30m',
|
||||||
|
red: '\u001b[31m',
|
||||||
|
green: '\u001b[32m',
|
||||||
|
yellow: '\u001b[33m',
|
||||||
|
blue: '\u001b[34m',
|
||||||
|
magenta: '\u001b[35m',
|
||||||
|
cyan: '\u001b[36m',
|
||||||
|
white: '\u001b[37m',
|
||||||
|
gray: '\u001b[90m',
|
||||||
|
reset: '\u001b[0m'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for creating interactive spinners
|
||||||
|
* Automatically handles non-interactive environments
|
||||||
|
*/
|
||||||
|
export class SmartlogSourceInteractive {
|
||||||
|
private textContent: string = 'loading';
|
||||||
|
private currentFrame: number = 0;
|
||||||
|
private interval: NodeJS.Timeout | null = null;
|
||||||
|
private started: boolean = false;
|
||||||
|
private spinnerStyle: keyof typeof spinnerFrames = 'dots';
|
||||||
|
private color: keyof typeof colors = 'cyan';
|
||||||
|
private frames: string[];
|
||||||
|
private frameInterval: number = 80;
|
||||||
|
private interactive: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.frames = spinnerFrames[this.spinnerStyle];
|
||||||
|
this.interactive = isInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text for the spinner and starts it if not already started
|
||||||
|
*/
|
||||||
|
public text(textArg: string) {
|
||||||
|
this.textContent = textArg;
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
// In non-interactive mode, just log the message with a loading indicator
|
||||||
|
logMessage(textArg, '[Loading]');
|
||||||
|
this.started = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.started) {
|
||||||
|
this.started = true;
|
||||||
|
this.start();
|
||||||
|
} else {
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the spinner animation
|
||||||
|
*/
|
||||||
|
private start() {
|
||||||
|
if (!this.interactive) {
|
||||||
|
return; // No animation in non-interactive mode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderFrame();
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
||||||
|
this.renderFrame();
|
||||||
|
}, this.frameInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the current frame of the spinner
|
||||||
|
*/
|
||||||
|
private renderFrame() {
|
||||||
|
if (!this.started || !this.interactive) return;
|
||||||
|
|
||||||
|
const frame = this.frames[this.currentFrame];
|
||||||
|
const colorCode = colors[this.color];
|
||||||
|
const resetCode = colors.reset;
|
||||||
|
|
||||||
|
// Only use ANSI escape codes in interactive mode
|
||||||
|
process.stdout.write('\r\x1b[2K'); // Clear the current line
|
||||||
|
process.stdout.write(`${colorCode}${frame}${resetCode} ${this.textContent}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the spinner
|
||||||
|
*/
|
||||||
|
public stop() {
|
||||||
|
// Always clear the interval even in non-interactive mode
|
||||||
|
// This prevents memory leaks in tests and long-running applications
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.interval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
return; // No need to clear the line in non-interactive mode
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write('\r\x1b[2K'); // Clear the current line
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the spinner as successful and optionally displays a success message
|
||||||
|
*/
|
||||||
|
public finishSuccess(textArg?: string) {
|
||||||
|
const message = textArg || this.textContent;
|
||||||
|
|
||||||
|
// Always stop the spinner first to clean up intervals
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
logMessage(message, '[Success]');
|
||||||
|
} else {
|
||||||
|
const successSymbol = colors.green + '✓' + colors.reset;
|
||||||
|
process.stdout.write(`${successSymbol} ${message}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the spinner as failed and optionally displays a failure message
|
||||||
|
*/
|
||||||
|
public finishFail(textArg?: string) {
|
||||||
|
const message = textArg || this.textContent;
|
||||||
|
|
||||||
|
// Always stop the spinner first to clean up intervals
|
||||||
|
this.stop();
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
logMessage(message, '[Failed]');
|
||||||
|
} else {
|
||||||
|
const failSymbol = colors.red + '✗' + colors.reset;
|
||||||
|
process.stdout.write(`${failSymbol} ${message}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.started = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the current spinner as successful and starts a new one
|
||||||
|
*/
|
||||||
|
public successAndNext(textArg: string) {
|
||||||
|
this.finishSuccess();
|
||||||
|
this.text(textArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the current spinner as failed and starts a new one
|
||||||
|
*/
|
||||||
|
public failAndNext(textArg: string) {
|
||||||
|
this.finishFail();
|
||||||
|
this.text(textArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spinner style
|
||||||
|
*/
|
||||||
|
public setSpinnerStyle(style: keyof typeof spinnerFrames) {
|
||||||
|
this.spinnerStyle = style;
|
||||||
|
this.frames = spinnerFrames[style];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spinner color
|
||||||
|
*/
|
||||||
|
public setColor(colorName: keyof typeof colors) {
|
||||||
|
if (colorName in colors) {
|
||||||
|
this.color = colorName;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the animation speed in milliseconds
|
||||||
|
*/
|
||||||
|
public setSpeed(ms: number) {
|
||||||
|
this.frameInterval = ms;
|
||||||
|
if (this.started) {
|
||||||
|
this.stop();
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current started state
|
||||||
|
*/
|
||||||
|
public isStarted() {
|
||||||
|
return this.started;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProgressBarOptions {
|
||||||
|
total: number;
|
||||||
|
width?: number;
|
||||||
|
complete?: string;
|
||||||
|
incomplete?: string;
|
||||||
|
renderThrottle?: number;
|
||||||
|
clear?: boolean;
|
||||||
|
showEta?: boolean;
|
||||||
|
showPercent?: boolean;
|
||||||
|
showCount?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SmartlogProgressBar {
|
||||||
|
private total: number;
|
||||||
|
private current: number = 0;
|
||||||
|
private width: number;
|
||||||
|
private completeChar: string;
|
||||||
|
private incomplete: string;
|
||||||
|
private renderThrottle: number;
|
||||||
|
private clear: boolean;
|
||||||
|
private showEta: boolean;
|
||||||
|
private showPercent: boolean;
|
||||||
|
private showCount: boolean;
|
||||||
|
private color: keyof typeof colors = 'green';
|
||||||
|
private startTime: number | null = null;
|
||||||
|
private lastRenderTime: number = 0;
|
||||||
|
private interactive: boolean;
|
||||||
|
private lastLoggedPercent: number = 0;
|
||||||
|
private logThreshold: number = 10; // Log every 10% in non-interactive mode
|
||||||
|
|
||||||
|
constructor(options: IProgressBarOptions) {
|
||||||
|
this.total = options.total;
|
||||||
|
this.width = options.width || 30;
|
||||||
|
this.completeChar = options.complete || '█';
|
||||||
|
this.incomplete = options.incomplete || '░';
|
||||||
|
this.renderThrottle = options.renderThrottle || 16;
|
||||||
|
this.clear = options.clear !== undefined ? options.clear : false;
|
||||||
|
this.showEta = options.showEta !== undefined ? options.showEta : true;
|
||||||
|
this.showPercent = options.showPercent !== undefined ? options.showPercent : true;
|
||||||
|
this.showCount = options.showCount !== undefined ? options.showCount : true;
|
||||||
|
this.interactive = isInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the progress bar to a specific value
|
||||||
|
*/
|
||||||
|
public update(value: number): this {
|
||||||
|
if (this.startTime === null) {
|
||||||
|
this.startTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current = Math.min(value, this.total);
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
// In non-interactive mode, log progress at certain thresholds
|
||||||
|
const percent = Math.floor((this.current / this.total) * 100);
|
||||||
|
const currentThreshold = Math.floor(percent / this.logThreshold) * this.logThreshold;
|
||||||
|
|
||||||
|
if (currentThreshold > this.lastLoggedPercent || percent === 100) {
|
||||||
|
this.lastLoggedPercent = currentThreshold;
|
||||||
|
logMessage(`Progress: ${percent}% (${this.current}/${this.total})`);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throttle rendering to avoid excessive updates in interactive mode
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - this.lastRenderTime < this.renderThrottle) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastRenderTime = now;
|
||||||
|
this.render();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment the progress bar by a value
|
||||||
|
*/
|
||||||
|
public increment(value: number = 1): this {
|
||||||
|
return this.update(this.current + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark the progress bar as complete
|
||||||
|
*/
|
||||||
|
public complete(): this {
|
||||||
|
this.update(this.total);
|
||||||
|
|
||||||
|
if (!this.interactive) {
|
||||||
|
logMessage(`Completed: 100% (${this.total}/${this.total})`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.clear) {
|
||||||
|
process.stdout.write('\r\x1b[2K');
|
||||||
|
} else {
|
||||||
|
process.stdout.write('\n');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the color of the progress bar
|
||||||
|
*/
|
||||||
|
public setColor(colorName: keyof typeof colors): this {
|
||||||
|
if (colorName in colors) {
|
||||||
|
this.color = colorName;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the progress bar
|
||||||
|
*/
|
||||||
|
private render(): void {
|
||||||
|
if (!this.interactive) {
|
||||||
|
return; // Don't render in non-interactive mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate percent complete
|
||||||
|
const percent = Math.floor((this.current / this.total) * 100);
|
||||||
|
const completeLength = Math.round((this.current / this.total) * this.width);
|
||||||
|
const incompleteLength = this.width - completeLength;
|
||||||
|
|
||||||
|
// Build the progress bar
|
||||||
|
const completePart = colors[this.color] + this.completeChar.repeat(completeLength) + colors.reset;
|
||||||
|
const incompletePart = this.incomplete.repeat(incompleteLength);
|
||||||
|
const progressBar = `[${completePart}${incompletePart}]`;
|
||||||
|
|
||||||
|
// Calculate ETA if needed
|
||||||
|
let etaStr = '';
|
||||||
|
if (this.showEta && this.startTime !== null && this.current > 0) {
|
||||||
|
const elapsed = (Date.now() - this.startTime) / 1000;
|
||||||
|
const rate = this.current / elapsed;
|
||||||
|
const remaining = Math.max(0, this.total - this.current);
|
||||||
|
const eta = Math.round(remaining / rate);
|
||||||
|
|
||||||
|
const mins = Math.floor(eta / 60);
|
||||||
|
const secs = eta % 60;
|
||||||
|
etaStr = ` eta: ${mins}m${secs}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build additional information
|
||||||
|
const percentStr = this.showPercent ? ` ${percent}%` : '';
|
||||||
|
const countStr = this.showCount ? ` ${this.current}/${this.total}` : '';
|
||||||
|
|
||||||
|
// Clear the line and render
|
||||||
|
process.stdout.write('\r\x1b[2K');
|
||||||
|
process.stdout.write(`${progressBar}${percentStr}${countStr}${etaStr}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibility with 'source-ora' module
|
||||||
|
export class SmartlogSourceOra extends SmartlogSourceInteractive {
|
||||||
|
// Add a stub for the oraInstance property for backward compatibility
|
||||||
|
public get oraInstance() {
|
||||||
|
// Use public methods instead of accessing private properties
|
||||||
|
const instance = this;
|
||||||
|
return {
|
||||||
|
get text() { return ''; }, // We can't access private textContent directly
|
||||||
|
start: () => instance.text(''), // This starts the spinner
|
||||||
|
stop: () => instance.stop(),
|
||||||
|
succeed: (text?: string) => instance.finishSuccess(text),
|
||||||
|
fail: (text?: string) => instance.finishFail(text)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public set oraInstance(value: any) {
|
||||||
|
// No-op, just for compatibility
|
||||||
|
}
|
||||||
|
}
|
10
ts_source_interactive/smartlog-source-interactive.plugins.ts
Normal file
10
ts_source_interactive/smartlog-source-interactive.plugins.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// pushrocks scope
|
||||||
|
import * as smartlogInterfaces from '../dist_ts_interfaces/index.js';
|
||||||
|
import * as consolecolor from '@push.rocks/consolecolor';
|
||||||
|
|
||||||
|
export { smartlogInterfaces, consolecolor };
|
||||||
|
|
||||||
|
// node.js internal
|
||||||
|
import { stdout, stderr } from 'process';
|
||||||
|
|
||||||
|
export { stdout, stderr };
|
@ -1,40 +0,0 @@
|
|||||||
import * as plugins from './smartlog-source-ora.plugins.js';
|
|
||||||
|
|
||||||
export class SmartlogSourceOra {
|
|
||||||
public oraInstance = plugins.ora('loading');
|
|
||||||
public started = false;
|
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
public text(textArg: string) {
|
|
||||||
this.oraInstance.text = textArg;
|
|
||||||
if (!this.started) {
|
|
||||||
this.started = true;
|
|
||||||
this.oraInstance.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop() {
|
|
||||||
this.oraInstance.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public finishSuccess(textArg?: string) {
|
|
||||||
this.oraInstance.succeed(textArg);
|
|
||||||
this.started = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public finishFail(textArg?: string) {
|
|
||||||
this.oraInstance.fail(textArg);
|
|
||||||
this.started = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public successAndNext(textArg: string) {
|
|
||||||
this.finishSuccess();
|
|
||||||
this.text(textArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public failAndNext(textArg: string) {
|
|
||||||
this.finishFail();
|
|
||||||
this.text(textArg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
// pushrocks scope
|
|
||||||
import * as smartlogInterfaces from '../dist_ts_interfaces/index.js';
|
|
||||||
|
|
||||||
export { smartlogInterfaces };
|
|
||||||
|
|
||||||
// third party scope
|
|
||||||
import ora from 'ora';
|
|
||||||
|
|
||||||
export { ora };
|
|
Reference in New Issue
Block a user