cligr 1.0.7 → 1.0.9
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/.claude/worktrees/agent-ac25cfb2/.claude/settings.local.json +30 -0
- package/.claude/worktrees/agent-ac25cfb2/README.md +65 -0
- package/.claude/worktrees/agent-ac25cfb2/docs/plans/2026-02-13-named-params-support.md +391 -0
- package/.claude/worktrees/agent-ac25cfb2/docs/plans/2026-02-25-named-items-design.md +164 -0
- package/.claude/worktrees/agent-ac25cfb2/docs/plans/2026-02-25-named-items-implementation.md +460 -0
- package/.claude/worktrees/agent-ac25cfb2/package-lock.json +554 -0
- package/.claude/worktrees/agent-ac25cfb2/package.json +27 -0
- package/.claude/worktrees/agent-ac25cfb2/scripts/build.js +20 -0
- package/.claude/worktrees/agent-ac25cfb2/scripts/test.js +168 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/config.ts +121 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/groups.ts +68 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/ls.ts +25 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/up.ts +49 -0
- package/.claude/worktrees/agent-ac25cfb2/src/config/loader.ts +148 -0
- package/.claude/worktrees/agent-ac25cfb2/src/config/types.ts +26 -0
- package/.claude/worktrees/agent-ac25cfb2/src/index.ts +97 -0
- package/.claude/worktrees/agent-ac25cfb2/src/process/manager.ts +270 -0
- package/.claude/worktrees/agent-ac25cfb2/src/process/pid-store.ts +203 -0
- package/.claude/worktrees/agent-ac25cfb2/src/process/template.ts +87 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/blocking-processes-fixed.test.ts +255 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/blocking-processes.test.ts +497 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/commands.test.ts +648 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/config-loader.test.ts +426 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/process-manager.test.ts +394 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/template-expander.test.ts +454 -0
- package/.claude/worktrees/agent-ac25cfb2/tsconfig.json +15 -0
- package/.claude/worktrees/agent-ac25cfb2/usage.md +9 -0
- package/dist/index.js +103 -46
- package/docs/superpowers/specs/2026-04-13-improve-web-ui-console-design.md +38 -0
- package/package.json +1 -1
- package/src/commands/groups.ts +1 -1
- package/src/commands/ls.ts +1 -1
- package/src/commands/serve.ts +65 -8
- package/src/config/loader.ts +6 -2
- package/src/config/types.ts +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(node scripts/test.js --verbose)",
|
|
5
|
+
"Bash(node:*)",
|
|
6
|
+
"Bash(npm test)",
|
|
7
|
+
"Bash(git add:*)",
|
|
8
|
+
"Bash(git commit:*)",
|
|
9
|
+
"Bash(git reset:*)",
|
|
10
|
+
"Bash(npm run build:*)",
|
|
11
|
+
"Bash(cd:*)",
|
|
12
|
+
"Bash(cd /c/code/easycli && npm run typecheck 2>&1)",
|
|
13
|
+
"Bash(grep:*)",
|
|
14
|
+
"Bash(cd C:/code/easycli && npx tsc --noEmit src/config/loader.ts 2>&1)",
|
|
15
|
+
"Bash(head:*)",
|
|
16
|
+
"Bash(true:*)",
|
|
17
|
+
"Bash(cd C:/code/easycli && npm run typecheck 2>&1)",
|
|
18
|
+
"Bash(cd /c/code/easycli && npx tsc --noEmit 2>&1)",
|
|
19
|
+
"Bash(cd /c/code/easycli && npx tsc --noEmit src/process/template.ts 2>&1)",
|
|
20
|
+
"Bash(cd /c/code/easycli && git stash && git checkout a97bc72 && npx tsc --noEmit 2>&1)",
|
|
21
|
+
"Bash(npm run:*)",
|
|
22
|
+
"Bash(cd C:\\\\code\\\\easycli:*)",
|
|
23
|
+
"Bash(ls:*)",
|
|
24
|
+
"Bash(xargs rm:*)",
|
|
25
|
+
"Bash(echo Exit code: $?:*)",
|
|
26
|
+
"Bash(cat:*)",
|
|
27
|
+
"Bash(cd /c/code/easycli && rm -rf dist && npm run build 2>&1)"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Cligr
|
|
2
|
+
|
|
3
|
+
A simple CLI tool for managing groups of concurrent processes.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g cligr
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Create a `.cligr.yml` configuration file. Cligr looks for the config in:
|
|
14
|
+
|
|
15
|
+
1. **User home directory** (`~/.cligr.yml`) - checked first
|
|
16
|
+
2. **Current directory** (`./.cligr.yml`) - fallback
|
|
17
|
+
|
|
18
|
+
You can keep a global config in your home directory and override it per project.
|
|
19
|
+
|
|
20
|
+
Quick start:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cligr config # Opens ~/.cligr.yml in your editor
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This creates a config file with examples if it doesn't exist.
|
|
27
|
+
|
|
28
|
+
Example config:
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
tools:
|
|
32
|
+
kubefwd:
|
|
33
|
+
cmd: kubectl port-forward $1 $2:$3
|
|
34
|
+
|
|
35
|
+
groups:
|
|
36
|
+
myapp:
|
|
37
|
+
tool: kubefwd
|
|
38
|
+
restart: yes
|
|
39
|
+
items:
|
|
40
|
+
- service1,8080,80
|
|
41
|
+
- service2,8081,80
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Syntax:**
|
|
45
|
+
- Items are comma-separated: `"name,arg2,arg3"`
|
|
46
|
+
- `$1` = name (first value)
|
|
47
|
+
- `$2`, `$3`... = additional arguments
|
|
48
|
+
- If no `tool` specified, executes directly
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cligr config # Open config file in editor
|
|
54
|
+
cligr up <group> # Start all processes in group
|
|
55
|
+
cligr ls <group> # List group items
|
|
56
|
+
cligr down <group> # Stop group (Ctrl+C also works)
|
|
57
|
+
cligr groups # List all groups
|
|
58
|
+
cligr groups -v # List groups with details
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Restart Policies
|
|
62
|
+
|
|
63
|
+
- `yes` - Always restart on exit
|
|
64
|
+
- `no` - Never restart
|
|
65
|
+
- `unless-stopped` - Restart unless killed by cligr
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Named Params Support Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add support for named parameters in group configs that can be referenced in tool templates using `$paramName` syntax.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Extend the existing `TemplateExpander` to handle named params alongside positional params ($1, $2). The params are defined at the group level and passed through during template expansion. Named params are replaced AFTER positional params to avoid conflicts.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Node.js, js-yaml
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Update Types
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `src/config/types.ts:5-9`
|
|
17
|
+
|
|
18
|
+
**Step 1: Add params field to GroupConfig type**
|
|
19
|
+
|
|
20
|
+
Add optional `params` field to `GroupConfig`:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
export interface GroupConfig {
|
|
24
|
+
tool: string;
|
|
25
|
+
restart?: 'yes' | 'no' | 'unless-stopped';
|
|
26
|
+
params?: Record<string, string>;
|
|
27
|
+
items: string[];
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Step 2: Verify TypeScript compiles**
|
|
32
|
+
|
|
33
|
+
Run: `npm run typecheck`
|
|
34
|
+
Expected: No errors
|
|
35
|
+
|
|
36
|
+
**Step 3: Commit**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git add src/config/types.ts
|
|
40
|
+
git commit -m "feat(types): add optional params field to GroupConfig"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Task 2: Update ConfigLoader
|
|
46
|
+
|
|
47
|
+
**Files:**
|
|
48
|
+
- Modify: `src/config/loader.ts:74-97`
|
|
49
|
+
|
|
50
|
+
**Step 1: Update getGroup return type to include params**
|
|
51
|
+
|
|
52
|
+
Modify the `getGroup` method to extract and return params from group config:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
getGroup(name: string): { config: GroupConfig; tool: string | null; toolTemplate: string | null; params: Record<string, string> } {
|
|
56
|
+
const config = this.load();
|
|
57
|
+
const group = config.groups[name];
|
|
58
|
+
|
|
59
|
+
if (!group) {
|
|
60
|
+
const available = Object.keys(config.groups).join(', ');
|
|
61
|
+
throw new ConfigError(`Unknown group: ${name}. Available: ${available}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Resolve tool
|
|
65
|
+
let toolTemplate: string | null = null;
|
|
66
|
+
let tool: string | null = null;
|
|
67
|
+
|
|
68
|
+
if (config.tools && config.tools[group.tool]) {
|
|
69
|
+
toolTemplate = config.tools[group.tool].cmd;
|
|
70
|
+
tool = group.tool;
|
|
71
|
+
} else {
|
|
72
|
+
tool = null;
|
|
73
|
+
toolTemplate = null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Extract params (default to empty object)
|
|
77
|
+
const params = group.params || {};
|
|
78
|
+
|
|
79
|
+
return { config: group, tool, toolTemplate, params };
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Step 2: Verify TypeScript compiles**
|
|
84
|
+
|
|
85
|
+
Run: `npm run typecheck`
|
|
86
|
+
Expected: No errors
|
|
87
|
+
|
|
88
|
+
**Step 3: Commit**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git add src/config/loader.ts
|
|
92
|
+
git commit -m "feat(loader): extract and return params from group config"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Task 3: Update TemplateExpander
|
|
98
|
+
|
|
99
|
+
**Files:**
|
|
100
|
+
- Modify: `src/process/template.ts:10-25`
|
|
101
|
+
|
|
102
|
+
**Step 1: Add expandNamedParams helper method**
|
|
103
|
+
|
|
104
|
+
Add a new static method to handle named parameter replacement:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
/**
|
|
108
|
+
* Replaces named params in template ($name, $env, etc.)
|
|
109
|
+
* @param template - Command template with $paramName placeholders
|
|
110
|
+
* @param params - Key-value pairs for substitution
|
|
111
|
+
* @returns Template with named params replaced
|
|
112
|
+
*/
|
|
113
|
+
private static expandNamedParams(template: string, params: Record<string, string>): string {
|
|
114
|
+
let result = template;
|
|
115
|
+
for (const [key, value] of Object.entries(params)) {
|
|
116
|
+
const placeholder = `$${key}`;
|
|
117
|
+
result = result.replaceAll(placeholder, value);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Step 2: Update expand method signature and logic**
|
|
124
|
+
|
|
125
|
+
Update the `expand` method to accept optional params and apply them after positional replacement:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
static expand(template: string, itemStr: string, index: number, params: Record<string, string> = {}): ProcessItem {
|
|
129
|
+
const args = itemStr.split(',').map(s => s.trim());
|
|
130
|
+
|
|
131
|
+
// Generate name from first arg or use index
|
|
132
|
+
const name = args[0] || `item-${index}`;
|
|
133
|
+
|
|
134
|
+
// Replace $1, $2, $3 etc. with args (positional params)
|
|
135
|
+
// Must replace in reverse order to avoid replacing $1 in $10, $11, etc.
|
|
136
|
+
let fullCmd = template;
|
|
137
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
138
|
+
const placeholder = `$${i + 1}`;
|
|
139
|
+
fullCmd = fullCmd.replaceAll(placeholder, args[i]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Replace named params ($name, $env, etc.) AFTER positional params
|
|
143
|
+
fullCmd = this.expandNamedParams(fullCmd, params);
|
|
144
|
+
|
|
145
|
+
return { name, args, fullCmd };
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Step 3: Update parseItem method signature**
|
|
150
|
+
|
|
151
|
+
Update `parseItem` to accept optional params:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
static parseItem(
|
|
155
|
+
tool: string | null,
|
|
156
|
+
toolTemplate: string | null,
|
|
157
|
+
itemStr: string,
|
|
158
|
+
index: number,
|
|
159
|
+
params: Record<string, string> = {}
|
|
160
|
+
): ProcessItem {
|
|
161
|
+
if (toolTemplate) {
|
|
162
|
+
// Use registered tool template
|
|
163
|
+
const result = this.expand(toolTemplate, itemStr, index, params);
|
|
164
|
+
|
|
165
|
+
// ... rest of the method stays the same
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Step 4: Verify TypeScript compiles**
|
|
169
|
+
|
|
170
|
+
Run: `npm run typecheck`
|
|
171
|
+
Expected: No errors
|
|
172
|
+
|
|
173
|
+
**Step 5: Commit**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git add src/process/template.ts
|
|
177
|
+
git commit -m "feat(template): add named params support to TemplateExpander"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Task 4: Write Tests for Named Params
|
|
183
|
+
|
|
184
|
+
**Files:**
|
|
185
|
+
- Modify: `tests/integration/template-expander.test.ts`
|
|
186
|
+
|
|
187
|
+
**Step 1: Add test block for named params**
|
|
188
|
+
|
|
189
|
+
Add new describe block at the end of the test file (before the closing of the outer describe):
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
describe('Named params', () => {
|
|
193
|
+
it('should replace named param in template', () => {
|
|
194
|
+
const template = 'node $1.js --name $name';
|
|
195
|
+
const itemStr = 'server';
|
|
196
|
+
const params = { name: 'John doe' };
|
|
197
|
+
|
|
198
|
+
const result = TemplateExpander.expand(template, itemStr, 0, params);
|
|
199
|
+
|
|
200
|
+
assert.strictEqual(result.name, 'server');
|
|
201
|
+
assert.strictEqual(result.fullCmd, 'node server.js --name John doe');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should replace multiple named params', () => {
|
|
205
|
+
const template = 'app --host $host --port $port --env $env';
|
|
206
|
+
const itemStr = 'myapp';
|
|
207
|
+
const params = { host: 'localhost', port: '3000', env: 'production' };
|
|
208
|
+
|
|
209
|
+
const result = TemplateExpander.expand(template, itemStr, 0, params);
|
|
210
|
+
|
|
211
|
+
assert.strictEqual(result.fullCmd, 'app --host localhost --port 3000 --env production');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should combine positional and named params', () => {
|
|
215
|
+
const template = 'node $1.js --name $name --port $port';
|
|
216
|
+
const itemStr = 'server';
|
|
217
|
+
const params = { name: 'Alice', port: '8080' };
|
|
218
|
+
|
|
219
|
+
const result = TemplateExpander.expand(template, itemStr, 0, params);
|
|
220
|
+
|
|
221
|
+
assert.strictEqual(result.fullCmd, 'node server.js --name Alice --port 8080');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should handle empty params object', () => {
|
|
225
|
+
const template = 'node $1.js';
|
|
226
|
+
const itemStr = 'server';
|
|
227
|
+
|
|
228
|
+
const result = TemplateExpander.expand(template, itemStr, 0, {});
|
|
229
|
+
|
|
230
|
+
assert.strictEqual(result.fullCmd, 'node server.js');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should leave unreplaced named params as-is', () => {
|
|
234
|
+
const template = 'node $1.js --name $name --env $env';
|
|
235
|
+
const itemStr = 'server';
|
|
236
|
+
const params = { name: 'Bob' }; // env not provided
|
|
237
|
+
|
|
238
|
+
const result = TemplateExpander.expand(template, itemStr, 0, params);
|
|
239
|
+
|
|
240
|
+
assert.strictEqual(result.fullCmd, 'node server.js --name Bob --env $env');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should replace all occurrences of named param', () => {
|
|
244
|
+
const template = 'echo $name and $name again';
|
|
245
|
+
const itemStr = 'test';
|
|
246
|
+
const params = { name: 'world' };
|
|
247
|
+
|
|
248
|
+
const result = TemplateExpander.expand(template, itemStr, 0, params);
|
|
249
|
+
|
|
250
|
+
assert.strictEqual(result.fullCmd, 'echo world and world again');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should work with parseItem for registered tools', () => {
|
|
254
|
+
const tool = 'node-param';
|
|
255
|
+
const toolTemplate = 'node $1.js --name $name';
|
|
256
|
+
const itemStr = 'server';
|
|
257
|
+
const params = { name: 'Charlie' };
|
|
258
|
+
|
|
259
|
+
const result = TemplateExpander.parseItem(tool, toolTemplate, itemStr, 0, params);
|
|
260
|
+
|
|
261
|
+
assert.strictEqual(result.name, 'server');
|
|
262
|
+
assert.strictEqual(result.fullCmd, 'node server.js --name Charlie');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should handle named params with spaces in values', () => {
|
|
266
|
+
const template = 'echo "Hello, $name!"';
|
|
267
|
+
const itemStr = 'test';
|
|
268
|
+
const params = { name: 'John Doe' };
|
|
269
|
+
|
|
270
|
+
const result = TemplateExpander.expand(template, itemStr, 0, params);
|
|
271
|
+
|
|
272
|
+
assert.strictEqual(result.fullCmd, 'echo "Hello, John Doe!"');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Step 2: Run tests to verify they fail**
|
|
278
|
+
|
|
279
|
+
Run: `npm test`
|
|
280
|
+
Expected: Named params tests fail (feature not implemented yet)
|
|
281
|
+
|
|
282
|
+
**Step 3: Commit**
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
git add tests/integration/template-expander.test.ts
|
|
286
|
+
git commit -m "test: add tests for named params support"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Task 5: Update up.ts Command
|
|
292
|
+
|
|
293
|
+
**Files:**
|
|
294
|
+
- Modify: `src/commands/up.ts:16-21`
|
|
295
|
+
|
|
296
|
+
**Step 1: Pass params to TemplateExpander.parseItem**
|
|
297
|
+
|
|
298
|
+
Update the `upCommand` to extract and pass params:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
export async function upCommand(groupName: string): Promise<number> {
|
|
302
|
+
const loader = new ConfigLoader();
|
|
303
|
+
const manager = new ProcessManager();
|
|
304
|
+
const pidStore = new PidStore();
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
// Clean up any stale PID files for this group on startup
|
|
308
|
+
await pidStore.cleanupStalePids();
|
|
309
|
+
|
|
310
|
+
// Load group config
|
|
311
|
+
const { config, tool, toolTemplate, params } = loader.getGroup(groupName);
|
|
312
|
+
|
|
313
|
+
// Build process items
|
|
314
|
+
const items = config.items.map((itemStr, index) =>
|
|
315
|
+
TemplateExpander.parseItem(tool, toolTemplate, itemStr, index, params)
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// ... rest stays the same
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Step 2: Verify TypeScript compiles**
|
|
322
|
+
|
|
323
|
+
Run: `npm run typecheck`
|
|
324
|
+
Expected: No errors
|
|
325
|
+
|
|
326
|
+
**Step 3: Commit**
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
git add src/commands/up.ts
|
|
330
|
+
git commit -m "feat(up): pass params to template expander"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Task 6: Run All Tests
|
|
336
|
+
|
|
337
|
+
**Step 1: Run full test suite**
|
|
338
|
+
|
|
339
|
+
Run: `npm test`
|
|
340
|
+
Expected: All tests pass including new named params tests
|
|
341
|
+
|
|
342
|
+
**Step 2: Run verbose tests for details**
|
|
343
|
+
|
|
344
|
+
Run: `npm run test:verbose`
|
|
345
|
+
Expected: All tests pass with detailed output
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Task 7: Integration Test
|
|
350
|
+
|
|
351
|
+
**Step 1: Build the project**
|
|
352
|
+
|
|
353
|
+
Run: `npm run build`
|
|
354
|
+
Expected: Build succeeds
|
|
355
|
+
|
|
356
|
+
**Step 2: Create test config file**
|
|
357
|
+
|
|
358
|
+
Create a temporary test config at `~/.cligr.yml` (or use existing one) with the named params example:
|
|
359
|
+
|
|
360
|
+
```yaml
|
|
361
|
+
groups:
|
|
362
|
+
test-named-params:
|
|
363
|
+
tool: node-param
|
|
364
|
+
params:
|
|
365
|
+
name: 'John doe'
|
|
366
|
+
items:
|
|
367
|
+
- "server"
|
|
368
|
+
|
|
369
|
+
tools:
|
|
370
|
+
node-param:
|
|
371
|
+
cmd: "echo $1.js --name $name"
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Step 3: Run the command**
|
|
375
|
+
|
|
376
|
+
Run: `node dist/index.js test-named-params`
|
|
377
|
+
Expected: Output shows `echo server.js --name John doe`
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Summary
|
|
382
|
+
|
|
383
|
+
| Task | Description | Files Modified |
|
|
384
|
+
|------|-------------|----------------|
|
|
385
|
+
| 1 | Update types | `src/config/types.ts` |
|
|
386
|
+
| 2 | Update loader | `src/config/loader.ts` |
|
|
387
|
+
| 3 | Update template expander | `src/process/template.ts` |
|
|
388
|
+
| 4 | Write tests | `tests/integration/template-expander.test.ts` |
|
|
389
|
+
| 5 | Update up command | `src/commands/up.ts` |
|
|
390
|
+
| 6 | Run all tests | - |
|
|
391
|
+
| 7 | Integration test | - |
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Named Items Design
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-02-25
|
|
4
|
+
**Status:** Approved
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
Update the config system to support named items in groups. Instead of an array of item strings, items will be a map where keys are explicit names and values are the item strings.
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
- **Better identification:** Explicit names make it easier to identify services in logs and `ls` output
|
|
13
|
+
- **Consistent naming:** Process names, log prefixes, and PID files all use the explicit name
|
|
14
|
+
- **Clearer config:** Named items are more self-documenting than positional arrays
|
|
15
|
+
|
|
16
|
+
## Changes
|
|
17
|
+
|
|
18
|
+
### 1. Type Changes (`src/config/types.ts`)
|
|
19
|
+
|
|
20
|
+
Add new `ItemEntry` type and update `GroupConfig`:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
export interface ItemEntry {
|
|
24
|
+
name: string; // the key from config (e.g., "nginxService1")
|
|
25
|
+
value: string; // the value string (e.g., "nginx,8080")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GroupConfig {
|
|
29
|
+
tool: string;
|
|
30
|
+
restart?: 'yes' | 'no' | 'unless-stopped';
|
|
31
|
+
params?: Record<string, string>;
|
|
32
|
+
items: Record<string, string>; // only named format
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Config Loader Changes (`src/config/loader.ts`)
|
|
37
|
+
|
|
38
|
+
- Add `normalizeItems()` method to convert object to `ItemEntry[]`
|
|
39
|
+
- Update `getGroup()` to return normalized items
|
|
40
|
+
- Add validation for items format and unique names
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
private normalizeItems(items: Record<string, string>): ItemEntry[] {
|
|
44
|
+
return Object.entries(items).map(([name, value]) => ({
|
|
45
|
+
name,
|
|
46
|
+
value
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private validateItems(items: unknown, groupName: string): void {
|
|
51
|
+
if (!items || typeof items !== 'object' || Array.isArray(items)) {
|
|
52
|
+
throw new ConfigError(
|
|
53
|
+
'items must be an object with named entries, e.g.:\n' +
|
|
54
|
+
' items:\n' +
|
|
55
|
+
' serviceName: "value1,value2"'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const seenNames = new Set<string>();
|
|
60
|
+
|
|
61
|
+
for (const [name, value] of Object.entries(items as Record<string, unknown>)) {
|
|
62
|
+
if (typeof value !== 'string') {
|
|
63
|
+
throw new ConfigError(`Item "${name}" must have a string value`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (seenNames.has(name)) {
|
|
67
|
+
throw new ConfigError(
|
|
68
|
+
`Duplicate item name "${name}" in group "${groupName}". ` +
|
|
69
|
+
`Item names must be unique within a group.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
seenNames.add(name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. TemplateExpander Changes (`src/process/template.ts`)
|
|
78
|
+
|
|
79
|
+
Update `expand()` and `parseItem()` to accept `ItemEntry`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
static expand(
|
|
83
|
+
template: string,
|
|
84
|
+
item: ItemEntry,
|
|
85
|
+
index: number,
|
|
86
|
+
params: Record<string, string> = {}
|
|
87
|
+
): ProcessItem {
|
|
88
|
+
const args = item.value.split(',').map(s => s.trim());
|
|
89
|
+
const name = item.name; // Use explicit name
|
|
90
|
+
// ... rest of expansion logic
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. ls Command Changes (`src/commands/ls.ts`)
|
|
95
|
+
|
|
96
|
+
Display items in `name: value` format:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
for (const item of items) {
|
|
100
|
+
console.log(` ${item.name}: ${item.value}`);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Example output:**
|
|
105
|
+
```
|
|
106
|
+
Group: web
|
|
107
|
+
Tool: docker
|
|
108
|
+
Restart: false
|
|
109
|
+
|
|
110
|
+
Items:
|
|
111
|
+
nginxService1: nginx,8080
|
|
112
|
+
nginxService2: nginx,3000
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. up Command Changes (`src/commands/up.ts`)
|
|
116
|
+
|
|
117
|
+
Pass `ItemEntry` to template expander:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const processItems = items.map((item, index) =>
|
|
121
|
+
TemplateExpander.parseItem(tool, toolTemplate, item, index, params)
|
|
122
|
+
);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 6. No Changes Required
|
|
126
|
+
|
|
127
|
+
- `src/process/manager.ts` - Uses `ProcessItem.name` as-is, no changes needed
|
|
128
|
+
- `src/process/pid-store.ts` - Uses item name passed from manager, no changes needed
|
|
129
|
+
|
|
130
|
+
## Config Example
|
|
131
|
+
|
|
132
|
+
**Before:**
|
|
133
|
+
```yaml
|
|
134
|
+
groups:
|
|
135
|
+
web:
|
|
136
|
+
tool: docker
|
|
137
|
+
restart: false
|
|
138
|
+
items:
|
|
139
|
+
- "nginx,8080"
|
|
140
|
+
- "nginx,3000"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**After:**
|
|
144
|
+
```yaml
|
|
145
|
+
groups:
|
|
146
|
+
web:
|
|
147
|
+
tool: docker
|
|
148
|
+
restart: false
|
|
149
|
+
items:
|
|
150
|
+
nginxService1: "nginx,8080"
|
|
151
|
+
nginxService2: "nginx,3000"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Files to Modify
|
|
155
|
+
|
|
156
|
+
1. `src/config/types.ts` - Add `ItemEntry`, update `GroupConfig.items`
|
|
157
|
+
2. `src/config/loader.ts` - Add normalization and validation
|
|
158
|
+
3. `src/process/template.ts` - Accept `ItemEntry`
|
|
159
|
+
4. `src/commands/ls.ts` - Update output format
|
|
160
|
+
5. `src/commands/up.ts` - Pass `ItemEntry` to expander
|
|
161
|
+
|
|
162
|
+
## Breaking Change
|
|
163
|
+
|
|
164
|
+
This is a **breaking change** for existing configs. Users must update their configs to use the named format.
|