Skip to content

Commit dee8025

Browse files
committed
Merge branch 'main' into cga
2 parents 127ba03 + ac1f58b commit dee8025

File tree

18 files changed

+5321
-2503
lines changed

18 files changed

+5321
-2503
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ jobs:
4747
matrix:
4848
package:
4949
- appstash
50+
- clean-ansi
5051
- fetch-api-client
5152
- find-pkg
5253
- http-errors

packages/clean-ansi/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file.
4+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5+
6+
## 0.1.1 (2025-11-24)
7+
8+
**Note:** Version bump only for package clean-ansi

packages/clean-ansi/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# clean-ansi 🧹
2+
3+
<p align="center">
4+
<img src="https://raw.githubusercontent.com/hyperweb-io/dev-utils/refs/heads/main/docs/img/logo.svg" width="80">
5+
<br />
6+
<strong>strip ANSI escape codes from strings</strong>
7+
<br />
8+
<br />
9+
Remove all ANSI escape codes from terminal output
10+
<br />
11+
<br />
12+
<a href="https://github.com/hyperweb-io/dev-utils/actions/workflows/ci.yml">
13+
<img height="20" src="https://github.com/hyperweb-io/dev-utils/actions/workflows/ci.yml/badge.svg" />
14+
</a>
15+
<a href="https://github.com/hyperweb-io/dev-utils/blob/main/LICENSE">
16+
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
17+
</a>
18+
</p>
19+
20+
## Why clean-ansi?
21+
22+
Terminal output often contains ANSI escape codes for colors, cursor movement, and formatting. `clean-ansi` removes these codes to give you clean, plain text that's perfect for:
23+
24+
- 🧪 Testing terminal applications and comparing output
25+
- 📝 Logging to files without formatting codes
26+
- 🔍 Parsing command-line tool output
27+
- 📊 Processing terminal data for analysis
28+
29+
## Install
30+
31+
```sh
32+
npm install clean-ansi
33+
```
34+
35+
## Usage
36+
37+
```typescript
38+
import { cleanAnsi } from 'clean-ansi';
39+
40+
const coloredText = '\u001b[31mRed text\u001b[0m';
41+
const cleanText = cleanAnsi(coloredText);
42+
console.log(cleanText); // 'Red text'
43+
```
44+
45+
## API
46+
47+
### `cleanAnsi(input: string): string`
48+
49+
Removes all ANSI escape codes from the input string.
50+
51+
**Parameters:**
52+
- `input` - The string containing ANSI escape codes
53+
54+
**Returns:**
55+
- A string with all ANSI escape codes removed
56+
57+
**Handles:**
58+
- CSI sequences (color codes, cursor movement, etc.)
59+
- OSC sequences (OS commands like window title)
60+
- Other escape sequences
61+
62+
### `stripAnsi(input: string): string`
63+
64+
Alias for `cleanAnsi` for compatibility.
65+
66+
## Examples
67+
68+
```typescript
69+
import { cleanAnsi } from 'clean-ansi';
70+
71+
// Remove color codes
72+
cleanAnsi('\u001b[31mRed\u001b[0m \u001b[32mGreen\u001b[0m');
73+
// Returns: 'Red Green'
74+
75+
// Remove cursor movement codes
76+
cleanAnsi('Hello\u001b[AWorld');
77+
// Returns: 'HelloWorld'
78+
79+
// Handle complex terminal output
80+
cleanAnsi('\u001b[?25l\u001b[2J\u001b[H\u001b[31mError:\u001b[0m Message\u001b[?25h');
81+
// Returns: 'Error: Message'
82+
83+
// Preserve newlines and whitespace
84+
cleanAnsi('\u001b[31mLine 1\u001b[0m\nLine 2');
85+
// Returns: 'Line 1\nLine 2'
86+
```
87+
88+
## Design Philosophy
89+
90+
clean-ansi embraces simplicity:
91+
- 🎯 Zero dependencies
92+
- 🪶 Tiny footprint
93+
- 🚀 Fast and predictable
94+
- 💎 Pure function
95+
- 📖 Clear, focused API
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { cleanAnsi, stripAnsi } from '../src';
2+
3+
describe('cleanAnsi', () => {
4+
it('should return empty string for null or undefined input', () => {
5+
expect(cleanAnsi('')).toBe('');
6+
expect(cleanAnsi(null as any)).toBe('');
7+
expect(cleanAnsi(undefined as any)).toBe('');
8+
});
9+
10+
it('should return the same string if no ANSI codes are present', () => {
11+
const text = 'Hello, World!';
12+
expect(cleanAnsi(text)).toBe(text);
13+
});
14+
15+
it('should remove CSI color codes', () => {
16+
const text = '\u001b[31mRed text\u001b[0m';
17+
expect(cleanAnsi(text)).toBe('Red text');
18+
});
19+
20+
it('should remove multiple color codes', () => {
21+
const text = '\u001b[31mRed\u001b[0m \u001b[32mGreen\u001b[0m \u001b[34mBlue\u001b[0m';
22+
expect(cleanAnsi(text)).toBe('Red Green Blue');
23+
});
24+
25+
it('should remove cursor movement codes', () => {
26+
const text = 'Hello\u001b[AWorld';
27+
expect(cleanAnsi(text)).toBe('HelloWorld');
28+
});
29+
30+
it('should remove clear screen codes', () => {
31+
const text = 'Before\u001b[2JAfter';
32+
expect(cleanAnsi(text)).toBe('BeforeAfter');
33+
});
34+
35+
it('should remove cursor visibility codes', () => {
36+
const text = 'Text\u001b[?25hMore text\u001b[?25l';
37+
expect(cleanAnsi(text)).toBe('TextMore text');
38+
});
39+
40+
it('should remove OSC sequences with BEL terminator', () => {
41+
const text = '\u001b]0;Window Title\u0007Content';
42+
expect(cleanAnsi(text)).toBe('Content');
43+
});
44+
45+
it('should remove OSC sequences with ESC\\ terminator', () => {
46+
const text = '\u001b]2;Title\u001b\\Content';
47+
expect(cleanAnsi(text)).toBe('Content');
48+
});
49+
50+
it('should remove other escape sequences', () => {
51+
const text = 'Save\u001b7Position\u001b8Restore';
52+
expect(cleanAnsi(text)).toBe('SavePositionRestore');
53+
});
54+
55+
it('should handle complex strings with multiple ANSI codes', () => {
56+
const text = '\u001b[31m\u001b[1mBold Red\u001b[0m\u001b[32m Green \u001b[0m\u001b]0;Title\u0007Normal';
57+
expect(cleanAnsi(text)).toBe('Bold Red Green Normal');
58+
});
59+
60+
it('should preserve newlines and other whitespace', () => {
61+
const text = '\u001b[31mLine 1\u001b[0m\nLine 2\n\tTabbed';
62+
expect(cleanAnsi(text)).toBe('Line 1\nLine 2\n\tTabbed');
63+
});
64+
65+
it('should handle strings with only ANSI codes', () => {
66+
const text = '\u001b[31m\u001b[0m\u001b[32m\u001b[0m';
67+
expect(cleanAnsi(text)).toBe('');
68+
});
69+
70+
it('should handle complex real-world terminal output', () => {
71+
const text = '\u001b[?25l\u001b[2J\u001b[H\u001b[31mError:\u001b[0m Something went wrong\u001b[?25h';
72+
expect(cleanAnsi(text)).toBe('Error: Something went wrong');
73+
});
74+
75+
it('should handle 256 color codes', () => {
76+
const text = '\u001b[38;5;208mOrange text\u001b[0m';
77+
expect(cleanAnsi(text)).toBe('Orange text');
78+
});
79+
80+
it('should handle RGB color codes', () => {
81+
const text = '\u001b[38;2;255;0;0mRed text\u001b[0m';
82+
expect(cleanAnsi(text)).toBe('Red text');
83+
});
84+
});
85+
86+
describe('stripAnsi (alias)', () => {
87+
it('should work the same as cleanAnsi', () => {
88+
const text = '\u001b[31mRed text\u001b[0m';
89+
expect(stripAnsi(text)).toBe('Red text');
90+
expect(stripAnsi(text)).toBe(cleanAnsi(text));
91+
});
92+
93+
it('should be the same function reference', () => {
94+
expect(stripAnsi).toBe(cleanAnsi);
95+
});
96+
});

