@shakenbake/core 0.0.1

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 (49) hide show
  1. package/dist/__tests__/errors.test.d.ts +2 -0
  2. package/dist/__tests__/errors.test.d.ts.map +1 -0
  3. package/dist/__tests__/errors.test.js +82 -0
  4. package/dist/__tests__/errors.test.js.map +1 -0
  5. package/dist/__tests__/mock-adapter.test.d.ts +2 -0
  6. package/dist/__tests__/mock-adapter.test.d.ts.map +1 -0
  7. package/dist/__tests__/mock-adapter.test.js +184 -0
  8. package/dist/__tests__/mock-adapter.test.js.map +1 -0
  9. package/dist/__tests__/plugin-registry.test.d.ts +2 -0
  10. package/dist/__tests__/plugin-registry.test.d.ts.map +1 -0
  11. package/dist/__tests__/plugin-registry.test.js +209 -0
  12. package/dist/__tests__/plugin-registry.test.js.map +1 -0
  13. package/dist/__tests__/redact.test.d.ts +2 -0
  14. package/dist/__tests__/redact.test.d.ts.map +1 -0
  15. package/dist/__tests__/redact.test.js +58 -0
  16. package/dist/__tests__/redact.test.js.map +1 -0
  17. package/dist/__tests__/report-builder.test.d.ts +2 -0
  18. package/dist/__tests__/report-builder.test.d.ts.map +1 -0
  19. package/dist/__tests__/report-builder.test.js +396 -0
  20. package/dist/__tests__/report-builder.test.js.map +1 -0
  21. package/dist/errors.d.ts +23 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +49 -0
  24. package/dist/errors.js.map +1 -0
  25. package/dist/index.d.ts +9 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +23 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/mock-adapter.d.ts +46 -0
  30. package/dist/mock-adapter.d.ts.map +1 -0
  31. package/dist/mock-adapter.js +95 -0
  32. package/dist/mock-adapter.js.map +1 -0
  33. package/dist/plugin-registry.d.ts +36 -0
  34. package/dist/plugin-registry.d.ts.map +1 -0
  35. package/dist/plugin-registry.js +116 -0
  36. package/dist/plugin-registry.js.map +1 -0
  37. package/dist/redact.d.ts +13 -0
  38. package/dist/redact.d.ts.map +1 -0
  39. package/dist/redact.js +45 -0
  40. package/dist/redact.js.map +1 -0
  41. package/dist/report-builder.d.ts +37 -0
  42. package/dist/report-builder.d.ts.map +1 -0
  43. package/dist/report-builder.js +147 -0
  44. package/dist/report-builder.js.map +1 -0
  45. package/dist/types.d.ts +223 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +6 -0
  48. package/dist/types.js.map +1 -0
  49. package/package.json +27 -0
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // @shakenbake/core — MockAdapter (testing/development DestinationAdapter)
4
+ // ---------------------------------------------------------------------------
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MockAdapter = void 0;
7
+ /**
8
+ * A no-op DestinationAdapter that stores submitted reports in memory.
9
+ *
10
+ * Useful for:
11
+ * - Unit and integration tests (assert on submitted reports)
12
+ * - Local development without a real Linear API key
13
+ * - Demo / example apps
14
+ */
15
+ class MockAdapter {
16
+ name = 'mock';
17
+ config;
18
+ submittedReports = [];
19
+ constructor(config) {
20
+ this.config = config ?? {};
21
+ }
22
+ /**
23
+ * Simulates uploading an image. Logs the filename and size, then returns
24
+ * a fake URL on mock.shakenbake.dev.
25
+ */
26
+ async uploadImage(imageData, filename) {
27
+ const size = imageData instanceof Blob ? imageData.size : imageData.byteLength;
28
+ console.log(`[MockAdapter] uploadImage: ${filename} (${size} bytes)`);
29
+ await this.simulateDelay();
30
+ const id = generateUUID();
31
+ return `https://mock.shakenbake.dev/images/${id}.png`;
32
+ }
33
+ /**
34
+ * Simulates creating an issue. Stores the report in memory and logs a
35
+ * summary to the console.
36
+ */
37
+ async createIssue(report) {
38
+ this.submittedReports.push(report);
39
+ console.log(`[MockAdapter] createIssue: "${report.title}" (${report.severity}/${report.category})`);
40
+ await this.simulateDelay();
41
+ const id = generateUUID();
42
+ return {
43
+ url: `https://mock.shakenbake.dev/issues/${id}`,
44
+ id,
45
+ success: true,
46
+ };
47
+ }
48
+ /**
49
+ * Always resolves to `true` -- the mock connection is always healthy.
50
+ */
51
+ async testConnection() {
52
+ await this.simulateDelay();
53
+ return true;
54
+ }
55
+ // ---------- Test helpers ----------
56
+ /**
57
+ * Returns a shallow copy of all reports submitted via `createIssue`.
58
+ */
59
+ getSubmittedReports() {
60
+ return [...this.submittedReports];
61
+ }
62
+ /**
63
+ * Clears the internal list of submitted reports.
64
+ */
65
+ clearReports() {
66
+ this.submittedReports = [];
67
+ }
68
+ // ---------- Internal ----------
69
+ async simulateDelay() {
70
+ const ms = this.config.delay;
71
+ if (ms !== undefined && ms > 0) {
72
+ await new Promise((resolve) => setTimeout(resolve, ms));
73
+ }
74
+ }
75
+ }
76
+ exports.MockAdapter = MockAdapter;
77
+ // ---------------------------------------------------------------------------
78
+ // Platform-agnostic UUID generator (works in Node.js AND browsers without
79
+ // requiring a `node:crypto` import that breaks bundlers like webpack).
80
+ // ---------------------------------------------------------------------------
81
+ function generateUUID() {
82
+ // globalThis.crypto.randomUUID is available in Node 19+ and all modern browsers.
83
+ if (typeof globalThis !== 'undefined' &&
84
+ globalThis.crypto &&
85
+ typeof globalThis.crypto.randomUUID === 'function') {
86
+ return globalThis.crypto.randomUUID();
87
+ }
88
+ // Fallback for older runtimes.
89
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
90
+ const r = (Math.random() * 16) | 0;
91
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
92
+ return v.toString(16);
93
+ });
94
+ }
95
+ //# sourceMappingURL=mock-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-adapter.js","sourceRoot":"","sources":["../src/mock-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { TriggerPlugin, CapturePlugin, ContextCollector, DeviceContext } from './types.js';
2
+ /**
3
+ * Manages plugin lifecycle: registration, activation, and context collection.
4
+ *
5
+ * Internal storage uses Maps keyed by plugin name to prevent duplicates.
6
+ * Registering a plugin with the same name as an existing one overwrites it.
7
+ */
8
+ export declare class PluginRegistry {
9
+ private readonly triggerMap;
10
+ private readonly captureMap;
11
+ private readonly collectorMap;
12
+ registerTrigger(plugin: TriggerPlugin): void;
13
+ unregisterTrigger(name: string): void;
14
+ getTriggers(): TriggerPlugin[];
15
+ activateTriggers(onTrigger: () => void): void;
16
+ deactivateTriggers(): void;
17
+ registerCapture(plugin: CapturePlugin): void;
18
+ unregisterCapture(name: string): void;
19
+ getCapture(): CapturePlugin | undefined;
20
+ registerCollector(collector: ContextCollector): void;
21
+ unregisterCollector(name: string): void;
22
+ getCollectors(): ContextCollector[];
23
+ /**
24
+ * Runs all registered collectors and deep-merges results into a
25
+ * single Partial<DeviceContext>.
26
+ *
27
+ * Each collector is wrapped in a try/catch so a single failing
28
+ * collector does not prevent the others from contributing.
29
+ */
30
+ collectContext(): Promise<Partial<DeviceContext>>;
31
+ /**
32
+ * Removes all registered plugins (triggers, capture, collectors).
33
+ */
34
+ clear(): void;
35
+ }
36
+ //# sourceMappingURL=plugin-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-registry.d.ts","sourceRoot":"","sources":["../src/plugin-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoC;IAC/D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuC;IAIpE,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAI5C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIrC,WAAW,IAAI,aAAa,EAAE;IAI9B,gBAAgB,CAAC,SAAS,EAAE,MAAM,IAAI,GAAG,IAAI;IAM7C,kBAAkB,IAAI,IAAI;IAQ1B,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAI5C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIrC,UAAU,IAAI,aAAa,GAAG,SAAS;IAQvC,iBAAiB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIpD,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIvC,aAAa,IAAI,gBAAgB,EAAE;IAInC;;;;;;OAMG;IACG,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAmBvD;;OAEG;IACH,KAAK,IAAI,IAAI;CAKd"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // @shakenbake/core — PluginRegistry
4
+ // ---------------------------------------------------------------------------
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PluginRegistry = void 0;
7
+ /**
8
+ * Manages plugin lifecycle: registration, activation, and context collection.
9
+ *
10
+ * Internal storage uses Maps keyed by plugin name to prevent duplicates.
11
+ * Registering a plugin with the same name as an existing one overwrites it.
12
+ */
13
+ class PluginRegistry {
14
+ triggerMap = new Map();
15
+ captureMap = new Map();
16
+ collectorMap = new Map();
17
+ // ---- Triggers ----
18
+ registerTrigger(plugin) {
19
+ this.triggerMap.set(plugin.name, plugin);
20
+ }
21
+ unregisterTrigger(name) {
22
+ this.triggerMap.delete(name);
23
+ }
24
+ getTriggers() {
25
+ return Array.from(this.triggerMap.values());
26
+ }
27
+ activateTriggers(onTrigger) {
28
+ for (const trigger of this.triggerMap.values()) {
29
+ trigger.activate(onTrigger);
30
+ }
31
+ }
32
+ deactivateTriggers() {
33
+ for (const trigger of this.triggerMap.values()) {
34
+ trigger.deactivate();
35
+ }
36
+ }
37
+ // ---- Capture ----
38
+ registerCapture(plugin) {
39
+ this.captureMap.set(plugin.name, plugin);
40
+ }
41
+ unregisterCapture(name) {
42
+ this.captureMap.delete(name);
43
+ }
44
+ getCapture() {
45
+ // Return the first registered capture plugin (insertion order).
46
+ const first = this.captureMap.values().next();
47
+ return first.done ? undefined : first.value;
48
+ }
49
+ // ---- Context Collectors ----
50
+ registerCollector(collector) {
51
+ this.collectorMap.set(collector.name, collector);
52
+ }
53
+ unregisterCollector(name) {
54
+ this.collectorMap.delete(name);
55
+ }
56
+ getCollectors() {
57
+ return Array.from(this.collectorMap.values());
58
+ }
59
+ /**
60
+ * Runs all registered collectors and deep-merges results into a
61
+ * single Partial<DeviceContext>.
62
+ *
63
+ * Each collector is wrapped in a try/catch so a single failing
64
+ * collector does not prevent the others from contributing.
65
+ */
66
+ async collectContext() {
67
+ const results = [];
68
+ for (const collector of this.collectorMap.values()) {
69
+ try {
70
+ const partial = await collector.collect();
71
+ results.push(partial);
72
+ }
73
+ catch {
74
+ // Swallow error — a failing collector should not block the rest.
75
+ }
76
+ }
77
+ return results.reduce((acc, partial) => {
78
+ return deepMerge(acc, partial);
79
+ }, {});
80
+ }
81
+ // ---- Bulk ----
82
+ /**
83
+ * Removes all registered plugins (triggers, capture, collectors).
84
+ */
85
+ clear() {
86
+ this.triggerMap.clear();
87
+ this.captureMap.clear();
88
+ this.collectorMap.clear();
89
+ }
90
+ }
91
+ exports.PluginRegistry = PluginRegistry;
92
+ // ---------------------------------------------------------------------------
93
+ // Utility: deep-merge two plain objects (no array concat — last wins).
94
+ // ---------------------------------------------------------------------------
95
+ function deepMerge(target, source) {
96
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
97
+ const output = { ...target };
98
+ for (const key of Object.keys(source)) {
99
+ if (DANGEROUS_KEYS.has(key))
100
+ continue;
101
+ const srcVal = source[key];
102
+ const tgtVal = output[key];
103
+ if (isPlainObject(tgtVal) &&
104
+ isPlainObject(srcVal)) {
105
+ output[key] = deepMerge(tgtVal, srcVal);
106
+ }
107
+ else {
108
+ output[key] = srcVal;
109
+ }
110
+ }
111
+ return output;
112
+ }
113
+ function isPlainObject(value) {
114
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
115
+ }
116
+ //# sourceMappingURL=plugin-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-registry.js","sourceRoot":"","sources":["../src/plugin-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { DeviceContext } from './types.js';
2
+ /**
3
+ * Redact fields from a context object based on dot-path patterns.
4
+ *
5
+ * Supported patterns:
6
+ * - `"app.url"` — removes the `url` key from the `app` section
7
+ * - `"console"` — removes the entire `console` section
8
+ * - `"network.*"` — removes all keys in the `network` section
9
+ *
10
+ * Returns a new object; the original is not mutated.
11
+ */
12
+ export declare function redactContext(context: Partial<DeviceContext>, fields: string[]): Partial<DeviceContext>;
13
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,EAC/B,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,aAAa,CAAC,CA2BxB"}
package/dist/redact.js ADDED
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // @shakenbake/core — Context redaction utility
4
+ // Removes fields from a DeviceContext partial based on dot-path patterns.
5
+ // ---------------------------------------------------------------------------
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.redactContext = redactContext;
8
+ /**
9
+ * Redact fields from a context object based on dot-path patterns.
10
+ *
11
+ * Supported patterns:
12
+ * - `"app.url"` — removes the `url` key from the `app` section
13
+ * - `"console"` — removes the entire `console` section
14
+ * - `"network.*"` — removes all keys in the `network` section
15
+ *
16
+ * Returns a new object; the original is not mutated.
17
+ */
18
+ function redactContext(context, fields) {
19
+ if (!fields.length)
20
+ return context;
21
+ // Deep-clone to avoid mutating the original.
22
+ const result = JSON.parse(JSON.stringify(context));
23
+ for (const field of fields) {
24
+ const parts = field.split('.');
25
+ if (parts.length === 1) {
26
+ // Top-level key: e.g. "console" — delete entire section.
27
+ delete result[parts[0]];
28
+ }
29
+ else if (parts.length === 2 && parts[1] === '*') {
30
+ // Wildcard: e.g. "network.*" — replace section with empty object.
31
+ if (parts[0] in result) {
32
+ result[parts[0]] = {};
33
+ }
34
+ }
35
+ else if (parts.length === 2) {
36
+ // Nested key: e.g. "app.url" — delete specific field.
37
+ const section = result[parts[0]];
38
+ if (section && typeof section === 'object' && !Array.isArray(section)) {
39
+ delete section[parts[1]];
40
+ }
41
+ }
42
+ }
43
+ return result;
44
+ }
45
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,+CAA+C;AAC/C,0EAA0E;AAC1E,8EAA8E;;AAc9E,sCA8BC;AAxCD;;;;;;;;;GASG;AACH,SAAgB,aAAa,CAC3B,OAA+B,EAC/B,MAAgB;IAEhB,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IAEnC,6CAA6C;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAA4B,CAAC;IAE9E,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,yDAAyD;YACzD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAClD,kEAAkE;YAClE,IAAI,KAAK,CAAC,CAAC,CAAE,IAAI,MAAM,EAAE,CAAC;gBACxB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,GAAG,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,sDAAsD;YACtD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YAClC,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBACtE,OAAQ,OAAmC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAgC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { BugReport, CaptureResult, DestinationAdapter, DeviceContext, ReportInput, SubmitResult } from './types.js';
2
+ import type { PluginRegistry } from './plugin-registry.js';
3
+ /**
4
+ * Orchestrates assembling a complete BugReport from user input,
5
+ * screenshots, optional audio, and automatically collected context.
6
+ */
7
+ export declare class ReportBuilder {
8
+ private readonly registry;
9
+ private readonly adapter;
10
+ constructor(registry: PluginRegistry, adapter: DestinationAdapter);
11
+ /**
12
+ * Captures a screenshot using the first registered CapturePlugin.
13
+ * Throws ShakeNbakeError if no CapturePlugin is registered.
14
+ */
15
+ startCapture(): Promise<CaptureResult>;
16
+ /**
17
+ * Merges context from all registered ContextCollectors into a full
18
+ * DeviceContext (using empty defaults for missing sections).
19
+ *
20
+ * Each collector is wrapped in try/catch in the registry, so a single
21
+ * failing collector does not prevent the others from contributing.
22
+ */
23
+ collectContext(): Promise<DeviceContext>;
24
+ /**
25
+ * Builds a full BugReport from user-provided input and collected context.
26
+ * Generates a UUID via crypto.randomUUID (with Math.random fallback for
27
+ * older runtimes) and an ISO 8601 timestamp.
28
+ */
29
+ build(input: ReportInput, context: DeviceContext): BugReport;
30
+ /**
31
+ * Submits a BugReport via the configured DestinationAdapter.
32
+ * Uploads the screenshot first, then creates the issue.
33
+ * Wraps any adapter errors as ShakeNbakeError.
34
+ */
35
+ submit(report: BugReport): Promise<SubmitResult>;
36
+ }
37
+ //# sourceMappingURL=report-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-builder.d.ts","sourceRoot":"","sources":["../src/report-builder.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAsB3D;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,kBAAkB;IAKjE;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC;IAW5C;;;;;;OAMG;IACG,cAAc,IAAI,OAAO,CAAC,aAAa,CAAC;IAkB9C;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,GAAG,SAAS;IAgD5D;;;;OAIG;IACG,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC;CAkBvD"}
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------
3
+ // @shakenbake/core — ReportBuilder
4
+ // ---------------------------------------------------------------------------
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ReportBuilder = void 0;
7
+ const errors_js_1 = require("./errors.js");
8
+ /**
9
+ * Default (empty) DeviceContext used when no collectors are registered.
10
+ */
11
+ function emptyDeviceContext() {
12
+ return {
13
+ platform: { os: 'unknown' },
14
+ device: {},
15
+ screen: { width: 0, height: 0 },
16
+ network: {},
17
+ battery: {},
18
+ locale: {},
19
+ app: {},
20
+ accessibility: {},
21
+ performance: {},
22
+ navigation: {},
23
+ console: {},
24
+ };
25
+ }
26
+ /**
27
+ * Orchestrates assembling a complete BugReport from user input,
28
+ * screenshots, optional audio, and automatically collected context.
29
+ */
30
+ class ReportBuilder {
31
+ registry;
32
+ adapter;
33
+ constructor(registry, adapter) {
34
+ this.registry = registry;
35
+ this.adapter = adapter;
36
+ }
37
+ /**
38
+ * Captures a screenshot using the first registered CapturePlugin.
39
+ * Throws ShakeNbakeError if no CapturePlugin is registered.
40
+ */
41
+ async startCapture() {
42
+ const capturePlugin = this.registry.getCapture();
43
+ if (!capturePlugin) {
44
+ throw new errors_js_1.ShakeNbakeError('No CapturePlugin registered. Register a capture plugin before calling startCapture().', 'UNKNOWN');
45
+ }
46
+ return capturePlugin.capture();
47
+ }
48
+ /**
49
+ * Merges context from all registered ContextCollectors into a full
50
+ * DeviceContext (using empty defaults for missing sections).
51
+ *
52
+ * Each collector is wrapped in try/catch in the registry, so a single
53
+ * failing collector does not prevent the others from contributing.
54
+ */
55
+ async collectContext() {
56
+ const partial = await this.registry.collectContext();
57
+ const base = emptyDeviceContext();
58
+ return {
59
+ platform: { ...base.platform, ...partial.platform },
60
+ device: { ...base.device, ...partial.device },
61
+ screen: { ...base.screen, ...partial.screen },
62
+ network: { ...base.network, ...partial.network },
63
+ battery: { ...base.battery, ...partial.battery },
64
+ locale: { ...base.locale, ...partial.locale },
65
+ app: { ...base.app, ...partial.app },
66
+ accessibility: { ...base.accessibility, ...partial.accessibility },
67
+ performance: { ...base.performance, ...partial.performance },
68
+ navigation: { ...base.navigation, ...partial.navigation },
69
+ console: { ...base.console, ...partial.console },
70
+ };
71
+ }
72
+ /**
73
+ * Builds a full BugReport from user-provided input and collected context.
74
+ * Generates a UUID via crypto.randomUUID (with Math.random fallback for
75
+ * older runtimes) and an ISO 8601 timestamp.
76
+ */
77
+ build(input, context) {
78
+ if (!input.title.trim()) {
79
+ throw new errors_js_1.ShakeNbakeError('Report title is required.', 'UNKNOWN');
80
+ }
81
+ if (!input.annotatedScreenshot) {
82
+ throw new errors_js_1.ShakeNbakeError('An annotated screenshot is required.', 'UNKNOWN');
83
+ }
84
+ let id;
85
+ try {
86
+ id = globalThis.crypto.randomUUID();
87
+ }
88
+ catch {
89
+ // Fallback for runtimes without crypto.randomUUID
90
+ id = fallbackUUID();
91
+ }
92
+ const report = {
93
+ id,
94
+ timestamp: new Date().toISOString(),
95
+ title: input.title,
96
+ description: input.description,
97
+ severity: input.severity,
98
+ category: input.category,
99
+ screenshot: {
100
+ annotated: input.annotatedScreenshot,
101
+ original: input.originalScreenshot,
102
+ dimensions: { width: 0, height: 0 }, // Overwritten by caller if known
103
+ },
104
+ context,
105
+ };
106
+ if (input.audio) {
107
+ report.audio = {
108
+ data: input.audio,
109
+ durationMs: 0,
110
+ mimeType: 'audio/webm',
111
+ };
112
+ }
113
+ return report;
114
+ }
115
+ /**
116
+ * Submits a BugReport via the configured DestinationAdapter.
117
+ * Uploads the screenshot first, then creates the issue.
118
+ * Wraps any adapter errors as ShakeNbakeError.
119
+ */
120
+ async submit(report) {
121
+ try {
122
+ // Note: We do NOT call uploadImage here separately.
123
+ // The DestinationAdapter.createIssue() is responsible for handling
124
+ // screenshot uploads internally (e.g., LinearAdapter uploads then creates the issue).
125
+ // This avoids double-uploading when adapters handle uploads in createIssue.
126
+ return await this.adapter.createIssue(report);
127
+ }
128
+ catch (error) {
129
+ if (error instanceof errors_js_1.ShakeNbakeError) {
130
+ throw error;
131
+ }
132
+ throw new errors_js_1.ShakeNbakeError(error instanceof Error ? error.message : 'Failed to submit report', 'UPLOAD_FAILED', { originalError: error });
133
+ }
134
+ }
135
+ }
136
+ exports.ReportBuilder = ReportBuilder;
137
+ // ---------------------------------------------------------------------------
138
+ // Fallback UUID generator for runtimes without crypto.randomUUID
139
+ // ---------------------------------------------------------------------------
140
+ function fallbackUUID() {
141
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
142
+ const r = (Math.random() * 16) | 0;
143
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
144
+ return v.toString(16);
145
+ });
146
+ }
147
+ //# sourceMappingURL=report-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-builder.js","sourceRoot":"","sources":["../src/report-builder.ts"],"names":[],"mappings":"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"}