@duckbug/js 0.1.3 → 1.0.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/README.md +348 -73
- package/dist/cjs/DuckBug/DuckBugHelper.cjs +136 -0
- package/dist/cjs/DuckBug/DuckBugProvider.cjs +82 -13
- package/dist/cjs/DuckBug/DuckBugService.cjs +158 -17
- package/dist/cjs/DuckBug/Pond.cjs +44 -0
- package/dist/cjs/DuckBug/ensureEventId.cjs +47 -0
- package/dist/cjs/DuckBug/finalizeIngestEvent.cjs +54 -0
- package/dist/cjs/DuckBug/index.cjs +18 -2
- package/dist/cjs/DuckBug/parseDuckBugDsn.cjs +82 -0
- package/dist/cjs/DuckBug/sanitizeIngestPayload.cjs +67 -0
- package/dist/cjs/DuckBug/stripIngestSections.cjs +43 -0
- package/dist/cjs/DuckBug/transportTypes.cjs +18 -0
- package/dist/cjs/SDK/DuckSDK.cjs +129 -12
- package/dist/cjs/SDK/index.cjs +5 -2
- package/dist/cjs/contract/index.cjs +18 -0
- package/dist/cjs/contract/ingestEvents.cjs +18 -0
- package/dist/cjs/index.cjs +24 -6
- package/dist/cjs/integrations/node.cjs +54 -0
- package/dist/cjs/sdkIdentity.cjs +47 -0
- package/dist/esm/DuckBug/DuckBugHelper.js +99 -0
- package/dist/esm/DuckBug/DuckBugProvider.js +82 -13
- package/dist/esm/DuckBug/DuckBugService.js +156 -15
- package/dist/esm/DuckBug/Pond.js +10 -0
- package/dist/esm/DuckBug/ensureEventId.js +13 -0
- package/dist/esm/DuckBug/finalizeIngestEvent.js +20 -0
- package/dist/esm/DuckBug/index.js +5 -1
- package/dist/esm/DuckBug/parseDuckBugDsn.js +36 -0
- package/dist/esm/DuckBug/sanitizeIngestPayload.js +33 -0
- package/dist/esm/DuckBug/stripIngestSections.js +9 -0
- package/dist/esm/DuckBug/transportTypes.js +0 -0
- package/dist/esm/SDK/DuckSDK.js +125 -11
- package/dist/esm/SDK/index.js +2 -2
- package/dist/esm/contract/index.js +0 -0
- package/dist/esm/contract/ingestEvents.js +0 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/integrations/node.js +20 -0
- package/dist/esm/sdkIdentity.js +7 -0
- package/dist/types/DuckBug/DuckBugConfig.d.ts +28 -0
- package/dist/types/DuckBug/DuckBugHelper.d.ts +22 -0
- package/dist/types/DuckBug/DuckBugProvider.d.ts +12 -1
- package/dist/types/DuckBug/DuckBugService.d.ts +30 -8
- package/dist/types/DuckBug/Log.d.ts +1 -7
- package/dist/types/DuckBug/Pond.d.ts +9 -0
- package/dist/types/DuckBug/ensureEventId.d.ts +4 -0
- package/dist/types/DuckBug/finalizeIngestEvent.d.ts +11 -0
- package/dist/types/DuckBug/index.d.ts +7 -1
- package/dist/types/DuckBug/parseDuckBugDsn.d.ts +17 -0
- package/dist/types/DuckBug/sanitizeIngestPayload.d.ts +4 -0
- package/dist/types/DuckBug/stripIngestSections.d.ts +4 -0
- package/dist/types/DuckBug/transportTypes.d.ts +7 -0
- package/dist/types/SDK/DuckSDK.d.ts +30 -3
- package/dist/types/SDK/Provider.d.ts +11 -3
- package/dist/types/SDK/index.d.ts +3 -2
- package/dist/types/contract/index.d.ts +1 -0
- package/dist/types/contract/ingestEvents.d.ts +58 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/integrations/node.d.ts +12 -0
- package/dist/types/sdkIdentity.d.ts +7 -0
- package/package.json +23 -7
- package/schemas/error-event.schema.json +147 -0
- package/schemas/log-event.schema.json +126 -0
package/README.md
CHANGED
|
@@ -31,76 +31,234 @@ pnpm add @duckbug/js
|
|
|
31
31
|
|
|
32
32
|
### Basic Usage
|
|
33
33
|
|
|
34
|
+
DSN must follow the ingest URL shape from the DuckBug SDK spec (`duckbug-sdk-spec`): `{origin}/ingest/{projectId}:{publicKey}` or `{origin}/api/ingest/{projectId}:{publicKey}` (for example on `duckbug.io`).
|
|
35
|
+
|
|
34
36
|
```typescript
|
|
35
|
-
import {
|
|
37
|
+
import { Duck, DuckBugProvider, Pond } from '@duckbug/js';
|
|
38
|
+
|
|
39
|
+
const dsn = 'https://api.duckbug.io/ingest/your-project-id:your-public-key';
|
|
40
|
+
const { extraSensitiveKeys } = Pond.ripple(['custom_secret']);
|
|
41
|
+
|
|
42
|
+
const duck = new Duck(
|
|
43
|
+
[new DuckBugProvider({ dsn, extraSensitiveKeys })],
|
|
44
|
+
{
|
|
45
|
+
logReports: {
|
|
46
|
+
log: false,
|
|
47
|
+
warn: true,
|
|
48
|
+
error: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Branded + idiomatic error capture
|
|
54
|
+
const err = new Error('Something failed');
|
|
55
|
+
duck.quack('checkout_failed', err);
|
|
56
|
+
duck.captureException(err, 'checkout_failed');
|
|
57
|
+
|
|
58
|
+
// Logging (levels normalized to DEBUG, INFO, WARN, ERROR, FATAL)
|
|
59
|
+
duck.warn('Slow query', { ms: 1200 });
|
|
60
|
+
```
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
const providers = [
|
|
39
|
-
new DuckBugProvider({
|
|
40
|
-
dsn: 'your-duckbug-dsn-here'
|
|
41
|
-
})
|
|
42
|
-
];
|
|
62
|
+
`DuckSDK` remains available as an alias of the same runtime; prefer `Duck` for new code.
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
Before process exit, await `duck.flush()` so queued HTTP work finishes (for example at the end of an `async main()`).
|
|
65
|
+
|
|
66
|
+
## Full usage example
|
|
67
|
+
|
|
68
|
+
End-to-end pattern for a Node/Bun service: DSN from env, scope (release, user, fingerprint), privacy pipeline, `beforeSend`, batched transport with retries, transport errors, console forwarding, global error handlers, structured logs, manual errors, and clean shutdown.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import {
|
|
72
|
+
Duck,
|
|
73
|
+
DuckBugProvider,
|
|
74
|
+
Pond,
|
|
75
|
+
registerNodeGlobalErrorHandlers,
|
|
76
|
+
} from "@duckbug/js";
|
|
77
|
+
|
|
78
|
+
const dsn = process.env.DUCKBUG_DSN;
|
|
79
|
+
if (!dsn) {
|
|
80
|
+
throw new Error("Set DUCKBUG_DSN to your ingest URL");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { extraSensitiveKeys } = Pond.ripple([
|
|
84
|
+
"custom_secret",
|
|
85
|
+
"internalToken",
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Transport + ingest errors. Privacy (strip / sanitize / eventId / beforeSend) is applied in `Duck` when you use the core client.
|
|
89
|
+
const duckBug = new DuckBugProvider({
|
|
90
|
+
dsn,
|
|
91
|
+
transport: {
|
|
92
|
+
maxBatchSize: 25,
|
|
93
|
+
maxRetries: 2,
|
|
94
|
+
retryDelayMs: 200,
|
|
95
|
+
},
|
|
96
|
+
onTransportError: (info) => {
|
|
97
|
+
console.error("[duckbug transport]", info.message, info.kind, info.itemCount);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const duck = new Duck(
|
|
102
|
+
[duckBug],
|
|
103
|
+
{
|
|
104
|
+
logReports: {
|
|
105
|
+
log: true,
|
|
106
|
+
warn: true,
|
|
107
|
+
error: true,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
extraSensitiveKeys,
|
|
112
|
+
// Omit whole sections before sanitize (see StrippableIngestSection in types)
|
|
113
|
+
stripSections: ["cookies", "headers"],
|
|
114
|
+
beforeSend: async (arg) => {
|
|
115
|
+
// Drop PII-heavy events in dev, or tweak payload
|
|
116
|
+
if (process.env.NODE_ENV === "test") {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (arg.kind === "log" && arg.event.message.includes("healthcheck")) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return arg.event;
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Applied to every subsequent log / error (merge into event)
|
|
128
|
+
duck.setScope({
|
|
129
|
+
release: "my-app@1.4.2",
|
|
130
|
+
environment: process.env.NODE_ENV ?? "development",
|
|
131
|
+
service: "checkout-api",
|
|
132
|
+
fingerprint: "checkout-api-default",
|
|
133
|
+
// Prefer nesting session-like data under context; top-level `session` may be rejected by ingest.
|
|
134
|
+
context: { deployment: "eu-west" },
|
|
51
135
|
});
|
|
52
136
|
|
|
53
|
-
|
|
54
|
-
duck
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
duck.fatal('Fatal message', { error: 'Ay, caramba' });
|
|
137
|
+
const unregisterGlobals = registerNodeGlobalErrorHandlers({
|
|
138
|
+
duck,
|
|
139
|
+
rejectionTag: "unhandledRejection",
|
|
140
|
+
exceptionTag: "uncaughtException",
|
|
141
|
+
});
|
|
59
142
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
testError.stack =
|
|
63
|
-
"Error: Integration test error\n at integration.test.ts:1:1";
|
|
143
|
+
async function main() {
|
|
144
|
+
duck.debug("boot", { pid: process.pid });
|
|
64
145
|
|
|
65
|
-
|
|
66
|
-
|
|
146
|
+
try {
|
|
147
|
+
await doCheckout();
|
|
148
|
+
} catch (e) {
|
|
149
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
150
|
+
duck.quack("checkout_failed", err);
|
|
151
|
+
duck.captureException(err, "checkout_failed");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
duck.warn("slow_query", { table: "orders", ms: 850 });
|
|
155
|
+
duck.error("inventory_low", { sku: "SKU-12", qty: 2 });
|
|
156
|
+
duck.fatal("migration_required", { from: "v10", to: "v11" });
|
|
157
|
+
|
|
158
|
+
await duck.flush();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function doCheckout() {
|
|
162
|
+
// ...
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
main()
|
|
166
|
+
.catch((e) => {
|
|
167
|
+
console.error(e);
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
})
|
|
170
|
+
.finally(async () => {
|
|
171
|
+
unregisterGlobals();
|
|
172
|
+
await duck.flush();
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Direct provider (no `Duck`)** — same privacy defaults via `finalizeIngestEvent` inside the provider; you call `sendLog` / `sendError` with `DuckBugLogEvent` / `DuckBugErrorEvent` shapes:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { DuckBugProvider, logLevel } from "@duckbug/js";
|
|
180
|
+
|
|
181
|
+
const provider = new DuckBugProvider({ dsn });
|
|
182
|
+
provider.sendLog({
|
|
183
|
+
time: Date.now(),
|
|
184
|
+
level: logLevel.INFO,
|
|
185
|
+
message: "Job finished",
|
|
186
|
+
platform: "node",
|
|
187
|
+
dTags: ["worker", "nightly"],
|
|
188
|
+
});
|
|
189
|
+
await provider.flush();
|
|
67
190
|
```
|
|
68
191
|
|
|
69
192
|
## API Reference
|
|
70
193
|
|
|
71
|
-
### DuckSDK
|
|
194
|
+
### Duck / DuckSDK
|
|
72
195
|
|
|
73
|
-
The main SDK class
|
|
196
|
+
The main SDK class fans out canonical log and error events to all registered providers.
|
|
74
197
|
|
|
75
198
|
#### Constructor
|
|
76
199
|
|
|
77
200
|
```typescript
|
|
78
|
-
new
|
|
201
|
+
new Duck(
|
|
202
|
+
providers: Provider[],
|
|
203
|
+
logProviderConfig?: LogProviderConfig,
|
|
204
|
+
options?: DuckSDKOptions,
|
|
205
|
+
)
|
|
79
206
|
```
|
|
80
207
|
|
|
81
208
|
- `providers`: Array of provider instances
|
|
82
|
-
- `
|
|
209
|
+
- `logProviderConfig`: Optional configuration for console interception (`LogProvider`)
|
|
210
|
+
- `options`: Optional `beforeSend`, `stripSections`, `extraSensitiveKeys` (strip → sanitize → `eventId` → `beforeSend` → providers; matches `duckbug-sdk-spec`)
|
|
83
211
|
|
|
84
212
|
#### Methods
|
|
85
213
|
|
|
86
|
-
- `log(tag
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
91
|
-
|
|
214
|
+
- `log` / `debug` / `warn` / `error` / `fatal(tag, payload?)`: structured logs
|
|
215
|
+
- `quack(tag, error)`: branded manual error capture; tag is sent as `dTags`, message comes from `error.message`
|
|
216
|
+
- `captureException(error, tag?)`: idiomatic alias for `quack` (default tag `error`)
|
|
217
|
+
- `setScope(partial)`: merge shared metadata into subsequent events
|
|
218
|
+
- `flush()`: await transport drains on providers that implement `flush` (for example `DuckBugProvider`)
|
|
219
|
+
|
|
220
|
+
Each captured log/error gets a UUID `eventId` when omitted (idempotency / retries).
|
|
92
221
|
|
|
93
222
|
### DuckBugProvider
|
|
94
223
|
|
|
95
|
-
|
|
224
|
+
First-party provider: posts JSON to single-event ingest by default, or to `/logs/batch` and `/errors/batch` when `transport.maxBatchSize > 1` (body is a JSON array, as required by the DuckBug API).
|
|
96
225
|
|
|
97
226
|
#### Constructor
|
|
98
227
|
|
|
99
228
|
```typescript
|
|
100
|
-
new DuckBugProvider(
|
|
229
|
+
new DuckBugProvider({
|
|
230
|
+
dsn: string,
|
|
231
|
+
extraSensitiveKeys?: string[],
|
|
232
|
+
stripSections?: StrippableIngestSection[],
|
|
233
|
+
beforeSend?: (arg) => event | null | undefined | Promise<...>,
|
|
234
|
+
transport?: {
|
|
235
|
+
maxBatchSize?: number; // default 1 — one POST per event
|
|
236
|
+
maxRetries?: number;
|
|
237
|
+
retryDelayMs?: number;
|
|
238
|
+
fetchImpl?: typeof fetch;
|
|
239
|
+
},
|
|
240
|
+
onTransportError?: (info: TransportFailureInfo) => void,
|
|
241
|
+
})
|
|
242
|
+
// or
|
|
243
|
+
DuckBugProvider.fromDSN(dsn)
|
|
101
244
|
```
|
|
102
245
|
|
|
103
|
-
- `config.dsn`:
|
|
246
|
+
- `config.dsn`: full ingest URL, e.g. `https://api.duckbug.io/ingest/myProject:myKey`
|
|
247
|
+
- `flush()`: returns a `Promise` that resolves when queued requests for this provider have been sent
|
|
248
|
+
|
|
249
|
+
### Privacy, batching, and Node hooks
|
|
250
|
+
|
|
251
|
+
- **Strip sections**: omit whole request fields (`headers`, `cookies`, `session`, …) before sanitize via `stripSections` on `DuckBugProvider` or `DuckSDK` options.
|
|
252
|
+
- **`beforeSend`**: on `Duck` / `DuckSDK` for all providers; on `DuckBugProvider` when using the provider without the core client. Return `null` to drop an event.
|
|
253
|
+
- **Node global errors** (optional, no core framework deps):
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { Duck, DuckBugProvider, registerNodeGlobalErrorHandlers } from '@duckbug/js';
|
|
257
|
+
|
|
258
|
+
const duck = new Duck([DuckBugProvider.fromDSN(dsn)]);
|
|
259
|
+
const unregister = registerNodeGlobalErrorHandlers({ duck });
|
|
260
|
+
// ... on shutdown: unregister();
|
|
261
|
+
```
|
|
104
262
|
|
|
105
263
|
### Log Provider Configuration
|
|
106
264
|
|
|
@@ -116,62 +274,54 @@ type LogProviderConfig = {
|
|
|
116
274
|
|
|
117
275
|
## Custom Providers
|
|
118
276
|
|
|
119
|
-
|
|
277
|
+
Implement `Provider`: handle canonical `DuckBugLogEvent` / `DuckBugErrorEvent` from `sendLog` / `sendError` (optional second argument `SendEventMeta` when events are already finalized in `DuckSDK`), and optional console-style methods for `LogProvider` hooks.
|
|
120
278
|
|
|
121
279
|
```typescript
|
|
122
|
-
import {
|
|
280
|
+
import type {
|
|
281
|
+
DuckBugErrorEvent,
|
|
282
|
+
DuckBugLogEvent,
|
|
283
|
+
Provider,
|
|
284
|
+
} from '@duckbug/js';
|
|
123
285
|
|
|
124
286
|
class TelegramProvider implements Provider {
|
|
125
287
|
constructor(private botToken: string, private chatId: string) {}
|
|
126
288
|
|
|
127
|
-
|
|
128
|
-
this.sendToTelegram('📝',
|
|
289
|
+
sendLog(event: DuckBugLogEvent): void {
|
|
290
|
+
this.sendToTelegram('📝', `${event.level} ${event.message}`);
|
|
129
291
|
}
|
|
130
292
|
|
|
131
|
-
|
|
132
|
-
this.sendToTelegram('
|
|
293
|
+
sendError(event: DuckBugErrorEvent): void {
|
|
294
|
+
this.sendToTelegram('💀', event.message);
|
|
133
295
|
}
|
|
134
296
|
|
|
135
|
-
|
|
136
|
-
this.sendToTelegram('
|
|
297
|
+
log(...args: unknown[]): void {
|
|
298
|
+
this.sendToTelegram('📝', String(args[0]));
|
|
137
299
|
}
|
|
138
300
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
INFO: '📝',
|
|
142
|
-
DEBUG: '🦆',
|
|
143
|
-
WARN: '⚠️',
|
|
144
|
-
ERROR: '🚨',
|
|
145
|
-
FATAL: '💀',
|
|
146
|
-
};
|
|
147
|
-
this.sendToTelegram(emojiMap[level], [tag, payload]);
|
|
301
|
+
warn(...args: unknown[]): void {
|
|
302
|
+
this.sendToTelegram('⚠️', String(args[0]));
|
|
148
303
|
}
|
|
149
304
|
|
|
150
|
-
|
|
151
|
-
this.sendToTelegram('
|
|
305
|
+
error(...args: unknown[]): void {
|
|
306
|
+
this.sendToTelegram('🚨', String(args[0]));
|
|
152
307
|
}
|
|
153
308
|
|
|
154
|
-
private sendToTelegram(emoji: string,
|
|
155
|
-
const message = `${emoji} ${
|
|
156
|
-
// Implementation to send message to Telegram
|
|
309
|
+
private sendToTelegram(emoji: string, text: string) {
|
|
310
|
+
const message = `${emoji} ${text}`;
|
|
157
311
|
fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
158
312
|
method: 'POST',
|
|
159
313
|
headers: { 'Content-Type': 'application/json' },
|
|
160
|
-
body: JSON.stringify({
|
|
161
|
-
chat_id: this.chatId,
|
|
162
|
-
text: message
|
|
163
|
-
})
|
|
314
|
+
body: JSON.stringify({ chat_id: this.chatId, text: message }),
|
|
164
315
|
});
|
|
165
316
|
}
|
|
166
317
|
}
|
|
167
318
|
|
|
168
|
-
// Usage
|
|
169
319
|
const providers = [
|
|
170
|
-
|
|
171
|
-
new TelegramProvider('your-bot-token', 'your-chat-id')
|
|
320
|
+
DuckBugProvider.fromDSN('https://api.duckbug.io/ingest/project:key'),
|
|
321
|
+
new TelegramProvider('your-bot-token', 'your-chat-id'),
|
|
172
322
|
];
|
|
173
323
|
|
|
174
|
-
const duck = new
|
|
324
|
+
const duck = new Duck(providers);
|
|
175
325
|
```
|
|
176
326
|
|
|
177
327
|
## Development
|
|
@@ -181,7 +331,7 @@ const duck = new DuckSDK(providers);
|
|
|
181
331
|
Install dependencies:
|
|
182
332
|
|
|
183
333
|
```bash
|
|
184
|
-
|
|
334
|
+
bun install
|
|
185
335
|
```
|
|
186
336
|
|
|
187
337
|
### Build
|
|
@@ -189,7 +339,7 @@ yarn install
|
|
|
189
339
|
Build the library:
|
|
190
340
|
|
|
191
341
|
```bash
|
|
192
|
-
|
|
342
|
+
bun run build
|
|
193
343
|
```
|
|
194
344
|
|
|
195
345
|
### Linting
|
|
@@ -197,15 +347,140 @@ yarn build
|
|
|
197
347
|
Run linting:
|
|
198
348
|
|
|
199
349
|
```bash
|
|
200
|
-
|
|
350
|
+
bun run lint
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Commit Messages
|
|
354
|
+
|
|
355
|
+
Этот проект использует [Conventional Commits](https://www.conventionalcommits.org/) для стандартизации сообщений коммитов. Все коммиты должны соответствовать следующему формату:
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
<type>(<scope>): <subject>
|
|
359
|
+
|
|
360
|
+
<body>
|
|
361
|
+
|
|
362
|
+
<footer>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Типы коммитов (обязательные)
|
|
366
|
+
|
|
367
|
+
- `feat`: Новая функциональность
|
|
368
|
+
- `fix`: Исправление бага
|
|
369
|
+
- `docs`: Изменения в документации
|
|
370
|
+
- `style`: Форматирование кода (не влияет на выполнение кода)
|
|
371
|
+
- `refactor`: Рефакторинг кода
|
|
372
|
+
- `perf`: Улучшение производительности
|
|
373
|
+
- `test`: Добавление или изменение тестов
|
|
374
|
+
- `build`: Изменения в системе сборки или внешних зависимостях
|
|
375
|
+
- `ci`: Изменения в CI конфигурации
|
|
376
|
+
- `chore`: Обновление задач сборки, настроек и т.д.
|
|
377
|
+
- `revert`: Откат предыдущего коммита
|
|
378
|
+
|
|
379
|
+
#### Примеры корректных коммитов
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
feat: добавить поддержку логирования ошибок
|
|
383
|
+
fix: исправить утечку памяти в DuckBugProvider
|
|
384
|
+
docs: обновить README с примерами использования
|
|
385
|
+
test: добавить тесты для DuckSDK
|
|
386
|
+
refactor: улучшить структуру классов провайдеров
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### Проверка коммитов
|
|
390
|
+
|
|
391
|
+
Автоматическая проверка формата коммитов выполняется через git hook. При создании коммита с неправильным форматом вы получите подробное сообщение об ошибке с описанием проблемы и примерами правильного формата.
|
|
392
|
+
|
|
393
|
+
**Примеры ошибок:**
|
|
394
|
+
|
|
395
|
+
❌ Если забыли указать тип:
|
|
201
396
|
```
|
|
397
|
+
❌ Тип коммита обязателен!
|
|
398
|
+
📝 Формат коммита: <type>: <описание>
|
|
399
|
+
💡 Примеры:
|
|
400
|
+
feat: добавить новую функцию
|
|
401
|
+
fix: исправить обработку ошибок
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
❌ Если использовали неправильный тип:
|
|
405
|
+
```
|
|
406
|
+
❌ Неверный тип коммита!
|
|
407
|
+
✅ Используйте один из допустимых типов:
|
|
408
|
+
- feat: новая функциональность
|
|
409
|
+
- fix: исправление бага
|
|
410
|
+
...
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Для ручной проверки сообщения коммита:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
bun run commitlint -- --from HEAD~1 --to HEAD
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Автоматические релизы
|
|
420
|
+
|
|
421
|
+
Этот проект использует [semantic-release](https://github.com/semantic-release/semantic-release) для автоматического управления версиями и релизами.
|
|
422
|
+
|
|
423
|
+
#### Как это работает:
|
|
424
|
+
|
|
425
|
+
- **Версионирование**: Версия автоматически обновляется на основе типов коммитов:
|
|
426
|
+
- `feat:` → минорное обновление (1.0.0 → 1.1.0)
|
|
427
|
+
- `fix:` → патч (1.0.0 → 1.0.1)
|
|
428
|
+
- `BREAKING CHANGE` или `feat!:` → мажорное обновление (1.0.0 → 2.0.0)
|
|
429
|
+
- `chore:`, `docs:`, `style:` и другие → без релиза
|
|
430
|
+
|
|
431
|
+
- **Автоматические действия при пуше в `main`**:
|
|
432
|
+
1. Анализ коммитов с последнего релиза
|
|
433
|
+
2. Определение новой версии
|
|
434
|
+
3. Генерация CHANGELOG.md
|
|
435
|
+
4. Обновление версии в package.json
|
|
436
|
+
5. Создание git тега
|
|
437
|
+
6. Публикация в npm
|
|
438
|
+
7. Создание GitHub Release с заметками
|
|
439
|
+
|
|
440
|
+
#### Настройка:
|
|
441
|
+
|
|
442
|
+
1. **Создайте NPM токен** (только для публикации):
|
|
443
|
+
- Перейдите на https://www.npmjs.com/settings/YOUR_USERNAME/tokens
|
|
444
|
+
- Создайте токен с правами `Automation`
|
|
445
|
+
- Добавьте его в GitHub Secrets как `NPM_TOKEN`
|
|
446
|
+
|
|
447
|
+
2. **GitHub Actions**:
|
|
448
|
+
- Workflow `release.yaml` автоматически запускается при пуше в `main` или `beta`
|
|
449
|
+
- Использует `GITHUB_TOKEN` (автоматически предоставляется GitHub Actions)
|
|
450
|
+
- Использует `NPM_TOKEN` из секретов для публикации в npm
|
|
451
|
+
|
|
452
|
+
#### Примеры коммитов для релизов:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
# Патч релиз (1.0.0 → 1.0.1)
|
|
456
|
+
fix: исправить обработку ошибок в DuckBugProvider
|
|
457
|
+
|
|
458
|
+
# Минорный релиз (1.0.0 → 1.1.0)
|
|
459
|
+
feat: добавить поддержку фильтрации логов
|
|
460
|
+
|
|
461
|
+
# Мажорный релиз (1.0.0 → 2.0.0)
|
|
462
|
+
feat!: изменить API провайдеров
|
|
463
|
+
|
|
464
|
+
# или
|
|
465
|
+
|
|
466
|
+
feat: добавить новую функцию
|
|
467
|
+
|
|
468
|
+
BREAKING CHANGE: изменена структура конфигурации DuckBugProvider
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Примечание**: Коммиты без типа или с типом `chore`, `docs`, `style` не создают новый релиз, но могут быть включены в CHANGELOG.
|
|
202
472
|
|
|
203
473
|
## TypeScript Support
|
|
204
474
|
|
|
205
475
|
This package includes TypeScript definitions. All exports are fully typed:
|
|
206
476
|
|
|
207
477
|
```typescript
|
|
208
|
-
import type {
|
|
478
|
+
import type {
|
|
479
|
+
Provider,
|
|
480
|
+
DuckBugConfig,
|
|
481
|
+
DuckBugLogEvent,
|
|
482
|
+
LogLevel,
|
|
483
|
+
} from "@duckbug/js";
|
|
209
484
|
```
|
|
210
485
|
|
|
211
486
|
## Browser Compatibility
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
processError: ()=>processError,
|
|
28
|
+
parseError: ()=>parseError
|
|
29
|
+
});
|
|
30
|
+
const external_sdkIdentity_cjs_namespaceObject = require("../sdkIdentity.cjs");
|
|
31
|
+
function tryJsonObjectFromMessage(message) {
|
|
32
|
+
const trimmed = message.trim();
|
|
33
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
34
|
+
try {
|
|
35
|
+
const v = JSON.parse(message);
|
|
36
|
+
if (null !== v && "object" == typeof v) return v;
|
|
37
|
+
} catch {}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function parseStacktrace(stack) {
|
|
41
|
+
let file = "unknown";
|
|
42
|
+
let line = 0;
|
|
43
|
+
let stacktrace;
|
|
44
|
+
if (stack) {
|
|
45
|
+
const stackLines = stack.split("\n");
|
|
46
|
+
let firstStackLineWithFile = null;
|
|
47
|
+
for(let i = 1; i < stackLines.length; i++){
|
|
48
|
+
const lineStr = stackLines[i];
|
|
49
|
+
if (-1 !== lineStr.indexOf("at ") && (-1 !== lineStr.indexOf(":") || -1 !== lineStr.indexOf("("))) {
|
|
50
|
+
firstStackLineWithFile = lineStr;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (firstStackLineWithFile) {
|
|
55
|
+
const match = firstStackLineWithFile.match(/\(([^)]+):(\d+):(\d+)\)/) || firstStackLineWithFile.match(/at\s+.*?\(([^)]+):(\d+):(\d+)\)/) || firstStackLineWithFile.match(/([^:()\s]+):(\d+):(\d+)/);
|
|
56
|
+
if (match?.[1]) {
|
|
57
|
+
file = match[1].replace(/^file:\/\//, "").replace(/^\/+/, "");
|
|
58
|
+
line = parseInt(match[2] || "0", 10);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
stacktrace = {
|
|
62
|
+
raw: stack,
|
|
63
|
+
frames: stackLines.filter((lineStr)=>lineStr.trim()).map((lineStr, index)=>({
|
|
64
|
+
index,
|
|
65
|
+
content: lineStr.trim()
|
|
66
|
+
}))
|
|
67
|
+
};
|
|
68
|
+
} else {
|
|
69
|
+
stacktrace = {
|
|
70
|
+
raw: "",
|
|
71
|
+
frames: []
|
|
72
|
+
};
|
|
73
|
+
file = "unknown";
|
|
74
|
+
line = 0;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
file,
|
|
78
|
+
line,
|
|
79
|
+
stacktrace
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function parseContext(contextStr) {
|
|
83
|
+
if (!contextStr) return null;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(contextStr);
|
|
86
|
+
} catch {
|
|
87
|
+
return {
|
|
88
|
+
message: contextStr
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function parseError(error) {
|
|
93
|
+
const { file, line, stacktrace } = parseStacktrace(error.stack);
|
|
94
|
+
const context = parseContext(error.message);
|
|
95
|
+
return {
|
|
96
|
+
file,
|
|
97
|
+
line,
|
|
98
|
+
stacktrace,
|
|
99
|
+
context
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function processError(error, tag, time) {
|
|
103
|
+
const parsed = parseError(error);
|
|
104
|
+
const message = "string" == typeof error.message && error.message.length > 0 ? error.message : "Error";
|
|
105
|
+
const event = {
|
|
106
|
+
time,
|
|
107
|
+
message,
|
|
108
|
+
stacktrace: parsed.stacktrace,
|
|
109
|
+
stacktraceAsString: error.stack ?? "",
|
|
110
|
+
file: parsed.file,
|
|
111
|
+
line: parsed.line,
|
|
112
|
+
exception: {
|
|
113
|
+
type: error.name,
|
|
114
|
+
message: error.message
|
|
115
|
+
},
|
|
116
|
+
platform: "node",
|
|
117
|
+
sdk: {
|
|
118
|
+
...external_sdkIdentity_cjs_namespaceObject.SDK_IDENTITY
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
if (tag.length > 0) event.dTags = [
|
|
122
|
+
tag
|
|
123
|
+
];
|
|
124
|
+
const fromJson = tryJsonObjectFromMessage(error.message);
|
|
125
|
+
if (fromJson) event.extra = fromJson;
|
|
126
|
+
return event;
|
|
127
|
+
}
|
|
128
|
+
exports.parseError = __webpack_exports__.parseError;
|
|
129
|
+
exports.processError = __webpack_exports__.processError;
|
|
130
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
131
|
+
"parseError",
|
|
132
|
+
"processError"
|
|
133
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
134
|
+
Object.defineProperty(exports, '__esModule', {
|
|
135
|
+
value: true
|
|
136
|
+
});
|