@goliapkg/sentori-cli 0.3.0 → 0.4.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/lib/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from 'node:util';
3
+ import { formatIssueLine, issueList, issuePatch } from './issue.js';
3
4
  import { reactNativeUpload } from './react-native.js';
4
5
  import { uploadSourcemaps } from './upload.js';
5
- const HELP = `sentori-cli — upload release artifacts to Sentori
6
+ const HELP = `sentori-cli — Sentori command-line interface
6
7
 
7
- Usage:
8
+ Source-map upload:
8
9
  sentori-cli upload sourcemap [options] <path...>
9
10
  Upload one or more files or directories. A directory is scanned
10
11
  (one level) for *.map / *.js / *.jsbundle / *.bundle / *.hbc;
@@ -16,14 +17,18 @@ Usage:
16
17
  and upload it (plus the bundle). Use this for a Hermes release
17
18
  build. Requires --metro-map and --hermes-map.
18
19
 
19
- Options (both commands):
20
+ CI triage:
21
+ sentori-cli issue list --project <uuid> [--status active|silenced|resolved|closed] [--limit N] [--error-type <t>]
22
+ sentori-cli issue resolve <issue-uuid> --project <uuid> [--in-release <r>]
23
+ sentori-cli issue silence <issue-uuid> --project <uuid>
24
+
25
+ Options (upload commands):
20
26
  --release <r> release identifier — MUST equal the value the SDK
21
- reports via init({ release }). Required. A mismatch
22
- means the dashboard silently can't symbolicate.
27
+ reports via init({ release }). Required.
23
28
  --token <t> Sentori token (or set $SENTORI_TOKEN).
24
29
  --api-url <url> Sentori API base (default https://api.sentori.golia.jp,
25
- or $SENTORI_API_URL). For a self-hosted instance,
26
- your host. (Accepts --ingest-url as an alias.)
30
+ or $SENTORI_API_URL). For a self-hosted instance, your
31
+ host. (Accepts --ingest-url as an alias.)
27
32
  --dry-run describe what would be uploaded; don't upload.
28
33
  -h, --help show this help.
29
34
 
@@ -32,6 +37,16 @@ Options (react-native upload):
32
37
  --hermes-map <p> the *.hbc.map the Hermes compiler emits.
33
38
  --bundle <p> optional: also upload the bundle (.jsbundle / .bundle).
34
39
 
40
+ Options (issue commands):
41
+ --project <uuid> project id (or set $SENTORI_PROJECT_ID).
42
+ --token <t> admin token, sk_… prefix (or $SENTORI_ADMIN_TOKEN /
43
+ $SENTORI_TOKEN). The ingest st_pk_ token may also work
44
+ on a self-hosted instance.
45
+ --api-url <url> Sentori API base (same as above).
46
+ --in-release <r> (resolve only) mark this release as where the fix
47
+ landed; the regression detector flips the issue back
48
+ to "regressed" if a matching event lands later.
49
+
35
50
  Hermes release build, by hand:
36
51
  npx react-native bundle --platform ios --dev false --entry-file index.js \\
37
52
  --bundle-output main.jsbundle --sourcemap-output main.jsbundle.packager.map
@@ -172,6 +187,126 @@ function reportUpload(result, c) {
172
187
  console.log(`uploaded ${result.uploaded ?? result.files.length} file(s) for release "${c.release}" — minified stacks on this release will now resolve to source.`);
173
188
  }
174
189
  }
