@rallycry/conveyor-incidents 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,24 @@ await incidents.report({
73
73
  });
74
74
  ```
75
75
 
76
+ ### Creator attribution (`createdBy`)
77
+
78
+ `createdBy` is an optional single email, matched against the target project's members the same way as `assigned`/`review`:
79
+
80
+ - A resolvable project-member email becomes the card's creator.
81
+ - When omitted — or when the email doesn't resolve to a member — the card is attributed to the **"Conveyor Incidents" system user**, not a human.
82
+ - Applied **only when a new card is created**; dedup hits keep the existing creator.
83
+
84
+ > **Behavior change in 0.3.0:** previously, cards reported through this endpoint were attributed to the token owner (suggestions/tasks) or the project owner (incidents). Pass `createdBy` explicitly if you want a specific member credited.
85
+
86
+ ```ts
87
+ await incidents.report({
88
+ type: "task",
89
+ title: "Wire up the new webhook",
90
+ createdBy: "logan@rallycry.gg",
91
+ });
92
+ ```
93
+
76
94
  ### About `source` and `fingerprintKey`
77
95
 
78
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.
@@ -114,6 +132,7 @@ client.report({
114
132
  type?: "incident" | "suggestion" | "task"; // default: "incident"
115
133
  assigned?: string[]; // project-member emails; first match becomes assignee
116
134
  review?: string[]; // project-member emails; every match becomes a reviewer
135
+ createdBy?: string; // project-member email; no match → "Conveyor Incidents" system user
117
136
  attachments?: Array<{
118
137
  fileName: string;
119
138
  mimeType: string;
package/dist/index.d.ts CHANGED
@@ -39,6 +39,12 @@ interface ReportIncidentInput {
39
39
  * skipped.
40
40
  */
41
41
  review?: string[];
42
+ /**
43
+ * Project-member email used as the card's creator. When omitted — or when
44
+ * the email doesn't resolve to a member of the target project — the card
45
+ * is attributed to the "Conveyor Incidents" system user.
46
+ */
47
+ createdBy?: string;
42
48
  }
43
49
  interface AttachmentResult {
44
50
  fileName: string;
package/dist/index.js CHANGED
@@ -46,7 +46,8 @@ var ConveyorIncidents = class {
46
46
  fingerprintKey: input.fingerprintKey,
47
47
  type: input.type,
48
48
  assigned: input.assigned,
49
- review: input.review
49
+ review: input.review,
50
+ createdBy: input.createdBy
50
51
  };
51
52
  const res = await this.fetchImpl(`${this.baseUrl}/api/incidents/report`, {
52
53
  method: "POST",
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\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 };\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":"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","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\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":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rallycry/conveyor-incidents",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Typed TypeScript client for Conveyor's public incidents REST API",
5
5
  "keywords": [
6
6
  "client",