datagrok-tools 6.4.6 → 6.4.8

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,13 @@
1
1
  # Datagrok-tools changelog
2
2
 
3
+ ## 6.4.8 (2026-07-22)
4
+
5
+ * GROK-20452: `grok publish` — always `docker build` the tools-generated worker dirs (`dockerfiles/queue`, `dockerfiles/celery`, `dockerfiles/<app>-celery`) instead of reusing a cached local tag. The "found local image" shortcut froze the worker at whatever stock base the daemon had when the tag was first built (CI ran day-old worker code with a fresh base available); the layer cache keeps an unchanged rebuild near-instant.
6
+
7
+ ## 6.4.7 (2026-07-22)
8
+
9
+ * 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).
10
+
3
11
  ## 6.4.6 (2026-07-22)
4
12
 
5
13
  * `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.
@@ -11,6 +11,6 @@ async function dockerGen(args) {
11
11
  color.setVerbose(args.verbose || args.v || false);
12
12
  const packageDir = process.cwd();
13
13
  const generated = (0, _pythonCeleryGen.generateCeleryArtifacts)(packageDir);
14
- if (generated) color.success('Generated Celery Docker artifacts');else console.log('No python/ directory with annotated functions found');
14
+ if (generated.length) color.success('Generated Celery Docker artifacts');else console.log('No python/ directory with annotated functions found');
15
15
  return true;
16
16
  }
@@ -284,7 +284,7 @@ async function resolveLatestCompatible(host, devKey, dockerName, version, conten
284
284
  };
285
285
  }
286
286
  }
287
- async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug, skipDockerRebuild = false) {
287
+ async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug, skipDockerRebuild = false, generatedDirs = []) {
288
288
  const dockerImages = discoverDockerfiles(packageName, version, debug);
289
289
  if (dockerImages.length === 0) return;
290
290
  color.log(`Found ${dockerImages.length} Dockerfile(s)`);
@@ -317,6 +317,13 @@ async function processDockerImages(packageName, version, registry, devKey, host,
317
317
  dockerRemove(img.fullLocalName);
318
318
  if (registry) dockerRemove(`${registry}/datagrok/${remoteFullName}`);
319
319
  result = buildAndPush() ?? (await fallbackImage(img, host, devKey, registry, version, contentHash));
320
+ } else if (generatedDirs.includes(img.dirName)) {
321
+ // Generated worker dirs are just FROM the stock base: a cached local tag
322
+ // pins whatever base the daemon had when it was first built (CI served a
323
+ // day-old worker this way). Always run docker build — the layer cache
324
+ // makes an unchanged rebuild near-instant, and a refreshed base lands.
325
+ color.log(`Rebuilding generated image ${img.fullLocalName} against the current base...`);
326
+ result = buildAndPush() ?? (await fallbackImage(img, host, devKey, registry, version, contentHash));
320
327
  } else {
321
328
  // Look for registry-qualified image first, then unqualified
322
329
  let foundLocalName = null;
@@ -468,6 +475,13 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
468
475
  const chunks = [];
469
476
  zip.on('data', chunk => chunks.push(chunk));
470
477
 
478
+ // Generate Celery / Node-worker Docker artifacts BEFORE gathering files: the
479
+ // generated dockerfiles/<dir>/Dockerfile must land in the zip, or the server
480
+ // never binds the queue funcs to the client-built image and falls back to a
481
+ // differently-named server-side container that never starts
482
+ // (<pkg>-queue-celery vs the built <pkg>-queue).
483
+ const generatedDockerDirs = [...(0, _pythonCeleryGen.generateCeleryArtifacts)(curDir), ...(0, _queueWorkerGen.generateQueueArtifacts)(curDir)];
484
+
471
485
  // Gather the files
472
486
  const localTimestamps = {};
473
487
  const files = await (0, _ignoreWalk.default)({
@@ -558,19 +572,13 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
558
572
  return 1;
559
573
  }
560
574
 
561
- // Generate Celery Docker artifacts from python/ if present
562
- (0, _pythonCeleryGen.generateCeleryArtifacts)(curDir);
563
-
564
- // Generate the Node worker container for meta.queue JS functions if present
565
- (0, _queueWorkerGen.generateQueueArtifacts)(curDir);
566
-
567
575
  // Process Docker images and inject image.json into the ZIP
568
576
  let dockerVersion = json.version;
569
577
  if (debug) {
570
578
  const userInfo = await getUserLogin(host, devKey);
571
579
  if (userInfo) dockerVersion = userInfo.login;
572
580
  }
573
- await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug, skipDockerRebuild ?? false);
581
+ await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug, skipDockerRebuild ?? false, generatedDockerDirs);
574
582
  zip.append(JSON.stringify(localTimestamps), {
575
583
  name: 'timestamps.json'
576
584
  });
@@ -230,24 +230,27 @@ function deployFolder(packageDir, folderPath, dockerSubfolder, base) {
230
230
  color.log(`Generated Celery Docker artifacts in dockerfiles/${dockerSubfolder}/`);
231
231
  return true;
232
232
  }
233
+
234
+ /** Returns the generated dockerfiles/<dir> names (empty when nothing generated). */
233
235
  function generateCeleryArtifacts(packageDir) {
234
236
  const pythonDir = _path.default.join(packageDir, 'python');
235
- if (!_fs.default.existsSync(pythonDir)) return false;
237
+ if (!_fs.default.existsSync(pythonDir)) return [];
236
238
  const packageJson = JSON.parse(_fs.default.readFileSync(_path.default.join(packageDir, 'package.json'), 'utf-8'));
237
239
  const base = (0, _utils.removeScope)(packageJson.name).toLowerCase();
238
240
  const entries = _fs.default.readdirSync(pythonDir, {
239
241
  withFileTypes: true
240
242
  });
241
243
  const isNested = entries.length > 0 && entries.every(e => e.isDirectory());
242
- let generated = false;
244
+ const generated = [];
243
245
  if (isNested) {
244
246
  for (const entry of entries) {
245
247
  if (!entry.isDirectory()) continue;
246
248
  const folderPath = _path.default.join(pythonDir, entry.name);
247
- if (deployFolder(packageDir, folderPath, `${entry.name.toLowerCase()}-celery`, base)) generated = true;
249
+ const subfolder = `${entry.name.toLowerCase()}-celery`;
250
+ if (deployFolder(packageDir, folderPath, subfolder, base)) generated.push(subfolder);
248
251
  }
249
252
  } else {
250
- if (deployFolder(packageDir, pythonDir, 'celery', base)) generated = true;
253
+ if (deployFolder(packageDir, pythonDir, 'celery', base)) generated.push('celery');
251
254
  }
252
255
  return generated;
253
256
  }
@@ -9,9 +9,10 @@ var _fs = _interopRequireDefault(require("fs"));
9
9
  var _path = _interopRequireDefault(require("path"));
10
10
  var color = _interopRequireWildcard(require("./color-utils"));
11
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;
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;
15
16
  const QUEUE_DIR = 'queue';
16
17
  const TEMPLATE_NODE_DOCKER = `FROM datagrok/celery_node_worker:bleeding-edge
