datagrok-tools 6.5.10 → 6.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Datagrok-tools changelog
2
2
 
3
+ ## 6.6.0 (WIP)
4
+
5
+ * The developer key is sent in the `Authorization` header (`Dev <key>`) instead of the URL path. `POST /users/login/dev/<key>` and `POST /packages/dev/<key>/<package>` put a long-lived credential into every nginx access log, proxy log and shell history along the way; the key-less routes (`/users/login/dev`, `/packages/dev/<package>`) take it as a header. Servers that predate the header form answer 404/401 and the old URL is used instead, so publishing to an older server still works - but a server from 1.28 on rejects the URL form and asks for datagrok-tools 6.6.0 or later.
6
+
3
7
  ## 6.5.10 (2026-09-11)
4
8
 
5
9
  * `grok s pull` — a bundle file name is capped at 200 bytes plus a digest of the full name, so it stays unique and stable across pulls. A nested view under a long space spells a longer name than a path component may hold (255 bytes on ext4 and NTFS): one TWIG snapshot reached 268 and the write failed with `ENAMETOOLONG`, taking down the whole part rather than the entity — a `--by-namespace` part that only touched the space as a dependency died with it.
package/CLAUDE.md CHANGED
@@ -94,7 +94,9 @@ The `publish` command executes these steps:
94
94
  3. Process environment variables in `/connections/*.json` files (replace `${VAR}`)
95
95
  4. **Process Docker images** (see below)
96
96
  5. Create ZIP archive with archiver-promise (includes `image.json` metadata per container)
97
- 6. Upload to server: `POST ${host}/packages/dev/${devKey}/${packageName}`
97
+ 6. Upload to server: `POST ${host}/packages/dev/${packageName}` with `Authorization: Dev ${devKey}`
98
+ (older servers only know `POST ${host}/packages/dev/${devKey}/${packageName}`; `devKeyFetch` in
99
+ `bin/utils/dev-key.ts` falls back to it on 404/401)
98
100
 
99
101
  **Key flags:**
100
102
  - `--debug` (default) - Package visible only to developer
