datagrok-tools 4.7.3 → 4.7.5

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.
@@ -8,10 +8,10 @@ var HELP = "\nUsage: grok <command>\n\nDatagrok's package management tool\n\nCom
8
8
  var HELP_ADD = "\nUsage: grok add <entity> <name>\n\nAdd an object template to your package:\n\ngrok add app <name>\ngrok add connection <name>\ngrok add detector <semantic-type-name>\ngrok add function [tag] <name>\ngrok add query <name>\ngrok add script [tag] <language> <name>\ngrok add view <name>\ngrok add viewer <name>\ngrok add tests\n\nPlease note that entity names may only include letters and numbers\n\nSupported languages for scripts:\njavascript, julia, node, octave, python, r\n\nAvailable tags:\npanel, init\n";
9
9
  var HELP_API = "\nUsage: grok api\n\nCreate wrapper functions for package scripts and queries\n";
10
10
  var HELP_CONFIG = "\nUsage: grok config\n\nCreate or update a configuration file\n\nOptions:\n[--reset] [--server] [--alias] [--key]\n\n--reset Restore the default config file template\n--server Use to add a server to the config (`grok config add --alias alias --server url --key key`)\n--alias Use in conjunction with the `server` option to set the server name\n--key Use in conjunction with the `server` option to set the developer key\n--default Use in conjunction with the `server` option to set the added server as default\n";
11
- var HELP_CREATE = "\nUsage: grok create [name]\n\nCreate a package:\n\ngrok create Create a package in the current working directory\ngrok create <name> Create a package in a folder with the specified name\n\nPlease note that the package name may only include letters, numbers, underscores, or hyphens\n\nOptions:\n[--eslint] [--ide] [--js|--ts] [--jest]\n\n--eslint Add a configuration for eslint\n--ide Add an IDE-specific configuration for debugging (vscode)\n--js Create a JavaScript package\n--ts Create a TypeScript package (default)\n--jest Add a configuration for jest\n";
11
+ var HELP_CREATE = "\nUsage: grok create [name]\n\nCreate a package:\n\ngrok create Create a package in the current working directory\ngrok create <name> Create a package in a folder with the specified name\n\nPlease note that the package name may only include letters, numbers, underscores, or hyphens\n\nOptions:\n[--eslint] [--ide] [--js|--ts]\n\n--eslint Add a configuration for eslint\n--ide Add an IDE-specific configuration for debugging (vscode)\n--js Create a JavaScript package\n--ts Create a TypeScript package (default)\n";
12
12
  var HELP_PUBLISH = "\nUsage: grok publish [host]\n\nUpload a package\n\nOptions:\n[--build|--rebuild] [--debug|--release] [--key] [--suffix]\n\nRunning `grok publish` is the same as running `grok publish defaultHost --build --debug`\n";
13
13
  var HELP_CHECK = "\nUsage: grok check\n\nOptions:\n[--dir]\n\n--dir Check all packages in a specified directory\n\nCheck package content (function signatures, import statements of external modules, etc.)\n";
14
- var HELP_TEST = "\nUsage: grok test\n\nOptions:\n[--host] [--csv]\n\n--host Host alias as in the config file\n--csv Save the test report in a CSV file\n\nRun package tests\n\nSee instructions:\nhttps://datagrok.ai/help/develop/how-to/test-packages#local-testing\n";
14
+ var HELP_TEST = "\nUsage: grok test\n\nOptions:\n[--host] [--csv]\n\n--host Host alias as in the config file\n--csv Save the test report in a CSV file\n--gui Launch graphical interface (non-headless mode)\n--skip-build Skip the package build step\n--skip-publish Skip the package publication step\n\nRun package tests\n\nSee instructions:\nhttps://datagrok.ai/help/develop/how-to/test-packages#local-testing\n";
15
15
  var HELP_LINK = "\nUsage: grok link\n\nLink `datagrok-api` and libraries for local development\n";
16
16
  var HELP_UNLINK = "\nUsage: grok unlink\n\nRevert `grok link`\n";
