datagrok-tools 6.1.11 → 6.1.12

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.
@@ -24,6 +24,10 @@ async function report(args) {
24
24
  return await handleResolve(args);
25
25
  case 'ticket':
26
26
  return await handleTicket(args);
27
+ case 'comment':
28
+ return await handleComment(args);
29
+ case 'label':
30
+ return await handleLabel(args);
27
31
  default:
28
32
  return false;
29
33
  }
@@ -103,7 +107,20 @@ async function handleFetch(args) {
103
107
  }
104
108
  }
105
109
  function looksLikePath(s) {
106
- return s.includes('/') || s.includes('\\') || /\.(zip|json)$/i.test(s) || _fs.default.existsSync(s);
110
+ if (s.includes('/') || s.includes('\\')) return true;
111
+ if (/\.(zip|json)$/i.test(s)) return true;
112
+ // Only treat a bare name as a path if it's an actual file. A *directory*
113
+ // collision (e.g. running `grok report read public 2147` from a checkout
114
+ // that has a `public/` submodule next to it) must not flip into the
115
+ // single-arg path branch — that would EISDIR on `readFileSync(p)`.
116
+ if (_fs.default.existsSync(s)) {
117
+ try {
118
+ return _fs.default.statSync(s).isFile();
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+ return false;
107
124
  }
108
125
  function loadFromZip(z) {
109
126
  const entries = z.getEntries();
@@ -202,7 +219,7 @@ function extractD42(zip, names, outDir) {
202
219
  recursive: true
203
220
  });
204
221
  const written = [];
205
- for (var name of names) {
222
+ for (const name of names) {
206
223
  const entry = zip.getEntry(name);
207
224
  if (entry == null) continue;
208
225
  const out = _path.default.join(outDir, _path.default.basename(name));
@@ -376,4 +393,132 @@ async function handleTicket(args) {
376
393
  color.error(`Error: ${err.message}`);
377
394
  return false;
378
395
  }
396
+ }
397
+
398
+ // ─── JIRA REST helpers (used by `grok report comment` / `grok report label`) ─
399
+ //
400
+ // These talk DIRECTLY to Atlassian Cloud REST v2 (not Datagrok). Auth is HTTP
401
+ // Basic with `JIRA_USER` (Atlassian email) + `JIRA_TOKEN` (API token from
402
+ // id.atlassian.com/manage-profile/security/api-tokens). Base URL defaults to
403
+ // the Datagrok org instance; override via --jira-url or $JIRA_URL.
404
+ //
405
+ // Why v2 and not v3: v3 requires comment bodies in ADF (Atlassian Document
406
+ // Format) JSON; v2 accepts plain text / wiki-markup. The handoff prompt emits
407
+ // markdown, which JIRA's plain-text path renders acceptably.
408
+
409
+ function resolveJiraBase(args) {
410
+ const cli = args['jira-url'] || '';
411
+ const env = process.env.JIRA_URL || '';
412
+ return (cli || env || 'https://reddata.atlassian.net').replace(/\/+$/, '');
413
+ }
414
+ function jiraAuthHeader() {
415
+ const user = process.env.JIRA_USER;
416
+ const token = process.env.JIRA_TOKEN;
417
+ if (!user || !token) return null;
418
+ return 'Basic ' + Buffer.from(`${user}:${token}`).toString('base64');
419
+ }
420
+ async function handleComment(args) {
421
+ const ticket = args._[2];
422
+ if (!ticket) {
423
+ color.error('Usage: grok report comment <ticket-key> [--body <text> | --body-file <path>] [--jira-url <url>]');
424
+ return false;
425
+ }
426
+ const auth = jiraAuthHeader();
427
+ if (auth == null) {
428
+ color.error('JIRA_USER and JIRA_TOKEN env vars are required for `grok report comment`.');
429
+ return false;
430
+ }
431
+ let body;
432
+ if (typeof args['body-file'] === 'string') {
433
+ const p = args['body-file'];
434
+ try {
435
+ body = _fs.default.readFileSync(p, 'utf-8');
436
+ } catch (e) {
437
+ color.error(`Failed to read --body-file ${p}: ${e.message}`);
438
+ return false;
439
+ }
440
+ } else if (typeof args['body'] === 'string') {
441
+ body = args['body'];
442
+ } else {
443
+ // Read from stdin if neither --body nor --body-file is provided.
444
+ body = _fs.default.readFileSync(0, 'utf-8');
445
+ }
446
+ if (!body || body.trim().length === 0) {
447
+ color.error('Comment body is empty (use --body, --body-file, or pipe to stdin).');
448
+ return false;
449
+ }
450
+ const base = resolveJiraBase(args);
451
+ const url = `${base}/rest/api/2/issue/${encodeURIComponent(ticket)}/comment`;
452
+ try {
453
+ const resp = await fetch(url, {
454
+ method: 'POST',
455
+ headers: {
456
+ Authorization: auth,
457
+ 'Content-Type': 'application/json',
458
+ Accept: 'application/json'
459
+ },
460
+ body: JSON.stringify({
461
+ body
462
+ })
463
+ });
464
+ if (resp.status !== 200 && resp.status !== 201) {
465
+ const text = await resp.text();
466
+ color.error(`JIRA comment POST failed (HTTP ${resp.status}): ${text.slice(0, 400)}`);
467
+ return false;
468
+ }
469
+ const result = await resp.json();
470
+ const id = result && (result.id || result.Id);
471
+ if (!id) {
472
+ color.error(`JIRA returned no comment id: ${JSON.stringify(result).slice(0, 200)}`);
473
+ return false;
474
+ }
475
+ color.success(`Posted comment ${id} on ${ticket}`);
476
+ console.log(id);
477
+ return true;
478
+ } catch (err) {
479
+ color.error(`Error: ${err.message}`);
480
+ return false;
481
+ }
482
+ }
483
+ async function handleLabel(args) {
484
+ const ticket = args._[2];
485
+ const labels = args._.slice(3).filter(s => s.length > 0);
486
+ if (!ticket || labels.length === 0) {
487
+ color.error('Usage: grok report label <ticket-key> <label> [<label2> ...] [--jira-url <url>]');
488
+ return false;
489
+ }
490
+ const auth = jiraAuthHeader();
491
+ if (auth == null) {
492
+ color.error('JIRA_USER and JIRA_TOKEN env vars are required for `grok report label`.');
493
+ return false;
494
+ }
495
+ const base = resolveJiraBase(args);
496
+ const url = `${base}/rest/api/2/issue/${encodeURIComponent(ticket)}`;
497
+ const update = {
498
+ labels: labels.map(l => ({
499
+ add: l
500
+ }))
501
+ };
502
+ try {
503
+ const resp = await fetch(url, {
504
+ method: 'PUT',
505
+ headers: {
506
+ Authorization: auth,
507
+ 'Content-Type': 'application/json'
508
+ },
509
+ body: JSON.stringify({
510
+ update
511
+ })
512
+ });
513
+ if (resp.status !== 204 && resp.status !== 200) {
514
+ const text = await resp.text();
515
+ color.error(`JIRA label PUT failed (HTTP ${resp.status}): ${text.slice(0, 400)}`);
516
+ return false;
517
+ }
518
+ color.success(`Applied labels to ${ticket}: ${labels.join(', ')}`);
519
+ return true;
520
+ } catch (err) {
521
+ color.error(`Error: ${err.message}`);
522
+ return false;
523
+ }
379
524
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "6.1.11",
3
+ "version": "6.1.12",
4
4
  "description": "Utility to upload and publish packages to Datagrok",
5
5
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
6
6
  "dependencies": {