claude-yes 1.28.0 → 1.30.0
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 +20037 -442
- package/dist/cli.js +20037 -442
- package/dist/cli.js.map +353 -9
- package/dist/codex-yes.js +20037 -442
- package/dist/copilot-yes.js +20037 -442
- package/dist/cursor-yes.js +20037 -442
- package/dist/gemini-yes.js +20037 -442
- package/dist/grok-yes.js +20037 -442
- package/dist/index.js +18433 -379
- package/dist/index.js.map +311 -7
- package/dist/qwen-yes.js +20037 -442
- package/package.json +15 -11
- package/ts/ReadyManager.spec.ts +72 -0
- package/ts/catcher.spec.ts +260 -0
- package/ts/catcher.ts +35 -0
- package/ts/cli.ts +24 -6
- package/ts/idleWaiter.spec.ts +55 -0
- package/ts/index.ts +65 -21
- package/ts/parseCliArgs.ts +8 -6
- package/ts/removeControlCharacters.spec.ts +74 -0
- package/ts/utils.spec.ts +169 -0
- package/ts/yesLog.spec.ts +74 -0
- package/ts/yesLog.ts +1 -1
- package/ts/tryCatch.ts +0 -25
package/ts/index.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
import { execaCommand, execaCommandSync, parseCommandString } from 'execa';
|
|
1
2
|
import { fromReadable, fromWritable } from 'from-node-stream';
|
|
2
|
-
import {
|
|
3
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import DIE from 'phpdie';
|
|
5
6
|
import sflow from 'sflow';
|
|
6
7
|
import { TerminalTextRender } from 'terminal-render';
|
|
7
|
-
import tsaComposer from 'tsa-composer';
|
|
8
8
|
import rawConfig from '../cli-yes.config.js';
|
|
9
|
+
import { catcher } from './catcher.js';
|
|
9
10
|
import {
|
|
10
11
|
extractSessionId,
|
|
11
12
|
getSessionForCwd,
|
|
12
13
|
storeSessionForCwd,
|
|
13
14
|
} from './codexSessionManager.js';
|
|
14
|
-
import { defineCliYesConfig } from './defineConfig.js';
|
|
15
15
|
import { IdleWaiter } from './idleWaiter';
|
|
16
16
|
import { ReadyManager } from './ReadyManager';
|
|
17
17
|
import { removeControlCharacters } from './removeControlCharacters';
|
|
@@ -21,10 +21,11 @@ import {
|
|
|
21
21
|
shouldUseLock,
|
|
22
22
|
updateCurrentTaskStatus,
|
|
23
23
|
} from './runningLock';
|
|
24
|
-
import { catcher } from './tryCatch';
|
|
25
|
-
import { deepMixin } from './utils';
|
|
26
24
|
import { yesLog } from './yesLog';
|
|
27
25
|
|
|
26
|
+
export { parseCliArgs } from './parseCliArgs';
|
|
27
|
+
export { removeControlCharacters };
|
|
28
|
+
|
|
28
29
|
export type AgentCliConfig = {
|
|
29
30
|
install?: string; // hint user for install command if not installed
|
|
30
31
|
version?: string; // hint user for version command to check if installed
|
|
@@ -36,6 +37,7 @@ export type AgentCliConfig = {
|
|
|
36
37
|
defaultArgs?: string[]; // function to ensure certain args are present
|
|
37
38
|
noEOL?: boolean; // if true, do not split lines by \n, used for codex, which uses cursor-move csi code instead of \n to move lines
|
|
38
39
|
promptArg?: (string & {}) | 'first-arg' | 'last-arg'; // argument name to pass the prompt, e.g. --prompt, or first-arg for positional arg
|
|
40
|
+
bunx?: boolean; // if true, use bunx to run the binary
|
|
39
41
|
};
|
|
40
42
|
export type CliYesConfig = {
|
|
41
43
|
clis: { [key: string]: AgentCliConfig };
|
|
@@ -91,6 +93,7 @@ export default async function cliYes({
|
|
|
91
93
|
removeControlCharactersFromStdout = false, // = !process.stdout.isTTY,
|
|
92
94
|
verbose = false,
|
|
93
95
|
queue = true,
|
|
96
|
+
install = false,
|
|
94
97
|
}: {
|
|
95
98
|
cli: SUPPORTED_CLIS;
|
|
96
99
|
cliArgs?: string[];
|
|
@@ -103,6 +106,7 @@ export default async function cliYes({
|
|
|
103
106
|
removeControlCharactersFromStdout?: boolean;
|
|
104
107
|
verbose?: boolean;
|
|
105
108
|
queue?: boolean;
|
|
109
|
+
install?: boolean; // if true, install the cli tool if not installed, e.g. will run `npm install -g cursor-agent`
|
|
106
110
|
}) {
|
|
107
111
|
// those overrides seems only works in bun
|
|
108
112
|
// await Promise.allSettled([
|
|
@@ -118,7 +122,11 @@ export default async function cliYes({
|
|
|
118
122
|
// });
|
|
119
123
|
|
|
120
124
|
if (!cli) throw new Error(`cli is required`);
|
|
121
|
-
const conf =
|
|
125
|
+
const conf =
|
|
126
|
+
CLIS_CONFIG[cli] ||
|
|
127
|
+
DIE(
|
|
128
|
+
`Unsupported cli tool: ${cli}, current process.argv: ${process.argv.join(' ')}`,
|
|
129
|
+
);
|
|
122
130
|
|
|
123
131
|
// Acquire lock before starting agent (if in git repo or same cwd and lock is not disabled)
|
|
124
132
|
const workingDir = cwd ?? process.cwd();
|
|
@@ -157,17 +165,29 @@ export default async function cliYes({
|
|
|
157
165
|
// const pty = await import('node-pty');
|
|
158
166
|
|
|
159
167
|
// its recommened to use bun-pty in windows
|
|
160
|
-
const pty = await
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
const pty = await (globalThis.Bun
|
|
169
|
+
? import('bun-pty')
|
|
170
|
+
: import('node-pty')
|
|
171
|
+
).catch(async () =>
|
|
172
|
+
DIE('Please install node-pty or bun-pty, run this: bun install bun-pty'),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Detect if running as sub-agent
|
|
176
|
+
const isSubAgent = !!process.env.CLAUDE_PPID;
|
|
177
|
+
if (isSubAgent) {
|
|
178
|
+
console.log(
|
|
179
|
+
`[${cli}-yes] Running as sub-agent (CLAUDE_PPID=${process.env.CLAUDE_PPID})`,
|
|
164
180
|
);
|
|
181
|
+
}
|
|
165
182
|
|
|
166
183
|
const getPtyOptions = () => ({
|
|
167
184
|
name: 'xterm-color',
|
|
168
185
|
...getTerminalDimensions(),
|
|
169
186
|
cwd: cwd ?? process.cwd(),
|
|
170
|
-
env:
|
|
187
|
+
env: {
|
|
188
|
+
...(env ?? (process.env as Record<string, string>)),
|
|
189
|
+
CLAUDE_PPID: String(process.ppid),
|
|
190
|
+
},
|
|
171
191
|
});
|
|
172
192
|
|
|
173
193
|
// Apply CLI specific configurations (moved to CLI_CONFIGURES)
|
|
@@ -211,34 +231,60 @@ export default async function cliYes({
|
|
|
211
231
|
}
|
|
212
232
|
const cliCommand = cliConf?.binary || cli;
|
|
213
233
|
|
|
234
|
+
const spawn = () => {
|
|
235
|
+
// const [bin, ...args] = [...parseCommandString((cliConf.bunx ? 'bunx --bun ' : '') + cliCommand), ...(cliArgs)];
|
|
236
|
+
// console.log(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
|
|
237
|
+
// return pty.spawn(bin!, args, getPtyOptions());
|
|
238
|
+
return pty.spawn(cliCommand, cliArgs, getPtyOptions());
|
|
239
|
+
};
|
|
214
240
|
let shell = catcher(
|
|
215
|
-
|
|
241
|
+
// error handler
|
|
242
|
+
(error: unknown, fn, ...args) => {
|
|
216
243
|
console.error(`Fatal: Failed to start ${cliCommand}.`);
|
|
244
|
+
|
|
217
245
|
if (cliConf?.install && isCommandNotFoundError(error))
|
|
246
|
+
if (install) {
|
|
247
|
+
console.log(`Attempting to install ${cli}...`);
|
|
248
|
+
execaCommandSync(cliConf.install, { stdio: 'inherit' });
|
|
249
|
+
console.log(
|
|
250
|
+
`${cli} installed successfully. Please rerun the command.`,
|
|
251
|
+
);
|
|
252
|
+
return spawn();
|
|
253
|
+
}
|
|
254
|
+
console.error(
|
|
255
|
+
`If you did not installed it yet, Please install it first: ${cliConf.install}`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
globalThis.Bun &&
|
|
260
|
+
error instanceof Error &&
|
|
261
|
+
error.stack?.includes('bun-pty')
|
|
262
|
+
) {
|
|
263
|
+
// try to fix bun-pty issues
|
|
218
264
|
console.error(
|
|
219
|
-
`
|
|
265
|
+
`Detected bun-pty issue, attempted to fix it. Please try again.`,
|
|
220
266
|
);
|
|
267
|
+
require('./fix-pty.js');
|
|
268
|
+
// unable to retry with same process, so exit here.
|
|
269
|
+
}
|
|
221
270
|
throw error;
|
|
222
271
|
|
|
223
272
|
function isCommandNotFoundError(e: unknown) {
|
|
224
273
|
if (e instanceof Error) {
|
|
225
274
|
return (
|
|
226
|
-
e.message.includes('command not found') ||
|
|
227
|
-
e.message.includes('ENOENT') ||
|
|
275
|
+
e.message.includes('command not found') || // unix
|
|
276
|
+
e.message.includes('ENOENT') || // unix
|
|
228
277
|
e.message.includes('spawn') // windows
|
|
229
278
|
);
|
|
230
279
|
}
|
|
231
280
|
return false;
|
|
232
281
|
}
|
|
233
282
|
},
|
|
234
|
-
|
|
283
|
+
spawn,
|
|
235
284
|
)();
|
|
236
285
|
const pendingExitCode = Promise.withResolvers<number | null>();
|
|
237
286
|
let pendingExitCodeValue = null;
|
|
238
287
|
|
|
239
|
-
// TODO handle error if claude is not installed, show msg:
|
|
240
|
-
// npm install -g @anthropic-ai/claude-code
|
|
241
|
-
|
|
242
288
|
async function onData(data: string) {
|
|
243
289
|
// append data to the buffer, so we can process it later
|
|
244
290
|
await outputWriter.write(data);
|
|
@@ -496,5 +542,3 @@ export default async function cliYes({
|
|
|
496
542
|
};
|
|
497
543
|
}
|
|
498
544
|
}
|
|
499
|
-
|
|
500
|
-
export { removeControlCharacters };
|
package/ts/parseCliArgs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ms from 'ms';
|
|
2
2
|
import yargs from 'yargs';
|
|
3
3
|
import { hideBin } from 'yargs/helpers';
|
|
4
4
|
import { SUPPORTED_CLIS } from '.';
|
|
@@ -12,7 +12,7 @@ export function parseCliArgs(argv: string[]) {
|
|
|
12
12
|
const scriptName = argv[1]?.split(/[\/\\]/).pop();
|
|
13
13
|
const cliName = ((e?: string) => {
|
|
14
14
|
if (e === 'cli' || e === 'cli.ts' || e === 'cli.js') return undefined;
|
|
15
|
-
return e?.replace(/-yes
|
|
15
|
+
return e?.replace(/-yes(\.[jt]s)?$/, '');
|
|
16
16
|
})(scriptName);
|
|
17
17
|
|
|
18
18
|
// Parse args with yargs (same logic as cli.ts:16-73)
|
|
@@ -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]
|
|
@@ -103,7 +105,7 @@ export function parseCliArgs(argv: string[]) {
|
|
|
103
105
|
[parsedArgv.prompt, dashPrompt].filter(Boolean).join(' ') || undefined,
|
|
104
106
|
exitOnIdle: Number(
|
|
105
107
|
(parsedArgv.idle || parsedArgv.exitOnIdle)?.replace(/.*/, (e) =>
|
|
106
|
-
String(
|
|
108
|
+
String(ms(e as ms.StringValue)),
|
|
107
109
|
) || 0,
|
|
108
110
|
),
|
|
109
111
|
queue: parsedArgv.queue,
|
|
@@ -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
|
+
});
|
package/ts/utils.spec.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { type DeepPartial, deepMixin, sleepms } from './utils';
|
|
3
|
+
|
|
4
|
+
describe('utils', () => {
|
|
5
|
+
describe('sleepms', () => {
|
|
6
|
+
it('should return a promise', () => {
|
|
7
|
+
const result = sleepms(100);
|
|
8
|
+
expect(result).toBeInstanceOf(Promise);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should resolve after some time', async () => {
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
await sleepms(10);
|
|
14
|
+
const end = Date.now();
|
|
15
|
+
expect(end - start).toBeGreaterThanOrEqual(5); // Allow some margin
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should handle zero milliseconds', async () => {
|
|
19
|
+
const start = Date.now();
|
|
20
|
+
await sleepms(0);
|
|
21
|
+
const end = Date.now();
|
|
22
|
+
expect(end - start).toBeLessThan(50); // Should be quick
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('deepMixin', () => {
|
|
27
|
+
it('should merge simple properties', () => {
|
|
28
|
+
const target = { a: 1, b: 2 };
|
|
29
|
+
const source = { b: 3, c: 4 };
|
|
30
|
+
const result = deepMixin(target, source);
|
|
31
|
+
|
|
32
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
33
|
+
expect(result).toBe(target); // Should modify original object
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should merge nested objects', () => {
|
|
37
|
+
const target = {
|
|
38
|
+
user: { name: 'John', age: 30 },
|
|
39
|
+
settings: { theme: 'dark' },
|
|
40
|
+
};
|
|
41
|
+
const source: DeepPartial<typeof target> = {
|
|
42
|
+
user: { age: 31 },
|
|
43
|
+
settings: { language: 'en' } as any,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
deepMixin(target, source);
|
|
47
|
+
|
|
48
|
+
expect(target).toEqual({
|
|
49
|
+
user: { name: 'John', age: 31 },
|
|
50
|
+
settings: { theme: 'dark', language: 'en' },
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should create nested objects when target property is null', () => {
|
|
55
|
+
const target: any = { config: null };
|
|
56
|
+
const source = { config: { enabled: true } };
|
|
57
|
+
|
|
58
|
+
deepMixin(target, source);
|
|
59
|
+
|
|
60
|
+
expect(target).toEqual({
|
|
61
|
+
config: { enabled: true },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should create nested objects when target property is primitive', () => {
|
|
66
|
+
const target: any = { config: 'string' };
|
|
67
|
+
const source = { config: { enabled: true } };
|
|
68
|
+
|
|
69
|
+
deepMixin(target, source);
|
|
70
|
+
|
|
71
|
+
expect(target).toEqual({
|
|
72
|
+
config: { enabled: true },
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle arrays by replacing them', () => {
|
|
77
|
+
const target = { items: [1, 2, 3] };
|
|
78
|
+
const source = { items: [4, 5] };
|
|
79
|
+
|
|
80
|
+
deepMixin(target, source);
|
|
81
|
+
|
|
82
|
+
expect(target).toEqual({ items: [4, 5] });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should ignore undefined values', () => {
|
|
86
|
+
const target = { a: 1, b: 2 };
|
|
87
|
+
const source = { a: undefined, c: 3 };
|
|
88
|
+
|
|
89
|
+
deepMixin(target, source);
|
|
90
|
+
|
|
91
|
+
expect(target).toEqual({ a: 1, b: 2, c: 3 });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle null values', () => {
|
|
95
|
+
const target = { a: 1, b: 2 };
|
|
96
|
+
const source = { a: null, c: 3 };
|
|
97
|
+
|
|
98
|
+
deepMixin(target, source);
|
|
99
|
+
|
|
100
|
+
expect(target).toEqual({ a: null, b: 2, c: 3 });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should handle deeply nested structures', () => {
|
|
104
|
+
const target = {
|
|
105
|
+
level1: {
|
|
106
|
+
level2: {
|
|
107
|
+
level3: { value: 'old' },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const source = {
|
|
112
|
+
level1: {
|
|
113
|
+
level2: {
|
|
114
|
+
level3: { value: 'new', extra: 'added' },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
deepMixin(target, source);
|
|
120
|
+
|
|
121
|
+
expect(target).toEqual({
|
|
122
|
+
level1: {
|
|
123
|
+
level2: {
|
|
124
|
+
level3: { value: 'new', extra: 'added' },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should handle empty objects', () => {
|
|
131
|
+
const target = {};
|
|
132
|
+
const source = {};
|
|
133
|
+
|
|
134
|
+
const result = deepMixin(target, source);
|
|
135
|
+
|
|
136
|
+
expect(result).toEqual({});
|
|
137
|
+
expect(result).toBe(target);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle complex mixed types', () => {
|
|
141
|
+
const target: any = {
|
|
142
|
+
string: 'value',
|
|
143
|
+
number: 42,
|
|
144
|
+
boolean: true,
|
|
145
|
+
object: { nested: 'value' },
|
|
146
|
+
array: [1, 2, 3],
|
|
147
|
+
};
|
|
148
|
+
const source: any = {
|
|
149
|
+
string: 'new value',
|
|
150
|
+
number: 100,
|
|
151
|
+
boolean: false,
|
|
152
|
+
object: { nested: 'new value', added: 'property' },
|
|
153
|
+
array: [4, 5],
|
|
154
|
+
newProp: 'added',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
deepMixin(target, source);
|
|
158
|
+
|
|
159
|
+
expect(target).toEqual({
|
|
160
|
+
string: 'new value',
|
|
161
|
+
number: 100,
|
|
162
|
+
boolean: false,
|
|
163
|
+
object: { nested: 'new value', added: 'property' },
|
|
164
|
+
array: [4, 5],
|
|
165
|
+
newProp: 'added',
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('yesLog', () => {
|
|
4
|
+
const originalVerbose = process.env.VERBOSE;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Reset modules to ensure fresh state
|
|
8
|
+
delete require.cache[require.resolve('./yesLog')];
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// Restore original VERBOSE setting
|
|
13
|
+
if (originalVerbose !== undefined) {
|
|
14
|
+
process.env.VERBOSE = originalVerbose;
|
|
15
|
+
} else {
|
|
16
|
+
delete process.env.VERBOSE;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should not crash when VERBOSE is not set', async () => {
|
|
21
|
+
delete process.env.VERBOSE;
|
|
22
|
+
|
|
23
|
+
const { yesLog } = await import('./yesLog');
|
|
24
|
+
|
|
25
|
+
// Should not throw and returns undefined
|
|
26
|
+
const result = yesLog`Test message`;
|
|
27
|
+
expect(result).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should be callable with template literals', async () => {
|
|
31
|
+
delete process.env.VERBOSE;
|
|
32
|
+
|
|
33
|
+
const { yesLog } = await import('./yesLog');
|
|
34
|
+
|
|
35
|
+
// Should not throw with variables
|
|
36
|
+
const variable = 'test value';
|
|
37
|
+
const result = yesLog`Message with ${variable}`;
|
|
38
|
+
expect(result).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle multiple calls', async () => {
|
|
42
|
+
delete process.env.VERBOSE;
|
|
43
|
+
|
|
44
|
+
const { yesLog } = await import('./yesLog');
|
|
45
|
+
|
|
46
|
+
// Multiple calls should not throw
|
|
47
|
+
expect(yesLog`First message`).toBeUndefined();
|
|
48
|
+
expect(yesLog`Second message`).toBeUndefined();
|
|
49
|
+
expect(yesLog`Third message`).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should work when VERBOSE is set', async () => {
|
|
53
|
+
process.env.VERBOSE = '1';
|
|
54
|
+
|
|
55
|
+
const { yesLog } = await import('./yesLog');
|
|
56
|
+
|
|
57
|
+
// Should not throw even when verbose
|
|
58
|
+
expect(yesLog`Verbose message`).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should handle template literals with different types', async () => {
|
|
62
|
+
delete process.env.VERBOSE;
|
|
63
|
+
|
|
64
|
+
const { yesLog } = await import('./yesLog');
|
|
65
|
+
|
|
66
|
+
const number = 42;
|
|
67
|
+
const object = { key: 'value' };
|
|
68
|
+
const array = [1, 2, 3];
|
|
69
|
+
|
|
70
|
+
expect(yesLog`Number: ${number}`).toBeUndefined();
|
|
71
|
+
expect(yesLog`Object: ${object}`).toBeUndefined();
|
|
72
|
+
expect(yesLog`Array: ${array}`).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
package/ts/yesLog.ts
CHANGED
package/ts/tryCatch.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// curried overload
|
|
2
|
-
export function catcher<F extends (...args: any[]) => any, R>(
|
|
3
|
-
catchFn: (error: unknown) => R,
|
|
4
|
-
): (fn: F) => (...args: Parameters<F>) => ReturnType<F> | R;
|
|
5
|
-
|
|
6
|
-
// direct overload
|
|
7
|
-
export function catcher<F extends (...args: any[]) => any, R>(
|
|
8
|
-
catchFn: (error: unknown) => R,
|
|
9
|
-
fn: F,
|
|
10
|
-
): (...args: Parameters<F>) => ReturnType<F> | R;
|
|
11
|
-
|
|
12
|
-
// implementation
|
|
13
|
-
export function catcher<F extends (...args: any[]) => any, R>(
|
|
14
|
-
catchFn: (error: unknown) => R,
|
|
15
|
-
fn?: F,
|
|
16
|
-
) {
|
|
17
|
-
if (!fn) return (fn: F) => catcher(catchFn, fn) as any;
|
|
18
|
-
return (...args: Parameters<F>) => {
|
|
19
|
-
try {
|
|
20
|
-
return fn(...args);
|
|
21
|
-
} catch (error) {
|
|
22
|
-
return catchFn(error);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
}
|