17
17
  var HELP_MIGRATE = "\nUsage: grok migrate\n\nSwitch to `grok` tools by copying your keys to the config\nfile and converting your scripts in the `package.json` file\n";
@@ -37,7 +37,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
37
37
 
38
38
  function test(args) {
39
39
  var options = Object.keys(args).slice(1);
40
- var commandOptions = ['host', 'csv'];
40
+ var commandOptions = ['host', 'csv', 'gui', 'skip-build', 'skip-publish'];
41
41
  var nArgs = args['_'].length;
42
42
  var curDir = process.cwd();
43
43
 
@@ -45,7 +45,7 @@ function test(args) {
45
45
 
46
46
  var confPath = _path["default"].join(grokDir, 'config.yaml');
47
47
 
48
- if (nArgs > 1 || options.length > 2 || options.length > 0 && !options.every(function (op) {
48
+ if (nArgs > 1 || options.length > commandOptions.length || options.length > 0 && !options.every(function (op) {
49
49
  return commandOptions.includes(op);
50
50
  })) return false;
51
51
 
@@ -79,188 +79,212 @@ function test(args) {
79
79
  var packageData = JSON.parse(_fs["default"].readFileSync(_path["default"].join(curDir, 'package.json'), {
80
80
  encoding: 'utf-8'
81
81
  }));
82
- var fullName = packageData.friendlyName || packageData.fullName;
83
82
 
84
- if (fullName) {
85
- fullName = utils.friendlyNameToName(utils.kebabToCamelCase(fullName));
86
- process.env.TARGET_PACKAGE = fullName;
83
+ if (packageData.name) {
84
+ process.env.TARGET_PACKAGE = utils.kebabToCamelCase(utils.removeScope(packageData.name));
87
85
  console.log('Environment variable `TARGET_PACKAGE` is set to', process.env.TARGET_PACKAGE);
88
86
  } else {
89
- color.error('Invalid full package name. Set `friendlyName` or `fullName` field in `package.json`');
87
+ color.error('Invalid package name. Set the `name` field in `package.json`');
90
88
  return false;
91
89
  }
92
90
 
93
- color.info("Building package...");
94
- (0, _child_process.exec)('npm run build', function (err, stdout, stderr) {
95
- if (err) throw err;else {
96
- console.log(stdout);
97
- color.warn(stderr);
98
- }
99
- color.info("Publishing package \"".concat(process.env.TARGET_PACKAGE, "\" to ").concat(process.env.HOST, "..."));
91
+ if (args['skip-build']) {
92
+ if (args['skip-publish']) test();else publish(test);
93
+ } else {
94
+ build(args['skip-publish'] ? test : function () {
95
+ return publish(test);
96
+ });
97
+ }
98
+
99
+ function build(callback) {
100
+ (0, _child_process.exec)('npm run build', function (err, stdout, stderr) {
101
+ color.info("Building package...");
102
+
103
+ if (err) {
104
+ console.log(stdout);
105
+ throw err;
106
+ } else {
107
+ console.log(stdout);
108
+ color.warn(stderr);
109
+ }
110
+
111
+ callback();
112
+ });
113
+ }
114
+
115
+ function publish(callback) {
100
116
  (0, _child_process.exec)("grok publish ".concat(process.platform === 'win32' ? '%HOST%' : '${HOST}'), function (err, stdout, stderr) {
117
+ color.info("Publishing package \"".concat(process.env.TARGET_PACKAGE, "\" to ").concat(process.env.HOST, "..."));
101
118
  if (err) throw err;else {
102
119
  console.log(stdout);
103
120
  color.warn(stderr);
104
121
  }
105
- color.info('Starting tests...');
106
- var P_START_TIMEOUT = 3600000;
107
- var browser;
108
- var page;
109
-
110
- function init(timeout) {
111
- return testUtils.runWithTimeout(timeout, /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
112
- var out;
113
- return _regenerator["default"].wrap(function _callee$(_context) {
114
- while (1) {
115
- switch (_context.prev = _context.next) {
116
- case 0:
117
- _context.next = 2;
118
- return testUtils.getBrowserPage(_puppeteer["default"]);
119
-
120
- case 2:
121
- out = _context.sent;
122
- browser = out.browser;
123
- page = out.page;
124
- return _context.abrupt("return", 'Initialization completed.');
125
-
126
- case 6:
127
- case "end":
128
- return _context.stop();
129
- }
130
- }
131
- }, _callee);
132
- })));
133
- }
122
+ callback();
123
+ });
124
+ }
134
125
 
135
- function runTest(timeout) {
136
- return testUtils.runWithTimeout(timeout, /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() {
137
- var _process$env$TARGET_P;
138
-
139
- var targetPackage, r;
140
- return _regenerator["default"].wrap(function _callee2$(_context2) {
141
- while (1) {
142
- switch (_context2.prev = _context2.next) {
143
- case 0:
144
- targetPackage = (_process$env$TARGET_P = process.env.TARGET_PACKAGE) !== null && _process$env$TARGET_P !== void 0 ? _process$env$TARGET_P : '#{PACKAGE_NAMESPACE}';
145
- console.log("Testing ".concat(targetPackage, " package...\n"));
146
- _context2.next = 4;
147
- return page.evaluate(function (targetPackage) {
148
- return new Promise(function (resolve, reject) {
149
- window.grok.functions.eval(targetPackage + ':test()').then(function (df) {
150
- var failed = false;
151
- var skipReport = '';
152
- var passReport = '';
153
- var failReport = '';
154
-
155
- if (df == null) {
156
- failed = true;
157
- failReport = 'Fail reason: No package tests found';
158
- resolve({
159
- failReport: failReport,
160
- skipReport: skipReport,
161
- passReport: passReport,
162
- failed: failed
163
- });
164
- }
126
+ function test() {
127
+ color.info('Starting tests...');
128
+ var P_START_TIMEOUT = 3600000;
129
+ var browser;
130
+ var page;
131
+
132
+ function init(timeout) {
133
+ var params = Object.assign({}, testUtils.defaultLaunchParameters);
134
+ if (args.gui) params['headless'] = false;
135
+ return testUtils.runWithTimeout(timeout, /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee() {
136
+ var out;
137
+ return _regenerator["default"].wrap(function _callee$(_context) {
138
+ while (1) {
139
+ switch (_context.prev = _context.next) {
140
+ case 0:
141
+ _context.next = 2;
142
+ return testUtils.getBrowserPage(_puppeteer["default"], params);
165
143
 
166
- var cStatus = df.columns.byName('success');
167
- var cSkipped = df.columns.byName('skipped');
168
- var cMessage = df.columns.byName('result');
169
- var cCat = df.columns.byName('category');
170
- var cName = df.columns.byName('name');
171
- var cTime = df.columns.byName('ms');
172
-
173
- for (var i = 0; i < df.rowCount; i++) {
174
- if (cStatus.get(i)) {
175
- if (cSkipped.get(i)) {
176
- skipReport += "Test result : Skipped : ".concat(cTime.get(i), " : ").concat(targetPackage, ".").concat(cCat.get(i), ".").concat(cName.get(i), " : ").concat(cMessage.get(i), "\n");
177
- } else {
178
- passReport += "Test result : Success : ".concat(cTime.get(i), " : ").concat(targetPackage, ".").concat(cCat.get(i), ".").concat(cName.get(i), " : ").concat(cMessage.get(i), "\n");
179
- }
180
- } else {
181
- failed = true;
182
- failReport += "Test result : Failed : ".concat(cTime.get(i), " : ").concat(targetPackage, ".").concat(cCat.get(i), ".").concat(cName.get(i), " : ").concat(cMessage.get(i), "\n");
183
- }
184
- }
144
+ case 2:
145
+ out = _context.sent;
146
+ browser = out.browser;
147
+ page = out.page;
148
+ return _context.abrupt("return", 'Initialization completed.');
185
149
 
186
- var csv = df.toCsv();
150
+ case 6:
151
+ case "end":
152
+ return _context.stop();
153
+ }
154
+ }
155
+ }, _callee);
156
+ })));
157
+ }
158
+
159
+ function runTest(timeout) {
160
+ return testUtils.runWithTimeout(timeout, /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2() {
161
+ var _process$env$TARGET_P;
162
+
163
+ var targetPackage, r;
164
+ return _regenerator["default"].wrap(function _callee2$(_context2) {
165
+ while (1) {
166
+ switch (_context2.prev = _context2.next) {
167
+ case 0:
168
+ targetPackage = (_process$env$TARGET_P = process.env.TARGET_PACKAGE) !== null && _process$env$TARGET_P !== void 0 ? _process$env$TARGET_P : '#{PACKAGE_NAMESPACE}';
169
+ console.log("Testing ".concat(targetPackage, " package...\n"));
170
+ _context2.next = 4;
171
+ return page.evaluate(function (targetPackage) {
172
+ return new Promise(function (resolve, reject) {
173
+ window.grok.functions.eval(targetPackage + ':test()').then(function (df) {
174
+ var failed = false;
175
+ var skipReport = '';
176
+ var passReport = '';
177
+ var failReport = '';
178
+
179
+ if (df == null) {
180
+ failed = true;
181
+ failReport = 'Fail reason: No package tests found';
187
182
  resolve({
188
183
  failReport: failReport,
189
184
  skipReport: skipReport,
190
185
  passReport: passReport,
191
- failed: failed,
192
- csv: csv
186
+ failed: failed
193
187
  });
194
- })["catch"](function (e) {
195
- return reject(e);
188
+ }
189
+
190
+ var cStatus = df.columns.byName('success');
191
+ var cSkipped = df.columns.byName('skipped');
192
+ var cMessage = df.columns.byName('result');
193
+ var cCat = df.columns.byName('category');
194
+ var cName = df.columns.byName('name');
195
+ var cTime = df.columns.byName('ms');
196
+
197
+ for (var i = 0; i < df.rowCount; i++) {
198
+ if (cStatus.get(i)) {
199
+ if (cSkipped.get(i)) {
200
+ skipReport += "Test result : Skipped : ".concat(cTime.get(i), " : ").concat(targetPackage, ".").concat(cCat.get(i), ".").concat(cName.get(i), " : ").concat(cMessage.get(i), "\n");
201
+ } else {
202
+ passReport += "Test result : Success : ".concat(cTime.get(i), " : ").concat(targetPackage, ".").concat(cCat.get(i), ".").concat(cName.get(i), " : ").concat(cMessage.get(i), "\n");
203
+ }
204
+ } else {
205
+ failed = true;
206
+ failReport += "Test result : Failed : ".concat(cTime.get(i), " : ").concat(targetPackage, ".").concat(cCat.get(i), ".").concat(cName.get(i), " : ").concat(cMessage.get(i), "\n");
207
+ }
208
+ }
209
+
210
+ var csv = df.toCsv();
211
+ resolve({
212
+ failReport: failReport,
213
+ skipReport: skipReport,
214
+ passReport: passReport,
215
+ failed: failed,
216
+ csv: csv
196
217
  });
218
+ })["catch"](function (e) {
219
+ return reject(e);
197
220
  });
198
- }, targetPackage);
221
+ });
222
+ }, targetPackage);
199
223
 
200
- case 4:
201
- r = _context2.sent;
202
- return _context2.abrupt("return", r);
224
+ case 4:
225
+ r = _context2.sent;
226
+ return _context2.abrupt("return", r);
203
227
 
204
- case 6:
205
- case "end":
206
- return _context2.stop();
207
- }
228
+ case 6:
229
+ case "end":
230
+ return _context2.stop();
208
231
  }
209
- }, _callee2);
210
- })));
211
- }
212
-
213
- (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() {
214
- var initMessage, r;
215
- return _regenerator["default"].wrap(function _callee3$(_context3) {
216
- while (1) {
217
- switch (_context3.prev = _context3.next) {
218
- case 0:
219
- _context3.next = 2;
220
- return init(P_START_TIMEOUT);
232
+ }
233
+ }, _callee2);
234
+ })));
235
+ }
221
236
 
222
- case 2:
223
- initMessage = _context3.sent;
224
- console.log(initMessage);
225
- _context3.next = 6;
226
- return runTest(7200000);
237
+ (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() {
238
+ var initMessage, r;
239
+ return _regenerator["default"].wrap(function _callee3$(_context3) {
240
+ while (1) {
241
+ switch (_context3.prev = _context3.next) {
242
+ case 0:
243
+ _context3.next = 2;
244
+ return init(P_START_TIMEOUT);
227
245
 
228
- case 6:
229
- r = _context3.sent;
246
+ case 2:
247
+ initMessage = _context3.sent;
248
+ console.log(initMessage);
249
+ _context3.next = 6;
250
+ return runTest(7200000);
230
251
 
231
- if (r.csv && args.csv) {
232
- _fs["default"].writeFileSync(_path["default"].join(curDir, 'test-report.csv'), r.csv, 'utf8');
252
+ case 6:
253
+ r = _context3.sent;
233
254
 
234
- color.info('Saved `test-report.csv`\n');
235
- }
255
+ if (r.csv && args.csv) {
256
+ _fs["default"].writeFileSync(_path["default"].join(curDir, 'test-report.csv'), r.csv, 'utf8');
236
257
 
237
- if (r.passReport) console.log(r.passReport);
238
- if (r.skipReport) console.log(r.skipReport);
258
+ color.info('Saved `test-report.csv`\n');
259
+ }
239
260
 
240
- if (r.failed) {
241
- console.log(r.failReport);
242
- color.fail('Tests failed.');
243
- } else {
244
- color.success('Tests passed.');
245
- } //@ts-ignore
261
+ if (r.passReport) console.log(r.passReport);
262
+ if (r.skipReport) console.log(r.skipReport);
246
263
 
264
+ if (r.failed) {
265
+ console.log(r.failReport);
266
+ color.fail('Tests failed.');
267
+ } else {
268
+ color.success('Tests passed.');
269
+ } //@ts-ignore
247
270
 
248
- if (!(browser != null)) {
249
- _context3.next = 14;
250
- break;
251
- }
252
271
 
272
+ if (!(browser != null)) {
253
273
  _context3.next = 14;
254
- return browser.close();
274
+ break;
275
+ }
255
276
 
256
- case 14:
257
- case "end":
258
- return _context3.stop();
259
- }
277
+ _context3.next = 14;
278
+ return browser.close();
279
+
280
+ case 14:
281
+ case "end":
282
+ return _context3.stop();
260
283
  }
261
- }, _callee3);
262
- }))();
263
- });
264
- });
284
+ }
285
+ }, _callee3);
286
+ }))();
287
+ }
288
+
265
289
  return true;
