datagrok-tools 6.4.5 → 6.4.6

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,10 @@
1
1
  # Datagrok-tools changelog
2
2
 
3
+ ## 6.4.6 (2026-07-22)
4
+
5
+ * `grok test` — the whole-run Puppeteer cap (60 min) is now overridable via `GROK_TEST_INVOCATION_TIMEOUT_MS`. When the cap fires mid-pass all collected results are discarded (empty `test-report.csv`, "Passed tests: 0"), which CI reports as "no results produced"; loaded CI agents can now raise the cap instead.
6
+ * `grok test` — `--no-retry` is now honored for Playwright runs. minimist parsed `--no-retry` as `{retry:false}`, so the flag was silently dropped and failed specs were still retried once; normalized so `--retries=0` reaches Playwright.
7
+
3
8
  ## 6.4.5 (2026-06-23)
4
9
 
5
10
  * `grok test` — screen recording (`--record`) is now optional: if `page.screencast()` can't start (e.g. `ffmpeg` is missing on the runner, `spawnSync ffmpeg ENOENT`), the run warns and continues instead of failing the whole Puppeteer pass with 0 tests executed.
@@ -19,6 +19,7 @@ var utils = _interopRequireWildcard(require("../utils/utils"));
19
19
  var color = _interopRequireWildcard(require("../utils/color-utils"));
20
20
  var _check = require("./check");
21
21
  var _pythonCeleryGen = require("../utils/python-celery-gen");
22
+ var _queueWorkerGen = require("../utils/queue-worker-gen");
22
23
  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); }
23
24
  // @ts-ignore
24
25
 
@@ -560,6 +561,9 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
560
561
  // Generate Celery Docker artifacts from python/ if present
561
562
  (0, _pythonCeleryGen.generateCeleryArtifacts)(curDir);
562
563
 
564
+ // Generate the Node worker container for meta.queue JS functions if present
565
+ (0, _queueWorkerGen.generateQueueArtifacts)(curDir);
566
+
563
567
  // Process Docker images and inject image.json into the ZIP
564
568
  let dockerVersion = json.version;
565
569
  if (debug) {
@@ -25,7 +25,10 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
25
25
 
26
26
  const execAsync = (0, _util.promisify)(_child_process.exec);
27
27
  const execFileAsync = (0, _util.promisify)(_child_process.execFile);
28
- const testInvocationTimeout = 3600000;
28
+ // Whole-run cap for the Puppeteer pass; when it fires, results collected so far
29
+ // are discarded (they live in the page until a pass completes). CI runners on
30
+ // loaded agents can exceed the 1h default — override via env.
31
+ const testInvocationTimeout = parseInt(process.env['GROK_TEST_INVOCATION_TIMEOUT_MS'] ?? '', 10) || 3600000;
29
32
  const availableCommandOptions = ['host', 'package', 'csv', 'gui', 'catchUnhandled', 'platform', 'core', 'report', 'skip-build', 'skip-publish', 'path', 'record', 'verbose', 'benchmark', 'category', 'test', 'stress-test', 'link', 'tag', 'ci-cd', 'debug', 'no-retry', 'dartium', 'f', 'params', 'logfailed', 'skip-playwright', 'skip-puppeteer'];
30
33
  const curDir = process.cwd();
31
34
 
package/bin/grok.js CHANGED
@@ -3,6 +3,10 @@ const argv = require('minimist')(process.argv.slice(2), {
3
3
  alias: {k: 'key', h: 'help', r: 'recursive'},
4
4
  boolean: ['dartium'],
5
5
  });
6
+ // minimist maps `--no-retry` to `{retry: false}`, so the `args['no-retry']` checks in
7
+ // test.ts / playwright-runner.ts never fired and `--no-retry` was silently ignored
8
+ // (Playwright kept retrying failed specs). Normalize back to the flag the commands read.
9
+ if (argv.retry === false) argv['no-retry'] = true;
6
10
  const help = require('./commands/help').help;
7
11
  const runAllCommand = require('./utils/utils').runAll;
8
12
 
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.generateQueueArtifacts = generateQueueArtifacts;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var color = _interopRequireWildcard(require("./color-utils"));
11
+ 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); }
12
+ // Matches `//meta.queue: true` in package.ts — functions executed as celery tasks
13
+ // in the Node worker (see help/develop/how-to/packages/js-server-functions.md).
14
+ const queueTrueRegex = /^\s*\/\/\s*meta\.queue\s*:\s*true\s*$/m;
15
+ const QUEUE_DIR = 'queue';
16
+ const TEMPLATE_NODE_DOCKER = `FROM datagrok/celery_node_worker:bleeding-edge
17
+ EXPOSE 8000
18
+ `;
19
+
20
+ /** Generates dockerfiles/queue/ for packages with \`meta.queue: true\` functions so the
21
+ * standard docker build/push/image.json flow covers the auto-created Node worker
22
+ * container (mirror of generateCeleryArtifacts for python/). The worker fetches the
23
+ * package bundle at runtime, so the image is just the stock worker base. */
24
+ function generateQueueArtifacts(packageDir) {
25
+ const candidates = ['package.ts', 'package.g.ts'].map(f => _path.default.join(packageDir, 'src', f)).filter(f => _fs.default.existsSync(f));
26
+ const hasQueueFuncs = candidates.some(f => queueTrueRegex.test(_fs.default.readFileSync(f, 'utf-8')));
27
+ if (!hasQueueFuncs) return false;
28
+ const dockerfilesDir = _path.default.join(packageDir, 'dockerfiles', QUEUE_DIR);
29
+ const dockerfilePath = _path.default.join(dockerfilesDir, 'Dockerfile');
30
+ _fs.default.mkdirSync(dockerfilesDir, {
31
+ recursive: true
32
+ });
33
+ if (!_fs.default.existsSync(dockerfilePath)) _fs.default.writeFileSync(dockerfilePath, TEMPLATE_NODE_DOCKER);
34
+
35
+ // Resource config lives in the reserved queue/ package folder
36
+ const containerJsonSrc = _path.default.join(packageDir, QUEUE_DIR, 'container.json');
37
+ const containerJsonDest = _path.default.join(dockerfilesDir, 'container.json');
38
+ if (_fs.default.existsSync(containerJsonSrc) && !_fs.default.existsSync(containerJsonDest)) _fs.default.copyFileSync(containerJsonSrc, containerJsonDest);
39
+ color.log(`Generated Node worker Docker artifacts in dockerfiles/${QUEUE_DIR}/`);
40
+ return true;
41
+ }
@@ -46,17 +46,44 @@ const defaultLaunchParameters = exports.defaultLaunchParameters = {
46
46
  protocolTimeout: 0
47
47
  };