17
18
  EXPOSE 8000
@@ -20,11 +21,12 @@ EXPOSE 8000
20
21
  /** Generates dockerfiles/queue/ for packages with \`meta.queue: true\` functions so the
21
22
  * standard docker build/push/image.json flow covers the auto-created Node worker
22
23
  * 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
+ * package bundle at runtime, so the image is just the stock worker base.
25
+ * Returns the generated dockerfiles/<dir> names (empty when nothing generated). */
24
26
  function generateQueueArtifacts(packageDir) {
25
27
  const candidates = ['package.ts', 'package.g.ts'].map(f => _path.default.join(packageDir, 'src', f)).filter(f => _fs.default.existsSync(f));
26
28
  const hasQueueFuncs = candidates.some(f => queueTrueRegex.test(_fs.default.readFileSync(f, 'utf-8')));
27
- if (!hasQueueFuncs) return false;
29
+ if (!hasQueueFuncs) return [];
28
30
  const dockerfilesDir = _path.default.join(packageDir, 'dockerfiles', QUEUE_DIR);
29
31
  const dockerfilePath = _path.default.join(dockerfilesDir, 'Dockerfile');
30
32
  _fs.default.mkdirSync(dockerfilesDir, {
@@ -37,5 +39,5 @@ function generateQueueArtifacts(packageDir) {
37
39
  const containerJsonDest = _path.default.join(dockerfilesDir, 'container.json');
38
40
  if (_fs.default.existsSync(containerJsonSrc) && !_fs.default.existsSync(containerJsonDest)) _fs.default.copyFileSync(containerJsonSrc, containerJsonDest);
39
41
  color.log(`Generated Node worker Docker artifacts in dockerfiles/${QUEUE_DIR}/`);
40
- return true;
42
+ return [QUEUE_DIR];
41
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "6.4.6",
3
+ "version": "6.4.8",
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": {