@adobe-commerce/aio-toolkit 1.0.13 → 1.0.15
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/CHANGELOG.md +116 -0
- package/README.md +122 -9
- package/dist/aio-toolkit-cursor-context/bin/cli.js +1089 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -0
- package/dist/index.d.mts +43 -16
- package/dist/index.d.ts +43 -16
- package/dist/index.js +1030 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1034 -167
- package/dist/index.mjs.map +1 -1
- package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +562 -0
- package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +531 -0
- package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +293 -0
- package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +439 -0
- package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +1321 -0
- package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +331 -0
- package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +354 -0
- package/package.json +6 -1
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Object-Oriented Programming Best Practices for Actions, Lib, and Scripts
|
|
3
|
+
globs: '**/{actions,lib,scripts}/**/*.{ts,js}'
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Object-Oriented Programming Best Practices
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This rule enforces essential OOP principles in `actions/`, `lib/`, and `scripts/` directories for maintainable, reusable code.
|
|
12
|
+
|
|
13
|
+
## Core Principles
|
|
14
|
+
|
|
15
|
+
### 1. Use Classes for Stateful Components
|
|
16
|
+
|
|
17
|
+
**When to use classes:**
|
|
18
|
+
- Components with multiple methods and shared state
|
|
19
|
+
- Services that need initialization
|
|
20
|
+
- Reusable utilities with configuration
|
|
21
|
+
- Client builders and singletons
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// ✅ Good - Class with state and behavior
|
|
25
|
+
class ProductService {
|
|
26
|
+
constructor(client, logger) {
|
|
27
|
+
this.client = client;
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getProduct(sku) {
|
|
32
|
+
this.logger.info(`Fetching product: ${sku}`);
|
|
33
|
+
return await this.client.get(`products/${sku}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async updateProduct(sku, data) {
|
|
37
|
+
this.logger.info(`Updating product: ${sku}`);
|
|
38
|
+
return await this.client.put(`products/${sku}`, {}, data);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Always Initialize Properties in Constructor
|
|
44
|
+
|
|
45
|
+
**Initialize all properties that your class will use:**
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// ✅ Good - All properties initialized
|
|
49
|
+
class OrderProcessor {
|
|
50
|
+
constructor(config, logger) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
this.logger = logger;
|
|
53
|
+
this.client = null;
|
|
54
|
+
this.cache = new Map();
|
|
55
|
+
this.retryCount = 3;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ❌ Bad - Properties not initialized
|
|
60
|
+
class OrderProcessor {
|
|
61
|
+
constructor(config) {
|
|
62
|
+
this.config = config;
|
|
63
|
+
// this.client, this.logger, etc. are undefined!
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Use Private Fields and Methods
|
|
69
|
+
|
|
70
|
+
**Keep implementation details private:**
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
// JavaScript (Node.js 12+)
|
|
74
|
+
class UserManager {
|
|
75
|
+
#apiKey; // Private field
|
|
76
|
+
|
|
77
|
+
constructor(apiKey) {
|
|
78
|
+
this.#apiKey = apiKey;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#validateKey() { // Private method
|
|
82
|
+
return this.#apiKey.length > 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async authenticate() {
|
|
86
|
+
if (!this.#validateKey()) {
|
|
87
|
+
throw new Error('Invalid API key');
|
|
88
|
+
}
|
|
89
|
+
// Use this.#apiKey
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// TypeScript
|
|
94
|
+
class UserManager {
|
|
95
|
+
private apiKey: string;
|
|
96
|
+
|
|
97
|
+
constructor(apiKey: string) {
|
|
98
|
+
this.apiKey = apiKey;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private validateKey(): boolean {
|
|
102
|
+
return this.apiKey.length > 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 4. Use Dependency Injection
|
|
108
|
+
|
|
109
|
+
**Inject dependencies instead of hardcoding them:**
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
// ✅ Good - Dependencies injected
|
|
113
|
+
class OrderService {
|
|
114
|
+
constructor(paymentProcessor, logger, emailService) {
|
|
115
|
+
this.paymentProcessor = paymentProcessor;
|
|
116
|
+
this.logger = logger;
|
|
117
|
+
this.emailService = emailService;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async processOrder(order) {
|
|
121
|
+
await this.paymentProcessor.charge(order.total);
|
|
122
|
+
await this.emailService.send(order.confirmationEmail);
|
|
123
|
+
this.logger.info('Order processed');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ❌ Bad - Dependencies hardcoded
|
|
128
|
+
class OrderService {
|
|
129
|
+
constructor() {
|
|
130
|
+
this.paymentProcessor = new PaymentProcessor(); // Hardcoded!
|
|
131
|
+
this.emailService = new EmailService(); // Hardcoded!
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 5. Prefer Composition Over Inheritance
|
|
137
|
+
|
|
138
|
+
**Use composition (has-a) over inheritance (is-a) when possible:**
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// ✅ Good - Composition
|
|
142
|
+
class OrderService {
|
|
143
|
+
constructor(paymentProcessor, inventoryManager) {
|
|
144
|
+
this.paymentProcessor = paymentProcessor;
|
|
145
|
+
this.inventoryManager = inventoryManager;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async processOrder(order) {
|
|
149
|
+
await this.inventoryManager.reserve(order.items);
|
|
150
|
+
await this.paymentProcessor.charge(order.total);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ❌ Bad - Unnecessary inheritance
|
|
155
|
+
class OrderService extends PaymentProcessor {
|
|
156
|
+
// OrderService is NOT a PaymentProcessor
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Common Patterns
|
|
161
|
+
|
|
162
|
+
### Singleton Pattern (for expensive resources)
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
class AdobeCommerceClientBuilder {
|
|
166
|
+
constructor(baseUrl, consumerKey, consumerSecret, accessToken, accessTokenSecret) {
|
|
167
|
+
this.baseUrl = baseUrl;
|
|
168
|
+
this.consumerKey = consumerKey;
|
|
169
|
+
this.consumerSecret = consumerSecret;
|
|
170
|
+
this.accessToken = accessToken;
|
|
171
|
+
this.accessTokenSecret = accessTokenSecret;
|
|
172
|
+
this.client = null; // Cached instance
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getClient() {
|
|
176
|
+
if (this.client) return this.client; // Return cached
|
|
177
|
+
|
|
178
|
+
const connection = new Oauth1aConnection(
|
|
179
|
+
this.consumerKey,
|
|
180
|
+
this.consumerSecret,
|
|
181
|
+
this.accessToken,
|
|
182
|
+
this.accessTokenSecret
|
|
183
|
+
);
|
|
184
|
+
this.client = new AdobeCommerceClient(this.baseUrl, connection);
|
|
185
|
+
return this.client;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Resolver Pattern (for GraphQL actions)
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
class GetUserResolver {
|
|
194
|
+
constructor(ctx) {
|
|
195
|
+
this.ctx = ctx;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async execute() {
|
|
199
|
+
return async (args) => {
|
|
200
|
+
const { logger } = this.ctx;
|
|
201
|
+
logger.info({ message: 'getUser-execution', args });
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// Business logic here
|
|
205
|
+
return result;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
logger.error({ message: 'getUser-error', error: error.message });
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Anti-Patterns to Avoid
|
|
216
|
+
|
|
217
|
+
### ❌ God Classes (doing too much)
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
// ❌ Bad
|
|
221
|
+
class ApplicationManager {
|
|
222
|
+
createUser() { }
|
|
223
|
+
deleteUser() { }
|
|
224
|
+
createProduct() { }
|
|
225
|
+
processOrder() { }
|
|
226
|
+
sendEmail() { }
|
|
227
|
+
generateReport() { }
|
|
228
|
+
// ... 50 more methods
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ✅ Good - Separate concerns
|
|
232
|
+
class UserService { }
|
|
233
|
+
class ProductService { }
|
|
234
|
+
class OrderService { }
|
|
235
|
+
class EmailService { }
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### ❌ Anemic Models (data only, no behavior)
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// ❌ Bad
|
|
242
|
+
class Order {
|
|
243
|
+
constructor(id, items, total) {
|
|
244
|
+
this.id = id;
|
|
245
|
+
this.items = items;
|
|
246
|
+
this.total = total;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// All logic in separate service
|
|
250
|
+
|
|
251
|
+
// ✅ Good - Rich model with behavior
|
|
252
|
+
class Order {
|
|
253
|
+
constructor(id, items) {
|
|
254
|
+
this.id = id;
|
|
255
|
+
this.items = items;
|
|
256
|
+
this.total = this.calculateTotal();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
calculateTotal() {
|
|
260
|
+
return this.items.reduce((sum, item) => sum + item.price, 0);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
addItem(item) {
|
|
264
|
+
this.items.push(item);
|
|
265
|
+
this.total = this.calculateTotal();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### ❌ Tight Coupling (hardcoded dependencies)
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
// ❌ Bad
|
|
274
|
+
class OrderService {
|
|
275
|
+
constructor() {
|
|
276
|
+
this.emailService = new EmailService(); // Hardcoded!
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ✅ Good - Loose coupling
|
|
281
|
+
class OrderService {
|
|
282
|
+
constructor(emailService) {
|
|
283
|
+
this.emailService = emailService; // Injected
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Key Takeaways
|
|
289
|
+
|
|
290
|
+
1. **Use classes for stateful, reusable components**
|
|
291
|
+
2. **Initialize all properties in constructor**
|
|
292
|
+
3. **Use private fields for encapsulation**
|
|
293
|
+
4. **Inject dependencies, don't hardcode them**
|
|
294
|
+
5. **Prefer composition over inheritance**
|
|
295
|
+
6. **Keep classes focused (Single Responsibility)**
|
|
296
|
+
7. **Avoid God classes and anemic models**
|
|
297
|
+
|
|
298
|
+
## Examples from @adobe-commerce/aio-toolkit
|
|
299
|
+
|
|
300
|
+
Follow the toolkit's class-based patterns:
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
// GraphQL Resolver
|
|
304
|
+
class HelloWorld {
|
|
305
|
+
constructor(ctx) {
|
|
306
|
+
this.ctx = ctx;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async execute() {
|
|
310
|
+
return async (args) => {
|
|
311
|
+
const { logger } = this.ctx;
|
|
312
|
+
// Implementation
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Client Builder with Singleton
|
|
318
|
+
class AdobeCommerceClientBuilder {
|
|
319
|
+
constructor(baseUrl, credentials) {
|
|
320
|
+
this.baseUrl = baseUrl;
|
|
321
|
+
this.credentials = credentials;
|
|
322
|
+
this.client = null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getClient() {
|
|
326
|
+
if (this.client) return this.client;
|
|
327
|
+
this.client = new AdobeCommerceClient(this.baseUrl, this.credentials);
|
|
328
|
+
return this.client;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Setting up New Relic Telemetry for Adobe I/O Runtime Actions
|
|
3
|
+
globs: '**/{app.config.yaml,ext.config.yaml,actions.config.yaml},.env'
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Setting up New Relic Telemetry
|
|
8
|
+
|
|
9
|
+
## Trigger Conditions
|
|
10
|
+
|
|
11
|
+
This rule applies when the user requests to add New Relic telemetry to their actions using any of these phrases:
|
|
12
|
+
|
|
13
|
+
- "Add New Relic telemetry"
|
|
14
|
+
- "Enable New Relic monitoring"
|
|
15
|
+
- "Setup telemetry for [action-name]"
|
|
16
|
+
- "Configure New Relic for my actions"
|
|
17
|
+
- "Add telemetry to [action-type] action"
|
|
18
|
+
- "Enable observability with New Relic"
|
|
19
|
+
- "Setup New Relic OTLP"
|
|
20
|
+
- "Add telemetry monitoring"
|
|
21
|
+
|
|
22
|
+
### Supported Action Types
|
|
23
|
+
|
|
24
|
+
New Relic telemetry can be added to:
|
|
25
|
+
|
|
26
|
+
- RuntimeAction
|
|
27
|
+
- WebhookAction
|
|
28
|
+
- EventConsumerAction
|
|
29
|
+
- GraphQlAction
|
|
30
|
+
- OpenwhiskAction
|
|
31
|
+
|
|
32
|
+
**Note**: This rule should be used AFTER an action has been created. It provides recommendations for enabling telemetry on existing actions.
|
|
33
|
+
|
|
34
|
+
## Overview
|
|
35
|
+
|
|
36
|
+
The `@adobe-commerce/aio-toolkit` library includes built-in New Relic telemetry support using OpenTelemetry Protocol (OTLP). When enabled, the toolkit automatically:
|
|
37
|
+
|
|
38
|
+
1. **Instruments actions** with OpenTelemetry
|
|
39
|
+
2. **Exports traces** - Captures action execution spans and timing
|
|
40
|
+
3. **Exports metrics** - Sends performance metrics
|
|
41
|
+
4. **Exports logs** - Forwards structured logs with JSON attribute extraction
|
|
42
|
+
5. **Tags data** - Adds environment, service name, and custom resource attributes
|
|
43
|
+
6. **Detects success/failure** - Automatically detects action outcomes
|
|
44
|
+
|
|
45
|
+
**No code changes required** - Telemetry is configured entirely through action inputs and environment variables.
|
|
46
|
+
|
|
47
|
+
## Step 1: Verify Prerequisites and Existing Configuration
|
|
48
|
+
|
|
49
|
+
Before proceeding, verify and analyze the current configuration:
|
|
50
|
+
|
|
51
|
+
1. **Action exists** - Check that the action is already created
|
|
52
|
+
2. **Toolkit is installed** - Confirm `@adobe-commerce/aio-toolkit` is in `package.json`
|
|
53
|
+
3. **Check existing telemetry configuration**:
|
|
54
|
+
- Check if `.env` file exists in project root directory (workspace root)
|
|
55
|
+
- If project root `.env` exists, check for these variables:
|
|
56
|
+
- `ENVIRONMENT` - Note the value if present (dev, staging, production)
|
|
57
|
+
- `NEW_RELIC_SERVICE_NAME` - Note the value if present
|
|
58
|
+
- `NEW_RELIC_LICENSE_KEY` - Note if present (don't show the value)
|
|
59
|
+
- `NEW_RELIC_URL` - Note if custom endpoint is configured
|
|
60
|
+
- Check existing actions in `app.config.yaml` for telemetry inputs to detect default environment
|
|
61
|
+
- Look for `ENVIRONMENT` input in any action
|
|
62
|
+
- Common values: `dev`, `staging`, `production`
|
|
63
|
+
4. Only proceed to Step 2 after analyzing the current configuration
|
|
64
|
+
|
|
65
|
+
## Step 2: Ask Required Questions
|
|
66
|
+
|
|
67
|
+
Before configuring telemetry, ask the user the following questions:
|
|
68
|
+
|
|
69
|
+
**Important**:
|
|
70
|
+
|
|
71
|
+
- **Smart Configuration Detection**: Based on Step 1 analysis, skip questions for values already configured in project root `.env` or detected from existing actions
|
|
72
|
+
- If `ENVIRONMENT` exists in project root `.env`: Skip Question 2, use existing value
|
|
73
|
+
- If `NEW_RELIC_SERVICE_NAME` exists in project root `.env`: Skip Question 3, use existing value
|
|
74
|
+
- If `NEW_RELIC_LICENSE_KEY` exists in project root `.env`: Skip Question 4, confirm it's already set
|
|
75
|
+
- If any question is unclear or you need more information to proceed, continue asking follow-up questions until you have complete clarity. Do not make assumptions - keep the conversation going until all details are clear.
|
|
76
|
+
- Once all questions are clearly answered, move to Step 3 to get user confirmation before making any configuration changes.
|
|
77
|
+
|
|
78
|
+
1. **Which actions need telemetry?**
|
|
79
|
+
- Provide comma-separated action names
|
|
80
|
+
- Example: `runtime-action, webhook-action, product-consumer`
|
|
81
|
+
- Or say "all actions" to add telemetry to every action in the project
|
|
82
|
+
2. **Environment name** (for filtering and tagging in New Relic)?
|
|
83
|
+
- **ONLY ask if `ENVIRONMENT` is NOT in project root `.env`** (from Step 1)
|
|
84
|
+
- **If already configured**: Skip this question and use the existing value
|
|
85
|
+
- Options: `dev`, `staging`, `production`, `test`, or custom name
|
|
86
|
+
- **If detected from existing actions in Step 1**: Show detected value and ask to confirm or change
|
|
87
|
+
- Example: "I detected `staging` from your existing actions. Use this environment or specify a different one?"
|
|
88
|
+
- **If not detected**: Ask what environment to use (no default assumption)
|
|
89
|
+
- Note: This appears as a tag in New Relic for filtering data
|
|
90
|
+
|
|
91
|
+
3. **Service name** (for New Relic identification)?
|
|
92
|
+
- **ONLY ask if `NEW_RELIC_SERVICE_NAME` is NOT in project root `.env`** (from Step 1)
|
|
93
|
+
- **If already configured**: Skip this question and use the existing value
|
|
94
|
+
- Provide a descriptive name for your service
|
|
95
|
+
- Example: `my-commerce-app`, `customer-portal`, `order-processing`
|
|
96
|
+
- Note: This appears as the service name in New Relic dashboards
|
|
97
|
+
|
|
98
|
+
4. **Do you have a New Relic license key?**
|
|
99
|
+
- **ONLY ask if `NEW_RELIC_LICENSE_KEY` is NOT in project root `.env`** (from Step 1)
|
|
100
|
+
- **If already configured**: Skip this question and confirm the key is set
|
|
101
|
+
- Options: Yes or No
|
|
102
|
+
|
|
103
|
+
**If No, provide instructions**:
|
|
104
|
+
- Log in to [New Relic](https://one.newrelic.com/)
|
|
105
|
+
- Navigate to **Account settings** > **API keys**
|
|
106
|
+
- Find or create an **Ingest - License** key type
|
|
107
|
+
- Copy the key value (it starts with `NRAK-`)
|
|
108
|
+
- You'll add this to your project root `.env` file
|
|
109
|
+
|
|
110
|
+
## Step 3: Confirm Telemetry Configuration
|
|
111
|
+
|
|
112
|
+
After receiving answers to ALL questions, present a comprehensive summary and **wait for user confirmation** before proceeding to Step 4:
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### 📊 New Relic Telemetry Configuration Summary
|
|
117
|
+
|
|
118
|
+
**Actions to Configure:**
|
|
119
|
+
|
|
120
|
+
- [List of action names]
|
|
121
|
+
|
|
122
|
+
**Existing Configuration** (detected from project root `.env` and actions):
|
|
123
|
+
[If any values were detected in Step 1, list them here:]
|
|
124
|
+
|
|
125
|
+
- ✅ **Environment:** `[existing-environment]` (already configured in project root `.env`)
|
|
126
|
+
- ✅ **Service Name:** `[existing-service-name]` (already configured in project root `.env`)
|
|
127
|
+
- ✅ **License Key:** Configured in project root `.env` (starts with `NRAK-...`)
|
|
128
|
+
[Only if NEW_RELIC_URL was detected:]
|
|
129
|
+
- ✅ **Custom Endpoint:** `[existing-endpoint]` (already configured in project root `.env`)
|
|
130
|
+
|
|
131
|
+
**New Configuration** (to be added):
|
|
132
|
+
[Only show values that are NEW or changed:]
|
|
133
|
+
|
|
134
|
+
- **Environment:** [environment name] [Only if NEW - not in project root .env]
|
|
135
|
+
- **Service Name:** [service name] [Only if NEW - not in project root .env]
|
|
136
|
+
- **License Key:** [To be added] [Only if NEW - not in project root .env]
|
|
137
|
+
- **OTLP Endpoint:** Default US endpoint (https://otlp.nr-data.net) [Only if no custom endpoint detected]
|
|
138
|
+
|
|
139
|
+
**What Will Be Added:**
|
|
140
|
+
|
|
141
|
+
For each action, the following inputs will be added to the action configuration:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
inputs:
|
|
145
|
+
LOG_LEVEL: debug
|
|
146
|
+
ENVIRONMENT: $ENVIRONMENT
|
|
147
|
+
ENABLE_TELEMETRY: true
|
|
148
|
+
NEW_RELIC_TELEMETRY: true
|
|
149
|
+
NEW_RELIC_SERVICE_NAME: $NEW_RELIC_SERVICE_NAME
|
|
150
|
+
NEW_RELIC_LICENSE_KEY: $NEW_RELIC_LICENSE_KEY
|
|
151
|
+
[NEW_RELIC_URL: $NEW_RELIC_URL] # Only if custom endpoint was detected in project root .env
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Environment Variables** (project root `.env` file):
|
|
155
|
+
|
|
156
|
+
[If all variables already exist:]
|
|
157
|
+
✅ All required environment variables are already configured in project root `.env`
|
|
158
|
+
|
|
159
|
+
[If some need to be added:]
|
|
160
|
+
You'll need to add these to your project root `.env` file:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
[Only show variables that DON'T already exist:]
|
|
164
|
+
ENVIRONMENT=[environment] # NEW (e.g., staging, production)
|
|
165
|
+
NEW_RELIC_SERVICE_NAME=[service-name] # NEW
|
|
166
|
+
NEW_RELIC_LICENSE_KEY=[your-license-key] # NEW
|
|
167
|
+
NEW_RELIC_URL=[custom-endpoint] # NEW (optional)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Files to Modify:**
|
|
171
|
+
|
|
172
|
+
- ✏️ `app.config.yaml` or `[extension-path]/ext.config.yaml` (for each action)
|
|
173
|
+
[Only if new variables:]
|
|
174
|
+
- 📄/✏️ `.env` (project root) - Create or update with new environment variables
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
**Should I proceed with adding New Relic telemetry to these actions?**
|
|
179
|
+
|
|
180
|
+
## Step 4: Add Telemetry Configuration
|
|
181
|
+
|
|
182
|
+
Once confirmed, update the action configurations and provide environment variable setup instructions.
|
|
183
|
+
|
|
184
|
+
### A. Update Action Configurations
|
|
185
|
+
|
|
186
|
+
For each action identified, add telemetry inputs to the action configuration.
|
|
187
|
+
|
|
188
|
+
#### Standard Actions (in `app.config.yaml`)
|
|
189
|
+
|
|
190
|
+
Locate the action under `application.runtimeManifest.packages.[package-name].actions.[action-name]` and add/update the `inputs` section:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
[action-name]:
|
|
194
|
+
function: actions/[action-name]/index.[js/ts]
|
|
195
|
+
web: 'yes' # or 'no' for event consumers
|
|
196
|
+
runtime: nodejs:22
|
|
197
|
+
inputs:
|
|
198
|
+
LOG_LEVEL: debug
|
|
199
|
+
ENVIRONMENT: $ENVIRONMENT
|
|
200
|
+
ENABLE_TELEMETRY: true
|
|
201
|
+
NEW_RELIC_TELEMETRY: true
|
|
202
|
+
NEW_RELIC_SERVICE_NAME: $NEW_RELIC_SERVICE_NAME
|
|
203
|
+
NEW_RELIC_LICENSE_KEY: $NEW_RELIC_LICENSE_KEY
|
|
204
|
+
# Only add if custom endpoint specified:
|
|
205
|
+
# NEW_RELIC_URL: $NEW_RELIC_URL
|
|
206
|
+
annotations:
|
|
207
|
+
require-adobe-auth: [true/false]
|
|
208
|
+
final: true
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### Packaged Actions (in `actions/[package]/actions.config.yaml`)
|
|
212
|
+
|
|
213
|
+
For actions defined in separate config files:
|
|
214
|
+
|
|
215
|
+
```yaml
|
|
216
|
+
[action-name]:
|
|
217
|
+
function: [action-name]/index.[js/ts]
|
|
218
|
+
web: 'yes' # or 'no' for event consumers
|
|
219
|
+
runtime: nodejs:22
|
|
220
|
+
inputs:
|
|
221
|
+
LOG_LEVEL: debug
|
|
222
|
+
ENVIRONMENT: $ENVIRONMENT
|
|
223
|
+
ENABLE_TELEMETRY: true
|
|
224
|
+
NEW_RELIC_TELEMETRY: true
|
|
225
|
+
NEW_RELIC_SERVICE_NAME: $NEW_RELIC_SERVICE_NAME
|
|
226
|
+
NEW_RELIC_LICENSE_KEY: $NEW_RELIC_LICENSE_KEY
|
|
227
|
+
# Only add if custom endpoint specified:
|
|
228
|
+
# NEW_RELIC_URL: $NEW_RELIC_URL
|
|
229
|
+
annotations:
|
|
230
|
+
require-adobe-auth: [true/false]
|
|
231
|
+
final: true
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
#### Extension Point Actions (in `[extension-path]/ext.config.yaml`)
|
|
235
|
+
|
|
236
|
+
For actions in extension points, follow the same pattern but update the extension's config file:
|
|
237
|
+
|
|
238
|
+
```yaml
|
|
239
|
+
runtimeManifest:
|
|
240
|
+
packages:
|
|
241
|
+
[package-name]:
|
|
242
|
+
license: Apache-2.0
|
|
243
|
+
actions:
|
|
244
|
+
[action-name]:
|
|
245
|
+
function: actions/[action-name]/index.js
|
|
246
|
+
web: 'yes'
|
|
247
|
+
runtime: nodejs:22
|
|
248
|
+
inputs:
|
|
249
|
+
LOG_LEVEL: debug
|
|
250
|
+
ENVIRONMENT: $ENVIRONMENT
|
|
251
|
+
ENABLE_TELEMETRY: true
|
|
252
|
+
NEW_RELIC_TELEMETRY: true
|
|
253
|
+
NEW_RELIC_SERVICE_NAME: $NEW_RELIC_SERVICE_NAME
|
|
254
|
+
NEW_RELIC_LICENSE_KEY: $NEW_RELIC_LICENSE_KEY
|
|
255
|
+
# Only add if custom endpoint specified:
|
|
256
|
+
# NEW_RELIC_URL: $NEW_RELIC_URL
|
|
257
|
+
annotations:
|
|
258
|
+
require-adobe-auth: [true/false]
|
|
259
|
+
final: true
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### B. Environment Variables Setup
|
|
263
|
+
|
|
264
|
+
**If all variables exist**: ✅ `ENVIRONMENT`, `NEW_RELIC_SERVICE_NAME`, `NEW_RELIC_LICENSE_KEY` (and optional `NEW_RELIC_URL`) already in project root `.env` - no changes needed.
|
|
265
|
+
|
|
266
|
+
**If variables need adding**:
|
|
267
|
+
|
|
268
|
+
1. Check/create project root `.env` file (workspace root)
|
|
269
|
+
2. Add missing variables (only show variables that need to be added)
|
|
270
|
+
3. Get license key from New Relic → Account settings → API keys → Ingest - License (starts with `NRAK-`)
|
|
271
|
+
4. Never commit `.env` - ensure it's in `.gitignore`
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Add to project root .env
|
|
275
|
+
ENVIRONMENT=[environment]
|
|
276
|
+
NEW_RELIC_SERVICE_NAME=[service-name]
|
|
277
|
+
NEW_RELIC_LICENSE_KEY=[license-key]
|
|
278
|
+
NEW_RELIC_URL=[custom-endpoint] # Optional: EU only
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Step 5: Testing and Verification
|
|
282
|
+
|
|
283
|
+
**Deploy**: Run `aio app deploy`
|
|
284
|
+
|
|
285
|
+
**Test**: Invoke action via `aio runtime action invoke [action-name] --result` or API/webhook call
|
|
286
|
+
|
|
287
|
+
**Verify in New Relic** (1-2 minutes delay): Login → APM & Services → Find service → View Traces/Logs/Metrics
|
|
288
|
+
|
|
289
|
+
**Troubleshooting**: Check `NEW_RELIC_LICENSE_KEY` and `NEW_RELIC_SERVICE_NAME` in project root `.env`, run `aio app logs` for errors, verify `ENABLE_TELEMETRY: true` in action inputs, confirm correct New Relic account/region.
|
|
290
|
+
|
|
291
|
+
**Common Errors**: Missing license key/service name in project root `.env`, invalid configuration, wrong data center (EU requires `NEW_RELIC_URL`).
|
|
292
|
+
|
|
293
|
+
## Additional Recommendations
|
|
294
|
+
|
|
295
|
+
**Dashboards & Monitoring**: Create custom dashboards for success/failure rates, execution duration, request volume, and error trends.
|
|
296
|
+
|
|
297
|
+
**Alerting**: Set up alerts for high error rates, slow execution, failed invocations, or missing telemetry data.
|
|
298
|
+
|
|
299
|
+
**Multiple Environments**: Use different `ENVIRONMENT` values (dev/staging/prod) in separate `.env.{env}` files in project root. Filter New Relic data by environment tag.
|
|
300
|
+
|
|
301
|
+
**CI/CD Integration**: Store `NEW_RELIC_LICENSE_KEY` in CI/CD secrets (GitHub Actions, etc.), set environment-specific values in deployment workflows.
|
|
302
|
+
|
|
303
|
+
**Custom OTLP Endpoint**: For EU data center, add `NEW_RELIC_URL=https://otlp.eu01.nr-data.net` to project root `.env` and reference as `NEW_RELIC_URL: $NEW_RELIC_URL` in action inputs. US uses default `https://otlp.nr-data.net`.
|
|
304
|
+
|
|
305
|
+
**Cost & Performance**: Telemetry adds ~1-5ms latency (asynchronous, non-blocking). Monitor data ingest volume, consider sampling for high-volume actions, review billing regularly.
|
|
306
|
+
|
|
307
|
+
## Key Features
|
|
308
|
+
|
|
309
|
+
**Automatic Instrumentation**: Captures traces (execution spans, timing, success/failure, HTTP codes), structured logs (`logger.info/error/warn`, JSON attributes, trace correlation), metrics (duration, invocation counts, error rates), and resource attributes (`service.name`, `environment`, `service.version`).
|
|
310
|
+
|
|
311
|
+
**Multi-Provider Support**: Priority order: New Relic → Grafana → None. Explicit configuration validation ensures failures when required config is missing (e.g., `NEW_RELIC_LICENSE_KEY`) rather than silent fallback.
|
|
312
|
+
|
|
313
|
+
## Important Notes
|
|
314
|
+
|
|
315
|
+
1. **No Code Changes**: Telemetry configured via action inputs only
|
|
316
|
+
2. **Auto-Export**: Toolkit handles OpenTelemetry setup, export, error handling
|
|
317
|
+
3. **Security**: Never commit license keys - use project root `.env` + `.gitignore`
|
|
318
|
+
4. **Environment Tags**: Set `ENVIRONMENT` in project root `.env` to filter by environment
|
|
319
|
+
5. **Service Names**: Use descriptive names for identification; same name for related actions or different names for separate services
|
|
320
|
+
6. **Data Center**: US (default), EU requires `NEW_RELIC_URL` in project root `.env`
|
|
321
|
+
7. **Providers**: Supports New Relic, Grafana; this rule focuses on New Relic
|
|
322
|
+
|
|
323
|
+
## Example Configuration
|
|
324
|
+
|
|
325
|
+
**Action inputs (app.config.yaml or ext.config.yaml)**:
|
|
326
|
+
|
|
327
|
+
```yaml
|
|
328
|
+
[action-name]:
|
|
329
|
+
inputs:
|
|
330
|
+
LOG_LEVEL: debug
|
|
331
|
+
ENVIRONMENT: $ENVIRONMENT
|
|
332
|
+
ENABLE_TELEMETRY: true
|
|
333
|
+
NEW_RELIC_TELEMETRY: true
|
|
334
|
+
NEW_RELIC_SERVICE_NAME: $NEW_RELIC_SERVICE_NAME
|
|
335
|
+
NEW_RELIC_LICENSE_KEY: $NEW_RELIC_LICENSE_KEY
|
|
336
|
+
NEW_RELIC_URL: $NEW_RELIC_URL # Optional: for EU data center
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Project root .env**:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
ENVIRONMENT=production # or dev, staging
|
|
343
|
+
NEW_RELIC_SERVICE_NAME=my-commerce-app
|
|
344
|
+
NEW_RELIC_LICENSE_KEY=NRAK-XXXXXXXXXXXXXXXXXXXXX
|
|
345
|
+
NEW_RELIC_URL=https://otlp.eu01.nr-data.net # Optional: EU only
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Reference Links
|
|
349
|
+
|
|
350
|
+
- [New Relic Account Setup](https://one.newrelic.com/)
|
|
351
|
+
- [New Relic API Keys](https://one.newrelic.com/api-keys)
|
|
352
|
+
- [New Relic OTLP Endpoint](https://docs.newrelic.com/docs/more-integrations/open-source-telemetry-integrations/opentelemetry/opentelemetry-setup/)
|
|
353
|
+
- [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
|
|
354
|
+
- [@adobe-commerce/aio-toolkit NPM](https://www.npmjs.com/package/@adobe-commerce/aio-toolkit)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe-commerce/aio-toolkit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "A comprehensive TypeScript toolkit for Adobe App Builder applications providing standardized Adobe Commerce integrations, I/O Events orchestration, file storage utilities, authentication helpers, and robust backend development tools with 100% test coverage.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -14,10 +14,14 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist/**/*",
|
|
17
|
+
"files/**/*",
|
|
17
18
|
"README.md",
|
|
18
19
|
"CHANGELOG.md",
|
|
19
20
|
"LICENSE"
|
|
20
21
|
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"aio-toolkit-cursor-context": "dist/aio-toolkit-cursor-context/bin/cli.js"
|
|
24
|
+
},
|
|
21
25
|
"scripts": {
|
|
22
26
|
"build": "tsup",
|
|
23
27
|
"build:watch": "tsup --watch",
|
|
@@ -93,6 +97,7 @@
|
|
|
93
97
|
"typescript": "^5.9.2"
|
|
94
98
|
},
|
|
95
99
|
"peerDependencies": {
|
|
100
|
+
"@adobe-commerce/commerce-extensibility-tools": "^1.6.0",
|
|
96
101
|
"typescript": ">=4.9.0"
|
|
97
102
|
},
|
|
98
103
|
"engines": {
|