cligr 1.0.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.
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Integration tests for TemplateExpander
3
+ *
4
+ * These tests verify the template expansion functionality including:
5
+ * - Command template expansion with $1, $2, etc.
6
+ * - Item string parsing
7
+ * - Name generation
8
+ * - Direct executable handling
9
+ */
10
+
11
+ import { describe, it } from 'node:test';
12
+ import assert from 'node:assert';
13
+ import { TemplateExpander } from '../../src/process/template.js';
14
+ import type { ProcessItem } from '../../src/config/types.js';
15
+
16
+ describe('TemplateExpander Integration Tests', () => {
17
+ describe('expand()', () => {
18
+ it('should expand template with single argument', () => {
19
+ const template = 'echo $1';
20
+ const itemStr = 'hello';
21
+
22
+ const result = TemplateExpander.expand(template, itemStr, 0);
23
+
24
+ assert.strictEqual(result.name, 'hello');
25
+ assert.deepStrictEqual(result.args, ['hello']);
26
+ assert.strictEqual(result.fullCmd, 'echo hello');
27
+ });
28
+
29
+ it('should expand template with multiple arguments', () => {
30
+ const template = 'docker run -p $2:$3 $1';
31
+ const itemStr = 'nginx,8080,80';
32
+
33
+ const result = TemplateExpander.expand(template, itemStr, 0);
34
+
35
+ assert.strictEqual(result.name, 'nginx');
36
+ assert.deepStrictEqual(result.args, ['nginx', '8080', '80']);
37
+ assert.strictEqual(result.fullCmd, 'docker run -p 8080:80 nginx');
38
+ });
39
+
40
+ it('should replace all occurrences of placeholder', () => {
41
+ const template = 'echo $1 and $1 again';
42
+ const itemStr = 'test';
43
+
44
+ const result = TemplateExpander.expand(template, itemStr, 0);
45
+
46
+ assert.strictEqual(result.fullCmd, 'echo test and test again');
47
+ });
48
+
49
+ it('should handle placeholders with spaces around', () => {
50
+ const template = 'cmd $1 opt $2 end';
51
+ const itemStr = 'arg1,arg2';
52
+
53
+ const result = TemplateExpander.expand(template, itemStr, 0);
54
+
55
+ assert.strictEqual(result.fullCmd, 'cmd arg1 opt arg2 end');
56
+ });
57
+
58
+ it('should use index as name when first argument is empty', () => {
59
+ const template = 'echo test';
60
+ const itemStr = ',arg2,arg3';
61
+
62
+ const result = TemplateExpander.expand(template, itemStr, 5);
63
+
64
+ assert.strictEqual(result.name, 'item-5');
65
+ assert.deepStrictEqual(result.args, ['', 'arg2', 'arg3']);
66
+ });
67
+
68
+ it('should handle single argument with no commas', () => {
69
+ const template = 'node $1';
70
+ const itemStr = 'server.js';
71
+
72
+ const result = TemplateExpander.expand(template, itemStr, 0);
73
+
74
+ assert.strictEqual(result.name, 'server.js');
75
+ assert.deepStrictEqual(result.args, ['server.js']);
76
+ assert.strictEqual(result.fullCmd, 'node server.js');
77
+ });
78
+
79
+ it('should handle more placeholders than arguments', () => {
80
+ const template = 'cmd $1 $2 $3 $4';
81
+ const itemStr = 'a,b';
82
+
83
+ const result = TemplateExpander.expand(template, itemStr, 0);
84
+
85
+ assert.strictEqual(result.fullCmd, 'cmd a b $3 $4');
86
+ });
87
+
88
+ it('should trim whitespace from arguments', () => {
89
+ const template = 'cmd $1 $2 $3';
90
+ const itemStr = ' a , b , c ';
91
+
92
+ const result = TemplateExpander.expand(template, itemStr, 0);
93
+
94
+ assert.deepStrictEqual(result.args, ['a', 'b', 'c']);
95
+ assert.strictEqual(result.fullCmd, 'cmd a b c');
96
+ });
97
+
98
+ it('should handle empty string argument', () => {
99
+ const template = 'cmd $1 $2 $3';
100
+ const itemStr = 'first,,third';
101
+
102
+ const result = TemplateExpander.expand(template, itemStr, 0);
103
+
104
+ assert.deepStrictEqual(result.args, ['first', '', 'third']);
105
+ assert.strictEqual(result.fullCmd, 'cmd first third');
106
+ });
107
+
108
+ it('should handle quoted paths with spaces in template', () => {
109
+ const template = '"C:\\Program Files\\app\\app.exe" $1';
110
+ const itemStr = 'arg1';
111
+
112
+ const result = TemplateExpander.expand(template, itemStr, 0);
113
+
114
+ assert.strictEqual(result.fullCmd, '"C:\\Program Files\\app\\app.exe" arg1');
115
+ });
116
+ });
117
+
118
+ describe('parseItem()', () => {
119
+ describe('with registered tool template', () => {
120
+ it('should use tool template for expansion', () => {
121
+ const tool = 'docker';
122
+ const toolTemplate = 'docker run -it --rm $1';
123
+ const itemStr = 'nginx,-p,80:80';
124
+
125
+ const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 0);
126
+
127
+ assert.strictEqual(result.name, 'nginx');
128
+ assert.strictEqual(result.fullCmd, 'docker run -it --rm nginx -p 80:80');
129
+ });
130
+
131
+ it('should handle complex docker compose templates', () => {
132
+ const tool = 'compose';
133
+ const toolTemplate = 'docker-compose run --service-ports $1';
134
+ const itemStr = 'web';
135
+
136
+ const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 0);
137
+
138
+ assert.strictEqual(result.name, 'web');
139
+ assert.strictEqual(result.fullCmd, 'docker-compose run --service-ports web');
140
+ });
141
+
142
+ it('should handle templates with no placeholders', () => {
143
+ const tool = 'echo';
144
+ const toolTemplate = 'echo hello world';
145
+ const itemStr = 'item1';
146
+
147
+ const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 0);
148
+
149
+ assert.strictEqual(result.name, 'item1');
150
+ assert.strictEqual(result.fullCmd, 'echo hello world');
151
+ });
152
+ });
153
+
154
+ describe('without registered tool (direct executable)', () => {
155
+ it('should prefix command with tool name', () => {
156
+ const tool = 'node';
157
+ const toolTemplate = null;
158
+ const itemStr = 'server.js,3000';
159
+
160
+ const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 0);
161
+
162
+ assert.strictEqual(result.name, 'server.js');
163
+ assert.strictEqual(result.fullCmd, 'node server.js,3000');
164
+ });
165
+
166
+ it('should handle null tool and toolTemplate', () => {
167
+ const tool = null;
168
+ const toolTemplate = null;
169
+ const itemStr = 'echo hello';
170
+
171
+ const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 0);
172
+
173
+ assert.strictEqual(result.name, 'echo');
174
+ assert.strictEqual(result.fullCmd, 'echo hello');
175
+ });
176
+
177
+ it('should use index for name when args are empty', () => {
178
+ const tool = null;
179
+ const toolTemplate = null;
180
+ const itemStr = '';
181
+
182
+ const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 3);
183
+
184
+ assert.strictEqual(result.name, 'item-3');
185
+ });
186
+
187
+ it('should handle multiple items with same tool', () => {
188
+ const tool = 'python';
189
+ const toolTemplate = null;
190
+
191
+ const result1 = TemplateExpander.parseItem(tool, toolTemplate, 'script1.py', 0);
192
+ const result2 = TemplateExpander.parseItem(tool, toolTemplate, 'script2.py', 1);
193
+
194
+ assert.strictEqual(result1.name, 'script1.py');
195
+ assert.strictEqual(result2.name, 'script2.py');
196
+ });
197
+ });
198
+ });
199
+
200
+ describe('Real-world scenarios', () => {
201
+ it('should handle docker service templates', () => {
202
+ const toolTemplate = 'docker run -d --name $1_$2 -p $2:$3 $1';
203
+ const itemStr = 'nginx,8080,80';
204
+
205
+ const result = TemplateExpander.expand(toolTemplate, itemStr, 0);
206
+
207
+ assert.strictEqual(result.name, 'nginx');
208
+ assert.strictEqual(result.fullCmd, 'docker run -d --name nginx_8080 -p 8080:80 nginx');
209
+ });
210
+
211
+ it('should handle node process manager templates', () => {
212
+ const toolTemplate = 'node $1 --port $2';
213
+ const itemStr = 'server.js,3000';
214
+
215
+ const result = TemplateExpander.expand(toolTemplate, itemStr, 0);
216
+
217
+ assert.strictEqual(result.name, 'server.js');
218
+ assert.strictEqual(result.fullCmd, 'node server.js --port 3000');
219
+ });
220
+
221
+ it('should handle database connection templates', () => {
222
+ const toolTemplate = 'psql -h $1 -p $2 -U $3 -d $4';
223
+ const itemStr = 'localhost,5432,admin,mydb';
224
+
225
+ const result = TemplateExpander.expand(toolTemplate, itemStr, 0);
226
+
227
+ assert.strictEqual(result.name, 'localhost');
228
+ assert.strictEqual(result.fullCmd, 'psql -h localhost -p 5432 -U admin -d mydb');
229
+ });
230
+
231
+ it('should handle custom script execution', () => {
232
+ const toolTemplate = './scripts/$1.sh $2 $3';
233
+ const itemStr = 'deploy,production,force';
234
+
235
+ const result = TemplateExpander.expand(toolTemplate, itemStr, 0);
236
+
237
+ assert.strictEqual(result.name, 'deploy');
238
+ assert.strictEqual(result.fullCmd, './scripts/deploy.sh production force');
239
+ });
240
+
241
+ it('should handle Windows executable paths', () => {
242
+ const toolTemplate = '"C:\\My App\\app.exe" --config $1 --port $2';
243
+ const itemStr = 'config.json,8080';
244
+
245
+ const result = TemplateExpander.expand(toolTemplate, itemStr, 0);
246
+
247
+ assert.strictEqual(result.name, 'config.json');
248
+ assert.strictEqual(result.fullCmd, '"C:\\My App\\app.exe" --config config.json --port 8080');
249
+ });
250
+ });
251
+
252
+ describe('Edge cases and error handling', () => {
253
+ it('should handle very long argument lists', () => {
254
+ const template = Array.from({ length: 20 }, (_, i) => `$${i + 1}`).join(' ');
255
+ const itemStr = Array.from({ length: 20 }, (_, i) => `arg${i}`).join(',');
256
+
257
+ const result = TemplateExpander.expand(template, itemStr, 0);
258
+
259
+ assert.strictEqual(result.args.length, 20);
260
+ assert.ok(result.fullCmd.includes('arg0'));
261
+ assert.ok(result.fullCmd.includes('arg19'));
262
+ });
263
+
264
+ it('should handle special characters in arguments', () => {
265
+ const template = 'echo "$1" "$2"';
266
+ const itemStr = 'hello world,test&argument';
267
+
268
+ const result = TemplateExpander.expand(template, itemStr, 0);
269
+
270
+ assert.deepStrictEqual(result.args, ['hello world', 'test&argument']);
271
+ });
272
+
273
+ it('should handle unicode characters', () => {
274
+ const template = 'echo $1 $2';
275
+ const itemStr = 'こんにちは,世界';
276
+
277
+ const result = TemplateExpander.expand(template, itemStr, 0);
278
+
279
+ assert.strictEqual(result.name, 'こんにちは');
280
+ assert.deepStrictEqual(result.args, ['こんにちは', '世界']);
281
+ assert.strictEqual(result.fullCmd, 'echo こんにちは 世界');
282
+ });
283
+
284
+ it('should handle numeric arguments', () => {
285
+ const template = 'app --port $1 --timeout $2 --retries $3';
286
+ const itemStr = '8080,5000,5';
287
+
288
+ const result = TemplateExpander.expand(template, itemStr, 0);
289
+
290
+ assert.strictEqual(result.fullCmd, 'app --port 8080 --timeout 5000 --retries 5');
291
+ });
292
+
293
+ it('should preserve whitespace in command template', () => {
294
+ const template = 'cmd $1 $2';
295
+ const itemStr = 'a,b';
296
+
297
+ const result = TemplateExpander.expand(template, itemStr, 0);
298
+
299
+ // Template whitespace is preserved
300
+ assert.strictEqual(result.fullCmd, 'cmd a b');
301
+ });
302
+ });
303
+
304
+ describe('Multiple items in sequence', () => {
305
+ it('should generate unique names using index', () => {
306
+ const template = 'worker $1';
307
+ const items = ['job1', 'job2', 'job3'];
308
+
309
+ const results = items.map((item, index) =>
310
+ TemplateExpander.expand(template, item, index)
311
+ );
312
+
313
+ assert.strictEqual(results[0].name, 'job1');
314
+ assert.strictEqual(results[1].name, 'job2');
315
+ assert.strictEqual(results[2].name, 'job3');
316
+ });
317
+
318
+ it('should use index as fallback for identical names', () => {
319
+ const template = 'echo $1';
320
+ const items = ['test', 'test', 'test'];
321
+
322
+ const results = items.map((item, index) =>
323
+ TemplateExpander.expand(template, item, index)
324
+ );
325
+
326
+ // All will have name 'test' since first arg is used
327
+ assert.strictEqual(results[0].name, 'test');
328
+ assert.strictEqual(results[1].name, 'test');
329
+ assert.strictEqual(results[2].name, 'test');
330
+ });
331
+ });
332
+
333
+ describe('ProcessItem type compliance', () => {
334
+ it('should return valid ProcessItem objects', () => {
335
+ const template = 'cmd $1';
336
+ const itemStr = 'arg1,arg2';
337
+
338
+ const result: ProcessItem = TemplateExpander.expand(template, itemStr, 0);
339
+
340
+ assert.strictEqual(typeof result.name, 'string');
341
+ assert.ok(Array.isArray(result.args));
342
+ assert.strictEqual(typeof result.fullCmd, 'string');
343
+ assert.ok(result.name.length > 0);
344
+ assert.ok(result.fullCmd.length > 0);
345
+ });
346
+
347
+ it('should have consistent structure across different calls', () => {
348
+ const results: ProcessItem[] = [
349
+ TemplateExpander.expand('echo $1', 'test', 0),
350
+ TemplateExpander.expand('cmd $1 $2', 'a,b', 1),
351
+ TemplateExpander.parseItem('node', null, 'app.js', 2),
352
+ ];
353
+
354
+ for (const result of results) {
355
+ assert.ok('name' in result);
356
+ assert.ok('args' in result);
357
+ assert.ok('fullCmd' in result);
358
+ assert.ok(Array.isArray(result.args));
359
+ }
360
+ });
361
+ });
362
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "esModuleInterop": true,
6
+ "skipLibCheck": true,
7
+ "allowJs": true,
8
+ "checkJs": false,
9
+ "moduleResolution": "bundler",
10
+ "outDir": "./dist",
11
+ "rootDir": "./src"
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
package/usage.md ADDED
@@ -0,0 +1,9 @@
1
+ # Commands
2
+ ## cligr up <group>
3
+ Runs each item in a attached thread to the current process using the tool/executable indicated.
4
+ ## cligr down <group>
5
+ Kills all threads from this group
6
+ ## cligr ls <group>
7
+ Shows all items from this group (indicating if running or stopped)
8
+ ## cligr config
9
+ Open config file