datagrok-tools 6.4.5 → 6.4.7
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 +9 -0
- package/bin/commands/publish.js +9 -3
- package/bin/commands/test.js +4 -1
- package/bin/grok.js +4 -0
- package/bin/utils/queue-worker-gen.js +42 -0
- package/bin/utils/test-utils.js +37 -10
- package/package.json +1 -1
- package/script-template/node.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Datagrok-tools changelog
|
|
2
2
|
|
|
3
|
+
## 6.4.7 (2026-07-22)
|
|
4
|
+
|
|
5
|
+
* GROK-20452: `grok publish` — generate `dockerfiles/celery` / `dockerfiles/queue` **before** gathering files for the zip. They were generated after, so the generated Dockerfile never reached the server: `meta.queue` functions fell back to a server-side container named `<pkg>-queue-celery` while the client had built `<pkg>-queue` — image validation 404'd and the worker container never started ("Container is not started" on every call).
|
|
6
|
+
|
|
7
|
+
## 6.4.6 (2026-07-22)
|
|
8
|
+
|
|
9
|
+
* `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.
|
|
10
|
+
* `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.
|
|
11
|
+
|
|
3
12
|
## 6.4.5 (2026-06-23)
|
|
4
13
|
|
|
5
14
|
* `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.
|
package/bin/commands/publish.js
CHANGED
|
@@ -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
|
|
|
@@ -467,6 +468,14 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
|
|
|
467
468
|
const chunks = [];
|
|
468
469
|
zip.on('data', chunk => chunks.push(chunk));
|
|
469
470
|
|
|
471
|
+
// Generate Celery / Node-worker Docker artifacts BEFORE gathering files: the
|
|
472
|
+
// generated dockerfiles/<dir>/Dockerfile must land in the zip, or the server
|
|
473
|
+
// never binds the queue funcs to the client-built image and falls back to a
|
|
474
|
+
// differently-named server-side container that never starts
|
|
475
|
+
// (<pkg>-queue-celery vs the built <pkg>-queue).
|
|
476
|
+
(0, _pythonCeleryGen.generateCeleryArtifacts)(curDir);
|
|
477
|
+
(0, _queueWorkerGen.generateQueueArtifacts)(curDir);
|
|
478
|
+
|
|
470
479
|
// Gather the files
|
|
471
480
|
const localTimestamps = {};
|
|
472
481
|
const files = await (0, _ignoreWalk.default)({
|
|
@@ -557,9 +566,6 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
|
|
|
557
566
|
return 1;
|
|
558
567
|
}
|
|
559
568
|
|
|
560
|
-
// Generate Celery Docker artifacts from python/ if present
|
|
561
|
-
(0, _pythonCeleryGen.generateCeleryArtifacts)(curDir);
|
|
562
|
-
|
|
563
569
|
// Process Docker images and inject image.json into the ZIP
|
|
564
570
|
let dockerVersion = json.version;
|
|
565
571
|
if (debug) {
|
package/bin/commands/test.js
CHANGED
|
@@ -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
|
-
|
|
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,42 @@
|
|
|
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` (or its alias `//meta.server: true`) in package.ts —
|
|
13
|
+
// functions executed as celery tasks in the Node worker
|
|
14
|
+
// (see help/develop/how-to/packages/js-server-functions.md).
|
|
15
|
+
const queueTrueRegex = /^\s*\/\/\s*meta\.(queue|server)\s*:\s*true\s*$/m;
|
|
16
|
+
const QUEUE_DIR = 'queue';
|
|
17
|
+
const TEMPLATE_NODE_DOCKER = `FROM datagrok/celery_node_worker:bleeding-edge
|
|
18
|
+
EXPOSE 8000
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
/** Generates dockerfiles/queue/ for packages with \`meta.queue: true\` functions so the
|
|
22
|
+
* standard docker build/push/image.json flow covers the auto-created Node worker
|
|
23
|
+
* container (mirror of generateCeleryArtifacts for python/). The worker fetches the
|
|
24
|
+
* package bundle at runtime, so the image is just the stock worker base. */
|
|
25
|
+
function generateQueueArtifacts(packageDir) {
|
|
26
|
+
const candidates = ['package.ts', 'package.g.ts'].map(f => _path.default.join(packageDir, 'src', f)).filter(f => _fs.default.existsSync(f));
|
|
27
|
+
const hasQueueFuncs = candidates.some(f => queueTrueRegex.test(_fs.default.readFileSync(f, 'utf-8')));
|
|
28
|
+
if (!hasQueueFuncs) return false;
|
|
29
|
+
const dockerfilesDir = _path.default.join(packageDir, 'dockerfiles', QUEUE_DIR);
|
|
30
|
+
const dockerfilePath = _path.default.join(dockerfilesDir, 'Dockerfile');
|
|
31
|
+
_fs.default.mkdirSync(dockerfilesDir, {
|
|
32
|
+
recursive: true
|
|
33
|
+
});
|
|
34
|
+
if (!_fs.default.existsSync(dockerfilePath)) _fs.default.writeFileSync(dockerfilePath, TEMPLATE_NODE_DOCKER);
|
|
35
|
+
|
|
36
|
+
// Resource config lives in the reserved queue/ package folder
|
|
37
|
+
const containerJsonSrc = _path.default.join(packageDir, QUEUE_DIR, 'container.json');
|
|
38
|
+
const containerJsonDest = _path.default.join(dockerfilesDir, 'container.json');
|
|
39
|
+
if (_fs.default.existsSync(containerJsonSrc) && !_fs.default.existsSync(containerJsonDest)) _fs.default.copyFileSync(containerJsonSrc, containerJsonDest);
|
|
40
|
+
color.log(`Generated Node worker Docker artifacts in dockerfiles/${QUEUE_DIR}/`);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
package/bin/utils/test-utils.js
CHANGED
|
@@ -46,17 +46,44 @@ const defaultLaunchParameters = exports.defaultLaunchParameters = {
|
|
|
46
46
|
protocolTimeout: 0
|
|
47
47
|
};
|
|
48
48
|
async function getToken(url, key) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
package/script-template/node.js
CHANGED