claude-yes 1.27.0 → 1.29.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-yes.js +11822 -426
- package/dist/cli.js +11822 -426
- package/dist/cli.js.map +163 -6
- package/dist/codex-yes.js +11822 -426
- package/dist/copilot-yes.js +11822 -426
- package/dist/cursor-yes.js +11822 -426
- package/dist/gemini-yes.js +11822 -426
- package/dist/grok-yes.js +11822 -426
- package/dist/index.js +11830 -361
- package/dist/index.js.map +163 -5
- package/dist/qwen-yes.js +11822 -426
- package/package.json +8 -8
- package/ts/ReadyManager.spec.ts +72 -0
- package/ts/cli.ts +4 -6
- package/ts/idleWaiter.spec.ts +55 -0
- package/ts/index.ts +19 -9
- package/ts/parseCliArgs.ts +5 -3
- package/ts/removeControlCharacters.spec.ts +74 -0
- package/ts/tryCatch.spec.ts +207 -0
- package/ts/utils.spec.ts +169 -0
- package/ts/yesLog.spec.ts +74 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-yes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.2",
|
|
4
4
|
"description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"import": "./dist/index.js",
|
|
34
34
|
"types": "./ts/index.ts"
|
|
35
35
|
},
|
|
36
|
-
"main": "dist/index.js",
|
|
37
36
|
"module": "ts/index.ts",
|
|
38
37
|
"types": "./ts/index.ts",
|
|
39
38
|
"bin": {
|
|
@@ -54,12 +53,14 @@
|
|
|
54
53
|
"dist"
|
|
55
54
|
],
|
|
56
55
|
"scripts": {
|
|
57
|
-
"build": "bun build
|
|
56
|
+
"build": "bun run build:index && bun run build:cli",
|
|
58
57
|
"postbuild": "bun ./ts/postbuild.ts",
|
|
59
|
-
"
|
|
58
|
+
"build:cli": "bun build ts/cli.ts --outdir=dist --target=node --sourcemap --external=node-pty --external=bun-pty --external=from-node-stream",
|
|
59
|
+
"build:index": "bun build ts/index.ts --outdir=dist --target=node --sourcemap --external=node-pty --external=bun-pty --external=from-node-stream",
|
|
60
|
+
"dev": "bun ts/index.ts",
|
|
60
61
|
"fmt": "bunx @biomejs/biome check --fix && bunx sort-package-json",
|
|
61
62
|
"prepack": "bun run build",
|
|
62
|
-
"prepare": "bunx husky",
|
|
63
|
+
"prepare": "bunx husky && bun run build",
|
|
63
64
|
"test": "bun test --coverage"
|
|
64
65
|
},
|
|
65
66
|
"lint-staged": {
|
|
@@ -88,7 +89,8 @@
|
|
|
88
89
|
]
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
|
-
"bun-pty": "^0.3.2"
|
|
92
|
+
"bun-pty": "^0.3.2",
|
|
93
|
+
"from-node-stream": "^0.0.11"
|
|
92
94
|
},
|
|
93
95
|
"devDependencies": {
|
|
94
96
|
"@biomejs/biome": "^2.2.5",
|
|
@@ -103,7 +105,6 @@
|
|
|
103
105
|
"cpu-wait": "^0.0.10",
|
|
104
106
|
"enhanced-ms": "^4.1.0",
|
|
105
107
|
"execa": "^9.6.0",
|
|
106
|
-
"from-node-stream": "^0.0.11",
|
|
107
108
|
"husky": "^9.1.7",
|
|
108
109
|
"lint-staged": "^16.1.4",
|
|
109
110
|
"p-map": "^7.0.3",
|
|
@@ -114,7 +115,6 @@
|
|
|
114
115
|
"strip-ansi-control-characters": "^2.0.0",
|
|
115
116
|
"terminal-render": "^1.2.0",
|
|
116
117
|
"tsa-composer": "^3.0.2",
|
|
117
|
-
"tsx": "^4.20.3",
|
|
118
118
|
"vitest": "^3.2.4",
|
|
119
119
|
"yargs": "^18.0.0"
|
|
120
120
|
},
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ReadyManager } from './ReadyManager';
|
|
3
|
+
|
|
4
|
+
describe('ReadyManager', () => {
|
|
5
|
+
it('should start in not ready state', () => {
|
|
6
|
+
const manager = new ReadyManager();
|
|
7
|
+
expect(manager.wait()).toBeInstanceOf(Promise);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should resolve wait when ready is called', async () => {
|
|
11
|
+
const manager = new ReadyManager();
|
|
12
|
+
const waitPromise = manager.wait();
|
|
13
|
+
|
|
14
|
+
manager.ready();
|
|
15
|
+
|
|
16
|
+
await expect(waitPromise).resolves.toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should resolve immediately if already ready', async () => {
|
|
20
|
+
const manager = new ReadyManager();
|
|
21
|
+
manager.ready();
|
|
22
|
+
|
|
23
|
+
const result = manager.wait();
|
|
24
|
+
expect(result).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle multiple waiters', async () => {
|
|
28
|
+
const manager = new ReadyManager();
|
|
29
|
+
const wait1 = manager.wait();
|
|
30
|
+
const wait2 = manager.wait();
|
|
31
|
+
const wait3 = manager.wait();
|
|
32
|
+
|
|
33
|
+
manager.ready();
|
|
34
|
+
|
|
35
|
+
await Promise.all([
|
|
36
|
+
expect(wait1).resolves.toBeUndefined(),
|
|
37
|
+
expect(wait2).resolves.toBeUndefined(),
|
|
38
|
+
expect(wait3).resolves.toBeUndefined(),
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should reset to not ready when unready is called', async () => {
|
|
43
|
+
const manager = new ReadyManager();
|
|
44
|
+
manager.ready();
|
|
45
|
+
manager.unready();
|
|
46
|
+
|
|
47
|
+
expect(manager.wait()).toBeInstanceOf(Promise);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle ready with no waiting queue', () => {
|
|
51
|
+
const manager = new ReadyManager();
|
|
52
|
+
manager.ready(); // Should not throw even if no one is waiting
|
|
53
|
+
expect(manager.wait()).toBeUndefined(); // Should be ready now
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle multiple ready/unready cycles', async () => {
|
|
57
|
+
const manager = new ReadyManager();
|
|
58
|
+
|
|
59
|
+
// First cycle
|
|
60
|
+
const wait1 = manager.wait();
|
|
61
|
+
manager.ready();
|
|
62
|
+
await wait1;
|
|
63
|
+
|
|
64
|
+
// Reset
|
|
65
|
+
manager.unready();
|
|
66
|
+
|
|
67
|
+
// Second cycle
|
|
68
|
+
const wait2 = manager.wait();
|
|
69
|
+
manager.ready();
|
|
70
|
+
await expect(wait2).resolves.toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
package/ts/cli.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import DIE from 'phpdie';
|
|
3
|
-
import cliYes, {
|
|
4
|
-
|
|
3
|
+
import cliYes, { parseCliArgs } from './';
|
|
4
|
+
|
|
5
|
+
// if (!globalThis.Bun) // run with same arguments in Bun
|
|
5
6
|
|
|
6
7
|
// Parse CLI arguments
|
|
7
8
|
const config = parseCliArgs(process.argv);
|
|
8
9
|
|
|
9
10
|
// Validate CLI name
|
|
10
|
-
if (!config.cli)
|
|
11
|
-
DIE('missing cli def');
|
|
12
|
-
}
|
|
11
|
+
if (!config.cli) DIE('missing cli def');
|
|
13
12
|
|
|
14
|
-
// console.clear();
|
|
15
13
|
if (config.verbose) {
|
|
16
14
|
process.env.VERBOSE = 'true'; // enable verbose logging in yesLog.ts
|
|
17
15
|
console.log(config);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { IdleWaiter } from './idleWaiter';
|
|
3
|
+
|
|
4
|
+
describe('IdleWaiter', () => {
|
|
5
|
+
it('should initialize with current time', () => {
|
|
6
|
+
const waiter = new IdleWaiter();
|
|
7
|
+
expect(waiter.lastActivityTime).toBeCloseTo(Date.now(), -2);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should update lastActivityTime when ping is called', () => {
|
|
11
|
+
const waiter = new IdleWaiter();
|
|
12
|
+
const initialTime = waiter.lastActivityTime;
|
|
13
|
+
|
|
14
|
+
// Wait a small amount
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
while (Date.now() - start < 10) {
|
|
17
|
+
// busy wait
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
waiter.ping();
|
|
21
|
+
expect(waiter.lastActivityTime).toBeGreaterThan(initialTime);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return this when ping is called for chaining', () => {
|
|
25
|
+
const waiter = new IdleWaiter();
|
|
26
|
+
expect(waiter.ping()).toBe(waiter);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should resolve wait immediately when already idle', async () => {
|
|
30
|
+
const waiter = new IdleWaiter();
|
|
31
|
+
|
|
32
|
+
// Wait enough time to be considered idle
|
|
33
|
+
const start = Date.now();
|
|
34
|
+
while (Date.now() - start < 50) {
|
|
35
|
+
// busy wait
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// This should resolve quickly since enough time has passed
|
|
39
|
+
const waitPromise = waiter.wait(10);
|
|
40
|
+
await expect(waitPromise).resolves.toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should respect custom check interval', () => {
|
|
44
|
+
const waiter = new IdleWaiter();
|
|
45
|
+
waiter.checkInterval = 200;
|
|
46
|
+
|
|
47
|
+
expect(waiter.checkInterval).toBe(200);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should have ping method that chains', () => {
|
|
51
|
+
const waiter = new IdleWaiter();
|
|
52
|
+
const result = waiter.ping().ping().ping();
|
|
53
|
+
expect(result).toBe(waiter);
|
|
54
|
+
});
|
|
55
|
+
});
|
package/ts/index.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { fromReadable, fromWritable } from 'from-node-stream';
|
|
2
|
-
import {
|
|
2
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import DIE from 'phpdie';
|
|
5
5
|
import sflow from 'sflow';
|
|
6
6
|
import { TerminalTextRender } from 'terminal-render';
|
|
7
|
-
import tsaComposer from 'tsa-composer';
|
|
8
7
|
import rawConfig from '../cli-yes.config.js';
|
|
9
8
|
import {
|
|
10
9
|
extractSessionId,
|
|
11
10
|
getSessionForCwd,
|
|
12
11
|
storeSessionForCwd,
|
|
13
12
|
} from './codexSessionManager.js';
|
|
14
|
-
import { defineCliYesConfig } from './defineConfig.js';
|
|
15
13
|
import { IdleWaiter } from './idleWaiter';
|
|
16
14
|
import { ReadyManager } from './ReadyManager';
|
|
17
15
|
import { removeControlCharacters } from './removeControlCharacters';
|
|
@@ -22,9 +20,11 @@ import {
|
|
|
22
20
|
updateCurrentTaskStatus,
|
|
23
21
|
} from './runningLock';
|
|
24
22
|
import { catcher } from './tryCatch';
|
|
25
|
-
import { deepMixin } from './utils';
|
|
26
23
|
import { yesLog } from './yesLog';
|
|
27
24
|
|
|
25
|
+
export { parseCliArgs } from './parseCliArgs';
|
|
26
|
+
export { removeControlCharacters };
|
|
27
|
+
|
|
28
28
|
export type AgentCliConfig = {
|
|
29
29
|
install?: string; // hint user for install command if not installed
|
|
30
30
|
version?: string; // hint user for version command to check if installed
|
|
@@ -157,17 +157,29 @@ export default async function cliYes({
|
|
|
157
157
|
// const pty = await import('node-pty');
|
|
158
158
|
|
|
159
159
|
// its recommened to use bun-pty in windows
|
|
160
|
-
const pty = await import('node-pty')
|
|
161
|
-
.catch(async () => await import('
|
|
160
|
+
const pty = await (globalThis.Bun ? import('bun-pty') : import('node-pty'))
|
|
161
|
+
// .catch(async () => await import('node-pty'))
|
|
162
162
|
.catch(async () =>
|
|
163
163
|
DIE('Please install node-pty or bun-pty, run this: bun install bun-pty'),
|
|
164
164
|
);
|
|
165
|
+
console.log(globalThis.Bun);
|
|
166
|
+
|
|
167
|
+
// Detect if running as sub-agent
|
|
168
|
+
const isSubAgent = !!process.env.CLAUDE_PPID;
|
|
169
|
+
if (isSubAgent) {
|
|
170
|
+
console.log(
|
|
171
|
+
`[${cli}-yes] Running as sub-agent (CLAUDE_PPID=${process.env.CLAUDE_PPID})`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
165
174
|
|
|
166
175
|
const getPtyOptions = () => ({
|
|
167
176
|
name: 'xterm-color',
|
|
168
177
|
...getTerminalDimensions(),
|
|
169
178
|
cwd: cwd ?? process.cwd(),
|
|
170
|
-
env:
|
|
179
|
+
env: {
|
|
180
|
+
...(env ?? (process.env as Record<string, string>)),
|
|
181
|
+
CLAUDE_PPID: String(process.ppid),
|
|
182
|
+
},
|
|
171
183
|
});
|
|
172
184
|
|
|
173
185
|
// Apply CLI specific configurations (moved to CLI_CONFIGURES)
|
|
@@ -496,5 +508,3 @@ export default async function cliYes({
|
|
|
496
508
|
};
|
|
497
509
|
}
|
|
498
510
|
}
|
|
499
|
-
|
|
500
|
-
export { removeControlCharacters };
|
package/ts/parseCliArgs.ts
CHANGED
|
@@ -87,12 +87,14 @@ export function parseCliArgs(argv: string[]) {
|
|
|
87
87
|
? rawArgs.slice(cliArgIndex ?? 0, dashIndex ?? undefined)
|
|
88
88
|
: [];
|
|
89
89
|
const dashPrompt: string | undefined =
|
|
90
|
-
dashIndex
|
|
91
|
-
?
|
|
92
|
-
:
|
|
90
|
+
dashIndex === undefined
|
|
91
|
+
? undefined
|
|
92
|
+
: rawArgs.slice(dashIndex + 1).join(' ');
|
|
93
93
|
|
|
94
94
|
// Return the config object that would be passed to cliYes (same logic as cli.ts:99-121)
|
|
95
95
|
return {
|
|
96
|
+
cwd: process.cwd(),
|
|
97
|
+
env: process.env as Record<string, string>,
|
|
96
98
|
cli: (cliName ||
|
|
97
99
|
parsedArgv.cli ||
|
|
98
100
|
parsedArgv._[0]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { removeControlCharacters } from './removeControlCharacters';
|
|
3
|
+
|
|
4
|
+
describe('removeControlCharacters', () => {
|
|
5
|
+
it('should remove ANSI escape sequences', () => {
|
|
6
|
+
const input = '\u001b[31mRed text\u001b[0m';
|
|
7
|
+
const expected = 'Red text';
|
|
8
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should remove cursor positioning codes', () => {
|
|
12
|
+
const input = '\u001b[1;1HHello\u001b[2;1HWorld';
|
|
13
|
+
const expected = 'HelloWorld';
|
|
14
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should remove color codes', () => {
|
|
18
|
+
const input = '\u001b[32mGreen\u001b[0m \u001b[31mRed\u001b[0m';
|
|
19
|
+
const expected = 'Green Red';
|
|
20
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should remove complex ANSI sequences', () => {
|
|
24
|
+
const input = '\u001b[1;33;40mYellow on black\u001b[0m';
|
|
25
|
+
const expected = 'Yellow on black';
|
|
26
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should handle empty string', () => {
|
|
30
|
+
expect(removeControlCharacters('')).toBe('');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should handle string with no control characters', () => {
|
|
34
|
+
const input = 'Plain text with no escape sequences';
|
|
35
|
+
expect(removeControlCharacters(input)).toBe(input);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should remove CSI sequences with multiple parameters', () => {
|
|
39
|
+
const input = '\u001b[38;5;196mBright red\u001b[0m';
|
|
40
|
+
const expected = 'Bright red';
|
|
41
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should remove C1 control characters', () => {
|
|
45
|
+
const input = '\u009b[32mGreen text\u009b[0m';
|
|
46
|
+
const expected = 'Green text';
|
|
47
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle mixed control and regular characters', () => {
|
|
51
|
+
const input =
|
|
52
|
+
'Start\u001b[1mBold\u001b[0mMiddle\u001b[4mUnderline\u001b[0mEnd';
|
|
53
|
+
const expected = 'StartBoldMiddleUnderlineEnd';
|
|
54
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should preserve spaces and newlines', () => {
|
|
58
|
+
const input = 'Line 1\u001b[31m\nRed Line 2\u001b[0m\n\nLine 4';
|
|
59
|
+
const expected = 'Line 1\nRed Line 2\n\nLine 4';
|
|
60
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle cursor movement sequences', () => {
|
|
64
|
+
const input = '\u001b[2AUp\u001b[3BDown\u001b[4CRight\u001b[5DLeft';
|
|
65
|
+
const expected = 'UpDownRightLeft';
|
|
66
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should handle erase sequences', () => {
|
|
70
|
+
const input = 'Text\u001b[2JClear\u001b[KLine';
|
|
71
|
+
const expected = 'TextClearLine';
|
|
72
|
+
expect(removeControlCharacters(input)).toBe(expected);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { catcher } from './tryCatch';
|
|
3
|
+
|
|
4
|
+
describe('tryCatch', () => {
|
|
5
|
+
describe('curried overload', () => {
|
|
6
|
+
it('should return a function when called with only catchFn', () => {
|
|
7
|
+
const catchFn = () => 'error';
|
|
8
|
+
const result = catcher(catchFn);
|
|
9
|
+
expect(typeof result).toBe('function');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should catch errors and call catchFn', () => {
|
|
13
|
+
let catchedError: unknown;
|
|
14
|
+
const catchFn = (error: unknown) => {
|
|
15
|
+
catchedError = error;
|
|
16
|
+
return 'caught';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let calledArgs: unknown[] = [];
|
|
20
|
+
const errorFn = (...args: unknown[]) => {
|
|
21
|
+
calledArgs = args;
|
|
22
|
+
throw new Error('test error');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const wrappedFn = catcher(catchFn)(errorFn);
|
|
26
|
+
const result = wrappedFn('arg1', 'arg2');
|
|
27
|
+
|
|
28
|
+
expect(result).toBe('caught');
|
|
29
|
+
expect(catchedError).toBeInstanceOf(Error);
|
|
30
|
+
expect(calledArgs).toEqual(['arg1', 'arg2']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should return normal result when no error occurs', () => {
|
|
34
|
+
let catchCalled = false;
|
|
35
|
+
const catchFn = () => {
|
|
36
|
+
catchCalled = true;
|
|
37
|
+
return 'error';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let calledArgs: unknown[] = [];
|
|
41
|
+
const normalFn = (...args: unknown[]) => {
|
|
42
|
+
calledArgs = args;
|
|
43
|
+
return 'success';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const wrappedFn = catcher(catchFn)(normalFn);
|
|
47
|
+
const result = wrappedFn('arg1', 'arg2');
|
|
48
|
+
|
|
49
|
+
expect(result).toBe('success');
|
|
50
|
+
expect(catchCalled).toBe(false);
|
|
51
|
+
expect(calledArgs).toEqual(['arg1', 'arg2']);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('direct overload', () => {
|
|
56
|
+
it('should catch errors and call catchFn directly', () => {
|
|
57
|
+
let catchedError: unknown;
|
|
58
|
+
const catchFn = (error: unknown) => {
|
|
59
|
+
catchedError = error;
|
|
60
|
+
return 'caught';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let calledArgs: unknown[] = [];
|
|
64
|
+
const errorFn = (...args: unknown[]) => {
|
|
65
|
+
calledArgs = args;
|
|
66
|
+
throw new Error('test error');
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const wrappedFn = catcher(catchFn, errorFn);
|
|
70
|
+
const result = wrappedFn('arg1', 'arg2');
|
|
71
|
+
|
|
72
|
+
expect(result).toBe('caught');
|
|
73
|
+
expect(catchedError).toBeInstanceOf(Error);
|
|
74
|
+
expect(calledArgs).toEqual(['arg1', 'arg2']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return normal result when no error occurs directly', () => {
|
|
78
|
+
let catchCalled = false;
|
|
79
|
+
const catchFn = () => {
|
|
80
|
+
catchCalled = true;
|
|
81
|
+
return 'error';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let calledArgs: unknown[] = [];
|
|
85
|
+
const normalFn = (...args: unknown[]) => {
|
|
86
|
+
calledArgs = args;
|
|
87
|
+
return 'success';
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const wrappedFn = catcher(catchFn, normalFn);
|
|
91
|
+
const result = wrappedFn('arg1', 'arg2');
|
|
92
|
+
|
|
93
|
+
expect(result).toBe('success');
|
|
94
|
+
expect(catchCalled).toBe(false);
|
|
95
|
+
expect(calledArgs).toEqual(['arg1', 'arg2']);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('error handling', () => {
|
|
100
|
+
it('should handle different error types', () => {
|
|
101
|
+
const results: unknown[] = [];
|
|
102
|
+
const catchFn = (error: unknown) => {
|
|
103
|
+
results.push(error);
|
|
104
|
+
return 'handled';
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// String error
|
|
108
|
+
const stringErrorFn = catcher(catchFn, () => {
|
|
109
|
+
throw 'string error';
|
|
110
|
+
});
|
|
111
|
+
expect(stringErrorFn()).toBe('handled');
|
|
112
|
+
expect(results[0]).toBe('string error');
|
|
113
|
+
|
|
114
|
+
// Object error
|
|
115
|
+
const objectError = { message: 'object error' };
|
|
116
|
+
const objectErrorFn = catcher(catchFn, () => {
|
|
117
|
+
throw objectError;
|
|
118
|
+
});
|
|
119
|
+
expect(objectErrorFn()).toBe('handled');
|
|
120
|
+
expect(results[1]).toBe(objectError);
|
|
121
|
+
|
|
122
|
+
// null error
|
|
123
|
+
const nullErrorFn = catcher(catchFn, () => {
|
|
124
|
+
throw null;
|
|
125
|
+
});
|
|
126
|
+
expect(nullErrorFn()).toBe('handled');
|
|
127
|
+
expect(results[2]).toBe(null);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should preserve function parameters', () => {
|
|
131
|
+
let caughtError: unknown;
|
|
132
|
+
const catchFn = (error: unknown) => {
|
|
133
|
+
caughtError = error;
|
|
134
|
+
return 'caught';
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let testArgs: [number, string, boolean] | undefined;
|
|
138
|
+
const testFn = (a: number, b: string, c: boolean) => {
|
|
139
|
+
testArgs = [a, b, c];
|
|
140
|
+
if (a > 5) throw new Error('too big');
|
|
141
|
+
return `${a}-${b}-${c}`;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const wrappedFn = catcher(catchFn, testFn);
|
|
145
|
+
|
|
146
|
+
// Normal execution
|
|
147
|
+
expect(wrappedFn(3, 'test', true)).toBe('3-test-true');
|
|
148
|
+
expect(testArgs).toEqual([3, 'test', true]);
|
|
149
|
+
|
|
150
|
+
// Error execution
|
|
151
|
+
expect(wrappedFn(10, 'error', false)).toBe('caught');
|
|
152
|
+
expect(testArgs).toEqual([10, 'error', false]);
|
|
153
|
+
expect(caughtError).toBeInstanceOf(Error);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should handle functions with no parameters', () => {
|
|
157
|
+
let caughtError: unknown;
|
|
158
|
+
const catchFn = (error: unknown) => {
|
|
159
|
+
caughtError = error;
|
|
160
|
+
return 'no params caught';
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
let called = false;
|
|
164
|
+
const noParamsFn = () => {
|
|
165
|
+
called = true;
|
|
166
|
+
throw new Error('no params error');
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const wrappedFn = catcher(catchFn, noParamsFn);
|
|
170
|
+
const result = wrappedFn();
|
|
171
|
+
|
|
172
|
+
expect(result).toBe('no params caught');
|
|
173
|
+
expect(called).toBe(true);
|
|
174
|
+
expect(caughtError).toBeInstanceOf(Error);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should handle functions returning different types', () => {
|
|
178
|
+
const catchFn = () => null;
|
|
179
|
+
|
|
180
|
+
// Function returning number
|
|
181
|
+
const numberFn = catcher(catchFn, () => 42);
|
|
182
|
+
expect(numberFn()).toBe(42);
|
|
183
|
+
|
|
184
|
+
// Function returning object
|
|
185
|
+
const obj = { key: 'value' };
|
|
186
|
+
const objectFn = catcher(catchFn, () => obj);
|
|
187
|
+
expect(objectFn()).toBe(obj);
|
|
188
|
+
|
|
189
|
+
// Function returning undefined
|
|
190
|
+
const undefinedFn = catcher(catchFn, () => undefined);
|
|
191
|
+
expect(undefinedFn()).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('type safety', () => {
|
|
196
|
+
it('should maintain function signature', () => {
|
|
197
|
+
const catchFn = (error: unknown) => 'error';
|
|
198
|
+
const originalFn = (a: number, b: string): string => `${a}-${b}`;
|
|
199
|
+
|
|
200
|
+
const wrappedFn = catcher(catchFn, originalFn);
|
|
201
|
+
|
|
202
|
+
// This should be type-safe
|
|
203
|
+
const result: string = wrappedFn(1, 'test');
|
|
204
|
+
expect(result).toBe('1-test');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|