48
48
  async function getToken(url, key) {
49
- let response;
50
- try {
51
- response = await fetch(`${url}/users/login/dev/${key}`, {
52
- method: 'POST'
53
- });
54
- } catch (error) {
55
- if (utils.isConnectivityError(error)) color.error(`Server is possibly offline: ${url}`);
56
- throw error;
49
+ // Retry transient failures: on a freshly-provisioned CI stack the datlas behind nginx may not
50
+ // be serving /api as JSON yet, so the dev-key exchange briefly returns an HTML error page
51
+ // (`Unexpected token '<'` from response.json()) or the node is momentarily unreachable. These
52
+ // are stack-readiness races, not bad credentials — re-poll for ~45s before giving up. A real
53
+ // auth failure (valid JSON with isSuccess=false) is returned immediately, no retry.
54
+ const maxAttempts = 15;
55
+ const delayMs = 3000;
56
+ let lastError;
57
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
58
+ try {
59
+ const response = await fetch(`${url}/users/login/dev/${key}`, {
60
+ method: 'POST'
61
+ });
62
+ const text = await response.text();
63
+ let json;
64
+ try {
65
+ json = JSON.parse(text);
66
+ } catch {
67
+ // Non-JSON body (HTML error page) => stack not ready yet; retry.
68
+ lastError = new Error(`non-JSON response from ${url}/users/login/dev (status ${response.status})`);
69
+ if (attempt < maxAttempts) {
70
+ color.warn(`Playwright: token exchange not ready (attempt ${attempt}/${maxAttempts}), retrying...`);
71
+ await new Promise(r => setTimeout(r, delayMs));
72
+ continue;
73
+ }
74
+ throw lastError;
75
+ }
76
+ if (json.isSuccess == true) return json.token;
77
+ // Valid JSON but login rejected => definitive failure, do not retry.
78
+ throw new Error('Unable to login to server. Check your dev key');
79
+ } catch (error) {
80
+ if (error?.message === 'Unable to login to server. Check your dev key') throw error;
81
+ lastError = error;
82
+ if (utils.isConnectivityError(error)) color.warn(`Playwright: server not reachable yet (attempt ${attempt}/${maxAttempts}): ${url}`);
83
+ if (attempt < maxAttempts) await new Promise(r => setTimeout(r, delayMs));
84
+ }
57
85
  }
58
- const json = await response.json();
59
- if (json.isSuccess == true) return json.token;else throw new Error('Unable to login to server. Check your dev key');
86
+ throw lastError ?? new Error(`Unable to exchange dev key for token at ${url}`);
60
87
  }
61
88
  async function isPackageOnServer(hostKey, packageName) {
62
89
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "6.4.5",
3
+ "version": "6.4.6",
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": {
@@ -4,4 +4,4 @@
4
4
  //sample: cars.csv
5
5
  //input: dataframe table [Data table]
6
6
  //output: int count [Number of cells in table]
7
- const count = table.dim()[0] * table.dim()[1];
7
+ const count = table.rowCount * table.columns.length;