190
+ function parseAdminCfg(values) {
191
+ const projectId = (typeof values.project === 'string' ? values.project : undefined) ??
192
+ process.env.SENTORI_PROJECT_ID;
193
+ if (!projectId) {
194
+ console.error('error: --project <uuid> (or $SENTORI_PROJECT_ID) is required');
195
+ return null;
196
+ }
197
+ const token = (typeof values.token === 'string' ? values.token : undefined) ??
198
+ process.env.SENTORI_ADMIN_TOKEN ??
199
+ process.env.SENTORI_TOKEN;
200
+ if (!token) {
201
+ console.error('error: --token (or $SENTORI_ADMIN_TOKEN / $SENTORI_TOKEN) is required for issue commands');
202
+ return null;
203
+ }
204
+ const apiUrl = (typeof values['api-url'] === 'string' ? values['api-url'] : undefined) ??
205
+ (typeof values['ingest-url'] === 'string' ? values['ingest-url'] : undefined) ??
206
+ process.env.SENTORI_API_URL ??
207
+ 'https://api.sentori.golia.jp';
208
+ return { apiUrl, projectId, token };
209
+ }
210
+ async function cmdIssueList(argv) {
211
+ let parsed;
212
+ try {
213
+ parsed = parseArgs({
214
+ args: argv,
215
+ options: {
216
+ 'api-url': { type: 'string' },
217
+ 'error-type': { type: 'string' },
218
+ help: { short: 'h', type: 'boolean' },
219
+ 'ingest-url': { type: 'string' },
220
+ limit: { type: 'string' },
221
+ project: { type: 'string' },
222
+ status: { type: 'string' },
223
+ token: { type: 'string' },
224
+ },
225
+ });
226
+ }
227
+ catch (e) {
228
+ console.error(`error: ${e.message}\n${HELP}`);
229
+ return 2;
230
+ }
231
+ if (parsed.values.help) {
232
+ console.log(HELP);
233
+ return 0;
234
+ }
235
+ const cfg = parseAdminCfg(parsed.values);
236
+ if (!cfg)
237
+ return 2;
238
+ const status = parsed.values.status;
239
+ if (status && !['active', 'closed', 'resolved', 'silenced'].includes(status)) {
240
+ console.error(`error: --status must be one of: active, silenced, resolved, closed`);
241
+ return 2;
242
+ }
243
+ const limitStr = parsed.values.limit;
244
+ const limit = limitStr ? Number.parseInt(limitStr, 10) : undefined;
245
+ try {
246
+ const rows = await issueList({
247
+ config: cfg,
248
+ errorType: parsed.values['error-type'],
249
+ limit,
250
+ status: status,
251
+ });
252
+ if (rows.length === 0) {
253
+ console.log('(no matching issues)');
254
+ return 0;
255
+ }
256
+ for (const r of rows)
257
+ console.log(formatIssueLine(r));
258
+ return 0;
259
+ }
260
+ catch (e) {
261
+ console.error(`issue list failed: ${e.message}`);
262
+ return 1;
263
+ }
264
+ }
265
+ async function cmdIssuePatch(argv, body, verb) {
266
+ let parsed;
267
+ try {
268
+ parsed = parseArgs({
269
+ allowPositionals: true,
270
+ args: argv,
271
+ options: {
272
+ 'api-url': { type: 'string' },
273
+ help: { short: 'h', type: 'boolean' },
274
+ 'in-release': { type: 'string' },
275
+ 'ingest-url': { type: 'string' },
276
+ project: { type: 'string' },
277
+ token: { type: 'string' },
278
+ },
279
+ });
280
+ }
281
+ catch (e) {
282
+ console.error(`error: ${e.message}\n${HELP}`);
283
+ return 2;
284
+ }
285
+ if (parsed.values.help) {
286
+ console.log(HELP);
287
+ return 0;
288
+ }
289
+ const cfg = parseAdminCfg(parsed.values);
290
+ if (!cfg)
291
+ return 2;
292
+ const issueId = parsed.positionals[0];
293
+ if (!issueId) {
294
+ console.error('error: <issue-uuid> is required');
295
+ return 2;
296
+ }
297
+ if (verb === 'resolved' && typeof parsed.values['in-release'] === 'string') {
298
+ body.resolvedInRelease = parsed.values['in-release'];
299
+ }
300
+ try {
301
+ const updated = await issuePatch(cfg, issueId, body);
302
+ console.log(`${issueId} → ${verb}${body.resolvedInRelease ? ` (in ${body.resolvedInRelease})` : ''}: ${updated.errorType}`);
303
+ return 0;
304
+ }
305
+ catch (e) {
306
+ console.error(`issue ${verb} failed: ${e.message}`);
307
+ return 1;
308
+ }
309
+ }
175
310
  async function main(argv) {
176
311
  if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
177
312
  console.log(HELP);
@@ -182,6 +317,14 @@ async function main(argv) {
182
317
  return cmdUploadSourcemap(rest);
183
318
  if (a === 'react-native' && b === 'upload')
184
319
  return cmdReactNativeUpload(rest);
320
+ if (a === 'issue' && b === 'list')
321
+ return cmdIssueList(rest);
322
+ if (a === 'issue' && b === 'resolve')
323
+ return cmdIssuePatch(rest, { status: 'resolved' }, 'resolved');
324
+ if (a === 'issue' && b === 'silence')
325
+ return cmdIssuePatch(rest, { status: 'silenced' }, 'silenced');
326
+ if (a === 'issue' && b === 'close')
327
+ return cmdIssuePatch(rest, { status: 'closed' }, 'closed');
185
328
  console.error(`unknown command: ${[a, b].filter(Boolean).join(' ') || '(none)'}\n`);
186
329
  console.error(HELP);
187
330
  return 2;
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/issue.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ type Issue = {
2
+ errorType: string;
3
+ eventCount: number;
4
+ id: string;
5
+ lastSeen: string;
6
+ messageSample: string;
7
+ status: 'active' | 'closed' | 'resolved' | 'silenced';
8
+ };
9
+ type AdminConfig = {
10
+ apiUrl: string;
11
+ projectId: string;
12
+ token: string;
13
+ };
14
+ export type IssueListOptions = {
15
+ config: AdminConfig;
16
+ errorType?: string;
17
+ limit?: number;
18
+ status?: 'active' | 'closed' | 'resolved' | 'silenced';
19
+ };
20
+ export declare function issueList(opts: IssueListOptions): Promise<Issue[]>;
21
+ export declare function issuePatch(config: AdminConfig, issueId: string, body: {
22
+ resolvedInRelease?: string;
23
+ status: 'active' | 'closed' | 'resolved' | 'silenced';
24
+ }): Promise<Issue>;
25
+ /** Format one issue for terminal output — short, one line, scannable. */
26
+ export declare function formatIssueLine(i: Issue): string;
27
+ export {};
28
+ //# sourceMappingURL=issue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"AAEA,KAAK,KAAK,GAAG;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;CACtD,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAgCD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;CACvD,CAAA;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAOxE;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAA;CAAE,GAC1F,OAAO,CAAC,KAAK,CAAC,CAKhB;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAKhD"}
package/lib/issue.js ADDED
@@ -0,0 +1,53 @@
1
+ // `sentori-cli issue list / resolve / silence` — CI triage helpers.
2
+ function url(c, path) {
3
+ return `${c.apiUrl.replace(/\/+$/, '')}/admin/api/projects/${c.projectId}${path}`;
4
+ }
5
+ async function adminFetch(c, path, init) {
6
+ const resp = await fetch(url(c, path), {
7
+ ...init,
8
+ headers: {
9
+ Authorization: `Bearer ${c.token}`,
10
+ 'Content-Type': 'application/json',
11
+ ...(init?.headers ?? {}),
12
+ },
13
+ });
14
+ if (!resp.ok) {
15
+ let detail = '';
16
+ try {
17
+ detail = await resp.text();
18
+ }
19
+ catch {
20
+ // ignore
21
+ }
22
+ throw new Error(`${resp.status} ${resp.statusText}${detail ? ` — ${detail.slice(0, 300)}` : ''}`);
23
+ }
24
+ // PATCH /issues/<id> returns the row; some endpoints might return no
25
+ // content — handle both.
26
+ const txt = await resp.text();
27
+ return (txt ? JSON.parse(txt) : null);
28
+ }
29
+ export async function issueList(opts) {
30
+ const q = new URLSearchParams();
31
+ if (opts.status)
32
+ q.set('status', opts.status);
33
+ if (opts.limit)
34
+ q.set('limit', String(opts.limit));
35
+ if (opts.errorType)
36
+ q.set('errorType', opts.errorType);
37
+ const qs = q.toString();
38
+ return adminFetch(opts.config, `/issues${qs ? '?' + qs : ''}`);
39
+ }
40
+ export async function issuePatch(config, issueId, body) {
41
+ return adminFetch(config, `/issues/${encodeURIComponent(issueId)}`, {
42
+ body: JSON.stringify(body),
43
+ method: 'PATCH',
44
+ });
45
+ }
46
+ /** Format one issue for terminal output — short, one line, scannable. */
47
+ export function formatIssueLine(i) {
48
+ const status = i.status.padEnd(9);
49
+ const title = `${i.errorType}${i.messageSample ? `: ${i.messageSample}` : ''}`;
50
+ const events = `${i.eventCount}×`;
51
+ return `${i.id} ${status} ${title.slice(0, 80).padEnd(80)} ${events}`;
52
+ }
53
+ //# sourceMappingURL=issue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue.js","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goliapkg/sentori-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Sentori CLI \u2014 upload source maps so dashboard stack traces resolve to your original source.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentori.golia.jp",
@@ -0,0 +1,95 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+
3
+ import { formatIssueLine, issueList, issuePatch } from '../issue.js'
4
+
5
+ const cfg = {
6
+ apiUrl: 'https://api.example.com/',
7
+ projectId: 'proj-1',
8
+ token: 'sk_test',
9
+ }
10
+
11
+ const sample = {
12
+ errorType: 'TypeError',
13
+ eventCount: 17,
14
+ id: 'issue-1',
15
+ lastSeen: '2026-05-13T00:00:00Z',
16
+ messageSample: 'undefined is not an object',
17
+ status: 'active' as const,
18
+ }
19
+
20
+ const origFetch = globalThis.fetch
21
+ let calls: { body: string | undefined; headers: Headers; method: string; url: string }[]
22
+ beforeEach(() => {
23
+ calls = []
24
+ })
25
+ afterEach(() => {
26
+ globalThis.fetch = origFetch
27
+ })
28
+
29
+ function mockFetch(respBody: string, init: ResponseInit = { status: 200 }): void {
30
+ globalThis.fetch = (async (url: Request | string | URL, opts?: RequestInit) => {
31
+ calls.push({
32
+ body: typeof opts?.body === 'string' ? opts.body : undefined,
33
+ headers: new Headers(opts?.headers),
34
+ method: opts?.method ?? 'GET',
35
+ url: String(url),
36
+ })
37
+ return new Response(respBody, {
38
+ headers: { 'content-type': 'application/json' },
39
+ ...init,
40
+ })
41
+ }) as typeof fetch
42
+ }
43
+
44
+ describe('issueList', () => {
45
+ test('GETs /admin/api/projects/<id>/issues with query params + Bearer', async () => {
46
+ mockFetch(JSON.stringify([sample]))
47
+ const rows = await issueList({ config: cfg, limit: 20, status: 'active' })
48
+ expect(calls).toHaveLength(1)
49
+ expect(calls[0]?.method).toBe('GET')
50
+ expect(calls[0]?.url).toBe(
51
+ 'https://api.example.com/admin/api/projects/proj-1/issues?status=active&limit=20',
52
+ )
53
+ expect(calls[0]?.headers.get('authorization')).toBe('Bearer sk_test')
54
+ expect(rows).toHaveLength(1)
55
+ expect(rows[0]?.id).toBe('issue-1')
56
+ })
57
+
58
+ test('omits empty query params', async () => {
59
+ mockFetch(JSON.stringify([]))
60
+ await issueList({ config: cfg })
61
+ expect(calls[0]?.url).toBe('https://api.example.com/admin/api/projects/proj-1/issues')
62
+ })
63
+
64
+ test('throws with server detail on non-2xx', async () => {
65
+ mockFetch('forbidden', { status: 403, statusText: 'Forbidden' })
66
+ await expect(issueList({ config: cfg })).rejects.toThrow('403 Forbidden — forbidden')
67
+ })
68
+ })
69
+
70
+ describe('issuePatch', () => {
71
+ test('PATCHes /issues/<id> with the body', async () => {
72
+ mockFetch(JSON.stringify({ ...sample, status: 'resolved' }))
73
+ const updated = await issuePatch(cfg, 'issue-1', {
74
+ resolvedInRelease: 'app@1.2.4+457',
75
+ status: 'resolved',
76
+ })
77
+ expect(calls[0]?.method).toBe('PATCH')
78
+ expect(calls[0]?.url).toBe('https://api.example.com/admin/api/projects/proj-1/issues/issue-1')
79
+ expect(JSON.parse(calls[0]?.body ?? '{}')).toEqual({
80
+ resolvedInRelease: 'app@1.2.4+457',
81
+ status: 'resolved',
82
+ })
83
+ expect(updated.status).toBe('resolved')
84
+ })
85
+ })
86
+
87
+ describe('formatIssueLine', () => {
88
+ test('renders id + status + title + event count on one line', () => {
89
+ const line = formatIssueLine(sample)
90
+ expect(line).toContain('issue-1')
91
+ expect(line).toContain('active')
92
+ expect(line).toContain('TypeError: undefined is not an object')
93
+ expect(line).toContain('17×')
94
+ })
95
+ })
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from 'node:util'
3
3
 
4
+ import { formatIssueLine, issueList, issuePatch } from './issue.js'
4
5
  import { reactNativeUpload } from './react-native.js'
5
6
  import { uploadSourcemaps } from './upload.js'
6
7
 
7
- const HELP = `sentori-cli — upload release artifacts to Sentori
8
+ const HELP = `sentori-cli — Sentori command-line interface
8
9
 
9
- Usage:
10
+ Source-map upload:
10
11
  sentori-cli upload sourcemap [options] <path...>
11
12
  Upload one or more files or directories. A directory is scanned
12
13
  (one level) for *.map / *.js / *.jsbundle / *.bundle / *.hbc;
@@ -18,14 +19,18 @@ Usage:
18
19
  and upload it (plus the bundle). Use this for a Hermes release
19
20
  build. Requires --metro-map and --hermes-map.
20
21
 
21
- Options (both commands):
22
+ CI triage:
23
+ sentori-cli issue list --project <uuid> [--status active|silenced|resolved|closed] [--limit N] [--error-type <t>]
24
+ sentori-cli issue resolve <issue-uuid> --project <uuid> [--in-release <r>]
25
+ sentori-cli issue silence <issue-uuid> --project <uuid>
26
+
27
+ Options (upload commands):
22
28
  --release <r> release identifier — MUST equal the value the SDK
23
- reports via init({ release }). Required. A mismatch
24
- means the dashboard silently can't symbolicate.
29
+ reports via init({ release }). Required.
25
30
  --token <t> Sentori token (or set $SENTORI_TOKEN).
26
31
  --api-url <url> Sentori API base (default https://api.sentori.golia.jp,
27
- or $SENTORI_API_URL). For a self-hosted instance,
28
- your host. (Accepts --ingest-url as an alias.)
32
+ or $SENTORI_API_URL). For a self-hosted instance, your
33
+ host. (Accepts --ingest-url as an alias.)
29
34
  --dry-run describe what would be uploaded; don't upload.
30
35
  -h, --help show this help.
31
36
 
@@ -34,6 +39,16 @@ Options (react-native upload):
34
39
  --hermes-map <p> the *.hbc.map the Hermes compiler emits.
35
40
  --bundle <p> optional: also upload the bundle (.jsbundle / .bundle).
36
41
 
42
+ Options (issue commands):
43
+ --project <uuid> project id (or set $SENTORI_PROJECT_ID).
44
+ --token <t> admin token, sk_… prefix (or $SENTORI_ADMIN_TOKEN /
45
+ $SENTORI_TOKEN). The ingest st_pk_ token may also work
46
+ on a self-hosted instance.
47
+ --api-url <url> Sentori API base (same as above).
48
+ --in-release <r> (resolve only) mark this release as where the fix
49
+ landed; the regression detector flips the issue back
50
+ to "regressed" if a matching event lands later.
51
+
37
52
  Hermes release build, by hand:
38
53
  npx react-native bundle --platform ios --dev false --entry-file index.js \\
39
54
  --bundle-output main.jsbundle --sourcemap-output main.jsbundle.packager.map
@@ -182,6 +197,137 @@ function reportUpload(
182
197
  }
183
198
  }