266
290
  }
@@ -7,6 +7,7 @@ var _typeof = require("@babel/runtime/helpers/typeof");
7
7
  Object.defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
10
+ exports.defaultLaunchParameters = void 0;
10
11
  exports.getBrowserPage = getBrowserPage;
11
12
  exports.getDevKey = getDevKey;
12
13
  exports.getToken = getToken;
@@ -37,6 +38,12 @@ var grokDir = _path["default"].join(_os["default"].homedir(), '.grok');
37
38
 
38
39
  var confPath = _path["default"].join(grokDir, 'config.yaml');
39
40
 
41
+ var defaultLaunchParameters = {
42
+ args: ['--disable-dev-shm-usage', '--disable-features=site-per-process'],
43
+ ignoreHTTPSErrors: true
44
+ };
45
+ exports.defaultLaunchParameters = defaultLaunchParameters;
46
+
40
47
  function getToken(_x, _x2) {
41
48
  return _getToken.apply(this, arguments);
42
49
  }
@@ -151,93 +158,98 @@ function _getBrowserPage() {
151
158
  _getBrowserPage = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(puppeteer) {
152
159
  var _process$env$HOST;
153
160
 
154
- var url, cfg, key, token, browser, page;
161
+ var params,
162
+ url,
163
+ cfg,
164
+ key,
165
+ token,
166
+ browser,
167
+ page,
168
+ _args4 = arguments;
155
169
  return _regenerator["default"].wrap(function _callee4$(_context4) {
156
170
  while (1) {
157
171
  switch (_context4.prev = _context4.next) {
158
172
  case 0:
173
+ params = _args4.length > 1 && _args4[1] !== undefined ? _args4[1] : defaultLaunchParameters;
159
174
  url = (_process$env$HOST = process.env.HOST) !== null && _process$env$HOST !== void 0 ? _process$env$HOST : '';
160
175
  cfg = getDevKey(url);
161
176
  url = cfg.url;
162
177
  key = cfg.key;
163
- _context4.next = 6;
178
+ _context4.next = 7;
164
179
  return getToken(url, key);
165
180
 
166
- case 6:
181
+ case 7:
167
182
  token = _context4.sent;
168
- _context4.next = 9;
183
+ _context4.next = 10;
169
184
  return getWebUrl(url, token);
170
185
 
171
- case 9:
186
+ case 10:
172
187
  url = _context4.sent;
173
188
  console.log("Using web root: ".concat(url));
174
- _context4.next = 13;
175
- return puppeteer.launch({
176
- args: ['--disable-dev-shm-usage', '--disable-features=site-per-process'],
177
- ignoreHTTPSErrors: true
178
- });
189
+ _context4.next = 14;
190
+ return puppeteer.launch(params);
179
191
 
180
- case 13:
192
+ case 14:
181
193
  browser = _context4.sent;
182
- _context4.next = 16;
194
+ _context4.next = 17;
183
195
  return browser.newPage();
184
196
 
185
- case 16:
197
+ case 17:
186
198
  page = _context4.sent;
187
- _context4.next = 19;
199
+ _context4.next = 20;
188
200
  return page.setDefaultNavigationTimeout(0);
189
201
 
190
- case 19:
191
- _context4.next = 21;
202
+ case 20:
203
+ _context4.next = 22;
192
204
  return page["goto"]("".concat(url, "/oauth/"));
193
205
 
194
- case 21:
195
- _context4.next = 23;
206
+ case 22:
207
+ _context4.next = 24;
196
208
  return page.setCookie({
197
209
  name: 'auth',
198
210
  value: token
199
211
  });
200
212
 
201
- case 23:
202
- _context4.next = 25;
213
+ case 24:
214
+ _context4.next = 26;
203
215
  return page.evaluate(function (token) {
204
216
  window.localStorage.setItem('auth', token);
205
217
  }, token);
206
218
 
207
- case 25:
208
- _context4.next = 27;
219
+ case 26:
220
+ _context4.next = 28;
209
221
  return page["goto"](url);
210
222
 
211
- case 27:
212
- _context4.prev = 27;
213
- _context4.next = 30;
223
+ case 28:
224
+ _context4.prev = 28;
225
+ _context4.next = 31;
214
226
  return page.waitForFunction(function () {
215
227
  return document.querySelector('.grok-preloader') == null;
216
228
  }, {
217
229
  timeout: 3600000
218
230
  });
219
231
 
220
- case 30:
221
- _context4.next = 35;
232
+ case 31:
233
+ _context4.next = 36;
222
234
  break;
223
235
 
224
- case 32:
225
- _context4.prev = 32;
226
- _context4.t0 = _context4["catch"](27);
236
+ case 33:
237
+ _context4.prev = 33;
238
+ _context4.t0 = _context4["catch"](28);
227
239
  throw _context4.t0;
228
240
 
229
- case 35:
241
+ case 36:
230
242
  return _context4.abrupt("return", {
231
243
  browser: browser,
232
244
  page: page
233
245
  });
234
246
 
235
- case 36:
247
+ case 37:
236
248
  case "end":
237
249
  return _context4.stop();
238
250
  }
239
251
  }
240
- }, _callee4, null, [[27, 32]]);
252
+ }, _callee4, null, [[28, 33]]);
241
253
  }));
242
254
  return _getBrowserPage.apply(this, arguments);
243
255
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "4.7.3",
3
+ "version": "4.7.5",
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": {