@autofleet/cli 2.23.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-marketplace/README.md +41 -27
- package/dist/claude-marketplace/plugins/autofleet/.claude-plugin/plugin.json +1 -1
- package/dist/claude-marketplace/plugins/autofleet/skills/add-endpoint/SKILL.md +136 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/add-endpoint/examples.md +77 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/dev-workflow/SKILL.md +208 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/dev-workflow/examples.md +189 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/node-test-runner/SKILL.md +71 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/pr-standards/SKILL.md +1 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/rabbit/SKILL.md +140 -0
- package/dist/claude-marketplace/plugins/autofleet/skills/rabbit/examples.md +109 -0
- package/dist/e2e.js +1 -1
- package/dist/git-autofleet.js +1 -1
- package/dist/index.js +29 -29
- package/dist/index.js.map +1 -1
- package/dist/{utils-CVjmPZwQ.js → utils-oIUjuHiV.js} +3 -3
- package/dist/utils-oIUjuHiV.js.map +1 -0
- package/package.json +3 -2
- package/vitest.config.ts +2 -2
- package/dist/utils-CVjmPZwQ.js.map +0 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Dev Workflow Examples
|
|
2
|
+
|
|
3
|
+
## Development Workflow Commands
|
|
4
|
+
|
|
5
|
+
### Option A — Run service locally, wire to simulation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Forward the simulation DB to localhost
|
|
9
|
+
autofleet forwardDb -q --cluster=expManager --variationId=<uuid>
|
|
10
|
+
# Opens Postico. Also prints DB_USERNAME, DB_PASSWORD, DB_NAME env vars for .env
|
|
11
|
+
|
|
12
|
+
# 2. Run service locally (uses local .env pointing at sim DB)
|
|
13
|
+
npm run dev
|
|
14
|
+
|
|
15
|
+
# 3. Intercept live simulation traffic locally via mirrord
|
|
16
|
+
autofleet mirrord -q --service=<service-name> --cluster=expManager --variationId=<uuid>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Option B — Deploy service to simulation, run control-center locally
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Build and push Docker image, update K8s deployment in simulation
|
|
23
|
+
# Run from the service repo root
|
|
24
|
+
autofleet updateExp -q --cluster=expManager --variationId=<uuid>
|
|
25
|
+
# This: builds image tagged gcr.io/autofleetprod/<dir>:<branch>-<HH-MM-SS>
|
|
26
|
+
# pushes to GCP registry
|
|
27
|
+
# patches K8s deployment and waits for rollout
|
|
28
|
+
|
|
29
|
+
# 2. Set up control-center to point at the simulation API gateway
|
|
30
|
+
# Run from control-center repo root
|
|
31
|
+
autofleet localDev -q --cluster=expManager --variationId=<uuid>
|
|
32
|
+
# Updates .env with API_GATEWAY_MS_SERVICE_HOST
|
|
33
|
+
# Port-forwards control-center-ms to port 8085
|
|
34
|
+
#
|
|
35
|
+
# If localDev fails (e.g. no ingress), set .env manually:
|
|
36
|
+
# API_GATEWAY_MS_SERVICE_HOST=<api-gateway-loadbalancer-ip>
|
|
37
|
+
# CONTROL_CENTER_MS_SERVICE_HOST=<control-center-loadbalancer-ip>
|
|
38
|
+
# The simulation control-center IP can be used directly for CONTROL_CENTER_MS
|
|
39
|
+
# since Vite proxies to it. Get IPs with:
|
|
40
|
+
# kubectl get svc --namespace=<variationId> | grep LoadBalancer
|
|
41
|
+
#
|
|
42
|
+
# IMPORTANT: IP/host env vars must NOT include the http:// protocol prefix.
|
|
43
|
+
# The Vite proxy config (vite/devServerProxy.ts) adds http:// automatically.
|
|
44
|
+
# Setting API_GATEWAY_MS_SERVICE_HOST=http://1.2.3.4 causes double-prefix errors.
|
|
45
|
+
|
|
46
|
+
# 3. Start control-center dev server
|
|
47
|
+
npm run dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Useful cluster operations
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Switch kubectl context to a cluster/namespace
|
|
54
|
+
autofleet switchCluster -q --cluster=expManager --variationId=<uuid>
|
|
55
|
+
|
|
56
|
+
# Forward a specific microservice port locally
|
|
57
|
+
autofleet forwardService -q --service=<service-name> --cluster=expManager --variationId=<uuid> --port=8080
|
|
58
|
+
|
|
59
|
+
# Get all microservice IPs in an environment
|
|
60
|
+
autofleet getIps -q --cluster=expManager --variationId=<uuid>
|
|
61
|
+
|
|
62
|
+
# Start local Docker infrastructure (Redis, RabbitMQ, Postgres)
|
|
63
|
+
autofleet dockerInit -q
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Debugging
|
|
67
|
+
|
|
68
|
+
### Replaying a request with curl
|
|
69
|
+
|
|
70
|
+
Copy the `Authorization` header from DevTools → Network → Headers, then:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
curl -s -H "Authorization: Bearer <token>" "http://<api-gateway-ip>/api/v1/..."
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Intercepting the auth token via browser console
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
const origSet = XMLHttpRequest.prototype.setRequestHeader;
|
|
80
|
+
XMLHttpRequest.prototype.setRequestHeader = function(name, val) {
|
|
81
|
+
if (name === 'Authorization') window._token = val;
|
|
82
|
+
return origSet.call(this, name, val);
|
|
83
|
+
};
|
|
84
|
+
// trigger any API action, then: window._token
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Querying GCP logs
|
|
88
|
+
|
|
89
|
+
> **Note:** GCP logs may have a delay of up to a few minutes. If a query returns no results immediately, wait and retry.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Logs for a specific service in a simulation namespace
|
|
93
|
+
gcloud logging read \
|
|
94
|
+
"resource.type=k8s_container \
|
|
95
|
+
AND resource.labels.namespace_name=<variationId> \
|
|
96
|
+
AND resource.labels.container_name=<service-name>" \
|
|
97
|
+
--project=af-experiment-manager \
|
|
98
|
+
--limit=100 \
|
|
99
|
+
--format=json \
|
|
100
|
+
| jq '.[].jsonPayload'
|
|
101
|
+
|
|
102
|
+
# Filter by trace ID
|
|
103
|
+
gcloud logging read \
|
|
104
|
+
"resource.type=k8s_container \
|
|
105
|
+
AND resource.labels.namespace_name=<variationId> \
|
|
106
|
+
AND jsonPayload.traceId=\"<x-trace-id>\"" \
|
|
107
|
+
--project=af-experiment-manager \
|
|
108
|
+
--limit=50 \
|
|
109
|
+
--format=json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Mirrord
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Intercept live traffic from simulation
|
|
116
|
+
autofleet mirrord -q --service=<service-name> --cluster=dev1 --variationId=<uuid>
|
|
117
|
+
|
|
118
|
+
# Generate mirrord config manually (creates .mirrord/mirrord.json)
|
|
119
|
+
autofleet mirrordConfig -q --service=<service-name> --cluster=dev1 --variationId=<uuid>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### DB access
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Port-forward PostgreSQL to localhost:5432
|
|
126
|
+
autofleet forwardDb -q --cluster=dev1 --variationId=<uuid>
|
|
127
|
+
# Credentials: postgres / afpass_<variationId>
|
|
128
|
+
# DB name: <service>_<variationId> (hyphens → underscores)
|
|
129
|
+
# Also auto-opens Postico
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## NPM Token Setup
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Check if already set
|
|
136
|
+
cat ~/.npmrc # look for _authToken=...
|
|
137
|
+
echo $NPM_TOKEN # check if already exported
|
|
138
|
+
|
|
139
|
+
# Export it
|
|
140
|
+
export NPM_TOKEN=<token>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Common Patterns
|
|
144
|
+
|
|
145
|
+
### Typical simulation cycle
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
autofleet switchCluster -q --cluster=expManager --variationId=<sim-id>
|
|
149
|
+
autofleet forwardDb -q --cluster=expManager --variationId=<sim-id> # if DB access needed
|
|
150
|
+
autofleet mirrord -q --service=<svc> --cluster=expManager --variationId=<sim-id> # OR
|
|
151
|
+
autofleet updateExp -q --cluster=expManager --variationId=<sim-id> # full deploy
|
|
152
|
+
autofleet localDev -q --cluster=expManager --variationId=<sim-id> # if running CC locally
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Staging deployment
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Same as simulation but with --cluster=staging (no --variationId needed)
|
|
159
|
+
autofleet updateExp -q --cluster=staging
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## End-of-Session Testing Options
|
|
163
|
+
|
|
164
|
+
### Option A — Deploy backend to simulation, run CC locally
|
|
165
|
+
```bash
|
|
166
|
+
autofleet updateExp -q --cluster=expManager --variationId=<id> # from each modified service repo
|
|
167
|
+
autofleet localDev -q --cluster=expManager --variationId=<id> # from control-center repo
|
|
168
|
+
npm run dev # in control-center
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Option B — Run everything locally, wire to simulation DB/infra
|
|
172
|
+
```bash
|
|
173
|
+
autofleet forwardDb -q --cluster=expManager --variationId=<id> # for each service needing DB
|
|
174
|
+
autofleet mirrord -q --service=<svc> --cluster=expManager --variationId=<id> # to intercept traffic
|
|
175
|
+
npm run dev # in each service + control-center
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Option C — CC locally, backend in simulation
|
|
179
|
+
```bash
|
|
180
|
+
autofleet updateExp -q --cluster=expManager --variationId=<id> # if backend changes need deploying
|
|
181
|
+
autofleet localDev -q --cluster=expManager --variationId=<id> # from control-center repo
|
|
182
|
+
npm run dev # in control-center
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Option D — Deploy everything to simulation
|
|
186
|
+
```bash
|
|
187
|
+
autofleet updateExp -q --cluster=expManager --variationId=<id> # for each changed service
|
|
188
|
+
# Then open the simulation control-center URL directly
|
|
189
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: node-test-runner
|
|
3
|
+
description: Use this skill when the user wants to run tests on a Node.js microservice in Autofleet locally, including setting up Docker dependencies, initializing the test database, and running jest with the correct env vars.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Running Tests on Autofleet Node.js Services
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
Local tests require Docker running with Postgres, Redis, and RabbitMQ. Use the `autofleet` CLI to spin them up:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
autofleet dockerInit -q
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This runs a `docker-compose up -d` with Postgres (port 5432), Redis (6379), RabbitMQ (5672), and Elasticsearch (9200).
|
|
18
|
+
|
|
19
|
+
**Known issue:** If Redis port 6379 is already allocated, the command fails but Postgres and RabbitMQ may still have started. Check with `docker ps` — if `t-postgres-1` is running, you can proceed.
|
|
20
|
+
|
|
21
|
+
Default Docker Postgres credentials:
|
|
22
|
+
- user: `postgres`
|
|
23
|
+
- password: `postgres`
|
|
24
|
+
- host: `127.0.0.1:5432`
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Initialize the Test Database
|
|
29
|
+
|
|
30
|
+
Services use `NODE_ENV=test` to connect to a separate test DB (e.g. `automation_ms_test`). Run this once (or after schema changes):
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
DB_USERNAME=postgres DB_PASSWORD=postgres NODE_ENV=test npm run init
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This creates the DB and runs all Sequelize migrations against it.
|
|
37
|
+
|
|
38
|
+
> Also run without `NODE_ENV=test` if you need the development DB:
|
|
39
|
+
> ```sh
|
|
40
|
+
> DB_USERNAME=postgres DB_PASSWORD=postgres npm run init
|
|
41
|
+
> ```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Running Tests
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
DB_USERNAME=postgres DB_PASSWORD=postgres NODE_ENV=test node_modules/.bin/jest --forceExit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To run a specific file:
|
|
52
|
+
```sh
|
|
53
|
+
DB_USERNAME=postgres DB_PASSWORD=postgres NODE_ENV=test node_modules/.bin/jest src/path/to/file.test.ts --forceExit
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To filter by test name:
|
|
57
|
+
```sh
|
|
58
|
+
DB_USERNAME=postgres DB_PASSWORD=postgres NODE_ENV=test node_modules/.bin/jest src/path/to/file.test.ts --testNamePattern="my test name" --forceExit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Required Env Vars Summary
|
|
64
|
+
|
|
65
|
+
| Var | Value |
|
|
66
|
+
|-----|-------|
|
|
67
|
+
| `DB_USERNAME` | `postgres` |
|
|
68
|
+
| `DB_PASSWORD` | `postgres` |
|
|
69
|
+
| `NODE_ENV` | `test` |
|
|
70
|
+
|
|
71
|
+
All other DB config (host, port, DB name) falls back to defaults in `src/config/config.js`.
|
|
@@ -55,6 +55,7 @@ If the branch name contains no Jira ticket, omit this section entirely.
|
|
|
55
55
|
|
|
56
56
|
## Pre-PR Checklist
|
|
57
57
|
|
|
58
|
+
- Ask the user if they'd like to run `npm audit fix` on affected services — it fixes known dependency vulnerabilities and is better to catch before QA or code review flags them
|
|
58
59
|
- Run linter (if configured)
|
|
59
60
|
- Review your own changes first
|
|
60
61
|
- Ensure tests pass (if running tests)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rabbit
|
|
3
|
+
description: Guides usage of the @autofleet/rabbit package — publishing to exchanges,
|
|
4
|
+
consuming from queues, consuming from exchanges (fanout), ConsumeOptions, error
|
|
5
|
+
handling with ack/nack, retries, dead-letter queues, and mocking in tests. Use
|
|
6
|
+
when the user wants to send or receive RabbitMQ messages, wire up a consumer,
|
|
7
|
+
understand retry/dead-letter behaviour, or mock rabbit in unit tests.
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
userInvocable: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# @autofleet/rabbit
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
**Required env var:** `RABBITMQ_SERVICE_HOST` — set to `<host>:<port>` (e.g. `localhost:5672`).
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import RabbitMq from '@autofleet/rabbit';
|
|
20
|
+
|
|
21
|
+
const rabbit = new RabbitMq();
|
|
22
|
+
// Optional: pass a logger
|
|
23
|
+
const rabbit = new RabbitMq({ logger });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The constructor registers `SIGTERM`/`SIGINT` handlers for graceful shutdown automatically. Pass `{ dontGracefulShutdown: true }` to manage shutdown yourself.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Publishing
|
|
31
|
+
|
|
32
|
+
### Publish to an exchange (fanout)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
await rabbit.publish('my-exchange', { vehicleId: 123, status: 'active' });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Send directly to a queue
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
await rabbit.sendToQueue('my-queue', { orderId: 456 });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Content is automatically JSON-serialized. Tracing headers (`x-trace-id`, `x-af-user-id`) are attached automatically from the current `@autofleet/zehut` context.
|
|
45
|
+
|
|
46
|
+
**Keep messages lean.** Publish only the minimum identifiers needed (e.g. `{ vehicleId, eventType }`) — not full entity objects. Consumers should fetch the full data themselves if needed. Large payloads increase broker memory pressure, slow down consumers, and make retries more expensive.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Consuming
|
|
51
|
+
|
|
52
|
+
### Consume from a queue
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
await rabbit.consume('my-queue', async (msg, ack, nack) => {
|
|
56
|
+
const payload = msg.content; // already parsed from JSON
|
|
57
|
+
try {
|
|
58
|
+
await processMessage(payload);
|
|
59
|
+
await ack();
|
|
60
|
+
} catch (err) {
|
|
61
|
+
await nack(msg); // retries up to options.retries times, then dead-letters
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Consume from an exchange (fanout)
|
|
67
|
+
|
|
68
|
+
Each consumer gets its own queue bound to the exchange:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
await rabbit.consumeFromExchange('my-queue', 'my-exchange', async (msg, ack, nack) => {
|
|
72
|
+
await processMessage(msg.content);
|
|
73
|
+
await ack();
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### ConsumeOptions
|
|
78
|
+
|
|
79
|
+
| Option | Default | Description |
|
|
80
|
+
|--------|---------|-------------|
|
|
81
|
+
| `retries` | `1` | Max retry attempts before dead-lettering |
|
|
82
|
+
| `limit` | `1` | Prefetch (concurrent messages in flight) |
|
|
83
|
+
| `deadMessageTtl` | 12 hours | How long dead messages are kept (ms) |
|
|
84
|
+
| `messageTtl` | — | Max age of a message before discard (ms) |
|
|
85
|
+
| `lockTimeout` | `5000` | Redis lock duration (ms), when using lock |
|
|
86
|
+
| `useConsumeWithLock` | `false` | Use Redis-based distributed lock per message |
|
|
87
|
+
| `enableRabbitTrace` | `false` | Log full rabbit trace headers |
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
await rabbit.consume('my-queue', handler, {
|
|
91
|
+
retries: 3,
|
|
92
|
+
limit: 5,
|
|
93
|
+
deadMessageTtl: 1000 * 60 * 60 * 24, // 24 hours
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## ack / nack
|
|
100
|
+
|
|
101
|
+
- `await ack()` — acknowledge; message is removed from the queue
|
|
102
|
+
- `await nack(msg)` — negative-acknowledge; retried up to `retries` times, then moved to the dead-letter queue
|
|
103
|
+
- `await nack(msg, { skipRetry: true })` — dead-letter immediately without retrying
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Dead-letter queues
|
|
108
|
+
|
|
109
|
+
On first consume, the package auto-creates a dead-letter queue named `<queue>-dead`. Messages land there after exhausting retries. `deadMessageTtl` controls TTL (default 12 hours).
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Mocking in tests
|
|
114
|
+
|
|
115
|
+
Import the Vitest mock class — it implements `IAfRabbitMq` with `vi.fn()` stubs for all methods:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import RabbitMqMock from '@autofleet/rabbit/mock/vitest';
|
|
119
|
+
|
|
120
|
+
const rabbit = new RabbitMqMock();
|
|
121
|
+
// rabbit.publish, rabbit.consume, rabbit.sendToQueue, etc. are vi.fn()
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See **examples.md → Testing** for full Vitest patterns.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Common Issues
|
|
129
|
+
|
|
130
|
+
### Messages not consumed after deploy
|
|
131
|
+
The queue or exchange may not be asserted yet — `consume`/`consumeFromExchange` assert on first call. Ensure the consumer starts before publishing.
|
|
132
|
+
|
|
133
|
+
### `nack` loop causing infinite retries
|
|
134
|
+
Check `retries` in `ConsumeOptions`. Default is 1. After `retries` exhausted, messages go to the dead-letter queue — not requeued forever.
|
|
135
|
+
|
|
136
|
+
### Message content is a Buffer instead of object
|
|
137
|
+
`msg.content` is pre-parsed by the package. If you see a Buffer, you're accessing the raw `amqplib` message — use `msg.content` from the `Message` type, not the underlying amqp object.
|
|
138
|
+
|
|
139
|
+
### `RABBITMQ_SERVICE_HOST` not set
|
|
140
|
+
Connection silently falls back to `localhost:5672`. Always verify the env var is set in `.env` / Kubernetes config.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Rabbit Examples
|
|
2
|
+
|
|
3
|
+
## Basic Setup
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import RabbitMq from '@autofleet/rabbit';
|
|
7
|
+
import { logger } from '@autofleet/logger';
|
|
8
|
+
|
|
9
|
+
const rabbit = new RabbitMq({ logger });
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Publish to Exchange
|
|
13
|
+
|
|
14
|
+
Publish only the minimum identifiers — consumers fetch full data themselves.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Good — lean payload
|
|
18
|
+
await rabbit.publish('vehicle-events', { vehicleId: 123, event: 'location-updated' });
|
|
19
|
+
|
|
20
|
+
// Avoid — full entity bloats the broker
|
|
21
|
+
await rabbit.publish('vehicle-events', { vehicleId: 123, lat: 32.1, lng: 34.9, status: 'active', ...allOtherFields });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Send to Queue
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
await rabbit.sendToQueue('order-processing', { orderId: 456 });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Consume from Queue
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
await rabbit.consume('order-processing', async (msg, ack, nack) => {
|
|
34
|
+
const { orderId } = msg.content;
|
|
35
|
+
try {
|
|
36
|
+
await orderService.process(orderId);
|
|
37
|
+
await ack();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
logger.error('Failed to process order', { err, orderId });
|
|
40
|
+
await nack(msg);
|
|
41
|
+
}
|
|
42
|
+
}, {
|
|
43
|
+
retries: 3,
|
|
44
|
+
limit: 5,
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Consume from Exchange (Fanout)
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
await rabbit.consumeFromExchange('my-service-vehicle-events', 'vehicle-events', async (msg, ack, nack) => {
|
|
52
|
+
await handleVehicleEvent(msg.content);
|
|
53
|
+
await ack();
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Skip Retry on Nack
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
await rabbit.consume('my-queue', async (msg, ack, nack) => {
|
|
61
|
+
if (!isValid(msg.content)) {
|
|
62
|
+
// Bad message — dead-letter immediately, don't retry
|
|
63
|
+
await nack(msg, { skipRetry: true });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
await process(msg.content);
|
|
67
|
+
await ack();
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Testing
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
75
|
+
import RabbitMqMock from '@autofleet/rabbit/mock/vitest';
|
|
76
|
+
import { MyService } from './my-service';
|
|
77
|
+
|
|
78
|
+
describe('MyService', () => {
|
|
79
|
+
let rabbit: RabbitMqMock;
|
|
80
|
+
let service: MyService;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
rabbit = new RabbitMqMock();
|
|
84
|
+
service = new MyService(rabbit);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('publishes an event on vehicle update', async () => {
|
|
88
|
+
await service.updateVehicle(123, { status: 'active' });
|
|
89
|
+
|
|
90
|
+
expect(rabbit.publish).toHaveBeenCalledWith(
|
|
91
|
+
'vehicle-events',
|
|
92
|
+
{ vehicleId: 123, status: 'active' },
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('processes a consumed message', async () => {
|
|
97
|
+
// Grab the callback registered via consume
|
|
98
|
+
await service.startConsuming();
|
|
99
|
+
const [[, callback]] = (rabbit.consume as ReturnType<typeof vi.fn>).mock.calls;
|
|
100
|
+
|
|
101
|
+
const ack = vi.fn().mockResolvedValue(undefined);
|
|
102
|
+
const nack = vi.fn().mockResolvedValue(undefined);
|
|
103
|
+
|
|
104
|
+
await callback({ content: { orderId: 1 } }, ack, nack);
|
|
105
|
+
|
|
106
|
+
expect(ack).toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
```
|
package/dist/e2e.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{C as e,c as t,i as n,l as r,n as i,x as a}from"./utils-
|
|
2
|
+
import{C as e,c as t,i as n,l as r,n as i,x as a}from"./utils-oIUjuHiV.js";import o from"node:path";import s from"p-retry";const c=[`identity-ms`,`vehicle-ms`,`ride-ms`,`vehicle-live-data-ms`,`pricing-ms`,`payment-ms`,`route-plan-ms`,`task-ms`,`audit-ms`,`pgweb`,`placement-ms`,`demand-prediction-ms`,`control-center-ms`,`setting-ms`,`api-gateway-ms`],l={start:`--e2eBranch=<branchName> --branches="{\\"ride-ms\\": \\"AUT-7070\\"}"`,getIpsForEnv:`--variationId=<yourVariationUuid> --cluster=<dev1/e2eManager>`},u=e=>Object.keys(l).includes(e),{positionals:d,values:f,help:p}=e({strict:!0,binName:`autofleet-e2e`,allowPositionals:!0,options:{e2eBranch:{type:`string`,default:`master`,description:`Name of e2e branch`},branches:{type:`string`,default:``,description:`"ride-ms": "AUT-7070"`},cluster:{type:`string`,short:`c`,description:`The cluster name. One of dev1, e2eManager, expManager`},variationId:{type:`string`,short:`i`,description:`The variation id`}},commands:l});d.length||(console.log(p),process.exit(0));const[m]=d;if(u(m)||n(Error(`Command ${m} does not exist`),p),m===`start`){let e=(f.branches?.replace(`{`,` `)||``).replace(/[^a-z0-9]/gi,``),t=`${o.resolve()}/bin/utils/e2e.sh`,n=`${o.resolve()}/e2e.sh`;r({newFilePath:n,filePath:t,linesToReplace:[{find:`$E2E_BRANCH_PLACE_HOLDER$`,replace:f.e2eBranch},{find:`$BRANCHES_PLACE_HOLDER$`,replace:f.branches},{find:`$NAME_PLACE_HOLDER$`,replace:e}]}),await a(`chmod`,[`+x ${n} && ${n}`]),console.log(`finished e2e`)}const{cluster:h,variationId:g}=f;h||n(Error(`cluster required`),p),g||n(Error(`variationId required`),p);let _;try{_=await i({clusterName:h,variationId:g})}catch(e){n(e)}m===`getIpsForEnv`&&(await Promise.all(c.map(e=>a(`kubectl`,[`annotate`,_,`service`,e,`cloud.google.com/load-balancer-type-`],{print:!1}))),await Promise.all(c.map(e=>a(`kubectl`,[`patch`,`svc`,_,e,`-p`,`{"spec": {"type": "LoadBalancer"}}`],{print:!1}))),console.log(`exposed services successfully. the services are now creating public ips.`),await s(async()=>{let{stdout:e}=await a(`kubectl`,[_,`get`,`services`],{print:!1}),n=t(e);if(n.some(e=>e.includes(`<pending>`)))throw Error(`Some services are still pending`);n.forEach(e=>console.log(e))},{retries:10,factor:1,maxTimeout:5e3,randomize:!0}),process.exit(0));export{};
|
|
3
3
|
//# sourceMappingURL=e2e.js.map
|
package/dist/git-autofleet.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{C as e,_ as t,g as n,h as r,i,m as a,v as o,x as s,y as c}from"./utils-
|
|
2
|
+
import{C as e,_ as t,g as n,h as r,i,m as a,v as o,x as s,y as c}from"./utils-oIUjuHiV.js";import{basename as l,sep as u}from"node:path";import{readdir as d}from"node:fs/promises";import{randomUUID as f}from"node:crypto";const p=[`premajor`,`preminor`,`prepatch`,`prerelease`],m=[`major`,`minor`,`patch`,...p],h={bumpVersion:`[${m.join(`|`)}]`,openPR:`Opens github UI for creating a PR from the current branch.`,releaseBeta:`Bumps version if needed, and publishes a beta release to NPM registry.`},g=e=>Object.keys(h).includes(e),_=async({versionType:e,packageName:t,preReleaseId:n=``})=>{let{stdout:r}=await s(`npm`,[`version`,e,`--no-git-tag-version`,`--preid`,n],{print:!1,cwd:t?`.${u}packages${u}${t}`:void 0});return await s(`git`,[`add`,`package*`],{print:!1}),await s(`git`,[`commit`,`-m`,`${e} version: ${r}`],{print:!1}),console.log(`Created commit for version bump: ${e}. Version is now ${r}`),r},v=async e=>{let t=await c(),{default:{version:n}}=await import(`${e?`${t}${u}packages${u}${e}`:t}${u}package.json`,{assert:{type:`json`},with:{type:`json`}});return n.replace(/\d+\.\d+\.\d+(?:-beta-([\w]+)?\.\d+)?/,`$1`)},{positionals:y,help:b}=e({strict:!0,binName:`git autofleet`,allowPositionals:!0,options:{},commands:h});y.length||(console.log(b),process.exit(0));const[x,S,C]=y;if(g(x)||i(Error(`Command ${x} does not exist`),b),x===`openPR`){let[e,t]=await Promise.all([c(),o()]);await s(`open`,[`https://github.com/Autofleet/${l(e)}/compare/${t}?expand=1`],{print:!1}),process.exit(0)}async function w(){let e=await c();return e.split(u).pop()===`autorepo`?e:!1}async function T({message:e,pathIfAutorepo:t}){t??=await w();let r=C;if(t){let a=(await d(`${t}${u}packages`)).filter(e=>!e.startsWith(`.`));r?a.includes(r)||i(Error(`Package name ${r} does not exist in autorepo packages`),b):r=await n({message:e,choices:a.map(e=>({name:e,value:e}))}),r||i(Error(`No package name provided, exiting...`),b)}return r}if(x===`bumpVersion`){let e=await T({message:`Which package do you want to bump?`}),t=S||await n({message:`Which version type do you want to bump?`,choices:(x===`bumpVersion`?m:p).map(e=>({name:e,value:e}))});t||i(Error(`Must explicitly define version type.\n\tVersion type can be one of: ${m.join(`, `)}`),b);let r=await o();[`main`,`master`].includes(r)&&(await a(`Are you sure you want to bump version on main branch?`)||process.exit(0)),await _({versionType:t,packageName:e}),process.exit(0)}if(x===`releaseBeta`){let e=await w(),n=await T({message:`Which package do you want to release?`,pathIfAutorepo:e});S&&!p.includes(S)&&i(Error(`Invalid version type: ${S}\n\tVersion must be a prerelease type. (${p.join(`, `)})`),b);let c=await o();[`main`,`master`].includes(c)&&i(Error(`Cannot release beta from main branch`),b);let l=n?`.${u}packages${u}${n}`:void 0,d=!(await t(n?`.${u}packages${u}${n}${u}package.json`:`package.json`)).split(`
|
|
3
3
|
`).some(e=>/^\+\s*"version":\s*"\d+\.\d+\.\d+(?:-(?:[\w-]+\.)?\d+)?",/.test(e))||await a(`Version already bumped on branch. Should version be re-bumped?`),m=d&&await v(n),h=d&&await _({versionType:S||`prerelease`,preReleaseId:`beta-${m||f().split(`-`)[0]}`,packageName:n}),g=e?`pnpm`:`npm`;await s(g,[`run`,`build`],{cwd:l});let y=await r({message:`In case your token requires an OTP, please enter it:`});try{await s(g,[`publish`,`--tag`,`beta`,`--@autofleet:registry=https://registry.npmjs.org`,...y?[`--otp`,y]:[],...e?[`--no-git-checks`]:[]],{cwd:l})}catch(e){console.error(e),i(`Failed to publish beta version`)}if(h)console.log(`Published beta version with new version: ${h}`);else{let{stdout:e}=await s(g,[`info`,`.`,`version`],{print:!1,cwd:l});console.log(`Published beta version with existing version: ${e}`)}process.exit(0)}export{};
|
|
4
4
|
//# sourceMappingURL=git-autofleet.js.map
|