@blockrun/clawrouter 0.12.62 → 0.12.63
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/docs/anthropic-cost-savings.md +349 -0
- package/docs/architecture.md +559 -0
- package/docs/assets/blockrun-248-day-cost-overrun-problem.png +0 -0
- package/docs/assets/blockrun-clawrouter-7-layer-token-compression-openclaw.png +0 -0
- package/docs/assets/blockrun-clawrouter-observation-compression-97-percent-token-savings.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-agentic-proxy-architecture.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-automatic-tier-routing-model-selection.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-error-classification-retry-storm-prevention.png +0 -0
- package/docs/assets/blockrun-clawrouter-openclaw-session-memory-journaling-vs-context-compounding.png +0 -0
- package/docs/assets/blockrun-clawrouter-vs-openclaw-standalone-comparison-production-safety.png +0 -0
- package/docs/assets/blockrun-clawrouter-x402-usdc-micropayment-wallet-budget-control.png +0 -0
- package/docs/assets/blockrun-openclaw-inference-layer-blind-spots.png +0 -0
- package/docs/blog-benchmark-2026-03.md +184 -0
- package/docs/blog-openclaw-cost-overruns.md +197 -0
- package/docs/clawrouter-savings.png +0 -0
- package/docs/configuration.md +512 -0
- package/docs/features.md +257 -0
- package/docs/image-generation.md +380 -0
- package/docs/plans/2026-02-03-smart-routing-design.md +267 -0
- package/docs/plans/2026-02-13-e2e-docker-deployment.md +1260 -0
- package/docs/plans/2026-02-28-worker-network.md +947 -0
- package/docs/plans/2026-03-18-error-classification.md +574 -0
- package/docs/plans/2026-03-19-exclude-models.md +538 -0
- package/docs/routing-profiles.md +81 -0
- package/docs/subscription-failover.md +320 -0
- package/docs/technical-routing-2026-03.md +322 -0
- package/docs/troubleshooting.md +159 -0
- package/docs/vision.md +49 -0
- package/docs/vs-openrouter.md +157 -0
- package/docs/worker-network.md +1241 -0
- package/package.json +2 -1
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
# ClawRouter E2E Testing, Docker Validation & Deployment
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Comprehensive E2E test coverage, Docker install/uninstall validation (10 cases), and automated deployment pipeline for ClawRouter.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Three-phase approach: (1) Expand E2E tests to cover error scenarios, edge cases, and the recent fixes (504 timeout, settlement retry, large payload handling), (2) Build Docker-based installation testing covering npm global, OpenClaw plugin, upgrade/downgrade, and cleanup scenarios, (3) Automate deployment with pre-publish validation and version management.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, tsx test runner, Docker, npm, OpenClaw CLI, bash scripting
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: E2E Test Expansion
|
|
14
|
+
|
|
15
|
+
**Goal:** Add 10+ new E2E test cases covering error handling, edge cases, and recent bug fixes.
|
|
16
|
+
|
|
17
|
+
**Files:**
|
|
18
|
+
|
|
19
|
+
- Modify: `test/test-e2e.ts`
|
|
20
|
+
- Run: `npx tsx test/test-e2e.ts`
|
|
21
|
+
|
|
22
|
+
### Step 1: Add test for 413 Payload Too Large (150KB limit)
|
|
23
|
+
|
|
24
|
+
**Code to add after existing tests (before cleanup section):**
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Test 8: 413 Payload Too Large — message array exceeds 150KB
|
|
28
|
+
allPassed =
|
|
29
|
+
(await test(
|
|
30
|
+
"413 error for oversized request (>150KB)",
|
|
31
|
+
async (p) => {
|
|
32
|
+
const largeMessage = "x".repeat(160 * 1024); // 160KB
|
|
33
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
model: "deepseek/deepseek-chat",
|
|
38
|
+
messages: [{ role: "user", content: largeMessage }],
|
|
39
|
+
max_tokens: 10,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
if (res.status !== 413) {
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
throw new Error(`Expected 413, got ${res.status}: ${text.slice(0, 200)}`);
|
|
45
|
+
}
|
|
46
|
+
const body = await res.json();
|
|
47
|
+
if (!body.error?.message?.includes("exceeds maximum"))
|
|
48
|
+
throw new Error("Missing size limit error message");
|
|
49
|
+
console.log(`(payload=${Math.round(largeMessage.length / 1024)}KB, status=413) `);
|
|
50
|
+
},
|
|
51
|
+
proxy,
|
|
52
|
+
)) && allPassed;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Step 2: Add test for 400 Bad Request (malformed JSON)
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Test 9: 400 Bad Request — malformed JSON
|
|
59
|
+
allPassed =
|
|
60
|
+
(await test(
|
|
61
|
+
"400 error for malformed JSON",
|
|
62
|
+
async (p) => {
|
|
63
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
body: "{invalid json}",
|
|
67
|
+
});
|
|
68
|
+
if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
|
|
69
|
+
const body = await res.json();
|
|
70
|
+
if (!body.error) throw new Error("Missing error object");
|
|
71
|
+
},
|
|
72
|
+
proxy,
|
|
73
|
+
)) && allPassed;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 3: Add test for 400 Bad Request (missing required fields)
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Test 10: 400 Bad Request — missing messages field
|
|
80
|
+
allPassed =
|
|
81
|
+
(await test(
|
|
82
|
+
"400 error for missing messages field",
|
|
83
|
+
async (p) => {
|
|
84
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
model: "deepseek/deepseek-chat",
|
|
89
|
+
max_tokens: 10,
|
|
90
|
+
// missing messages
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
|
|
94
|
+
const body = await res.json();
|
|
95
|
+
if (!body.error?.message?.includes("messages"))
|
|
96
|
+
throw new Error("Error should mention missing messages");
|
|
97
|
+
},
|
|
98
|
+
proxy,
|
|
99
|
+
)) && allPassed;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Step 4: Add test for large message array (200 messages limit)
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Test 11: 400 error for too many messages (>200)
|
|
106
|
+
allPassed =
|
|
107
|
+
(await test(
|
|
108
|
+
"400 error for message array exceeding 200 items",
|
|
109
|
+
async (p) => {
|
|
110
|
+
const messages = Array(201)
|
|
111
|
+
.fill(null)
|
|
112
|
+
.map((_, i) => ({ role: i % 2 === 0 ? "user" : "assistant", content: "test" }));
|
|
113
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "Content-Type": "application/json" },
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
model: "deepseek/deepseek-chat",
|
|
118
|
+
messages,
|
|
119
|
+
max_tokens: 10,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
if (res.status !== 400) {
|
|
123
|
+
const text = await res.text();
|
|
124
|
+
throw new Error(`Expected 400, got ${res.status}: ${text.slice(0, 200)}`);
|
|
125
|
+
}
|
|
126
|
+
const body = await res.json();
|
|
127
|
+
if (!body.error?.message?.includes("200"))
|
|
128
|
+
throw new Error("Error should mention message limit");
|
|
129
|
+
console.log(`(messages=${messages.length}, status=400) `);
|
|
130
|
+
},
|
|
131
|
+
proxy,
|
|
132
|
+
)) && allPassed;
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Step 5: Add test for invalid model name
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Test 12: Model fallback — invalid model should fail gracefully
|
|
139
|
+
allPassed =
|
|
140
|
+
(await test(
|
|
141
|
+
"Invalid model returns clear error",
|
|
142
|
+
async (p) => {
|
|
143
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify({
|
|
147
|
+
model: "invalid/nonexistent-model",
|
|
148
|
+
messages: [{ role: "user", content: "test" }],
|
|
149
|
+
max_tokens: 10,
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
if (res.status !== 400) {
|
|
153
|
+
const text = await res.text();
|
|
154
|
+
throw new Error(`Expected 400, got ${res.status}: ${text.slice(0, 200)}`);
|
|
155
|
+
}
|
|
156
|
+
const body = await res.json();
|
|
157
|
+
if (!body.error) throw new Error("Missing error object");
|
|
158
|
+
},
|
|
159
|
+
proxy,
|
|
160
|
+
)) && allPassed;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Step 6: Add test for concurrent requests (stress test)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// Test 13: Concurrent requests — send 5 parallel requests
|
|
167
|
+
allPassed =
|
|
168
|
+
(await test(
|
|
169
|
+
"Concurrent requests (5 parallel)",
|
|
170
|
+
async (p) => {
|
|
171
|
+
const makeRequest = () =>
|
|
172
|
+
fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
model: "deepseek/deepseek-chat",
|
|
177
|
+
messages: [{ role: "user", content: `Test ${Math.random()}` }],
|
|
178
|
+
max_tokens: 5,
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const start = Date.now();
|
|
183
|
+
const results = await Promise.all([
|
|
184
|
+
makeRequest(),
|
|
185
|
+
makeRequest(),
|
|
186
|
+
makeRequest(),
|
|
187
|
+
makeRequest(),
|
|
188
|
+
makeRequest(),
|
|
189
|
+
]);
|
|
190
|
+
const elapsed = Date.now() - start;
|
|
191
|
+
|
|
192
|
+
const allSucceeded = results.every((r) => r.status === 200);
|
|
193
|
+
if (!allSucceeded) {
|
|
194
|
+
const statuses = results.map((r) => r.status).join(", ");
|
|
195
|
+
throw new Error(`Not all requests succeeded: ${statuses}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`(5 requests in ${elapsed}ms, avg=${Math.round(elapsed / 5)}ms) `);
|
|
199
|
+
},
|
|
200
|
+
proxy,
|
|
201
|
+
)) && allPassed;
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Step 7: Add test for negative max_tokens (should be rejected)
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Test 14: Negative max_tokens should be rejected
|
|
208
|
+
allPassed =
|
|
209
|
+
(await test(
|
|
210
|
+
"400 error for negative max_tokens",
|
|
211
|
+
async (p) => {
|
|
212
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers: { "Content-Type": "application/json" },
|
|
215
|
+
body: JSON.stringify({
|
|
216
|
+
model: "deepseek/deepseek-chat",
|
|
217
|
+
messages: [{ role: "user", content: "test" }],
|
|
218
|
+
max_tokens: -100,
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
|
|
222
|
+
const body = await res.json();
|
|
223
|
+
if (!body.error) throw new Error("Missing error object");
|
|
224
|
+
},
|
|
225
|
+
proxy,
|
|
226
|
+
)) && allPassed;
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Step 8: Add test for empty messages array
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Test 15: Empty messages array should be rejected
|
|
233
|
+
allPassed =
|
|
234
|
+
(await test(
|
|
235
|
+
"400 error for empty messages array",
|
|
236
|
+
async (p) => {
|
|
237
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
headers: { "Content-Type": "application/json" },
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
model: "deepseek/deepseek-chat",
|
|
242
|
+
messages: [],
|
|
243
|
+
max_tokens: 10,
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
|
|
247
|
+
},
|
|
248
|
+
proxy,
|
|
249
|
+
)) && allPassed;
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Step 9: Add test for streaming with large response (token counting)
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// Test 16: Streaming large response — verify token counting
|
|
256
|
+
allPassed =
|
|
257
|
+
(await test(
|
|
258
|
+
"Streaming with large output (token counting)",
|
|
259
|
+
async (p) => {
|
|
260
|
+
const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: { "Content-Type": "application/json" },
|
|
263
|
+
body: JSON.stringify({
|
|
264
|
+
model: "deepseek/deepseek-chat",
|
|
265
|
+
messages: [
|
|
266
|
+
{
|
|
267
|
+
role: "user",
|
|
268
|
+
content: "Write a 50-word story about a robot. Be concise.",
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
max_tokens: 100,
|
|
272
|
+
stream: true,
|
|
273
|
+
}),
|
|
274
|
+
});
|
|
275
|
+
if (res.status !== 200) throw new Error(`Expected 200, got ${res.status}`);
|
|
276
|
+
|
|
277
|
+
const text = await res.text();
|
|
278
|
+
const lines = text.split("\n").filter((l) => l.startsWith("data: "));
|
|
279
|
+
const hasDone = lines.some((l) => l === "data: [DONE]");
|
|
280
|
+
if (!hasDone) throw new Error("Missing [DONE] marker");
|
|
281
|
+
|
|
282
|
+
let fullContent = "";
|
|
283
|
+
for (const line of lines.filter((l) => l !== "data: [DONE]")) {
|
|
284
|
+
try {
|
|
285
|
+
const parsed = JSON.parse(line.slice(6));
|
|
286
|
+
const delta = parsed.choices?.[0]?.delta?.content;
|
|
287
|
+
if (delta) fullContent += delta;
|
|
288
|
+
} catch {
|
|
289
|
+
// skip
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const wordCount = fullContent.trim().split(/\s+/).length;
|
|
294
|
+
console.log(`(words=${wordCount}, chunks=${lines.length - 1}) `);
|
|
295
|
+
if (wordCount < 10) throw new Error(`Response too short: ${wordCount} words`);
|
|
296
|
+
},
|
|
297
|
+
proxy,
|
|
298
|
+
)) && allPassed;
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Step 10: Add test for balance check (verify wallet has funds)
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// Test 17: Balance check before test (ensure wallet is funded)
|
|
305
|
+
allPassed =
|
|
306
|
+
(await test(
|
|
307
|
+
"Wallet has sufficient balance",
|
|
308
|
+
async (p) => {
|
|
309
|
+
if (!p.balanceMonitor) throw new Error("Balance monitor not available");
|
|
310
|
+
const balance = await p.balanceMonitor.checkBalance();
|
|
311
|
+
if (balance.isEmpty) throw new Error("Wallet is empty - please fund it");
|
|
312
|
+
console.log(`(balance=$${balance.balanceUSD.toFixed(2)}) `);
|
|
313
|
+
},
|
|
314
|
+
proxy,
|
|
315
|
+
)) && allPassed;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Step 11: Run expanded E2E tests
|
|
319
|
+
|
|
320
|
+
Run:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
BLOCKRUN_WALLET_KEY=0x... npx tsx test/test-e2e.ts
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Expected output:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
=== ClawRouter e2e tests ===
|
|
330
|
+
|
|
331
|
+
Starting proxy...
|
|
332
|
+
Proxy ready on port 8405
|
|
333
|
+
Health check ... (wallet: 0xABC...) PASS
|
|
334
|
+
Non-streaming request (deepseek/deepseek-chat) ... (response: "4") PASS
|
|
335
|
+
Streaming request (google/gemini-2.5-flash) ... (heartbeat=true, done=true, content="Hello") PASS
|
|
336
|
+
Smart routing: simple query (blockrun/auto → should pick cheap model) ... PASS
|
|
337
|
+
Smart routing: streaming (blockrun/auto, stream=true) ... PASS
|
|
338
|
+
Dedup: identical request returns cached response ... PASS
|
|
339
|
+
404 for unknown path ... PASS
|
|
340
|
+
413 error for oversized request (>150KB) ... (payload=160KB, status=413) PASS
|
|
341
|
+
400 error for malformed JSON ... PASS
|
|
342
|
+
400 error for missing messages field ... PASS
|
|
343
|
+
400 error for message array exceeding 200 items ... (messages=201, status=400) PASS
|
|
344
|
+
Invalid model returns clear error ... PASS
|
|
345
|
+
Concurrent requests (5 parallel) ... (5 requests in 2500ms, avg=500ms) PASS
|
|
346
|
+
400 error for negative max_tokens ... PASS
|
|
347
|
+
400 error for empty messages array ... PASS
|
|
348
|
+
Streaming with large output (token counting) ... (words=52, chunks=15) PASS
|
|
349
|
+
Wallet has sufficient balance ... (balance=$5.23) PASS
|
|
350
|
+
|
|
351
|
+
=== ALL TESTS PASSED ===
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Step 12: Commit E2E test expansion
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
git add test/test-e2e.ts
|
|
358
|
+
git commit -m "test: expand E2E coverage with 10 new test cases
|
|
359
|
+
|
|
360
|
+
- Add 413 Payload Too Large test (150KB limit)
|
|
361
|
+
- Add 400 Bad Request tests (malformed JSON, missing fields)
|
|
362
|
+
- Add message array limit test (200 messages)
|
|
363
|
+
- Add invalid model error handling test
|
|
364
|
+
- Add concurrent request stress test (5 parallel)
|
|
365
|
+
- Add negative max_tokens validation test
|
|
366
|
+
- Add empty messages array validation test
|
|
367
|
+
- Add streaming large response test with token counting
|
|
368
|
+
- Add wallet balance check test
|
|
369
|
+
|
|
370
|
+
Covers recent bug fixes: 504 timeout prevention, settlement retry,
|
|
371
|
+
large payload truncation."
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Task 2: Docker Install/Uninstall Tests (10 Cases)
|
|
377
|
+
|
|
378
|
+
**Goal:** Validate ClawRouter installation, upgrade, uninstall across different methods and environments.
|
|
379
|
+
|
|
380
|
+
**Files:**
|
|
381
|
+
|
|
382
|
+
- Create: `test/docker-install-tests.sh`
|
|
383
|
+
- Create: `test/Dockerfile.install-test`
|
|
384
|
+
- Modify: `test/run-docker-test.sh`
|
|
385
|
+
|
|
386
|
+
### Step 1: Create Dockerfile for installation testing
|
|
387
|
+
|
|
388
|
+
**Create `test/Dockerfile.install-test`:**
|
|
389
|
+
|
|
390
|
+
```dockerfile
|
|
391
|
+
FROM node:22-slim
|
|
392
|
+
|
|
393
|
+
# Install system dependencies
|
|
394
|
+
RUN apt-get update && apt-get install -y \
|
|
395
|
+
git \
|
|
396
|
+
curl \
|
|
397
|
+
jq \
|
|
398
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
399
|
+
|
|
400
|
+
# Create test user
|
|
401
|
+
RUN useradd -m -s /bin/bash testuser
|
|
402
|
+
|
|
403
|
+
# Set up environment
|
|
404
|
+
USER testuser
|
|
405
|
+
WORKDIR /home/testuser
|
|
406
|
+
|
|
407
|
+
# Initialize npm config
|
|
408
|
+
RUN npm config set prefix ~/.npm-global
|
|
409
|
+
ENV PATH="/home/testuser/.npm-global/bin:$PATH"
|
|
410
|
+
|
|
411
|
+
CMD ["/bin/bash"]
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Step 2: Create bash test script with 10 test cases
|
|
415
|
+
|
|
416
|
+
**Create `test/docker-install-tests.sh`:**
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
#!/bin/bash
|
|
420
|
+
set -e
|
|
421
|
+
|
|
422
|
+
PASS=0
|
|
423
|
+
FAIL=0
|
|
424
|
+
|
|
425
|
+
# Colors
|
|
426
|
+
GREEN='\033[0;32m'
|
|
427
|
+
RED='\033[0;31m'
|
|
428
|
+
NC='\033[0m' # No Color
|
|
429
|
+
|
|
430
|
+
# Test runner
|
|
431
|
+
test_case() {
|
|
432
|
+
local name=$1
|
|
433
|
+
local fn=$2
|
|
434
|
+
|
|
435
|
+
echo ""
|
|
436
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
437
|
+
echo "Test: $name"
|
|
438
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
439
|
+
|
|
440
|
+
if $fn; then
|
|
441
|
+
echo -e "${GREEN}✓ PASS${NC}"
|
|
442
|
+
((PASS++))
|
|
443
|
+
else
|
|
444
|
+
echo -e "${RED}✗ FAIL${NC}"
|
|
445
|
+
((FAIL++))
|
|
446
|
+
fi
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
# Test 1: Fresh npm global installation
|
|
450
|
+
test_fresh_install() {
|
|
451
|
+
echo "Installing @blockrun/clawrouter globally..."
|
|
452
|
+
npm install -g @blockrun/clawrouter@latest
|
|
453
|
+
|
|
454
|
+
echo "Verifying clawrouter command exists..."
|
|
455
|
+
which clawrouter || return 1
|
|
456
|
+
|
|
457
|
+
echo "Checking version..."
|
|
458
|
+
clawrouter --version || return 1
|
|
459
|
+
|
|
460
|
+
echo "Verifying package is in npm global list..."
|
|
461
|
+
npm list -g @blockrun/clawrouter || return 1
|
|
462
|
+
|
|
463
|
+
return 0
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
# Test 2: Uninstall verification
|
|
467
|
+
test_uninstall() {
|
|
468
|
+
echo "Uninstalling @blockrun/clawrouter..."
|
|
469
|
+
npm uninstall -g @blockrun/clawrouter
|
|
470
|
+
|
|
471
|
+
echo "Verifying clawrouter command is gone..."
|
|
472
|
+
if which clawrouter 2>/dev/null; then
|
|
473
|
+
echo "ERROR: clawrouter command still exists after uninstall"
|
|
474
|
+
return 1
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
echo "Verifying package is not in npm global list..."
|
|
478
|
+
if npm list -g @blockrun/clawrouter 2>/dev/null; then
|
|
479
|
+
echo "ERROR: package still in npm list after uninstall"
|
|
480
|
+
return 1
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
return 0
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
# Test 3: Reinstall after uninstall
|
|
487
|
+
test_reinstall() {
|
|
488
|
+
echo "Reinstalling @blockrun/clawrouter..."
|
|
489
|
+
npm install -g @blockrun/clawrouter@latest
|
|
490
|
+
|
|
491
|
+
echo "Verifying reinstall works..."
|
|
492
|
+
clawrouter --version || return 1
|
|
493
|
+
|
|
494
|
+
return 0
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
# Test 4: Installation as OpenClaw plugin (if OpenClaw available)
|
|
498
|
+
test_openclaw_plugin_install() {
|
|
499
|
+
echo "Installing OpenClaw..."
|
|
500
|
+
npm install -g openclaw@latest || {
|
|
501
|
+
echo "OpenClaw not available, skipping test"
|
|
502
|
+
return 0
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
echo "Installing ClawRouter as OpenClaw plugin..."
|
|
506
|
+
openclaw plugins install @blockrun/clawrouter || return 1
|
|
507
|
+
|
|
508
|
+
echo "Verifying plugin is listed..."
|
|
509
|
+
openclaw plugins list | grep -q "clawrouter" || return 1
|
|
510
|
+
|
|
511
|
+
return 0
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
# Test 5: OpenClaw plugin uninstall
|
|
515
|
+
test_openclaw_plugin_uninstall() {
|
|
516
|
+
if ! which openclaw 2>/dev/null; then
|
|
517
|
+
echo "OpenClaw not available, skipping test"
|
|
518
|
+
return 0
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
echo "Uninstalling ClawRouter plugin..."
|
|
522
|
+
openclaw plugins uninstall clawrouter || return 1
|
|
523
|
+
|
|
524
|
+
echo "Verifying plugin is removed..."
|
|
525
|
+
if openclaw plugins list 2>/dev/null | grep -q "clawrouter"; then
|
|
526
|
+
echo "ERROR: plugin still listed after uninstall"
|
|
527
|
+
return 1
|
|
528
|
+
fi
|
|
529
|
+
|
|
530
|
+
return 0
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
# Test 6: Upgrade from previous version
|
|
534
|
+
test_upgrade() {
|
|
535
|
+
echo "Installing older version (0.8.25)..."
|
|
536
|
+
npm install -g @blockrun/clawrouter@0.8.25
|
|
537
|
+
|
|
538
|
+
echo "Verifying old version..."
|
|
539
|
+
local old_version=$(clawrouter --version)
|
|
540
|
+
echo "Installed: $old_version"
|
|
541
|
+
|
|
542
|
+
echo "Upgrading to latest..."
|
|
543
|
+
npm install -g @blockrun/clawrouter@latest
|
|
544
|
+
|
|
545
|
+
echo "Verifying upgrade..."
|
|
546
|
+
local new_version=$(clawrouter --version)
|
|
547
|
+
echo "Upgraded to: $new_version"
|
|
548
|
+
|
|
549
|
+
if [ "$old_version" = "$new_version" ]; then
|
|
550
|
+
echo "ERROR: version did not change after upgrade"
|
|
551
|
+
return 1
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
return 0
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
# Test 7: Installation with custom wallet key
|
|
558
|
+
test_custom_wallet() {
|
|
559
|
+
echo "Setting custom wallet key..."
|
|
560
|
+
export BLOCKRUN_WALLET_KEY="0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
561
|
+
|
|
562
|
+
echo "Installing with wallet key..."
|
|
563
|
+
npm install -g @blockrun/clawrouter@latest
|
|
564
|
+
|
|
565
|
+
echo "Verifying installation..."
|
|
566
|
+
clawrouter --version || return 1
|
|
567
|
+
|
|
568
|
+
unset BLOCKRUN_WALLET_KEY
|
|
569
|
+
return 0
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
# Test 8: Verify package files exist
|
|
573
|
+
test_package_files() {
|
|
574
|
+
echo "Installing @blockrun/clawrouter..."
|
|
575
|
+
npm install -g @blockrun/clawrouter@latest
|
|
576
|
+
|
|
577
|
+
echo "Finding package installation directory..."
|
|
578
|
+
local pkg_dir=$(npm root -g)/@blockrun/clawrouter
|
|
579
|
+
|
|
580
|
+
echo "Checking for required files..."
|
|
581
|
+
[ -f "$pkg_dir/dist/index.js" ] || { echo "Missing dist/index.js"; return 1; }
|
|
582
|
+
[ -f "$pkg_dir/dist/cli.js" ] || { echo "Missing dist/cli.js"; return 1; }
|
|
583
|
+
[ -f "$pkg_dir/package.json" ] || { echo "Missing package.json"; return 1; }
|
|
584
|
+
[ -f "$pkg_dir/openclaw.plugin.json" ] || { echo "Missing openclaw.plugin.json"; return 1; }
|
|
585
|
+
|
|
586
|
+
echo "All required files present"
|
|
587
|
+
return 0
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
# Test 9: Version command accuracy
|
|
591
|
+
test_version_command() {
|
|
592
|
+
echo "Installing @blockrun/clawrouter..."
|
|
593
|
+
npm install -g @blockrun/clawrouter@latest
|
|
594
|
+
|
|
595
|
+
echo "Running version command..."
|
|
596
|
+
local cli_version=$(clawrouter --version)
|
|
597
|
+
|
|
598
|
+
echo "Reading package.json version..."
|
|
599
|
+
local pkg_dir=$(npm root -g)/@blockrun/clawrouter
|
|
600
|
+
local pkg_version=$(node -p "require('$pkg_dir/package.json').version")
|
|
601
|
+
|
|
602
|
+
echo "CLI version: $cli_version"
|
|
603
|
+
echo "Package version: $pkg_version"
|
|
604
|
+
|
|
605
|
+
if [ "$cli_version" != "$pkg_version" ]; then
|
|
606
|
+
echo "ERROR: version mismatch"
|
|
607
|
+
return 1
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
return 0
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
# Test 10: Full cleanup verification
|
|
614
|
+
test_full_cleanup() {
|
|
615
|
+
echo "Installing @blockrun/clawrouter..."
|
|
616
|
+
npm install -g @blockrun/clawrouter@latest
|
|
617
|
+
|
|
618
|
+
echo "Finding all ClawRouter files..."
|
|
619
|
+
local pkg_dir=$(npm root -g)/@blockrun/clawrouter
|
|
620
|
+
local bin_link=$(which clawrouter)
|
|
621
|
+
|
|
622
|
+
echo "Package dir: $pkg_dir"
|
|
623
|
+
echo "Binary link: $bin_link"
|
|
624
|
+
|
|
625
|
+
echo "Uninstalling..."
|
|
626
|
+
npm uninstall -g @blockrun/clawrouter
|
|
627
|
+
|
|
628
|
+
echo "Verifying complete cleanup..."
|
|
629
|
+
if [ -d "$pkg_dir" ]; then
|
|
630
|
+
echo "ERROR: package directory still exists: $pkg_dir"
|
|
631
|
+
return 1
|
|
632
|
+
fi
|
|
633
|
+
|
|
634
|
+
if [ -f "$bin_link" ] || [ -L "$bin_link" ]; then
|
|
635
|
+
echo "ERROR: binary link still exists: $bin_link"
|
|
636
|
+
return 1
|
|
637
|
+
fi
|
|
638
|
+
|
|
639
|
+
echo "Complete cleanup verified"
|
|
640
|
+
return 0
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
# Run all tests
|
|
644
|
+
echo "╔════════════════════════════════════════════════════════╗"
|
|
645
|
+
echo "║ ClawRouter Docker Installation Test Suite ║"
|
|
646
|
+
echo "╚════════════════════════════════════════════════════════╝"
|
|
647
|
+
|
|
648
|
+
test_case "1. Fresh npm global installation" test_fresh_install
|
|
649
|
+
test_case "2. Uninstall verification" test_uninstall
|
|
650
|
+
test_case "3. Reinstall after uninstall" test_reinstall
|
|
651
|
+
test_case "4. OpenClaw plugin installation" test_openclaw_plugin_install
|
|
652
|
+
test_case "5. OpenClaw plugin uninstall" test_openclaw_plugin_uninstall
|
|
653
|
+
test_case "6. Upgrade from previous version" test_upgrade
|
|
654
|
+
test_case "7. Installation with custom wallet" test_custom_wallet
|
|
655
|
+
test_case "8. Package files verification" test_package_files
|
|
656
|
+
test_case "9. Version command accuracy" test_version_command
|
|
657
|
+
test_case "10. Full cleanup verification" test_full_cleanup
|
|
658
|
+
|
|
659
|
+
echo ""
|
|
660
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
661
|
+
echo "Summary: $PASS passed, $FAIL failed"
|
|
662
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
663
|
+
|
|
664
|
+
[ $FAIL -eq 0 ] && exit 0 || exit 1
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Step 3: Make test script executable
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
chmod +x test/docker-install-tests.sh
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### Step 4: Update run-docker-test.sh to include installation tests
|
|
674
|
+
|
|
675
|
+
**Modify `test/run-docker-test.sh`:**
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
#!/bin/bash
|
|
679
|
+
set -e
|
|
680
|
+
|
|
681
|
+
cd "$(dirname "$0")/.."
|
|
682
|
+
|
|
683
|
+
echo "🐳 Building Docker test environment for installation tests..."
|
|
684
|
+
docker build -f test/Dockerfile.install-test -t clawrouter-install-test .
|
|
685
|
+
|
|
686
|
+
echo ""
|
|
687
|
+
echo "🧪 Running installation test suite (10 test cases)..."
|
|
688
|
+
docker run --rm \
|
|
689
|
+
-v "$(pwd)/test/docker-install-tests.sh:/test.sh:ro" \
|
|
690
|
+
clawrouter-install-test \
|
|
691
|
+
bash -c "cp /test.sh /tmp/test.sh && chmod +x /tmp/test.sh && /tmp/test.sh"
|
|
692
|
+
|
|
693
|
+
echo ""
|
|
694
|
+
echo "✅ Docker installation tests completed successfully!"
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Step 5: Run Docker installation tests
|
|
698
|
+
|
|
699
|
+
Run:
|
|
700
|
+
|
|
701
|
+
```bash
|
|
702
|
+
./test/run-docker-test.sh
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Expected output:
|
|
706
|
+
|
|
707
|
+
```
|
|
708
|
+
🐳 Building Docker test environment for installation tests...
|
|
709
|
+
...
|
|
710
|
+
🧪 Running installation test suite (10 test cases)...
|
|
711
|
+
|
|
712
|
+
╔════════════════════════════════════════════════════════╗
|
|
713
|
+
║ ClawRouter Docker Installation Test Suite ║
|
|
714
|
+
╚════════════════════════════════════════════════════════╝
|
|
715
|
+
|
|
716
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
717
|
+
Test: 1. Fresh npm global installation
|
|
718
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
719
|
+
Installing @blockrun/clawrouter globally...
|
|
720
|
+
Verifying clawrouter command exists...
|
|
721
|
+
Checking version...
|
|
722
|
+
0.8.30
|
|
723
|
+
✓ PASS
|
|
724
|
+
|
|
725
|
+
[... 9 more tests ...]
|
|
726
|
+
|
|
727
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
728
|
+
Summary: 10 passed, 0 failed
|
|
729
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
730
|
+
|
|
731
|
+
✅ Docker installation tests completed successfully!
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### Step 6: Commit Docker installation tests
|
|
735
|
+
|
|
736
|
+
```bash
|
|
737
|
+
git add test/Dockerfile.install-test test/docker-install-tests.sh test/run-docker-test.sh
|
|
738
|
+
git commit -m "test: add Docker-based installation testing (10 test cases)
|
|
739
|
+
|
|
740
|
+
Test coverage:
|
|
741
|
+
- Fresh npm global installation
|
|
742
|
+
- Uninstall verification
|
|
743
|
+
- Reinstall after uninstall
|
|
744
|
+
- OpenClaw plugin install/uninstall
|
|
745
|
+
- Upgrade from previous version
|
|
746
|
+
- Custom wallet key installation
|
|
747
|
+
- Package files verification
|
|
748
|
+
- Version command accuracy
|
|
749
|
+
- Full cleanup verification
|
|
750
|
+
|
|
751
|
+
Validates installation, upgrade, uninstall workflows in isolated Docker
|
|
752
|
+
environment."
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Task 3: Deployment Automation
|
|
758
|
+
|
|
759
|
+
**Goal:** Automate pre-publish validation, version bumping, npm publish, and GitHub release creation.
|
|
760
|
+
|
|
761
|
+
**Files:**
|
|
762
|
+
|
|
763
|
+
- Create: `scripts/deploy.sh`
|
|
764
|
+
- Modify: `package.json` (add deploy script)
|
|
765
|
+
|
|
766
|
+
### Step 1: Create deployment script
|
|
767
|
+
|
|
768
|
+
**Create `scripts/deploy.sh`:**
|
|
769
|
+
|
|
770
|
+
```bash
|
|
771
|
+
#!/bin/bash
|
|
772
|
+
set -e
|
|
773
|
+
|
|
774
|
+
# Colors
|
|
775
|
+
GREEN='\033[0;32m'
|
|
776
|
+
YELLOW='\033[1;33m'
|
|
777
|
+
RED='\033[0;31m'
|
|
778
|
+
NC='\033[0m' # No Color
|
|
779
|
+
|
|
780
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
781
|
+
echo -e "${GREEN} ClawRouter Deployment Pipeline${NC}"
|
|
782
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
783
|
+
|
|
784
|
+
# Step 1: Check git status (must be clean)
|
|
785
|
+
echo ""
|
|
786
|
+
echo -e "${YELLOW}1. Checking git status...${NC}"
|
|
787
|
+
if [[ -n $(git status --porcelain) ]]; then
|
|
788
|
+
echo -e "${RED}ERROR: Working directory is not clean. Commit or stash changes first.${NC}"
|
|
789
|
+
exit 1
|
|
790
|
+
fi
|
|
791
|
+
echo "✓ Working directory is clean"
|
|
792
|
+
|
|
793
|
+
# Step 2: Check we're on main branch
|
|
794
|
+
echo ""
|
|
795
|
+
echo -e "${YELLOW}2. Checking branch...${NC}"
|
|
796
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
797
|
+
if [ "$BRANCH" != "main" ]; then
|
|
798
|
+
echo -e "${RED}ERROR: Must be on main branch (currently on $BRANCH)${NC}"
|
|
799
|
+
exit 1
|
|
800
|
+
fi
|
|
801
|
+
echo "✓ On main branch"
|
|
802
|
+
|
|
803
|
+
# Step 3: Pull latest changes
|
|
804
|
+
echo ""
|
|
805
|
+
echo -e "${YELLOW}3. Pulling latest changes...${NC}"
|
|
806
|
+
git pull origin main
|
|
807
|
+
echo "✓ Up to date with origin/main"
|
|
808
|
+
|
|
809
|
+
# Step 4: Install dependencies
|
|
810
|
+
echo ""
|
|
811
|
+
echo -e "${YELLOW}4. Installing dependencies...${NC}"
|
|
812
|
+
npm ci
|
|
813
|
+
echo "✓ Dependencies installed"
|
|
814
|
+
|
|
815
|
+
# Step 5: Run typecheck
|
|
816
|
+
echo ""
|
|
817
|
+
echo -e "${YELLOW}5. Running typecheck...${NC}"
|
|
818
|
+
npm run typecheck
|
|
819
|
+
echo "✓ Typecheck passed"
|
|
820
|
+
|
|
821
|
+
# Step 6: Run build
|
|
822
|
+
echo ""
|
|
823
|
+
echo -e "${YELLOW}6. Building project...${NC}"
|
|
824
|
+
npm run build
|
|
825
|
+
echo "✓ Build successful"
|
|
826
|
+
|
|
827
|
+
# Step 7: Run tests
|
|
828
|
+
echo ""
|
|
829
|
+
echo -e "${YELLOW}7. Running tests...${NC}"
|
|
830
|
+
|
|
831
|
+
# Check if wallet key is set
|
|
832
|
+
if [ -z "$BLOCKRUN_WALLET_KEY" ]; then
|
|
833
|
+
echo -e "${YELLOW}WARNING: BLOCKRUN_WALLET_KEY not set. Skipping E2E tests.${NC}"
|
|
834
|
+
echo "Set BLOCKRUN_WALLET_KEY to run E2E tests during deployment."
|
|
835
|
+
else
|
|
836
|
+
echo "Running E2E tests..."
|
|
837
|
+
npx tsx test/test-e2e.ts
|
|
838
|
+
echo "✓ E2E tests passed"
|
|
839
|
+
fi
|
|
840
|
+
|
|
841
|
+
# Step 8: Get version bump type
|
|
842
|
+
echo ""
|
|
843
|
+
echo -e "${YELLOW}8. Version bump${NC}"
|
|
844
|
+
echo "Current version: $(node -p "require('./package.json').version")"
|
|
845
|
+
echo ""
|
|
846
|
+
echo "Select version bump type:"
|
|
847
|
+
echo " 1) patch (0.8.30 → 0.8.31)"
|
|
848
|
+
echo " 2) minor (0.8.30 → 0.9.0)"
|
|
849
|
+
echo " 3) major (0.8.30 → 1.0.0)"
|
|
850
|
+
echo " 4) custom"
|
|
851
|
+
read -p "Enter choice (1-4): " VERSION_CHOICE
|
|
852
|
+
|
|
853
|
+
case $VERSION_CHOICE in
|
|
854
|
+
1)
|
|
855
|
+
VERSION_TYPE="patch"
|
|
856
|
+
;;
|
|
857
|
+
2)
|
|
858
|
+
VERSION_TYPE="minor"
|
|
859
|
+
;;
|
|
860
|
+
3)
|
|
861
|
+
VERSION_TYPE="major"
|
|
862
|
+
;;
|
|
863
|
+
4)
|
|
864
|
+
read -p "Enter custom version (e.g., 1.0.0-beta.1): " CUSTOM_VERSION
|
|
865
|
+
npm version "$CUSTOM_VERSION" --no-git-tag-version
|
|
866
|
+
NEW_VERSION="$CUSTOM_VERSION"
|
|
867
|
+
;;
|
|
868
|
+
*)
|
|
869
|
+
echo -e "${RED}Invalid choice${NC}"
|
|
870
|
+
exit 1
|
|
871
|
+
;;
|
|
872
|
+
esac
|
|
873
|
+
|
|
874
|
+
# Bump version if not custom
|
|
875
|
+
if [ -n "$VERSION_TYPE" ]; then
|
|
876
|
+
NEW_VERSION=$(npm version "$VERSION_TYPE" --no-git-tag-version)
|
|
877
|
+
NEW_VERSION=${NEW_VERSION#v} # Remove leading 'v'
|
|
878
|
+
fi
|
|
879
|
+
|
|
880
|
+
echo "✓ Version bumped to $NEW_VERSION"
|
|
881
|
+
|
|
882
|
+
# Step 9: Update version in src/version.ts
|
|
883
|
+
echo ""
|
|
884
|
+
echo -e "${YELLOW}9. Updating version in source files...${NC}"
|
|
885
|
+
cat > src/version.ts <<EOF
|
|
886
|
+
/**
|
|
887
|
+
* ClawRouter version
|
|
888
|
+
* Auto-generated during deployment
|
|
889
|
+
*/
|
|
890
|
+
export const VERSION = "$NEW_VERSION";
|
|
891
|
+
EOF
|
|
892
|
+
echo "✓ Version updated in src/version.ts"
|
|
893
|
+
|
|
894
|
+
# Step 10: Rebuild with new version
|
|
895
|
+
echo ""
|
|
896
|
+
echo -e "${YELLOW}10. Rebuilding with new version...${NC}"
|
|
897
|
+
npm run build
|
|
898
|
+
echo "✓ Rebuild successful"
|
|
899
|
+
|
|
900
|
+
# Step 11: Commit version bump
|
|
901
|
+
echo ""
|
|
902
|
+
echo -e "${YELLOW}11. Committing version bump...${NC}"
|
|
903
|
+
git add package.json package-lock.json src/version.ts
|
|
904
|
+
git commit -m "$NEW_VERSION"
|
|
905
|
+
echo "✓ Version bump committed"
|
|
906
|
+
|
|
907
|
+
# Step 12: Create git tag
|
|
908
|
+
echo ""
|
|
909
|
+
echo -e "${YELLOW}12. Creating git tag...${NC}"
|
|
910
|
+
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
|
|
911
|
+
echo "✓ Tag v$NEW_VERSION created"
|
|
912
|
+
|
|
913
|
+
# Step 13: Confirm publish
|
|
914
|
+
echo ""
|
|
915
|
+
echo -e "${YELLOW}13. Ready to publish${NC}"
|
|
916
|
+
echo ""
|
|
917
|
+
echo "Package: @blockrun/clawrouter"
|
|
918
|
+
echo "Version: $NEW_VERSION"
|
|
919
|
+
echo "Registry: https://registry.npmjs.org"
|
|
920
|
+
echo ""
|
|
921
|
+
read -p "Publish to npm? (y/N): " CONFIRM_PUBLISH
|
|
922
|
+
|
|
923
|
+
if [ "$CONFIRM_PUBLISH" != "y" ] && [ "$CONFIRM_PUBLISH" != "Y" ]; then
|
|
924
|
+
echo -e "${YELLOW}Publish cancelled. Version was bumped but not published.${NC}"
|
|
925
|
+
echo "To publish later, run: npm publish"
|
|
926
|
+
exit 0
|
|
927
|
+
fi
|
|
928
|
+
|
|
929
|
+
# Step 14: Publish to npm
|
|
930
|
+
echo ""
|
|
931
|
+
echo -e "${YELLOW}14. Publishing to npm...${NC}"
|
|
932
|
+
npm publish --access public
|
|
933
|
+
echo "✓ Published to npm"
|
|
934
|
+
|
|
935
|
+
# Step 15: Push to GitHub
|
|
936
|
+
echo ""
|
|
937
|
+
echo -e "${YELLOW}15. Pushing to GitHub...${NC}"
|
|
938
|
+
git push origin main
|
|
939
|
+
git push origin "v$NEW_VERSION"
|
|
940
|
+
echo "✓ Pushed to GitHub"
|
|
941
|
+
|
|
942
|
+
# Step 16: Create GitHub release
|
|
943
|
+
echo ""
|
|
944
|
+
echo -e "${YELLOW}16. Creating GitHub release...${NC}"
|
|
945
|
+
if command -v gh &> /dev/null; then
|
|
946
|
+
gh release create "v$NEW_VERSION" \
|
|
947
|
+
--title "v$NEW_VERSION" \
|
|
948
|
+
--notes "Release v$NEW_VERSION" \
|
|
949
|
+
--generate-notes
|
|
950
|
+
echo "✓ GitHub release created"
|
|
951
|
+
else
|
|
952
|
+
echo -e "${YELLOW}WARNING: gh CLI not found. Skipping GitHub release creation.${NC}"
|
|
953
|
+
echo "Create release manually at: https://github.com/BlockRunAI/ClawRouter/releases/new"
|
|
954
|
+
fi
|
|
955
|
+
|
|
956
|
+
echo ""
|
|
957
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
958
|
+
echo -e "${GREEN} Deployment Complete! 🎉${NC}"
|
|
959
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
960
|
+
echo ""
|
|
961
|
+
echo "Package: @blockrun/clawrouter@$NEW_VERSION"
|
|
962
|
+
echo "npm: https://www.npmjs.com/package/@blockrun/clawrouter"
|
|
963
|
+
echo "GitHub: https://github.com/BlockRunAI/ClawRouter/releases/tag/v$NEW_VERSION"
|
|
964
|
+
echo ""
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
### Step 2: Make deployment script executable
|
|
968
|
+
|
|
969
|
+
```bash
|
|
970
|
+
chmod +x scripts/deploy.sh
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
### Step 3: Add deploy command to package.json
|
|
974
|
+
|
|
975
|
+
**Modify `package.json` scripts section:**
|
|
976
|
+
|
|
977
|
+
```json
|
|
978
|
+
{
|
|
979
|
+
"scripts": {
|
|
980
|
+
"build": "tsup",
|
|
981
|
+
"dev": "tsup --watch",
|
|
982
|
+
"typecheck": "tsc --noEmit",
|
|
983
|
+
"lint": "eslint src/",
|
|
984
|
+
"format": "prettier --write .",
|
|
985
|
+
"format:check": "prettier --check .",
|
|
986
|
+
"test:resilience:errors": "npx tsx test/resilience-errors.ts",
|
|
987
|
+
"test:resilience:stability": "DURATION_MINUTES=5 npx tsx test/resilience-stability.ts",
|
|
988
|
+
"test:resilience:stability:full": "DURATION_MINUTES=240 npx tsx test/resilience-stability.ts",
|
|
989
|
+
"test:resilience:lifecycle": "npx tsx test/resilience-lifecycle.ts",
|
|
990
|
+
"test:resilience:quick": "npm run test:resilience:errors && npm run test:resilience:lifecycle",
|
|
991
|
+
"test:resilience:full": "npm run test:resilience:errors && npm run test:resilience:lifecycle && npm run test:resilience:stability:full",
|
|
992
|
+
"test:e2e:tool-ids": "npx tsx test/e2e-tool-id-sanitization.ts",
|
|
993
|
+
"test:docker:install": "./test/run-docker-test.sh",
|
|
994
|
+
"deploy": "./scripts/deploy.sh"
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
### Step 4: Test deployment script (dry run)
|
|
1000
|
+
|
|
1001
|
+
**Before running the full deployment, test the validation steps:**
|
|
1002
|
+
|
|
1003
|
+
```bash
|
|
1004
|
+
# Test git status check
|
|
1005
|
+
git status
|
|
1006
|
+
|
|
1007
|
+
# Test typecheck
|
|
1008
|
+
npm run typecheck
|
|
1009
|
+
|
|
1010
|
+
# Test build
|
|
1011
|
+
npm run build
|
|
1012
|
+
|
|
1013
|
+
# Test E2E (if wallet key set)
|
|
1014
|
+
BLOCKRUN_WALLET_KEY=0x... npx tsx test/test-e2e.ts
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
### Step 5: Document deployment process
|
|
1018
|
+
|
|
1019
|
+
**Create `docs/deployment.md`:**
|
|
1020
|
+
|
|
1021
|
+
````markdown
|
|
1022
|
+
# ClawRouter Deployment Guide
|
|
1023
|
+
|
|
1024
|
+
## Prerequisites
|
|
1025
|
+
|
|
1026
|
+
1. **npm account with publish access** to `@blockrun/clawrouter`
|
|
1027
|
+
2. **GitHub CLI (`gh`)** installed (optional, for automated release creation)
|
|
1028
|
+
3. **Funded wallet** for E2E tests (optional, but recommended)
|
|
1029
|
+
|
|
1030
|
+
## Deployment Process
|
|
1031
|
+
|
|
1032
|
+
### Option 1: Automated Deployment (Recommended)
|
|
1033
|
+
|
|
1034
|
+
```bash
|
|
1035
|
+
# Set wallet key for E2E tests (optional)
|
|
1036
|
+
export BLOCKRUN_WALLET_KEY=0x...
|
|
1037
|
+
|
|
1038
|
+
# Run deployment script
|
|
1039
|
+
npm run deploy
|
|
1040
|
+
```
|
|
1041
|
+
````
|
|
1042
|
+
|
|
1043
|
+
The script will:
|
|
1044
|
+
|
|
1045
|
+
1. ✓ Check git status is clean
|
|
1046
|
+
2. ✓ Verify on main branch
|
|
1047
|
+
3. ✓ Pull latest changes
|
|
1048
|
+
4. ✓ Install dependencies
|
|
1049
|
+
5. ✓ Run typecheck
|
|
1050
|
+
6. ✓ Build project
|
|
1051
|
+
7. ✓ Run E2E tests (if wallet key set)
|
|
1052
|
+
8. ✓ Prompt for version bump type
|
|
1053
|
+
9. ✓ Update version in package.json and src/version.ts
|
|
1054
|
+
10. ✓ Rebuild with new version
|
|
1055
|
+
11. ✓ Commit version bump
|
|
1056
|
+
12. ✓ Create git tag
|
|
1057
|
+
13. ✓ Publish to npm
|
|
1058
|
+
14. ✓ Push to GitHub
|
|
1059
|
+
15. ✓ Create GitHub release
|
|
1060
|
+
|
|
1061
|
+
### Option 2: Manual Deployment
|
|
1062
|
+
|
|
1063
|
+
```bash
|
|
1064
|
+
# 1. Update version
|
|
1065
|
+
npm version patch # or minor, or major
|
|
1066
|
+
|
|
1067
|
+
# 2. Update src/version.ts
|
|
1068
|
+
echo 'export const VERSION = "0.8.31";' > src/version.ts
|
|
1069
|
+
|
|
1070
|
+
# 3. Build
|
|
1071
|
+
npm run build
|
|
1072
|
+
|
|
1073
|
+
# 4. Commit
|
|
1074
|
+
git add package.json package-lock.json src/version.ts
|
|
1075
|
+
git commit -m "0.8.31"
|
|
1076
|
+
git tag -a v0.8.31 -m "Release v0.8.31"
|
|
1077
|
+
|
|
1078
|
+
# 5. Publish
|
|
1079
|
+
npm publish --access public
|
|
1080
|
+
|
|
1081
|
+
# 6. Push
|
|
1082
|
+
git push origin main
|
|
1083
|
+
git push origin v0.8.31
|
|
1084
|
+
|
|
1085
|
+
# 7. Create GitHub release
|
|
1086
|
+
gh release create v0.8.31 --title "v0.8.31" --generate-notes
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
## Version Bump Types
|
|
1090
|
+
|
|
1091
|
+
- **patch**: Bug fixes, minor changes (0.8.30 → 0.8.31)
|
|
1092
|
+
- **minor**: New features, non-breaking changes (0.8.30 → 0.9.0)
|
|
1093
|
+
- **major**: Breaking changes (0.8.30 → 1.0.0)
|
|
1094
|
+
- **custom**: Pre-release versions (0.8.30 → 1.0.0-beta.1)
|
|
1095
|
+
|
|
1096
|
+
## Post-Deployment Verification
|
|
1097
|
+
|
|
1098
|
+
1. Check npm package: https://www.npmjs.com/package/@blockrun/clawrouter
|
|
1099
|
+
2. Verify installation: `npm install -g @blockrun/clawrouter@latest`
|
|
1100
|
+
3. Test version: `clawrouter --version`
|
|
1101
|
+
4. Check GitHub release: https://github.com/BlockRunAI/ClawRouter/releases
|
|
1102
|
+
|
|
1103
|
+
## Rollback
|
|
1104
|
+
|
|
1105
|
+
If deployment fails:
|
|
1106
|
+
|
|
1107
|
+
```bash
|
|
1108
|
+
# Delete tag locally and remotely
|
|
1109
|
+
git tag -d v0.8.31
|
|
1110
|
+
git push origin :refs/tags/v0.8.31
|
|
1111
|
+
|
|
1112
|
+
# Revert version commit
|
|
1113
|
+
git revert HEAD
|
|
1114
|
+
git push origin main
|
|
1115
|
+
|
|
1116
|
+
# Unpublish from npm (within 72 hours)
|
|
1117
|
+
npm unpublish @blockrun/clawrouter@0.8.31
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
## Troubleshooting
|
|
1121
|
+
|
|
1122
|
+
### "Working directory is not clean"
|
|
1123
|
+
|
|
1124
|
+
Commit or stash changes before deploying:
|
|
1125
|
+
|
|
1126
|
+
```bash
|
|
1127
|
+
git status
|
|
1128
|
+
git add .
|
|
1129
|
+
git commit -m "feat: ..."
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
### "Must be on main branch"
|
|
1133
|
+
|
|
1134
|
+
Switch to main:
|
|
1135
|
+
|
|
1136
|
+
```bash
|
|
1137
|
+
git checkout main
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
### E2E tests fail
|
|
1141
|
+
|
|
1142
|
+
Set wallet key:
|
|
1143
|
+
|
|
1144
|
+
```bash
|
|
1145
|
+
export BLOCKRUN_WALLET_KEY=0x...
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
Or skip E2E tests (not recommended):
|
|
1149
|
+
|
|
1150
|
+
```bash
|
|
1151
|
+
# Edit scripts/deploy.sh and comment out E2E test section
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
````
|
|
1155
|
+
|
|
1156
|
+
### Step 6: Run deployment script (test mode)
|
|
1157
|
+
|
|
1158
|
+
**Test the deployment script without publishing:**
|
|
1159
|
+
|
|
1160
|
+
```bash
|
|
1161
|
+
# Comment out the npm publish and git push steps in scripts/deploy.sh
|
|
1162
|
+
# Then run:
|
|
1163
|
+
npm run deploy
|
|
1164
|
+
|
|
1165
|
+
# Select patch version bump
|
|
1166
|
+
# Review all steps
|
|
1167
|
+
# Decline publish when prompted
|
|
1168
|
+
````
|
|
1169
|
+
|
|
1170
|
+
Expected output:
|
|
1171
|
+
|
|
1172
|
+
```
|
|
1173
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1174
|
+
ClawRouter Deployment Pipeline
|
|
1175
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1176
|
+
|
|
1177
|
+
1. Checking git status...
|
|
1178
|
+
✓ Working directory is clean
|
|
1179
|
+
|
|
1180
|
+
2. Checking branch...
|
|
1181
|
+
✓ On main branch
|
|
1182
|
+
|
|
1183
|
+
3. Pulling latest changes...
|
|
1184
|
+
✓ Up to date with origin/main
|
|
1185
|
+
|
|
1186
|
+
4. Installing dependencies...
|
|
1187
|
+
✓ Dependencies installed
|
|
1188
|
+
|
|
1189
|
+
5. Running typecheck...
|
|
1190
|
+
✓ Typecheck passed
|
|
1191
|
+
|
|
1192
|
+
6. Building project...
|
|
1193
|
+
✓ Build successful
|
|
1194
|
+
|
|
1195
|
+
7. Running tests...
|
|
1196
|
+
✓ E2E tests passed
|
|
1197
|
+
|
|
1198
|
+
8. Version bump
|
|
1199
|
+
Current version: 0.8.30
|
|
1200
|
+
|
|
1201
|
+
Select version bump type:
|
|
1202
|
+
1) patch (0.8.30 → 0.8.31)
|
|
1203
|
+
2) minor (0.8.30 → 0.9.0)
|
|
1204
|
+
3) major (0.8.30 → 1.0.0)
|
|
1205
|
+
4) custom
|
|
1206
|
+
Enter choice (1-4): 1
|
|
1207
|
+
✓ Version bumped to 0.8.31
|
|
1208
|
+
|
|
1209
|
+
9. Updating version in source files...
|
|
1210
|
+
✓ Version updated in src/version.ts
|
|
1211
|
+
|
|
1212
|
+
10. Rebuilding with new version...
|
|
1213
|
+
✓ Rebuild successful
|
|
1214
|
+
|
|
1215
|
+
11. Committing version bump...
|
|
1216
|
+
✓ Version bump committed
|
|
1217
|
+
|
|
1218
|
+
12. Creating git tag...
|
|
1219
|
+
✓ Tag v0.8.31 created
|
|
1220
|
+
|
|
1221
|
+
13. Ready to publish
|
|
1222
|
+
|
|
1223
|
+
Package: @blockrun/clawrouter
|
|
1224
|
+
Version: 0.8.31
|
|
1225
|
+
Registry: https://registry.npmjs.org
|
|
1226
|
+
|
|
1227
|
+
Publish to npm? (y/N): N
|
|
1228
|
+
Publish cancelled. Version was bumped but not published.
|
|
1229
|
+
To publish later, run: npm publish
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
### Step 7: Commit deployment automation
|
|
1233
|
+
|
|
1234
|
+
```bash
|
|
1235
|
+
git add scripts/deploy.sh package.json docs/deployment.md
|
|
1236
|
+
git commit -m "chore: add automated deployment pipeline
|
|
1237
|
+
|
|
1238
|
+
- Add deployment script with pre-publish validation
|
|
1239
|
+
- Version bump with interactive selection
|
|
1240
|
+
- Automatic git tag creation
|
|
1241
|
+
- npm publish with confirmation
|
|
1242
|
+
- GitHub release creation (requires gh CLI)
|
|
1243
|
+
- Add deployment documentation
|
|
1244
|
+
|
|
1245
|
+
Usage: npm run deploy"
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
---
|
|
1249
|
+
|
|
1250
|
+
## Execution Handoff
|
|
1251
|
+
|
|
1252
|
+
Plan complete and saved to `docs/plans/2026-02-13-e2e-docker-deployment.md`.
|
|
1253
|
+
|
|
1254
|
+
**Two execution options:**
|
|
1255
|
+
|
|
1256
|
+
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
|
|
1257
|
+
|
|
1258
|
+
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
|
|
1259
|
+
|
|
1260
|
+
**Which approach would you prefer, Your Majesty?**
|