184
199
 
200
+ // ── issue commands ────────────────────────────────────────────────
201
+
202
+ type AdminCfg = { apiUrl: string; projectId: string; token: string }
203
+
204
+ function parseAdminCfg(values: Record<string, unknown>): AdminCfg | null {
205
+ const projectId =
206
+ (typeof values.project === 'string' ? values.project : undefined) ??
207
+ process.env.SENTORI_PROJECT_ID
208
+ if (!projectId) {
209
+ console.error('error: --project <uuid> (or $SENTORI_PROJECT_ID) is required')
210
+ return null
211
+ }
212
+ const token =
213
+ (typeof values.token === 'string' ? values.token : undefined) ??
214
+ process.env.SENTORI_ADMIN_TOKEN ??
215
+ process.env.SENTORI_TOKEN
216
+ if (!token) {
217
+ console.error(
218
+ 'error: --token (or $SENTORI_ADMIN_TOKEN / $SENTORI_TOKEN) is required for issue commands',
219
+ )
220
+ return null
221
+ }
222
+ const apiUrl =
223
+ (typeof values['api-url'] === 'string' ? values['api-url'] : undefined) ??
224
+ (typeof values['ingest-url'] === 'string' ? values['ingest-url'] : undefined) ??
225
+ process.env.SENTORI_API_URL ??
226
+ 'https://api.sentori.golia.jp'
227
+ return { apiUrl, projectId, token }
228
+ }
229
+
230
+ async function cmdIssueList(argv: string[]): Promise<number> {
231
+ let parsed
232
+ try {
233
+ parsed = parseArgs({
234
+ args: argv,
235
+ options: {
236
+ 'api-url': { type: 'string' },
237
+ 'error-type': { type: 'string' },
238
+ help: { short: 'h', type: 'boolean' },
239
+ 'ingest-url': { type: 'string' },
240
+ limit: { type: 'string' },
241
+ project: { type: 'string' },
242
+ status: { type: 'string' },
243
+ token: { type: 'string' },
244
+ },
245
+ })
246
+ } catch (e) {
247
+ console.error(`error: ${(e as Error).message}\n${HELP}`)
248
+ return 2
249
+ }
250
+ if (parsed.values.help) {
251
+ console.log(HELP)
252
+ return 0
253
+ }
254
+ const cfg = parseAdminCfg(parsed.values)
255
+ if (!cfg) return 2
256
+ const status = parsed.values.status
257
+ if (status && !['active', 'closed', 'resolved', 'silenced'].includes(status)) {
258
+ console.error(`error: --status must be one of: active, silenced, resolved, closed`)
259
+ return 2
260
+ }
261
+ const limitStr = parsed.values.limit
262
+ const limit = limitStr ? Number.parseInt(limitStr, 10) : undefined
263
+ try {
264
+ const rows = await issueList({
265
+ config: cfg,
266
+ errorType: parsed.values['error-type'],
267
+ limit,
268
+ status: status as 'active' | 'closed' | 'resolved' | 'silenced' | undefined,
269
+ })
270
+ if (rows.length === 0) {
271
+ console.log('(no matching issues)')
272
+ return 0
273
+ }
274
+ for (const r of rows) console.log(formatIssueLine(r))
275
+ return 0
276
+ } catch (e) {
277
+ console.error(`issue list failed: ${(e as Error).message}`)
278
+ return 1
279
+ }
280
+ }
281
+
282
+ async function cmdIssuePatch(
283
+ argv: string[],
284
+ body: { resolvedInRelease?: string; status: 'active' | 'closed' | 'resolved' | 'silenced' },
285
+ verb: 'closed' | 'resolved' | 'silenced',
286
+ ): Promise<number> {
287
+ let parsed
288
+ try {
289
+ parsed = parseArgs({
290
+ allowPositionals: true,
291
+ args: argv,
292
+ options: {
293
+ 'api-url': { type: 'string' },
294
+ help: { short: 'h', type: 'boolean' },
295
+ 'in-release': { type: 'string' },
296
+ 'ingest-url': { type: 'string' },
297
+ project: { type: 'string' },
298
+ token: { type: 'string' },
299
+ },
300
+ })
301
+ } catch (e) {
302
+ console.error(`error: ${(e as Error).message}\n${HELP}`)
303
+ return 2
304
+ }
305
+ if (parsed.values.help) {
306
+ console.log(HELP)
307
+ return 0
308
+ }
309
+ const cfg = parseAdminCfg(parsed.values)
310
+ if (!cfg) return 2
311
+ const issueId = parsed.positionals[0]
312
+ if (!issueId) {
313
+ console.error('error: <issue-uuid> is required')
314
+ return 2
315
+ }
316
+ if (verb === 'resolved' && typeof parsed.values['in-release'] === 'string') {
317
+ body.resolvedInRelease = parsed.values['in-release']
318
+ }
319
+ try {
320
+ const updated = await issuePatch(cfg, issueId, body)
321
+ console.log(
322
+ `${issueId} → ${verb}${body.resolvedInRelease ? ` (in ${body.resolvedInRelease})` : ''}: ${updated.errorType}`,
323
+ )
324
+ return 0
325
+ } catch (e) {
326
+ console.error(`issue ${verb} failed: ${(e as Error).message}`)
327
+ return 1
328
+ }
329
+ }
330
+
185
331
  async function main(argv: string[]): Promise<number> {
186
332
  if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') {
187
333
  console.log(HELP)
@@ -190,6 +336,10 @@ async function main(argv: string[]): Promise<number> {
190
336
  const [a, b, ...rest] = argv
191
337
  if (a === 'upload' && b === 'sourcemap') return cmdUploadSourcemap(rest)
192
338
  if (a === 'react-native' && b === 'upload') return cmdReactNativeUpload(rest)
339
+ if (a === 'issue' && b === 'list') return cmdIssueList(rest)
340
+ if (a === 'issue' && b === 'resolve') return cmdIssuePatch(rest, { status: 'resolved' }, 'resolved')
341
+ if (a === 'issue' && b === 'silence') return cmdIssuePatch(rest, { status: 'silenced' }, 'silenced')
342
+ if (a === 'issue' && b === 'close') return cmdIssuePatch(rest, { status: 'closed' }, 'closed')
193
343
  console.error(`unknown command: ${[a, b].filter(Boolean).join(' ') || '(none)'}\n`)
194
344
  console.error(HELP)
195
345
  return 2
package/src/issue.ts ADDED
@@ -0,0 +1,81 @@
1
+ // `sentori-cli issue list / resolve / silence` — CI triage helpers.
2
+
3
+ type Issue = {
4
+ errorType: string
5
+ eventCount: number
6
+ id: string
7
+ lastSeen: string
8
+ messageSample: string
9
+ status: 'active' | 'closed' | 'resolved' | 'silenced'
10
+ }
11
+
12
+ type AdminConfig = {
13
+ apiUrl: string
14
+ projectId: string
15
+ token: string
16
+ }
17
+
18
+ function url(c: AdminConfig, path: string): string {
19
+ return `${c.apiUrl.replace(/\/+$/, '')}/admin/api/projects/${c.projectId}${path}`
20
+ }
21
+
22
+ async function adminFetch<T>(c: AdminConfig, path: string, init?: RequestInit): Promise<T> {
23
+ const resp = await fetch(url(c, path), {
24
+ ...init,
25
+ headers: {
26
+ Authorization: `Bearer ${c.token}`,
27
+ 'Content-Type': 'application/json',
28
+ ...(init?.headers ?? {}),
29
+ },
30
+ })
31
+ if (!resp.ok) {
32
+ let detail = ''
33
+ try {
34
+ detail = await resp.text()
35
+ } catch {
36
+ // ignore
37
+ }
38
+ throw new Error(
39
+ `${resp.status} ${resp.statusText}${detail ? ` — ${detail.slice(0, 300)}` : ''}`,
40
+ )
41
+ }
42
+ // PATCH /issues/<id> returns the row; some endpoints might return no
43
+ // content — handle both.
44
+ const txt = await resp.text()
45
+ return (txt ? JSON.parse(txt) : null) as T
46
+ }
47
+
48
+ export type IssueListOptions = {
49
+ config: AdminConfig
50
+ errorType?: string
51
+ limit?: number
52
+ status?: 'active' | 'closed' | 'resolved' | 'silenced'
53
+ }
54
+
55
+ export async function issueList(opts: IssueListOptions): Promise<Issue[]> {
56
+ const q = new URLSearchParams()
57
+ if (opts.status) q.set('status', opts.status)
58
+ if (opts.limit) q.set('limit', String(opts.limit))
59
+ if (opts.errorType) q.set('errorType', opts.errorType)
60
+ const qs = q.toString()
61
+ return adminFetch<Issue[]>(opts.config, `/issues${qs ? '?' + qs : ''}`)
62
+ }
63
+
64
+ export async function issuePatch(
65
+ config: AdminConfig,
66
+ issueId: string,
67
+ body: { resolvedInRelease?: string; status: 'active' | 'closed' | 'resolved' | 'silenced' },
68
+ ): Promise<Issue> {
69
+ return adminFetch<Issue>(config, `/issues/${encodeURIComponent(issueId)}`, {
70
+ body: JSON.stringify(body),
71
+ method: 'PATCH',
72
+ })
73
+ }
74
+
75
+ /** Format one issue for terminal output — short, one line, scannable. */
76
+ export function formatIssueLine(i: Issue): string {
77
+ const status = i.status.padEnd(9)
78
+ const title = `${i.errorType}${i.messageSample ? `: ${i.messageSample}` : ''}`
79
+ const events = `${i.eventCount}×`
80
+ return `${i.id} ${status} ${title.slice(0, 80).padEnd(80)} ${events}`
81
+ }