package/README.md CHANGED
@@ -100,6 +100,11 @@ For more information on configuring connections, refer to the [Connections](http
100
100
  - `--suffix`: a string containing package version hash
101
101
 
102
102
  Running `grok publish` is the same as running `grok publish defaultHost --build --debug`.
103
+ - `report` works with user error reports on a Datagrok instance (the instance is a server alias from
104
+ `config.yaml`): `grok report fetch <instance> <number>` downloads the report zip, `grok report read
105
+ <zip|json|instance number>` prints one normalized JSON object (flags `--extract-screenshot`,
106
+ `--extract-d42`, `--extract-client-log`), `grok report resolve <instance> <number>` marks it resolved.
107
+ `ticket`, `comment`, `label`, `attach` talk to JIRA with `JIRA_TOKEN`. See `grok report --help`.
103
108
  - `check` checks package content (function signatures, import statements of external modules,
104
109
  etc.). The check is also run during package publication.
105
110
  - `init` modifies a package template by adding config files for linters, IDE, and so on
@@ -72,7 +72,8 @@ async function processPackage() {
72
72
  let timestamps = {};
73
73
  if (debug) {
74
74
  try {
75
- timestamps = await (await fetch(`${host}/packages/dev/${devKey}/${packageName}/timestamps`)).json();
75
+ timestamps = await (await fetch(`${host}/packages/dev/${packageName}/timestamps`,
76
+ {headers: {Authorization: `Dev ${devKey}`}})).json();
76
77
  if (timestamps['#type'] === 'ApiError') {
77
78
  console.log(timestamps.message);
78
79
  return 1;
@@ -134,8 +135,9 @@ async function processPackage() {
134
135
 
135
136
  //upload
136
137
  const uploadPromise = new Promise((resolve, reject) => {
137
- fetch(`${host}/packages/dev/${devKey}/${packageName}?debug=${debug.toString()}&rebuild=${rebuild.toString()}`, {
138
+ fetch(`${host}/packages/dev/${packageName}?debug=${debug.toString()}&rebuild=${rebuild.toString()}`, {
138
139
  method: 'POST',
140
+ headers: {Authorization: `Dev ${devKey}`},
139
141
  body: zip,
140
142
  }).then((body) => body.json()).then((j) => resolve(j)).catch((err) => {
141
143
  reject(err);
@@ -371,10 +371,17 @@ Usage: grok report <subcommand> [args]
371
371
  Manage Datagrok user error reports
372
372
 
373
373
  Subcommands:
374
- fetch Download a report zip from a managed instance
374
+ fetch Download a report zip from a managed instance (writes <stem>_meta.json next to it)
375
375
  read Normalize a report (zip or json) into one JSON object on stdout
376
- resolve Mark a report as resolved
377
- ticket Create a JIRA ticket for a report via the Datlas API
376
+ resolve Mark a report as resolved (needs the _meta.json written by fetch)
377
+ ticket Create a JIRA ticket for a report directly in JIRA (no dedup; the key is NOT
378
+ written back to the report — prefer POST /reports/{id}/jira for that)
379
+ comment Add a comment to a JIRA ticket (--body <text> | --body-file <path>)
380
+ label Add labels to a JIRA ticket
381
+ attach Attach a file to a JIRA ticket
382
+
383
+ JIRA subcommands need JIRA_TOKEN (plus JIRA_USER for a user API token); ticket also needs
384
+ --project or $JIRA_PROJECT.
378
385
 
379
386
  Read flags:
380
387
  --extract-screenshot <path> Write the screenshot binary to <path>
@@ -20,6 +20,7 @@ var color = _interopRequireWildcard(require("../utils/color-utils"));
20
20
  var _check = require("./check");
21
21
  var _pythonCeleryGen = require("../utils/python-celery-gen");
22
22
  var _queueWorkerGen = require("../utils/queue-worker-gen");
23
+ var _devKey = require("../utils/dev-key");
23
24
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
24
25
  // @ts-ignore
25
26
 
@@ -243,7 +244,7 @@ function listRecursive(basePath, rel) {
243
244
  async function getUserLogin(host, devKey) {
244
245
  let loginResp;
245
246
  try {
246
- loginResp = await (0, _nodeFetch.default)(`${host}/users/login/dev/${devKey}`, {
247
+ loginResp = await (0, _devKey.devKeyFetch)(`${host}/users/login/dev`, `${host}/users/login/dev/${devKey}`, devKey, {
247
248
  method: 'POST'
248
249
  });
249
250
  } catch (e) {
@@ -485,9 +486,10 @@ async function fallbackImage(img, host, devKey, registry, version, contentHash)
485
486
  async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker, skipDockerRebuild) {
486
487
  // Validate server connectivity and dev key
487
488
  let timestamps = {};
488
- let url = `${host}/packages/dev/${devKey}/${packageName}`;
489
+ const url = `${host}/packages/dev/${packageName}`;
490
+ const legacyUrl = `${host}/packages/dev/${devKey}/${packageName}`;
489
491
  try {
490
- const checkResp = await (0, _nodeFetch.default)(url + '/timestamps');
492
+ const checkResp = await (0, _devKey.devKeyFetch)(`${url}/timestamps`, `${legacyUrl}/timestamps`, devKey);
491
493
  const checkData = await checkResp.json();
492
494
  if (checkData['#type'] === 'ApiError') {
493
495
  color.error(checkData.message);
@@ -614,12 +616,12 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
614
616
  });
615
617
 
616
618
  // Upload
617
- url += `?debug=${debug.toString()}&rebuild=${rebuild.toString()}&dropDb=${(dropDb ?? false).toString()}`;
618
- if (suffix) url += `&suffix=${suffix.toString()}`;
619
+ let query = `?debug=${debug.toString()}&rebuild=${rebuild.toString()}&dropDb=${(dropDb ?? false).toString()}`;
620
+ if (suffix) query += `&suffix=${suffix.toString()}`;
619
621
  await zip.finalize();
620
622
  const zipBuffer = Buffer.concat(chunks);
621
623
  try {
622
- const body = await (0, _nodeFetch.default)(url, {
624
+ const body = await (0, _devKey.devKeyFetch)(url + query, legacyUrl + query, devKey, {
623
625
  method: 'POST',
624
626
  body: zipBuffer
625
627
  });
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.devKeyFetch = devKeyFetch;
7
+ exports.devKeyHeaders = devKeyHeaders;
8
+ const fetch = require('node-fetch');
9
+
10
+ /** `Authorization` header carrying the developer key. */
11
+ function devKeyHeaders(key, headers = {}) {
12
+ return {
13
+ ...headers,
14
+ 'Authorization': `Dev ${key}`
15
+ };
16
+ }
17
+
18
+ /**
19
+ * Calls [url] with the developer key in the `Authorization` header, falling back to
20
+ * [legacyUrl] - which carries the key as a path segment - for servers that predate the
21
+ * header form. Those answer 404 (no such route) or 401 (the route-less path is not on
22
+ * their anonymous allow-list); a server that knows the header form answers neither.
23
+ */
24
+ async function devKeyFetch(url, legacyUrl, key, init = {}) {
25
+ const response = await fetch(url, {
26
+ ...init,
27
+ headers: devKeyHeaders(key, init.headers)
28
+ });
29
+ if (response.status !== 404 && response.status !== 401) return response;
30
+ return await fetch(legacyUrl, init);
31
+ }
@@ -68,7 +68,15 @@ class NodeApiClient {
68
68
  this.devKey = devKey;
69
69
  }
70
70
  static async login(baseUrl, devKey) {
71
- const res = await fetch(`${baseUrl}/users/login/dev/${devKey}`, {
71
+ // Servers before 1.28 only knew the key-in-URL form, where it leaked into every
72
+ // access log on the way; they answer 404 or 401 to the key-less route.
73
+ let res = await fetch(`${baseUrl}/users/login/dev`, {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Authorization': `Dev ${devKey}`
77
+ }
78
+ });
79
+ if (res.status === 404 || res.status === 401) res = await fetch(`${baseUrl}/users/login/dev/${devKey}`, {
72
80
  method: 'POST'
73
81
  });
74
82
  const json = (res.headers.get('content-type') ?? '').includes('application/json') ? await res.json() : null;
@@ -35,6 +35,7 @@ var utils = _utils;
35
35
  var _puppeteer = _interopRequireDefault(require("puppeteer"));
36
36
  var color = _interopRequireWildcard(require("../utils/color-utils"));
37
37
  var _papaparse = _interopRequireDefault(require("papaparse"));
38
+ var _devKey = require("./dev-key");
38
39
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
39
40
  const fetch = require('node-fetch');
40
41
  const grokDir = _path.default.join(_os.default.homedir(), '.grok');
@@ -57,7 +58,7 @@ async function getToken(url, key) {
57
58
  let lastError;
58
59
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
59
60
  try {
60
- const response = await fetch(`${url}/users/login/dev/${key}`, {
61
+ const response = await (0, _devKey.devKeyFetch)(`${url}/users/login/dev`, `${url}/users/login/dev/${key}`, key, {
61
62
  method: 'POST'
62
63
  });
63
64
  const text = await response.text();
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/datagrok-ai/public.git"
6
6
  },
7
- "version": "6.5.10",
7
+ "version": "6.6.0",
8
8
  "description": "Utility to upload and publish packages to Datagrok",
9
9
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
10
10
  "dependencies": {