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