packages/clean-ansi/jest.config.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: "ts-jest",
4+
testEnvironment: "node",
5+
transform: {
6+
"^.+\\.tsx?$": [
7+
"ts-jest",
8+
{
9+
babelConfig: false,
10+
tsconfig: "tsconfig.json",
11+
},
12+
],
13+
},
14+
transformIgnorePatterns: [`/node_modules/*`],
15+
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
16+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
17+
modulePathIgnorePatterns: ["dist/*"]
18+
};

packages/clean-ansi/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "clean-ansi",
3+
"version": "0.1.1",
4+
"author": "Dan Lynch <[email protected]>",
5+
"description": "Remove ANSI escape codes from strings",
6+
"main": "index.js",
7+
"module": "esm/index.js",
8+
"types": "index.d.ts",
9+
"homepage": "https://github.com/hyperweb-io/dev-utils",
10+
"license": "MIT",
11+
"publishConfig": {
12+
"access": "public",
13+
"directory": "dist"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/hyperweb-io/dev-utils"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/hyperweb-io/dev-utils/issues"
21+
},
22+
"scripts": {
23+
"copy": "makage assets",
24+
"clean": "makage clean",
25+
"prepublishOnly": "npm run build",
26+
"build": "npm run clean && makage build-ts && npm run copy",
27+
"lint": "eslint . --fix",
28+
"test": "jest",
29+
"test:watch": "jest --watch"
30+
},
31+
"devDependencies": {
32+
"makage": "0.1.5"
33+
},
34+
"keywords": [
35+
"ansi",
36+
"strip",
37+
"clean",
38+
"escape codes",
39+
"terminal",
40+
"colors"
41+
]
42+
}
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
1-
// stripAnsi.ts
21
/**
32
* Remove ANSI escape codes from a string.
43
* Handles:
54
* - CSI sequences: \u001b[... (color codes, cursor movement, etc.)
65
* - OSC sequences: \u001b]... (OS commands like window title)
76
* - Other escape sequences: \u001b followed by single characters
87
*/
9-
export function stripAnsi(input: string): string {
8+
export function cleanAnsi(input: string): string {
109
if (!input) return '';
11-
10+
1211
// Match CSI (Control Sequence Introducer) sequences: ESC[ followed by parameters and final character
1312
// Examples: \u001b[31m, \u001b[A, \u001b[2J, \u001b[?25h
1413
const csiPattern = /\u001b\[[0-9;?]*[A-Za-z]/g;
15-
14+
1615
// Match OSC (Operating System Command) sequences: ESC] followed by parameters and terminator
1716
// Terminated by BEL (\u0007) or ESC\ (\u001b\\)
1817
// Examples: \u001b]0;title\u0007, \u001b]2;title\u001b\\
1918
// Handle BEL-terminated sequences
2019
const oscBelPattern = /\u001b\][^\u0007]*\u0007/g;
2120
// Handle ESC\-terminated sequences (more complex, need to match ESC\ specifically)
2221
const oscEscPattern = /\u001b\][^\u001b]*\u001b\\/g;
23-
22+
2423
// Match other escape sequences: ESC followed by single character
2524
// Examples: \u001bD (index), \u001bM (reverse index), \u001b7 (save cursor), \u001b8 (restore cursor)
2625
const otherEscPattern = /\u001b[0-9A-Za-z]/g;
27-
26+
2827
return input
2928
.replace(csiPattern, '')
3029
.replace(oscBelPattern, '')
3130
.replace(oscEscPattern, '')
3231
.replace(otherEscPattern, '');
3332
}
34-
33+
34+
// Alias for compatibility
35+
export const stripAnsi = cleanAnsi;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist/esm",
5+
"module": "ES2015"
6+
}
7+
}

packages/clean-ansi/tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "src/"
6+
},
7+
"include": ["src/**/*.ts"],
8+
"exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"]
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file.
4+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5+
6+
## 0.1.1 (2025-11-24)
7+
8+
**Note:** Version bump only for package create-gen-app-test

0 commit comments

Comments
 (0)