cligr 1.0.5 → 1.0.7
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/README.md +1 -1
- package/dist/index.js +192 -39
- package/docs/plans/2026-02-25-named-items-design.md +164 -0
- package/docs/plans/2026-02-25-named-items-implementation.md +460 -0
- package/docs/superpowers/plans/2026-04-13-serve-command.md +1299 -0
- package/docs/superpowers/specs/2026-04-13-serve-command-design.md +93 -0
- package/package.json +3 -3
- package/scripts/test.js +17 -13
- package/src/commands/groups.ts +1 -1
- package/src/commands/ls.ts +10 -6
- package/src/commands/serve.ts +360 -0
- package/src/commands/up.ts +5 -5
- package/src/config/loader.ts +116 -5
- package/src/config/types.ts +7 -1
- package/src/index.ts +10 -3
- package/src/process/manager.ts +36 -2
- package/src/process/template.ts +13 -24
- package/tests/integration/commands.test.ts +65 -41
- package/tests/integration/config-loader.test.ts +143 -33
- package/tests/integration/process-manager.test.ts +107 -1
- package/tests/integration/serve.test.ts +245 -0
- package/tests/integration/template-expander.test.ts +101 -93
- package/.claude/settings.local.json +0 -13
- package/dist/commands/config.js +0 -102
- package/dist/commands/down.js +0 -26
- package/dist/commands/groups.js +0 -43
- package/dist/commands/ls.js +0 -23
- package/dist/commands/up.js +0 -39
- package/dist/config/loader.js +0 -82
- package/dist/config/types.js +0 -0
- package/dist/process/manager.js +0 -226
- package/dist/process/pid-store.js +0 -141
- package/dist/process/template.js +0 -53
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
import { describe, it } from 'node:test';
|
|
12
12
|
import assert from 'node:assert';
|
|
13
13
|
import { TemplateExpander } from '../../src/process/template.js';
|
|
14
|
-
import type { ProcessItem } from '../../src/config/types.js';
|
|
14
|
+
import type { ProcessItem, ItemEntry } from '../../src/config/types.js';
|
|
15
15
|
|
|
16
16
|
describe('TemplateExpander Integration Tests', () => {
|
|
17
17
|
describe('expand()', () => {
|
|
18
18
|
it('should expand template with single argument', () => {
|
|
19
19
|
const template = 'echo $1';
|
|
20
|
-
const
|
|
20
|
+
const item: ItemEntry = { name: 'hello', value: 'hello' };
|
|
21
21
|
|
|
22
|
-
const result = TemplateExpander.expand(template,
|
|
22
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
23
23
|
|
|
24
24
|
assert.strictEqual(result.name, 'hello');
|
|
25
25
|
assert.deepStrictEqual(result.args, ['hello']);
|
|
@@ -28,9 +28,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
28
28
|
|
|
29
29
|
it('should expand template with multiple arguments', () => {
|
|
30
30
|
const template = 'docker run -p $2:$3 $1';
|
|
31
|
-
const
|
|
31
|
+
const item: ItemEntry = { name: 'nginx', value: 'nginx,8080,80' };
|
|
32
32
|
|
|
33
|
-
const result = TemplateExpander.expand(template,
|
|
33
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
34
34
|
|
|
35
35
|
assert.strictEqual(result.name, 'nginx');
|
|
36
36
|
assert.deepStrictEqual(result.args, ['nginx', '8080', '80']);
|
|
@@ -39,27 +39,27 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
39
39
|
|
|
40
40
|
it('should replace all occurrences of placeholder', () => {
|
|
41
41
|
const template = 'echo $1 and $1 again';
|
|
42
|
-
const
|
|
42
|
+
const item: ItemEntry = { name: 'test', value: 'test' };
|
|
43
43
|
|
|
44
|
-
const result = TemplateExpander.expand(template,
|
|
44
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
45
45
|
|
|
46
46
|
assert.strictEqual(result.fullCmd, 'echo test and test again');
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it('should handle placeholders with spaces around', () => {
|
|
50
50
|
const template = 'cmd $1 opt $2 end';
|
|
51
|
-
const
|
|
51
|
+
const item: ItemEntry = { name: 'args', value: 'arg1,arg2' };
|
|
52
52
|
|
|
53
|
-
const result = TemplateExpander.expand(template,
|
|
53
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
54
54
|
|
|
55
55
|
assert.strictEqual(result.fullCmd, 'cmd arg1 opt arg2 end');
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it('should use
|
|
58
|
+
it('should use ItemEntry name when first argument is empty', () => {
|
|
59
59
|
const template = 'echo test';
|
|
60
|
-
const
|
|
60
|
+
const item: ItemEntry = { name: 'item-5', value: ',arg2,arg3' };
|
|
61
61
|
|
|
62
|
-
const result = TemplateExpander.expand(template,
|
|
62
|
+
const result = TemplateExpander.expand(template, item, 5);
|
|
63
63
|
|
|
64
64
|
assert.strictEqual(result.name, 'item-5');
|
|
65
65
|
assert.deepStrictEqual(result.args, ['', 'arg2', 'arg3']);
|
|
@@ -67,29 +67,29 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
67
67
|
|
|
68
68
|
it('should handle single argument with no commas', () => {
|
|
69
69
|
const template = 'node $1';
|
|
70
|
-
const
|
|
70
|
+
const item: ItemEntry = { name: 'server', value: 'server.js' };
|
|
71
71
|
|
|
72
|
-
const result = TemplateExpander.expand(template,
|
|
72
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
73
73
|
|
|
74
|
-
assert.strictEqual(result.name, 'server
|
|
74
|
+
assert.strictEqual(result.name, 'server');
|
|
75
75
|
assert.deepStrictEqual(result.args, ['server.js']);
|
|
76
76
|
assert.strictEqual(result.fullCmd, 'node server.js');
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('should handle more placeholders than arguments', () => {
|
|
80
80
|
const template = 'cmd $1 $2 $3 $4';
|
|
81
|
-
const
|
|
81
|
+
const item: ItemEntry = { name: 'args', value: 'a,b' };
|
|
82
82
|
|
|
83
|
-
const result = TemplateExpander.expand(template,
|
|
83
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
84
84
|
|
|
85
85
|
assert.strictEqual(result.fullCmd, 'cmd a b $3 $4');
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('should trim whitespace from arguments', () => {
|
|
89
89
|
const template = 'cmd $1 $2 $3';
|
|
90
|
-
const
|
|
90
|
+
const item: ItemEntry = { name: 'args', value: ' a , b , c ' };
|
|
91
91
|
|
|
92
|
-
const result = TemplateExpander.expand(template,
|
|
92
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
93
93
|
|
|
94
94
|
assert.deepStrictEqual(result.args, ['a', 'b', 'c']);
|
|
95
95
|
assert.strictEqual(result.fullCmd, 'cmd a b c');
|
|
@@ -97,9 +97,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
97
97
|
|
|
98
98
|
it('should handle empty string argument', () => {
|
|
99
99
|
const template = 'cmd $1 $2 $3';
|
|
100
|
-
const
|
|
100
|
+
const item: ItemEntry = { name: 'args', value: 'first,,third' };
|
|
101
101
|
|
|
102
|
-
const result = TemplateExpander.expand(template,
|
|
102
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
103
103
|
|
|
104
104
|
assert.deepStrictEqual(result.args, ['first', '', 'third']);
|
|
105
105
|
assert.strictEqual(result.fullCmd, 'cmd first third');
|
|
@@ -107,9 +107,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
107
107
|
|
|
108
108
|
it('should handle quoted paths with spaces in template', () => {
|
|
109
109
|
const template = '"C:\\Program Files\\app\\app.exe" $1';
|
|
110
|
-
const
|
|
110
|
+
const item: ItemEntry = { name: 'arg', value: 'arg1' };
|
|
111
111
|
|
|
112
|
-
const result = TemplateExpander.expand(template,
|
|
112
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
113
113
|
|
|
114
114
|
assert.strictEqual(result.fullCmd, '"C:\\Program Files\\app\\app.exe" arg1');
|
|
115
115
|
});
|
|
@@ -120,9 +120,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
120
120
|
it('should use tool template for expansion', () => {
|
|
121
121
|
const tool = 'docker';
|
|
122
122
|
const toolTemplate = 'docker run -it --rm $1';
|
|
123
|
-
const
|
|
123
|
+
const item: ItemEntry = { name: 'nginx', value: 'nginx,-p,80:80' };
|
|
124
124
|
|
|
125
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
125
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 0);
|
|
126
126
|
|
|
127
127
|
assert.strictEqual(result.name, 'nginx');
|
|
128
128
|
assert.strictEqual(result.fullCmd, 'docker run -it --rm nginx -p 80:80');
|
|
@@ -131,9 +131,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
131
131
|
it('should handle complex docker compose templates', () => {
|
|
132
132
|
const tool = 'compose';
|
|
133
133
|
const toolTemplate = 'docker-compose run --service-ports $1';
|
|
134
|
-
const
|
|
134
|
+
const item: ItemEntry = { name: 'web', value: 'web' };
|
|
135
135
|
|
|
136
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
136
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 0);
|
|
137
137
|
|
|
138
138
|
assert.strictEqual(result.name, 'web');
|
|
139
139
|
assert.strictEqual(result.fullCmd, 'docker-compose run --service-ports web');
|
|
@@ -142,9 +142,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
142
142
|
it('should handle templates with no placeholders', () => {
|
|
143
143
|
const tool = 'echo';
|
|
144
144
|
const toolTemplate = 'echo hello world';
|
|
145
|
-
const
|
|
145
|
+
const item: ItemEntry = { name: 'item1', value: 'item1' };
|
|
146
146
|
|
|
147
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
147
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 0);
|
|
148
148
|
|
|
149
149
|
assert.strictEqual(result.name, 'item1');
|
|
150
150
|
assert.strictEqual(result.fullCmd, 'echo hello world');
|
|
@@ -155,31 +155,31 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
155
155
|
it('should prefix command with tool name', () => {
|
|
156
156
|
const tool = 'node';
|
|
157
157
|
const toolTemplate = null;
|
|
158
|
-
const
|
|
158
|
+
const item: ItemEntry = { name: 'server', value: 'server.js,3000' };
|
|
159
159
|
|
|
160
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
160
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 0);
|
|
161
161
|
|
|
162
|
-
assert.strictEqual(result.name, 'server
|
|
162
|
+
assert.strictEqual(result.name, 'server');
|
|
163
163
|
assert.strictEqual(result.fullCmd, 'node server.js,3000');
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
it('should handle null tool and toolTemplate', () => {
|
|
167
167
|
const tool = null;
|
|
168
168
|
const toolTemplate = null;
|
|
169
|
-
const
|
|
169
|
+
const item: ItemEntry = { name: 'echo', value: 'echo hello' };
|
|
170
170
|
|
|
171
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
171
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 0);
|
|
172
172
|
|
|
173
173
|
assert.strictEqual(result.name, 'echo');
|
|
174
174
|
assert.strictEqual(result.fullCmd, 'echo hello');
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
it('should use
|
|
177
|
+
it('should use ItemEntry name when value is empty', () => {
|
|
178
178
|
const tool = null;
|
|
179
179
|
const toolTemplate = null;
|
|
180
|
-
const
|
|
180
|
+
const item: ItemEntry = { name: 'item-3', value: '' };
|
|
181
181
|
|
|
182
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
182
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 3);
|
|
183
183
|
|
|
184
184
|
assert.strictEqual(result.name, 'item-3');
|
|
185
185
|
});
|
|
@@ -188,11 +188,11 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
188
188
|
const tool = 'python';
|
|
189
189
|
const toolTemplate = null;
|
|
190
190
|
|
|
191
|
-
const result1 = TemplateExpander.parseItem(tool, toolTemplate, 'script1.py', 0);
|
|
192
|
-
const result2 = TemplateExpander.parseItem(tool, toolTemplate, 'script2.py', 1);
|
|
191
|
+
const result1 = TemplateExpander.parseItem(tool, toolTemplate, { name: 'script1', value: 'script1.py' }, 0);
|
|
192
|
+
const result2 = TemplateExpander.parseItem(tool, toolTemplate, { name: 'script2', value: 'script2.py' }, 1);
|
|
193
193
|
|
|
194
|
-
assert.strictEqual(result1.name, 'script1
|
|
195
|
-
assert.strictEqual(result2.name, 'script2
|
|
194
|
+
assert.strictEqual(result1.name, 'script1');
|
|
195
|
+
assert.strictEqual(result2.name, 'script2');
|
|
196
196
|
});
|
|
197
197
|
});
|
|
198
198
|
});
|
|
@@ -200,9 +200,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
200
200
|
describe('Real-world scenarios', () => {
|
|
201
201
|
it('should handle docker service templates', () => {
|
|
202
202
|
const toolTemplate = 'docker run -d --name $1_$2 -p $2:$3 $1';
|
|
203
|
-
const
|
|
203
|
+
const item: ItemEntry = { name: 'nginx', value: 'nginx,8080,80' };
|
|
204
204
|
|
|
205
|
-
const result = TemplateExpander.expand(toolTemplate,
|
|
205
|
+
const result = TemplateExpander.expand(toolTemplate, item, 0);
|
|
206
206
|
|
|
207
207
|
assert.strictEqual(result.name, 'nginx');
|
|
208
208
|
assert.strictEqual(result.fullCmd, 'docker run -d --name nginx_8080 -p 8080:80 nginx');
|
|
@@ -210,19 +210,19 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
210
210
|
|
|
211
211
|
it('should handle node process manager templates', () => {
|
|
212
212
|
const toolTemplate = 'node $1 --port $2';
|
|
213
|
-
const
|
|
213
|
+
const item: ItemEntry = { name: 'server', value: 'server.js,3000' };
|
|
214
214
|
|
|
215
|
-
const result = TemplateExpander.expand(toolTemplate,
|
|
215
|
+
const result = TemplateExpander.expand(toolTemplate, item, 0);
|
|
216
216
|
|
|
217
|
-
assert.strictEqual(result.name, 'server
|
|
217
|
+
assert.strictEqual(result.name, 'server');
|
|
218
218
|
assert.strictEqual(result.fullCmd, 'node server.js --port 3000');
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
it('should handle database connection templates', () => {
|
|
222
222
|
const toolTemplate = 'psql -h $1 -p $2 -U $3 -d $4';
|
|
223
|
-
const
|
|
223
|
+
const item: ItemEntry = { name: 'localhost', value: 'localhost,5432,admin,mydb' };
|
|
224
224
|
|
|
225
|
-
const result = TemplateExpander.expand(toolTemplate,
|
|
225
|
+
const result = TemplateExpander.expand(toolTemplate, item, 0);
|
|
226
226
|
|
|
227
227
|
assert.strictEqual(result.name, 'localhost');
|
|
228
228
|
assert.strictEqual(result.fullCmd, 'psql -h localhost -p 5432 -U admin -d mydb');
|
|
@@ -230,9 +230,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
230
230
|
|
|
231
231
|
it('should handle custom script execution', () => {
|
|
232
232
|
const toolTemplate = './scripts/$1.sh $2 $3';
|
|
233
|
-
const
|
|
233
|
+
const item: ItemEntry = { name: 'deploy', value: 'deploy,production,force' };
|
|
234
234
|
|
|
235
|
-
const result = TemplateExpander.expand(toolTemplate,
|
|
235
|
+
const result = TemplateExpander.expand(toolTemplate, item, 0);
|
|
236
236
|
|
|
237
237
|
assert.strictEqual(result.name, 'deploy');
|
|
238
238
|
assert.strictEqual(result.fullCmd, './scripts/deploy.sh production force');
|
|
@@ -240,11 +240,11 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
240
240
|
|
|
241
241
|
it('should handle Windows executable paths', () => {
|
|
242
242
|
const toolTemplate = '"C:\\My App\\app.exe" --config $1 --port $2';
|
|
243
|
-
const
|
|
243
|
+
const item: ItemEntry = { name: 'config', value: 'config.json,8080' };
|
|
244
244
|
|
|
245
|
-
const result = TemplateExpander.expand(toolTemplate,
|
|
245
|
+
const result = TemplateExpander.expand(toolTemplate, item, 0);
|
|
246
246
|
|
|
247
|
-
assert.strictEqual(result.name, 'config
|
|
247
|
+
assert.strictEqual(result.name, 'config');
|
|
248
248
|
assert.strictEqual(result.fullCmd, '"C:\\My App\\app.exe" --config config.json --port 8080');
|
|
249
249
|
});
|
|
250
250
|
});
|
|
@@ -252,9 +252,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
252
252
|
describe('Edge cases and error handling', () => {
|
|
253
253
|
it('should handle very long argument lists', () => {
|
|
254
254
|
const template = Array.from({ length: 20 }, (_, i) => `$${i + 1}`).join(' ');
|
|
255
|
-
const
|
|
255
|
+
const item: ItemEntry = { name: 'args', value: Array.from({ length: 20 }, (_, i) => `arg${i}`).join(',') };
|
|
256
256
|
|
|
257
|
-
const result = TemplateExpander.expand(template,
|
|
257
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
258
258
|
|
|
259
259
|
assert.strictEqual(result.args.length, 20);
|
|
260
260
|
assert.ok(result.fullCmd.includes('arg0'));
|
|
@@ -263,38 +263,38 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
263
263
|
|
|
264
264
|
it('should handle special characters in arguments', () => {
|
|
265
265
|
const template = 'echo "$1" "$2"';
|
|
266
|
-
const
|
|
266
|
+
const item: ItemEntry = { name: 'args', value: 'hello world,test&argument' };
|
|
267
267
|
|
|
268
|
-
const result = TemplateExpander.expand(template,
|
|
268
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
269
269
|
|
|
270
270
|
assert.deepStrictEqual(result.args, ['hello world', 'test&argument']);
|
|
271
271
|
});
|
|
272
272
|
|
|
273
273
|
it('should handle unicode characters', () => {
|
|
274
274
|
const template = 'echo $1 $2';
|
|
275
|
-
const
|
|
275
|
+
const item: ItemEntry = { name: 'unicode', value: 'こんにちは,世界' };
|
|
276
276
|
|
|
277
|
-
const result = TemplateExpander.expand(template,
|
|
277
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
278
278
|
|
|
279
|
-
assert.strictEqual(result.name, '
|
|
279
|
+
assert.strictEqual(result.name, 'unicode');
|
|
280
280
|
assert.deepStrictEqual(result.args, ['こんにちは', '世界']);
|
|
281
281
|
assert.strictEqual(result.fullCmd, 'echo こんにちは 世界');
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it('should handle numeric arguments', () => {
|
|
285
285
|
const template = 'app --port $1 --timeout $2 --retries $3';
|
|
286
|
-
const
|
|
286
|
+
const item: ItemEntry = { name: 'ports', value: '8080,5000,5' };
|
|
287
287
|
|
|
288
|
-
const result = TemplateExpander.expand(template,
|
|
288
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
289
289
|
|
|
290
290
|
assert.strictEqual(result.fullCmd, 'app --port 8080 --timeout 5000 --retries 5');
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
it('should preserve whitespace in command template', () => {
|
|
294
294
|
const template = 'cmd $1 $2';
|
|
295
|
-
const
|
|
295
|
+
const item: ItemEntry = { name: 'args', value: 'a,b' };
|
|
296
296
|
|
|
297
|
-
const result = TemplateExpander.expand(template,
|
|
297
|
+
const result = TemplateExpander.expand(template, item, 0);
|
|
298
298
|
|
|
299
299
|
// Template whitespace is preserved
|
|
300
300
|
assert.strictEqual(result.fullCmd, 'cmd a b');
|
|
@@ -302,9 +302,13 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
302
302
|
});
|
|
303
303
|
|
|
304
304
|
describe('Multiple items in sequence', () => {
|
|
305
|
-
it('should generate unique names using
|
|
305
|
+
it('should generate unique names using ItemEntry name', () => {
|
|
306
306
|
const template = 'worker $1';
|
|
307
|
-
const items = [
|
|
307
|
+
const items: ItemEntry[] = [
|
|
308
|
+
{ name: 'job1', value: 'job1' },
|
|
309
|
+
{ name: 'job2', value: 'job2' },
|
|
310
|
+
{ name: 'job3', value: 'job3' }
|
|
311
|
+
];
|
|
308
312
|
|
|
309
313
|
const results = items.map((item, index) =>
|
|
310
314
|
TemplateExpander.expand(template, item, index)
|
|
@@ -315,27 +319,31 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
315
319
|
assert.strictEqual(results[2].name, 'job3');
|
|
316
320
|
});
|
|
317
321
|
|
|
318
|
-
it('should use
|
|
322
|
+
it('should use ItemEntry name for identical values', () => {
|
|
319
323
|
const template = 'echo $1';
|
|
320
|
-
const items = [
|
|
324
|
+
const items: ItemEntry[] = [
|
|
325
|
+
{ name: 'test1', value: 'test' },
|
|
326
|
+
{ name: 'test2', value: 'test' },
|
|
327
|
+
{ name: 'test3', value: 'test' }
|
|
328
|
+
];
|
|
321
329
|
|
|
322
330
|
const results = items.map((item, index) =>
|
|
323
331
|
TemplateExpander.expand(template, item, index)
|
|
324
332
|
);
|
|
325
333
|
|
|
326
|
-
// All will have
|
|
327
|
-
assert.strictEqual(results[0].name, '
|
|
328
|
-
assert.strictEqual(results[1].name, '
|
|
329
|
-
assert.strictEqual(results[2].name, '
|
|
334
|
+
// All will have their unique ItemEntry name
|
|
335
|
+
assert.strictEqual(results[0].name, 'test1');
|
|
336
|
+
assert.strictEqual(results[1].name, 'test2');
|
|
337
|
+
assert.strictEqual(results[2].name, 'test3');
|
|
330
338
|
});
|
|
331
339
|
});
|
|
332
340
|
|
|
333
341
|
describe('ProcessItem type compliance', () => {
|
|
334
342
|
it('should return valid ProcessItem objects', () => {
|
|
335
343
|
const template = 'cmd $1';
|
|
336
|
-
const
|
|
344
|
+
const item: ItemEntry = { name: 'arg1', value: 'arg1,arg2' };
|
|
337
345
|
|
|
338
|
-
const result: ProcessItem = TemplateExpander.expand(template,
|
|
346
|
+
const result: ProcessItem = TemplateExpander.expand(template, item, 0);
|
|
339
347
|
|
|
340
348
|
assert.strictEqual(typeof result.name, 'string');
|
|
341
349
|
assert.ok(Array.isArray(result.args));
|
|
@@ -346,9 +354,9 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
346
354
|
|
|
347
355
|
it('should have consistent structure across different calls', () => {
|
|
348
356
|
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),
|
|
357
|
+
TemplateExpander.expand('echo $1', { name: 'test', value: 'test' }, 0),
|
|
358
|
+
TemplateExpander.expand('cmd $1 $2', { name: 'args', value: 'a,b' }, 1),
|
|
359
|
+
TemplateExpander.parseItem('node', null, { name: 'app', value: 'app.js' }, 2),
|
|
352
360
|
];
|
|
353
361
|
|
|
354
362
|
for (const result of results) {
|
|
@@ -363,10 +371,10 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
363
371
|
describe('Named params', () => {
|
|
364
372
|
it('should replace named param in template', () => {
|
|
365
373
|
const template = 'node $1.js --name $name';
|
|
366
|
-
const
|
|
374
|
+
const item: ItemEntry = { name: 'server', value: 'server' };
|
|
367
375
|
const params = { name: 'John doe' };
|
|
368
376
|
|
|
369
|
-
const result = TemplateExpander.expand(template,
|
|
377
|
+
const result = TemplateExpander.expand(template, item, 0, params);
|
|
370
378
|
|
|
371
379
|
assert.strictEqual(result.name, 'server');
|
|
372
380
|
assert.strictEqual(result.fullCmd, 'node server.js --name John doe');
|
|
@@ -374,49 +382,49 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
374
382
|
|
|
375
383
|
it('should replace multiple named params', () => {
|
|
376
384
|
const template = 'app --host $host --port $port --env $env';
|
|
377
|
-
const
|
|
385
|
+
const item: ItemEntry = { name: 'myapp', value: 'myapp' };
|
|
378
386
|
const params = { host: 'localhost', port: '3000', env: 'production' };
|
|
379
387
|
|
|
380
|
-
const result = TemplateExpander.expand(template,
|
|
388
|
+
const result = TemplateExpander.expand(template, item, 0, params);
|
|
381
389
|
|
|
382
390
|
assert.strictEqual(result.fullCmd, 'app --host localhost --port 3000 --env production');
|
|
383
391
|
});
|
|
384
392
|
|
|
385
393
|
it('should combine positional and named params', () => {
|
|
386
394
|
const template = 'node $1.js --name $name --port $port';
|
|
387
|
-
const
|
|
395
|
+
const item: ItemEntry = { name: 'server', value: 'server' };
|
|
388
396
|
const params = { name: 'Alice', port: '8080' };
|
|
389
397
|
|
|
390
|
-
const result = TemplateExpander.expand(template,
|
|
398
|
+
const result = TemplateExpander.expand(template, item, 0, params);
|
|
391
399
|
|
|
392
400
|
assert.strictEqual(result.fullCmd, 'node server.js --name Alice --port 8080');
|
|
393
401
|
});
|
|
394
402
|
|
|
395
403
|
it('should handle empty params object', () => {
|
|
396
404
|
const template = 'node $1.js';
|
|
397
|
-
const
|
|
405
|
+
const item: ItemEntry = { name: 'server', value: 'server' };
|
|
398
406
|
|
|
399
|
-
const result = TemplateExpander.expand(template,
|
|
407
|
+
const result = TemplateExpander.expand(template, item, 0, {});
|
|
400
408
|
|
|
401
409
|
assert.strictEqual(result.fullCmd, 'node server.js');
|
|
402
410
|
});
|
|
403
411
|
|
|
404
412
|
it('should leave unreplaced named params as-is', () => {
|
|
405
413
|
const template = 'node $1.js --name $name --env $env';
|
|
406
|
-
const
|
|
414
|
+
const item: ItemEntry = { name: 'server', value: 'server' };
|
|
407
415
|
const params = { name: 'Bob' }; // env not provided
|
|
408
416
|
|
|
409
|
-
const result = TemplateExpander.expand(template,
|
|
417
|
+
const result = TemplateExpander.expand(template, item, 0, params);
|
|
410
418
|
|
|
411
419
|
assert.strictEqual(result.fullCmd, 'node server.js --name Bob --env $env');
|
|
412
420
|
});
|
|
413
421
|
|
|
414
422
|
it('should replace all occurrences of named param', () => {
|
|
415
423
|
const template = 'echo $name and $name again';
|
|
416
|
-
const
|
|
424
|
+
const item: ItemEntry = { name: 'test', value: 'test' };
|
|
417
425
|
const params = { name: 'world' };
|
|
418
426
|
|
|
419
|
-
const result = TemplateExpander.expand(template,
|
|
427
|
+
const result = TemplateExpander.expand(template, item, 0, params);
|
|
420
428
|
|
|
421
429
|
assert.strictEqual(result.fullCmd, 'echo world and world again');
|
|
422
430
|
});
|
|
@@ -424,10 +432,10 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
424
432
|
it('should work with parseItem for registered tools', () => {
|
|
425
433
|
const tool = 'node-param';
|
|
426
434
|
const toolTemplate = 'node $1.js --name $name';
|
|
427
|
-
const
|
|
435
|
+
const item: ItemEntry = { name: 'server', value: 'server' };
|
|
428
436
|
const params = { name: 'Charlie' };
|
|
429
437
|
|
|
430
|
-
const result = TemplateExpander.parseItem(tool, toolTemplate,
|
|
438
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, item, 0, params);
|
|
431
439
|
|
|
432
440
|
assert.strictEqual(result.name, 'server');
|
|
433
441
|
assert.strictEqual(result.fullCmd, 'node server.js --name Charlie');
|
|
@@ -435,10 +443,10 @@ describe('TemplateExpander Integration Tests', () => {
|
|
|
435
443
|
|
|
436
444
|
it('should handle named params with spaces in values', () => {
|
|
437
445
|
const template = 'echo "Hello, $name!"';
|
|
438
|
-
const
|
|
446
|
+
const item: ItemEntry = { name: 'test', value: 'test' };
|
|
439
447
|
const params = { name: 'John Doe' };
|
|
440
448
|
|
|
441
|
-
const result = TemplateExpander.expand(template,
|
|
449
|
+
const result = TemplateExpander.expand(template, item, 0, params);
|
|
442
450
|
|
|
443
451
|
assert.strictEqual(result.fullCmd, 'echo "Hello, John Doe!"');
|
|
444
452
|
});
|
package/dist/commands/config.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "child_process";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
const CONFIG_FILENAME = ".cligr.yml";
|
|
6
|
-
const TEMPLATE = `# Cligr Configuration
|
|
7
|
-
|
|
8
|
-
groups:
|
|
9
|
-
web:
|
|
10
|
-
tool: docker
|
|
11
|
-
restart: false
|
|
12
|
-
items:
|
|
13
|
-
- "nginx,8080" # $1=nginx (name), $2=8080 (port)
|
|
14
|
-
- "nginx,3000"
|
|
15
|
-
|
|
16
|
-
simple:
|
|
17
|
-
tool: node
|
|
18
|
-
items:
|
|
19
|
-
- "server" # $1=server (name only)
|
|
20
|
-
|
|
21
|
-
tools:
|
|
22
|
-
docker:
|
|
23
|
-
cmd: "docker run -p $2:$2 nginx" # $1=name, $2=port
|
|
24
|
-
node:
|
|
25
|
-
cmd: "node $1.js" # $1=file name
|
|
26
|
-
|
|
27
|
-
# Syntax:
|
|
28
|
-
# - Items are comma-separated: "name,arg2,arg3"
|
|
29
|
-
# - $1 = name (first value)
|
|
30
|
-
# - $2, $3... = additional arguments
|
|
31
|
-
# - If no tool specified, executes directly
|
|
32
|
-
`;
|
|
33
|
-
function detectEditor() {
|
|
34
|
-
const platform = process.platform;
|
|
35
|
-
const whichCmd = platform === "win32" ? "where" : "which";
|
|
36
|
-
const codeCheck = spawnSync(whichCmd, ["code"], { stdio: "ignore" });
|
|
37
|
-
if (codeCheck.status === 0) {
|
|
38
|
-
return "code";
|
|
39
|
-
}
|
|
40
|
-
if (process.env.EDITOR) {
|
|
41
|
-
return process.env.EDITOR;
|
|
42
|
-
}
|
|
43
|
-
if (platform === "win32") {
|
|
44
|
-
return "notepad.exe";
|
|
45
|
-
}
|
|
46
|
-
return "vim";
|
|
47
|
-
}
|
|
48
|
-
function spawnEditor(filePath, editorCmd) {
|
|
49
|
-
const platform = process.platform;
|
|
50
|
-
const whichCmd = platform === "win32" ? "where" : "which";
|
|
51
|
-
const editorCheck = spawnSync(whichCmd, [editorCmd], { stdio: "ignore" });
|
|
52
|
-
if (editorCheck.status !== 0 && editorCmd !== process.env.EDITOR) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Editor '${editorCmd}' not found.
|
|
55
|
-
Install VS Code or set EDITOR environment variable.
|
|
56
|
-
|
|
57
|
-
Example:
|
|
58
|
-
export EDITOR=vim
|
|
59
|
-
cligr config`
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
const child = spawn(editorCmd, [filePath], {
|
|
63
|
-
detached: true,
|
|
64
|
-
stdio: "ignore",
|
|
65
|
-
shell: platform === "win32"
|
|
66
|
-
});
|
|
67
|
-
child.unref();
|
|
68
|
-
}
|
|
69
|
-
function createTemplate(filePath) {
|
|
70
|
-
const dir = path.dirname(filePath);
|
|
71
|
-
if (!fs.existsSync(dir)) {
|
|
72
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
fs.writeFileSync(filePath, TEMPLATE, "utf-8");
|
|
75
|
-
}
|
|
76
|
-
async function configCommand() {
|
|
77
|
-
try {
|
|
78
|
-
const homeDirConfig = path.join(os.homedir(), CONFIG_FILENAME);
|
|
79
|
-
const currentDirConfig = path.resolve(CONFIG_FILENAME);
|
|
80
|
-
let configPath;
|
|
81
|
-
if (fs.existsSync(homeDirConfig)) {
|
|
82
|
-
configPath = homeDirConfig;
|
|
83
|
-
} else if (fs.existsSync(currentDirConfig)) {
|
|
84
|
-
configPath = currentDirConfig;
|
|
85
|
-
} else {
|
|
86
|
-
configPath = homeDirConfig;
|
|
87
|
-
}
|
|
88
|
-
if (!fs.existsSync(configPath)) {
|
|
89
|
-
createTemplate(configPath);
|
|
90
|
-
}
|
|
91
|
-
const editor = detectEditor();
|
|
92
|
-
spawnEditor(configPath, editor);
|
|
93
|
-
console.log(`Opening ${configPath} in ${editor}...`);
|
|
94
|
-
return 0;
|
|
95
|
-
} catch (err) {
|
|
96
|
-
console.error(`Error: ${err.message}`);
|
|
97
|
-
return 1;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
export {
|
|
101
|
-
configCommand
|
|
102
|
-
};
|
package/dist/commands/down.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ProcessManager } from "../process/manager.js";
|
|
2
|
-
async function downCommand(groupName) {
|
|
3
|
-
const manager = new ProcessManager();
|
|
4
|
-
const result = await manager.killGroupByPid(groupName);
|
|
5
|
-
if (result.killed === 0 && result.notRunning === 0) {
|
|
6
|
-
console.log(`Group '${groupName}' is not running`);
|
|
7
|
-
return 0;
|
|
8
|
-
}
|
|
9
|
-
if (result.killed > 0) {
|
|
10
|
-
console.log(`Stopped ${result.killed} process(es) for group '${groupName}'`);
|
|
11
|
-
}
|
|
12
|
-
if (result.notRunning > 0) {
|
|
13
|
-
console.log(`Cleaned up ${result.notRunning} stale PID file(s) for group '${groupName}'`);
|
|
14
|
-
}
|
|
15
|
-
if (result.errors.length > 0) {
|
|
16
|
-
console.error("Errors while stopping processes:");
|
|
17
|
-
for (const err of result.errors) {
|
|
18
|
-
console.error(` ${err}`);
|
|
19
|
-
}
|
|
20
|
-
return 1;
|
|
21
|
-
}
|
|
22
|
-
return 0;
|
|
23
|
-
}
|
|
24
|
-
export {
|
|
25
|
-
downCommand
|
|
26
|
-
};
|