@rallycry/conveyor-incidents 0.3.0 → 0.3.2

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 CHANGED
@@ -95,6 +95,34 @@ await incidents.report({
95
95
 
96
96
  Conveyor deduplicates reports by a fingerprint derived from `(projectId, source, fingerprintKey)` — or by `(projectId, source, title)` if `fingerprintKey` is omitted. **Use stable values** for `source` (e.g. `"prod-web"`, not `"node-pid-1234"`) so that repeat reports collapse into a single incident instead of creating noise.
97
97
 
98
+ ### Attachment limits
99
+
100
+ - **5 MB per file** (`MAX_ATTACHMENT_BYTES`, exported). Oversized files are rejected client-side with a descriptive `attachmentResults` error — no HTTP call is made.
101
+ - **10 attachments per card** (server-enforced).
102
+
103
+ ### Browser network evidence
104
+
105
+ For browser-based incident reporters, opt into the bounded fetch recorder early in your app shell and attach its log when the user reports a bug:
106
+
107
+ ```ts
108
+ import {
109
+ buildNetworkLogAttachment,
110
+ installNetworkCapture,
111
+ } from "@rallycry/conveyor-incidents";
112
+
113
+ installNetworkCapture({ maxEntries: 120, maxBodyChars: 6000 });
114
+
115
+ await incidents.report({
116
+ title: "Competition page showed 403",
117
+ attachments: [
118
+ buildNetworkLogAttachment(),
119
+ // ...context.json, screenshot.jpg, console.log, etc.
120
+ ].filter((it): it is NonNullable<typeof it> => it !== undefined),
121
+ });
122
+ ```
123
+
124
+ `network.log` is newline-delimited JSON. Each entry includes method, URL with sensitive query parameters redacted, status, duration, `traceparent` headers when available, tRPC procedure names for `/trpc/a,b,c` batches, and capped response snippets for failed responses and tRPC batches. This is intentionally diagnostic, not a full traffic archive.
125
+
98
126
  ### Error handling
99
127
 
100
128
  - The main `report()` call throws `ConveyorIncidentsError` on non-2xx responses; it exposes `.status` and `.body`.
@@ -148,6 +176,17 @@ client.report({
148
176
  error?: string;
149
177
  }>;
150
178
  }>;
179
+
180
+ installNetworkCapture({
181
+ maxEntries?: number; // default: 80
182
+ maxBodyChars?: number; // default: 4000
183
+ captureTrpcResponses?: boolean; // default: true
184
+ }): void;
185
+
186
+ getNetworkLogEntries(): NetworkLogEntry[];
187
+ clearNetworkLogEntries(): void;
188
+ uninstallNetworkCapture(): void;
189
+ buildNetworkLogAttachment(fileName?: string): IncidentAttachment | undefined;
151
190
  ```
152
191
 
153
192
  ## License
package/dist/index.d.ts CHANGED
@@ -4,6 +4,10 @@ declare class ConveyorIncidentsError extends Error {
4
4
  constructor(message: string, status: number, body: unknown);
5
5
  }
6
6
 
7
+ /** Server-side per-file ceiling — keep in sync with MAX_ATTACHMENT_BYTES in
8
+ * conveyor's incident-routes.ts. Checked client-side so an oversized file
9
+ * fails with a descriptive `attachmentResults` error instead of an HTTP 413. */
10
+ declare const MAX_ATTACHMENT_BYTES: number;
7
11
  type IncidentSeverity = "critical" | "high" | "medium" | "low";
8
12
  type ConveyorCardType = "incident" | "suggestion" | "task";
9
13
  interface ConveyorIncidentsOptions {
@@ -17,6 +21,30 @@ interface IncidentAttachment {
17
21
  mimeType: string;
18
22
  content: Uint8Array | string;
19
23
  }
24
+ interface NetworkCaptureOptions {
25
+ /** Maximum entries kept in memory. Defaults to 80. */
26
+ maxEntries?: number;
27
+ /** Maximum response body characters kept per captured entry. Defaults to 4000. */
28
+ maxBodyChars?: number;
29
+ /** Capture response snippets for successful tRPC batch calls as well as failures. Defaults to true. */
30
+ captureTrpcResponses?: boolean;
31
+ }
32
+ interface NetworkLogEntry {
33
+ timestamp: string;
34
+ kind: "fetch";
35
+ method: string;
36
+ url: string;
37
+ status: number | null;
38
+ ok: boolean | null;
39
+ durationMs: number;
40
+ responseBytes: number | null;
41
+ contentType?: string;
42
+ requestTraceparent?: string;
43
+ responseTraceparent?: string;
44
+ trpcProcedures?: string[];
45
+ responseSnippet?: string;
46
+ error?: string;
47
+ }
20
48
  interface ReportIncidentInput {
21
49
  title: string;
22
50
  description?: string;
@@ -58,6 +86,11 @@ interface ReportIncidentResult {
58
86
  cardId: string;
59
87
  attachmentResults: AttachmentResult[];
60
88
  }
89
+ declare function installNetworkCapture(options?: NetworkCaptureOptions): void;
90
+ declare function uninstallNetworkCapture(): void;
91
+ declare function clearNetworkLogEntries(): void;
92
+ declare function getNetworkLogEntries(): NetworkLogEntry[];
93
+ declare function buildNetworkLogAttachment(fileName?: string): IncidentAttachment | undefined;
61
94
  declare class ConveyorIncidents {
62
95
  private readonly apiKey;
63
96
  private readonly projectId;
@@ -68,4 +101,4 @@ declare class ConveyorIncidents {
68
101
  private uploadAttachment;
69
102
  }
70
103
 
71
- export { type AttachmentResult, type ConveyorCardType, ConveyorIncidents, ConveyorIncidentsError, type ConveyorIncidentsOptions, type IncidentAttachment, type IncidentSeverity, type ReportIncidentInput, type ReportIncidentResult };
104
+ export { type AttachmentResult, type ConveyorCardType, ConveyorIncidents, ConveyorIncidentsError, type ConveyorIncidentsOptions, type IncidentAttachment, type IncidentSeverity, MAX_ATTACHMENT_BYTES, type NetworkCaptureOptions, type NetworkLogEntry, type ReportIncidentInput, type ReportIncidentResult, buildNetworkLogAttachment, clearNetworkLogEntries, getNetworkLogEntries, installNetworkCapture, uninstallNetworkCapture };
package/dist/index.js CHANGED
@@ -14,6 +14,86 @@ var ConveyorIncidentsError = class extends Error {
14
14
  var DEFAULT_BASE_URL = "https://api.conveyor.rallycryapp.com";
15
15
  var DEFAULT_SOURCE = "client";
16
16
  var DEFAULT_SEVERITY = "medium";
17
+ var MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;
18
+ var networkCaptureState;
19
+ var DEFAULT_NETWORK_CAPTURE_OPTIONS = {
20
+ maxEntries: 80,
21
+ maxBodyChars: 4e3,
22
+ captureTrpcResponses: true
23
+ };
24
+ function installNetworkCapture(options = {}) {
25
+ if (typeof globalThis.fetch !== "function") return;
26
+ const fetchImpl = globalThis.fetch.bind(globalThis);
27
+ const resolvedOptions = {
28
+ ...DEFAULT_NETWORK_CAPTURE_OPTIONS,
29
+ ...options
30
+ };
31
+ if (networkCaptureState) {
32
+ networkCaptureState.options = resolvedOptions;
33
+ return;
34
+ }
35
+ const entries = [];
36
+ networkCaptureState = {
37
+ installed: true,
38
+ originalFetch: fetchImpl,
39
+ entries,
40
+ options: resolvedOptions
41
+ };
42
+ globalThis.fetch = async (input, init) => {
43
+ const state = networkCaptureState;
44
+ const originalFetch = state?.originalFetch ?? fetchImpl;
45
+ const startedAt = Date.now();
46
+ const timestamp = new Date(startedAt).toISOString();
47
+ const request = normalizeRequest(input, init);
48
+ try {
49
+ const response = await originalFetch(input, init);
50
+ const entry = await buildNetworkEntry({
51
+ timestamp,
52
+ request,
53
+ response,
54
+ durationMs: Date.now() - startedAt,
55
+ options: state?.options ?? resolvedOptions
56
+ });
57
+ pushNetworkEntry(entry);
58
+ return response;
59
+ } catch (err) {
60
+ pushNetworkEntry({
61
+ timestamp,
62
+ kind: "fetch",
63
+ method: request.method,
64
+ url: redactUrl(request.url),
65
+ status: null,
66
+ ok: null,
67
+ durationMs: Date.now() - startedAt,
68
+ responseBytes: null,
69
+ requestTraceparent: request.traceparent,
70
+ trpcProcedures: extractTrpcProcedures(request.url),
71
+ error: err instanceof Error ? err.message : String(err)
72
+ });
73
+ throw err;
74
+ }
75
+ };
76
+ }
77
+ function uninstallNetworkCapture() {
78
+ if (!networkCaptureState) return;
79
+ globalThis.fetch = networkCaptureState.originalFetch;
80
+ networkCaptureState = void 0;
81
+ }
82
+ function clearNetworkLogEntries() {
83
+ networkCaptureState?.entries.splice(0);
84
+ }
85
+ function getNetworkLogEntries() {
86
+ return [...networkCaptureState?.entries ?? []];
87
+ }
88
+ function buildNetworkLogAttachment(fileName = "network.log") {
89
+ const entries = getNetworkLogEntries();
90
+ if (entries.length === 0) return void 0;
91
+ return {
92
+ fileName,
93
+ mimeType: "application/x-ndjson",
94
+ content: entries.map((entry) => JSON.stringify(entry)).join("\n")
95
+ };
96
+ }
17
97
  var ConveyorIncidents = class {
18
98
  apiKey;
19
99
  projectId;
@@ -83,7 +163,15 @@ var ConveyorIncidents = class {
83
163
  }
84
164
  async uploadAttachment(incidentId, attachment) {
85
165
  try {
86
- const contentBase64 = toBase64(attachment.content);
166
+ const bytes = typeof attachment.content === "string" ? new TextEncoder().encode(attachment.content) : attachment.content;
167
+ if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {
168
+ return {
169
+ fileName: attachment.fileName,
170
+ ok: false,
171
+ error: `Attachment is ${bytes.byteLength} bytes \u2014 exceeds the ${MAX_ATTACHMENT_BYTES}-byte (5 MB) server limit; upload skipped`
172
+ };
173
+ }
174
+ const contentBase64 = bytesToBase64(bytes);
87
175
  const res = await this.fetchImpl(
88
176
  `${this.baseUrl}/api/incidents/${encodeURIComponent(incidentId)}/files`,
89
177
  {
@@ -118,6 +206,102 @@ var ConveyorIncidents = class {
118
206
  }
119
207
  }
120
208
  };
209
+ function pushNetworkEntry(entry) {
210
+ const state = networkCaptureState;
211
+ if (!state) return;
212
+ state.entries.push(entry);
213
+ const overflow = state.entries.length - state.options.maxEntries;
214
+ if (overflow > 0) state.entries.splice(0, overflow);
215
+ }
216
+ function normalizeRequest(input, init) {
217
+ const request = typeof Request !== "undefined" && input instanceof Request ? input : void 0;
218
+ const method = init?.method ?? request?.method ?? "GET";
219
+ const url = request?.url ?? String(input);
220
+ const traceparent = readHeader(init?.headers, "traceparent") ?? readHeader(request?.headers, "traceparent");
221
+ return { method: method.toUpperCase(), url, traceparent };
222
+ }
223
+ async function buildNetworkEntry(params) {
224
+ const { timestamp, request, response, durationMs, options } = params;
225
+ const contentType = response.headers.get("content-type") ?? void 0;
226
+ const trpcProcedures = extractTrpcProcedures(request.url);
227
+ const shouldCaptureBody = !response.ok || options.captureTrpcResponses && trpcProcedures !== void 0;
228
+ const bodyText = shouldCaptureBody ? await readResponseSnippet(response, options.maxBodyChars) : void 0;
229
+ const responseBytes = readContentLength(response.headers) ?? estimateUtf8Bytes(bodyText);
230
+ return {
231
+ timestamp,
232
+ kind: "fetch",
233
+ method: request.method,
234
+ url: redactUrl(request.url),
235
+ status: response.status,
236
+ ok: response.ok,
237
+ durationMs,
238
+ responseBytes,
239
+ contentType,
240
+ requestTraceparent: request.traceparent,
241
+ responseTraceparent: response.headers.get("traceparent") ?? void 0,
242
+ trpcProcedures,
243
+ responseSnippet: bodyText
244
+ };
245
+ }
246
+ function readHeader(headers, name) {
247
+ if (!headers) return void 0;
248
+ if (headers instanceof Headers) return headers.get(name) ?? void 0;
249
+ const lowerName = name.toLowerCase();
250
+ if (Array.isArray(headers)) {
251
+ const hit = headers.find(([key]) => key.toLowerCase() === lowerName);
252
+ return hit?.[1];
253
+ }
254
+ const value = Object.entries(headers).find(([key]) => key.toLowerCase() === lowerName)?.[1];
255
+ return value;
256
+ }
257
+ function readContentLength(headers) {
258
+ const raw = headers.get("content-length");
259
+ if (!raw) return null;
260
+ const parsed = Number.parseInt(raw, 10);
261
+ return Number.isFinite(parsed) ? parsed : null;
262
+ }
263
+ function estimateUtf8Bytes(value) {
264
+ if (value === void 0) return null;
265
+ return new TextEncoder().encode(value).byteLength;
266
+ }
267
+ async function readResponseSnippet(response, maxChars) {
268
+ try {
269
+ const text = await response.clone().text();
270
+ if (text.length <= maxChars) return text;
271
+ return `${text.slice(0, maxChars)}...[truncated ${String(text.length - maxChars)} chars]`;
272
+ } catch {
273
+ return void 0;
274
+ }
275
+ }
276
+ function extractTrpcProcedures(rawUrl) {
277
+ let url;
278
+ try {
279
+ url = new URL(rawUrl, "https://placeholder.invalid");
280
+ } catch {
281
+ return void 0;
282
+ }
283
+ const marker = "/trpc/";
284
+ const index = url.pathname.indexOf(marker);
285
+ if (index === -1) return void 0;
286
+ const path = url.pathname.slice(index + marker.length);
287
+ const procedures = path.split(",").map((part) => decodeURIComponent(part).trim()).filter((part) => part.length > 0);
288
+ return procedures.length > 0 ? procedures : void 0;
289
+ }
290
+ function redactUrl(rawUrl) {
291
+ let url;
292
+ try {
293
+ url = new URL(rawUrl, "https://placeholder.invalid");
294
+ } catch {
295
+ return rawUrl;
296
+ }
297
+ for (const key of [...url.searchParams.keys()]) {
298
+ const lower = key.toLowerCase();
299
+ if (lower.includes("token") || lower.includes("key") || lower.includes("secret") || lower.includes("signature") || lower.includes("sig")) {
300
+ url.searchParams.set(key, "[redacted]");
301
+ }
302
+ }
303
+ return url.origin === "https://placeholder.invalid" ? `${url.pathname}${url.search}` : url.toString();
304
+ }
121
305
  function stripTrailingSlash(url) {
122
306
  return url.endsWith("/") ? url.slice(0, -1) : url;
123
307
  }
@@ -147,10 +331,6 @@ function formatErrorBody(status, statusText, body) {
147
331
  }
148
332
  return prefix;
149
333
  }
150
- function toBase64(content) {
151
- const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
152
- return bytesToBase64(bytes);
153
- }
154
334
  function bytesToBase64(bytes) {
155
335
  if (typeof Buffer !== "undefined") {
156
336
  return Buffer.from(bytes).toString("base64");
@@ -163,6 +343,12 @@ function bytesToBase64(bytes) {
163
343
  }
164
344
  export {
165
345
  ConveyorIncidents,
166
- ConveyorIncidentsError
346
+ ConveyorIncidentsError,
347
+ MAX_ATTACHMENT_BYTES,
348
+ buildNetworkLogAttachment,
349
+ clearNetworkLogEntries,
350
+ getNetworkLogEntries,
351
+ installNetworkCapture,
352
+ uninstallNetworkCapture
167
353
  };
168
354
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["export class ConveyorIncidentsError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"ConveyorIncidentsError\";\n this.status = status;\n this.body = body;\n }\n}\n","import { ConveyorIncidentsError } from \"./errors.js\";\n\nexport { ConveyorIncidentsError } from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.conveyor.rallycryapp.com\";\nconst DEFAULT_SOURCE = \"client\";\nconst DEFAULT_SEVERITY: IncidentSeverity = \"medium\";\n\nexport type IncidentSeverity = \"critical\" | \"high\" | \"medium\" | \"low\";\n\nexport type ConveyorCardType = \"incident\" | \"suggestion\" | \"task\";\n\nexport interface ConveyorIncidentsOptions {\n apiKey: string;\n projectId: string;\n baseUrl?: string;\n fetch?: typeof fetch;\n}\n\nexport interface IncidentAttachment {\n fileName: string;\n mimeType: string;\n content: Uint8Array | string;\n}\n\nexport interface ReportIncidentInput {\n title: string;\n description?: string;\n severity?: IncidentSeverity;\n source?: string;\n fingerprintKey?: string;\n plan?: string;\n attachments?: IncidentAttachment[];\n /** Card type to create. The server defaults to \"incident\" when omitted. */\n type?: ConveyorCardType;\n /**\n * Project-member emails. The FIRST email that resolves to a member of the\n * target project becomes the card's assignee; the rest are ignored.\n * Non-member emails are silently skipped.\n */\n assigned?: string[];\n /**\n * Project-member emails. EVERY email that resolves to a member of the\n * target project is added as a reviewer. Non-member emails are silently\n * skipped.\n */\n review?: string[];\n /**\n * Project-member email used as the card's creator. When omitted — or when\n * the email doesn't resolve to a member of the target project — the card\n * is attributed to the \"Conveyor Incidents\" system user.\n */\n createdBy?: string;\n}\n\nexport interface AttachmentResult {\n fileName: string;\n ok: boolean;\n fileId?: string;\n error?: string;\n}\n\nexport interface ReportIncidentResult {\n incidentId: string;\n /** Alias of `incidentId` — prefer this when reporting type \"suggestion\" or \"task\". */\n cardId: string;\n attachmentResults: AttachmentResult[];\n}\n\nexport class ConveyorIncidents {\n private readonly apiKey: string;\n private readonly projectId: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: ConveyorIncidentsOptions) {\n if (!opts.apiKey) {\n throw new Error(\"ConveyorIncidents: apiKey is required\");\n }\n if (!opts.projectId) {\n throw new Error(\"ConveyorIncidents: projectId is required\");\n }\n const resolvedFetch = opts.fetch ?? globalThis.fetch;\n if (typeof resolvedFetch !== \"function\") {\n throw new Error(\"ConveyorIncidents: no global fetch available — pass opts.fetch on Node <18\");\n }\n this.apiKey = opts.apiKey;\n this.projectId = opts.projectId;\n this.baseUrl = stripTrailingSlash(opts.baseUrl ?? DEFAULT_BASE_URL);\n this.fetchImpl = resolvedFetch;\n }\n\n async report(input: ReportIncidentInput): Promise<ReportIncidentResult> {\n const body = {\n projectId: this.projectId,\n source: input.source ?? DEFAULT_SOURCE,\n severity: input.severity ?? DEFAULT_SEVERITY,\n title: input.title,\n description: input.description,\n plan: input.plan,\n fingerprintKey: input.fingerprintKey,\n type: input.type,\n assigned: input.assigned,\n review: input.review,\n createdBy: input.createdBy,\n };\n\n const res = await this.fetchImpl(`${this.baseUrl}/api/incidents/report`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n throw new ConveyorIncidentsError(\n `Failed to report incident: ${res.status} ${res.statusText}`,\n res.status,\n payload,\n );\n }\n\n const incidentId = extractId(payload, \"incidentId\") ?? extractId(payload, \"cardId\");\n\n if (!incidentId) {\n throw new ConveyorIncidentsError(\n \"Invalid response from /api/incidents/report: missing incidentId\",\n res.status,\n payload,\n );\n }\n\n const attachmentResults: AttachmentResult[] = [];\n if (input.attachments && input.attachments.length > 0) {\n for (const attachment of input.attachments) {\n attachmentResults.push(await this.uploadAttachment(incidentId, attachment));\n }\n }\n\n return { incidentId, cardId: incidentId, attachmentResults };\n }\n\n private async uploadAttachment(\n incidentId: string,\n attachment: IncidentAttachment,\n ): Promise<AttachmentResult> {\n try {\n const contentBase64 = toBase64(attachment.content);\n const res = await this.fetchImpl(\n `${this.baseUrl}/api/incidents/${encodeURIComponent(incidentId)}/files`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n fileName: attachment.fileName,\n mimeType: attachment.mimeType,\n contentBase64,\n }),\n },\n );\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: formatErrorBody(res.status, res.statusText, payload),\n };\n }\n\n const fileId =\n payload && typeof payload === \"object\" && \"fileId\" in payload\n ? String((payload as { fileId: unknown }).fileId)\n : undefined;\n\n return { fileName: attachment.fileName, ok: true, fileId };\n } catch (err) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\nfunction extractId(payload: unknown, key: \"incidentId\" | \"cardId\"): string | undefined {\n if (payload && typeof payload === \"object\" && key in payload) {\n const value = (payload as Record<string, unknown>)[key];\n if (value !== undefined && value !== null) return String(value);\n }\n return undefined;\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction formatErrorBody(status: number, statusText: string, body: unknown): string {\n const prefix = `${status} ${statusText}`.trim();\n if (body && typeof body === \"object\" && \"error\" in body) {\n return `${prefix}: ${String((body as { error: unknown }).error)}`;\n }\n if (typeof body === \"string\" && body.length > 0) {\n return `${prefix}: ${body}`;\n }\n return prefix;\n}\n\nfunction toBase64(content: Uint8Array | string): string {\n const bytes = typeof content === \"string\" ? new TextEncoder().encode(content) : content;\n return bytesToBase64(bytes);\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(bytes).toString(\"base64\");\n }\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n"],"mappings":";AAAO,IAAM,yBAAN,cAAqC,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EAET,YAAY,SAAiB,QAAgB,MAAe;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;;;ACNA,IAAM,mBAAmB;AACzB,IAAM,iBAAiB;AACvB,IAAM,mBAAqC;AA+DpC,IAAM,oBAAN,MAAwB;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAgC;AAC1C,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AACA,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AACA,UAAM,gBAAgB,KAAK,SAAS,WAAW;AAC/C,QAAI,OAAO,kBAAkB,YAAY;AACvC,YAAM,IAAI,MAAM,iFAA4E;AAAA,IAC9F;AACA,SAAK,SAAS,KAAK;AACnB,SAAK,YAAY,KAAK;AACtB,SAAK,UAAU,mBAAmB,KAAK,WAAW,gBAAgB;AAClE,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,OAAO,OAA2D;AACtE,UAAM,OAAO;AAAA,MACX,WAAW,KAAK;AAAA,MAChB,QAAQ,MAAM,UAAU;AAAA,MACxB,UAAU,MAAM,YAAY;AAAA,MAC5B,OAAO,MAAM;AAAA,MACb,aAAa,MAAM;AAAA,MACnB,MAAM,MAAM;AAAA,MACZ,gBAAgB,MAAM;AAAA,MACtB,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,QAAQ,MAAM;AAAA,MACd,WAAW,MAAM;AAAA,IACnB;AAEA,UAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO,yBAAyB;AAAA,MACvE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,KAAK,MAAM;AAAA,MACtC;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAED,UAAM,UAAU,MAAM,SAAS,GAAG;AAElC,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,8BAA8B,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,QAC1D,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAEA,UAAM,aAAa,UAAU,SAAS,YAAY,KAAK,UAAU,SAAS,QAAQ;AAElF,QAAI,CAAC,YAAY;AACf,YAAM,IAAI;AAAA,QACR;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAEA,UAAM,oBAAwC,CAAC;AAC/C,QAAI,MAAM,eAAe,MAAM,YAAY,SAAS,GAAG;AACrD,iBAAW,cAAc,MAAM,aAAa;AAC1C,0BAAkB,KAAK,MAAM,KAAK,iBAAiB,YAAY,UAAU,CAAC;AAAA,MAC5E;AAAA,IACF;AAEA,WAAO,EAAE,YAAY,QAAQ,YAAY,kBAAkB;AAAA,EAC7D;AAAA,EAEA,MAAc,iBACZ,YACA,YAC2B;AAC3B,QAAI;AACF,YAAM,gBAAgB,SAAS,WAAW,OAAO;AACjD,YAAM,MAAM,MAAM,KAAK;AAAA,QACrB,GAAG,KAAK,OAAO,kBAAkB,mBAAmB,UAAU,CAAC;AAAA,QAC/D;AAAA,UACE,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,eAAe,UAAU,KAAK,MAAM;AAAA,UACtC;AAAA,UACA,MAAM,KAAK,UAAU;AAAA,YACnB,UAAU,WAAW;AAAA,YACrB,UAAU,WAAW;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,SAAS,GAAG;AAElC,UAAI,CAAC,IAAI,IAAI;AACX,eAAO;AAAA,UACL,UAAU,WAAW;AAAA,UACrB,IAAI;AAAA,UACJ,OAAO,gBAAgB,IAAI,QAAQ,IAAI,YAAY,OAAO;AAAA,QAC5D;AAAA,MACF;AAEA,YAAM,SACJ,WAAW,OAAO,YAAY,YAAY,YAAY,UAClD,OAAQ,QAAgC,MAAM,IAC9C;AAEN,aAAO,EAAE,UAAU,WAAW,UAAU,IAAI,MAAM,OAAO;AAAA,IAC3D,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,UAAU,WAAW;AAAA,QACrB,IAAI;AAAA,QACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,KAAqB;AAC/C,SAAO,IAAI,SAAS,GAAG,IAAI,IAAI,MAAM,GAAG,EAAE,IAAI;AAChD;AAEA,SAAS,UAAU,SAAkB,KAAkD;AACrF,MAAI,WAAW,OAAO,YAAY,YAAY,OAAO,SAAS;AAC5D,UAAM,QAAS,QAAoC,GAAG;AACtD,QAAI,UAAU,UAAa,UAAU,KAAM,QAAO,OAAO,KAAK;AAAA,EAChE;AACA,SAAO;AACT;AAEA,eAAe,SAAS,KAAiC;AACvD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,gBAAgB,QAAgB,YAAoB,MAAuB;AAClF,QAAM,SAAS,GAAG,MAAM,IAAI,UAAU,GAAG,KAAK;AAC9C,MAAI,QAAQ,OAAO,SAAS,YAAY,WAAW,MAAM;AACvD,WAAO,GAAG,MAAM,KAAK,OAAQ,KAA4B,KAAK,CAAC;AAAA,EACjE;AACA,MAAI,OAAO,SAAS,YAAY,KAAK,SAAS,GAAG;AAC/C,WAAO,GAAG,MAAM,KAAK,IAAI;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,SAAS,SAAS,SAAsC;AACtD,QAAM,QAAQ,OAAO,YAAY,WAAW,IAAI,YAAY,EAAE,OAAO,OAAO,IAAI;AAChF,SAAO,cAAc,KAAK;AAC5B;AAEA,SAAS,cAAc,OAA2B;AAChD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,KAAK,EAAE,SAAS,QAAQ;AAAA,EAC7C;AACA,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACxB,cAAU,OAAO,aAAa,IAAI;AAAA,EACpC;AACA,SAAO,KAAK,MAAM;AACpB;","names":[]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["export class ConveyorIncidentsError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"ConveyorIncidentsError\";\n this.status = status;\n this.body = body;\n }\n}\n","import { ConveyorIncidentsError } from \"./errors.js\";\n\nexport { ConveyorIncidentsError } from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.conveyor.rallycryapp.com\";\nconst DEFAULT_SOURCE = \"client\";\nconst DEFAULT_SEVERITY: IncidentSeverity = \"medium\";\n\n/** Server-side per-file ceiling — keep in sync with MAX_ATTACHMENT_BYTES in\n * conveyor's incident-routes.ts. Checked client-side so an oversized file\n * fails with a descriptive `attachmentResults` error instead of an HTTP 413. */\nexport const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nexport type IncidentSeverity = \"critical\" | \"high\" | \"medium\" | \"low\";\n\nexport type ConveyorCardType = \"incident\" | \"suggestion\" | \"task\";\n\nexport interface ConveyorIncidentsOptions {\n apiKey: string;\n projectId: string;\n baseUrl?: string;\n fetch?: typeof fetch;\n}\n\nexport interface IncidentAttachment {\n fileName: string;\n mimeType: string;\n content: Uint8Array | string;\n}\n\nexport interface NetworkCaptureOptions {\n /** Maximum entries kept in memory. Defaults to 80. */\n maxEntries?: number;\n /** Maximum response body characters kept per captured entry. Defaults to 4000. */\n maxBodyChars?: number;\n /** Capture response snippets for successful tRPC batch calls as well as failures. Defaults to true. */\n captureTrpcResponses?: boolean;\n}\n\nexport interface NetworkLogEntry {\n timestamp: string;\n kind: \"fetch\";\n method: string;\n url: string;\n status: number | null;\n ok: boolean | null;\n durationMs: number;\n responseBytes: number | null;\n contentType?: string;\n requestTraceparent?: string;\n responseTraceparent?: string;\n trpcProcedures?: string[];\n responseSnippet?: string;\n error?: string;\n}\n\nexport interface ReportIncidentInput {\n title: string;\n description?: string;\n severity?: IncidentSeverity;\n source?: string;\n fingerprintKey?: string;\n plan?: string;\n attachments?: IncidentAttachment[];\n /** Card type to create. The server defaults to \"incident\" when omitted. */\n type?: ConveyorCardType;\n /**\n * Project-member emails. The FIRST email that resolves to a member of the\n * target project becomes the card's assignee; the rest are ignored.\n * Non-member emails are silently skipped.\n */\n assigned?: string[];\n /**\n * Project-member emails. EVERY email that resolves to a member of the\n * target project is added as a reviewer. Non-member emails are silently\n * skipped.\n */\n review?: string[];\n /**\n * Project-member email used as the card's creator. When omitted — or when\n * the email doesn't resolve to a member of the target project — the card\n * is attributed to the \"Conveyor Incidents\" system user.\n */\n createdBy?: string;\n}\n\nexport interface AttachmentResult {\n fileName: string;\n ok: boolean;\n fileId?: string;\n error?: string;\n}\n\nexport interface ReportIncidentResult {\n incidentId: string;\n /** Alias of `incidentId` — prefer this when reporting type \"suggestion\" or \"task\". */\n cardId: string;\n attachmentResults: AttachmentResult[];\n}\n\nlet networkCaptureState:\n | {\n installed: true;\n originalFetch: typeof fetch;\n entries: NetworkLogEntry[];\n options: Required<NetworkCaptureOptions>;\n }\n | undefined;\n\nconst DEFAULT_NETWORK_CAPTURE_OPTIONS: Required<NetworkCaptureOptions> = {\n maxEntries: 80,\n maxBodyChars: 4000,\n captureTrpcResponses: true,\n};\n\nexport function installNetworkCapture(options: NetworkCaptureOptions = {}): void {\n if (typeof globalThis.fetch !== \"function\") return;\n // Bind to globalThis: some browsers (notably Safari/WebKit) throw\n // \"Illegal invocation\" when the native fetch is called without its\n // original receiver, which a bare captured reference loses.\n const fetchImpl = globalThis.fetch.bind(globalThis);\n\n const resolvedOptions = {\n ...DEFAULT_NETWORK_CAPTURE_OPTIONS,\n ...options,\n };\n\n if (networkCaptureState) {\n networkCaptureState.options = resolvedOptions;\n return;\n }\n\n const entries: NetworkLogEntry[] = [];\n networkCaptureState = {\n installed: true,\n originalFetch: fetchImpl,\n entries,\n options: resolvedOptions,\n };\n\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const state = networkCaptureState;\n const originalFetch = state?.originalFetch ?? fetchImpl;\n const startedAt = Date.now();\n const timestamp = new Date(startedAt).toISOString();\n const request = normalizeRequest(input, init);\n\n try {\n const response = await originalFetch(input, init);\n const entry = await buildNetworkEntry({\n timestamp,\n request,\n response,\n durationMs: Date.now() - startedAt,\n options: state?.options ?? resolvedOptions,\n });\n pushNetworkEntry(entry);\n return response;\n } catch (err) {\n pushNetworkEntry({\n timestamp,\n kind: \"fetch\",\n method: request.method,\n url: redactUrl(request.url),\n status: null,\n ok: null,\n durationMs: Date.now() - startedAt,\n responseBytes: null,\n requestTraceparent: request.traceparent,\n trpcProcedures: extractTrpcProcedures(request.url),\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n };\n}\n\nexport function uninstallNetworkCapture(): void {\n if (!networkCaptureState) return;\n globalThis.fetch = networkCaptureState.originalFetch;\n networkCaptureState = undefined;\n}\n\nexport function clearNetworkLogEntries(): void {\n networkCaptureState?.entries.splice(0);\n}\n\nexport function getNetworkLogEntries(): NetworkLogEntry[] {\n return [...(networkCaptureState?.entries ?? [])];\n}\n\nexport function buildNetworkLogAttachment(\n fileName = \"network.log\",\n): IncidentAttachment | undefined {\n const entries = getNetworkLogEntries();\n if (entries.length === 0) return undefined;\n return {\n fileName,\n mimeType: \"application/x-ndjson\",\n content: entries.map((entry) => JSON.stringify(entry)).join(\"\\n\"),\n };\n}\n\nexport class ConveyorIncidents {\n private readonly apiKey: string;\n private readonly projectId: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: ConveyorIncidentsOptions) {\n if (!opts.apiKey) {\n throw new Error(\"ConveyorIncidents: apiKey is required\");\n }\n if (!opts.projectId) {\n throw new Error(\"ConveyorIncidents: projectId is required\");\n }\n const resolvedFetch = opts.fetch ?? globalThis.fetch;\n if (typeof resolvedFetch !== \"function\") {\n throw new Error(\"ConveyorIncidents: no global fetch available — pass opts.fetch on Node <18\");\n }\n this.apiKey = opts.apiKey;\n this.projectId = opts.projectId;\n this.baseUrl = stripTrailingSlash(opts.baseUrl ?? DEFAULT_BASE_URL);\n this.fetchImpl = resolvedFetch;\n }\n\n async report(input: ReportIncidentInput): Promise<ReportIncidentResult> {\n const body = {\n projectId: this.projectId,\n source: input.source ?? DEFAULT_SOURCE,\n severity: input.severity ?? DEFAULT_SEVERITY,\n title: input.title,\n description: input.description,\n plan: input.plan,\n fingerprintKey: input.fingerprintKey,\n type: input.type,\n assigned: input.assigned,\n review: input.review,\n createdBy: input.createdBy,\n };\n\n const res = await this.fetchImpl(`${this.baseUrl}/api/incidents/report`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n throw new ConveyorIncidentsError(\n `Failed to report incident: ${res.status} ${res.statusText}`,\n res.status,\n payload,\n );\n }\n\n const incidentId = extractId(payload, \"incidentId\") ?? extractId(payload, \"cardId\");\n\n if (!incidentId) {\n throw new ConveyorIncidentsError(\n \"Invalid response from /api/incidents/report: missing incidentId\",\n res.status,\n payload,\n );\n }\n\n const attachmentResults: AttachmentResult[] = [];\n if (input.attachments && input.attachments.length > 0) {\n for (const attachment of input.attachments) {\n attachmentResults.push(await this.uploadAttachment(incidentId, attachment));\n }\n }\n\n return { incidentId, cardId: incidentId, attachmentResults };\n }\n\n private async uploadAttachment(\n incidentId: string,\n attachment: IncidentAttachment,\n ): Promise<AttachmentResult> {\n try {\n const bytes =\n typeof attachment.content === \"string\"\n ? new TextEncoder().encode(attachment.content)\n : attachment.content;\n if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: `Attachment is ${bytes.byteLength} bytes — exceeds the ${MAX_ATTACHMENT_BYTES}-byte (5 MB) server limit; upload skipped`,\n };\n }\n const contentBase64 = bytesToBase64(bytes);\n const res = await this.fetchImpl(\n `${this.baseUrl}/api/incidents/${encodeURIComponent(incidentId)}/files`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n fileName: attachment.fileName,\n mimeType: attachment.mimeType,\n contentBase64,\n }),\n },\n );\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: formatErrorBody(res.status, res.statusText, payload),\n };\n }\n\n const fileId =\n payload && typeof payload === \"object\" && \"fileId\" in payload\n ? String((payload as { fileId: unknown }).fileId)\n : undefined;\n\n return { fileName: attachment.fileName, ok: true, fileId };\n } catch (err) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\nfunction pushNetworkEntry(entry: NetworkLogEntry): void {\n const state = networkCaptureState;\n if (!state) return;\n state.entries.push(entry);\n const overflow = state.entries.length - state.options.maxEntries;\n if (overflow > 0) state.entries.splice(0, overflow);\n}\n\nfunction normalizeRequest(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n): { method: string; url: string; traceparent?: string } {\n const request = typeof Request !== \"undefined\" && input instanceof Request ? input : undefined;\n const method = init?.method ?? request?.method ?? \"GET\";\n const url = request?.url ?? String(input);\n const traceparent =\n readHeader(init?.headers, \"traceparent\") ?? readHeader(request?.headers, \"traceparent\");\n return { method: method.toUpperCase(), url, traceparent };\n}\n\nasync function buildNetworkEntry(params: {\n timestamp: string;\n request: { method: string; url: string; traceparent?: string };\n response: Response;\n durationMs: number;\n options: Required<NetworkCaptureOptions>;\n}): Promise<NetworkLogEntry> {\n const { timestamp, request, response, durationMs, options } = params;\n const contentType = response.headers.get(\"content-type\") ?? undefined;\n const trpcProcedures = extractTrpcProcedures(request.url);\n const shouldCaptureBody =\n !response.ok || (options.captureTrpcResponses && trpcProcedures !== undefined);\n const bodyText = shouldCaptureBody\n ? await readResponseSnippet(response, options.maxBodyChars)\n : undefined;\n const responseBytes = readContentLength(response.headers) ?? estimateUtf8Bytes(bodyText);\n\n return {\n timestamp,\n kind: \"fetch\",\n method: request.method,\n url: redactUrl(request.url),\n status: response.status,\n ok: response.ok,\n durationMs,\n responseBytes,\n contentType,\n requestTraceparent: request.traceparent,\n responseTraceparent: response.headers.get(\"traceparent\") ?? undefined,\n trpcProcedures,\n responseSnippet: bodyText,\n };\n}\n\nfunction readHeader(headers: HeadersInit | undefined, name: string): string | undefined {\n if (!headers) return undefined;\n if (headers instanceof Headers) return headers.get(name) ?? undefined;\n const lowerName = name.toLowerCase();\n if (Array.isArray(headers)) {\n const hit = headers.find(([key]) => key.toLowerCase() === lowerName);\n return hit?.[1];\n }\n const value = Object.entries(headers).find(([key]) => key.toLowerCase() === lowerName)?.[1];\n return value;\n}\n\nfunction readContentLength(headers: Headers): number | null {\n const raw = headers.get(\"content-length\");\n if (!raw) return null;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction estimateUtf8Bytes(value: string | undefined): number | null {\n if (value === undefined) return null;\n return new TextEncoder().encode(value).byteLength;\n}\n\nasync function readResponseSnippet(\n response: Response,\n maxChars: number,\n): Promise<string | undefined> {\n try {\n const text = await response.clone().text();\n if (text.length <= maxChars) return text;\n return `${text.slice(0, maxChars)}...[truncated ${String(text.length - maxChars)} chars]`;\n } catch {\n return undefined;\n }\n}\n\nfunction extractTrpcProcedures(rawUrl: string): string[] | undefined {\n let url: URL;\n try {\n url = new URL(rawUrl, \"https://placeholder.invalid\");\n } catch {\n return undefined;\n }\n const marker = \"/trpc/\";\n const index = url.pathname.indexOf(marker);\n if (index === -1) return undefined;\n const path = url.pathname.slice(index + marker.length);\n const procedures = path\n .split(\",\")\n .map((part) => decodeURIComponent(part).trim())\n .filter((part) => part.length > 0);\n return procedures.length > 0 ? procedures : undefined;\n}\n\nfunction redactUrl(rawUrl: string): string {\n let url: URL;\n try {\n url = new URL(rawUrl, \"https://placeholder.invalid\");\n } catch {\n return rawUrl;\n }\n for (const key of [...url.searchParams.keys()]) {\n const lower = key.toLowerCase();\n if (\n lower.includes(\"token\") ||\n lower.includes(\"key\") ||\n lower.includes(\"secret\") ||\n lower.includes(\"signature\") ||\n lower.includes(\"sig\")\n ) {\n url.searchParams.set(key, \"[redacted]\");\n }\n }\n return url.origin === \"https://placeholder.invalid\"\n ? `${url.pathname}${url.search}`\n : url.toString();\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\nfunction extractId(payload: unknown, key: \"incidentId\" | \"cardId\"): string | undefined {\n if (payload && typeof payload === \"object\" && key in payload) {\n const value = (payload as Record<string, unknown>)[key];\n if (value !== undefined && value !== null) return String(value);\n }\n return undefined;\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction formatErrorBody(status: number, statusText: string, body: unknown): string {\n const prefix = `${status} ${statusText}`.trim();\n if (body && typeof body === \"object\" && \"error\" in body) {\n return `${prefix}: ${String((body as { error: unknown }).error)}`;\n }\n if (typeof body === \"string\" && body.length > 0) {\n return `${prefix}: ${body}`;\n }\n return prefix;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(bytes).toString(\"base64\");\n }\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-incidents",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Typed TypeScript client for Conveyor's public incidents REST API",
5
5
  "keywords": [
6
6
  "client",
@@ -23,7 +23,7 @@
23
23
  "prepublishOnly": "tsup",
24
24
  "build": "tsup",
25
25
  "build:js": "tsup --no-dts --no-clean",
26
- "dev": "tsup --watch",
26
+ "dev": "tsup --watch --no-dts --no-clean",
27
27
  "test": "vitest run --passWithNoTests",
28
28
  "test:unit": "vitest run --passWithNoTests",
29
29
  "test:watch": "vitest",