datagrok-tools 6.7.1 → 6.7.3

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > datagrok-tools@6.7.1 build /home/runner/work/public/public/tools
2
+ > datagrok-tools@6.7.3 build /home/runner/work/public/public/tools
3
3
  > node build.js
4
4
 
5
5
  Successfully compiled 51 files with Babel.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,23 @@
1
1
  # Datagrok-tools changelog
2
2
 
3
- ## 6.7.0 (WIP)
3
+ ## 6.7.3 (2026-09-17)
4
+
5
+ * The function-metadata plugin emits decorator roles in camel case again (`fileHandler`, `app`, `viewer`, ...); since 6.7.1 it capitalised them, which broke JS code comparing `options.role` with `DG.FUNC_TYPES`.
6
+ * `grok check` accepts `## v.next` as the top changelog heading, the section the repository keeps unreleased changes in.
7
+
8
+ ## 6.7.2 (2026-09-16)
9
+
10
+ * `grok publish` marks a bundled package for servers older than 1.28.0, which detect one only by a `webpack.config.js` in the archive; the marker is generated at publish time and never written to the package folder.
11
+
12
+ ## 6.7.1 (2026-09-15)
4
13
 
5
14
  * `grok login <server>` — keypair authentication, the replacement for the developer key. Generates an EC P-256 key, registers only its public half (in the browser, or with a one-shot `--code` from Profile > Public keys...), and keeps the private half in `~/.grok/keys/<alias>.json`. Logging in signs a server-issued nonce, so nothing reusable crosses the wire; keys can carry an expiry (`--expires`) and are revoked one at a time. `grok publish`, `grok test`, `grok stresstest` and `grok s` use it automatically whenever one is configured for the server, and fall back to the developer key otherwise. For CI, `GROK_PRIVATE_KEY` holds the private JWK (raw or base64) instead of a config file. Needs a server from 1.28 on; see https://datagrok.ai/help/govern/access-control/keypair-authentication
6
15
  * `grok s token` — prints a session token for the configured server, so shell scripts stop curling `/users/login/dev` with a long-lived key.
7
16
  * `grok config add` — `--key` is now optional: a server reached with a keypair has no developer key to record.
8
17
  * Against a server older than 1.28 — which has no keypair endpoints and refuses their paths before routing — the CLI names the version needed instead of reporting a bare 401, and falls back to the developer key when one is still configured for that server.
9
18
  * `grok login` reports its progress step by step (spinner on a terminal, one line per step in a log), including while it waits for the browser approval.
19
+ * `grok check` — warns when a package pins its own version of a platform-served library (`build-config/platform-deps.json`) instead of `catalog:`; the `common/*.js` shared-library map is read from the same manifest.
20
+ * `grok s push/migrate` — a migrated entity is stamped in its `metaParams` with `migrated_from` (the source stand URL) and `migrated_on`, so on the target it is clear the entity came from another stand. Written wherever the entity already carries `metaParams` (as `sync_id` is) and stripped before the idempotency comparison, so a re-push of an unchanged entity still reads as identical.
10
21
 
11
22
  ## 6.6.0 (2026-09-13)
12
23
 
@@ -21,7 +21,7 @@ var utils = _interopRequireWildcard(require("../utils/utils"));
21
21
  var color = _interopRequireWildcard(require("../utils/color-utils"));
22
22
  var _const = require("../utils/const");
23
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); }
24
- const warns = ['Latest package version', 'Datagrok API version should contain'];
24
+ const warns = ['Latest package version', 'Datagrok API version should contain', 'is served by the platform'];
25
25
  const forbiddenNames = ['function', 'class', 'export'];
26
26
  /** Static imports of these libraries significantly increase the main bundle size. */
27
27
  const HEAVY_IMPORT_RULES = [
@@ -375,26 +375,29 @@ function checkFuncSignatures(packagePath, files) {
375
375
 
376
376
  return [warnings, errors];
377
377
  }
378
- const sharedLibExternals = {
379
- 'common/html2canvas.min.js': {
380
- 'exceljs': 'ExcelJS'
381
- },
382
- 'common/exceljs.min.js': {
383
- 'html2canvas': 'html2canvas'
384
- },
385
- 'common/ngl_viewer/ngl.js': {
386
- 'NGL': 'NGL'
387
- },
388
- 'common/openchemlib-full.js': {
389
- 'openchemlib/full': 'OCL'
390
- },
391
- 'common/codemirror/codemirror.js': {
392
- 'codemirror': 'CodeMirror'
393
- },
394
- 'common/vue.js': {
395
- 'vue': 'Vue'
378
+
379
+ /** Libraries the platform serves at runtime; versions live in build-config/platform-deps.json. */
380
+ function platformDeps(packagePath) {
381
+ try {
382
+ return require(require.resolve('@datagrok/build-config/platform-deps.json', {
383
+ paths: [packagePath]
384
+ }));
385
+ } catch {
386
+ return {};
396
387
  }
397
- };
388
+ }
389
+
390
+ /** For each `common/*.js` source: the import specifiers a bundled package must externalize, with their globals. */
391
+ function sharedLibExternals(packagePath) {
392
+ const result = {};
393
+ for (const [name, d] of Object.entries(platformDeps(packagePath))) {
394
+ if (d.source) result[d.source] = {
395
+ [name]: d.global,
396
+ ...d.imports
397
+ };
398
+ }
399
+ return result;
400
+ }
398
401
  function checkPackageFile(packagePath, json, options) {
399
402
  const warnings = [];
400
403
  const isPublicPackage = _path.default.basename(_path.default.dirname(packagePath)) === 'packages' && _path.default.basename(_path.default.dirname(_path.default.dirname(packagePath))) === 'public';
@@ -416,6 +419,14 @@ function checkPackageFile(packagePath, json, options) {
416
419
  // if (dt && dt !== 'latest')
417
420
  // warnings.push('File "package.json": "datagrok-tools" dependency must be "latest" version.');
418
421
 
422
+ const platform = platformDeps(packagePath);
423
+ for (const [name, spec] of Object.entries({
424
+ ...json.dependencies,
425
+ ...json.devDependencies
426
+ })) {
427
+ if (name in platform && !spec.startsWith('catalog:')) warnings.push(`File "package.json": "${name}" is served by the platform (${platform[name].version}, ` + `see build-config/platform-deps.json); "${spec}" overrides it. Use "catalog:" unless the override is intended.`);
428
+ }
429
+ const shared = sharedLibExternals(packagePath);
419
430
  if (Array.isArray(json.sources) && json.sources.length > 0) {
420
431
  for (const source of json.sources) {
421
432
  if (typeof source !== 'string') warnings.push(`File "package.json": Only file paths and URLs are allowed in sources. Modify the source ${source}`);
@@ -423,14 +434,15 @@ function checkPackageFile(packagePath, json, options) {
423
434
  if (source.startsWith('common/')) {
424
435
  if (options?.isWebpack && source.endsWith('.js')) {
425
436
  if (options?.externals) {
426
- if (source in sharedLibExternals) {
427
- const [lib, name] = Object.entries(sharedLibExternals[source])[0];
428
- if (!(lib in options.externals && options.externals[lib] === name)) {
437
+ if (source in shared) {
438
+ const expected = Object.entries(shared[source]);
439
+ if (!expected.some(([lib, name]) => options.externals[lib] === name)) {
440
+ const [lib, name] = expected[0];
429
441
  warnings.push(`Webpack config parsing: Consider adding source "${source}" to webpack externals:\n` + `'${lib}': '${name}'\n`);
430
442
  }
431
443
  } else warnings.push(`File "package.json": source "${source}" not in the list of shared libraries`);
432
444
  } else {
433
- warnings.push('Webpack config parsing: External modules not found.\n' + `Consider adding source "${source}" to webpack externals` + (source in sharedLibExternals ? ':\n' + `'${Object.keys(sharedLibExternals[source])[0]}': '${Object.values(sharedLibExternals[source])[0]}'\n` : ''));
445
+ warnings.push('Webpack config parsing: External modules not found.\n' + `Consider adding source "${source}" to webpack externals` + (source in shared ? ':\n' + `'${Object.keys(shared[source])[0]}': '${Object.values(shared[source])[0]}'\n` : ''));
434
446
  }
435
447
  }
436
448
  continue;
@@ -475,9 +487,9 @@ function checkChangelog(packagePath, json) {
475
487
  let regex = /^##[^#].*$/gm;
476
488
  const h2 = clf.match(regex);
477
489
  if (!h2) return ['No versions found in CHANGELOG.md\n'];
478
- regex = /^## \d+\.\d+\.\d+ \((\d{4}-\d{2}-\d{2}|WIP)\)$/;
490
+ regex = /^## (v\.next|\d+\.\d+\.\d+ \((\d{4}-\d{2}-\d{2}|WIP)\))$/;
479
491
  for (const h of h2) {
480
- if (!regex.test(h)) warnings.push(`CHANGELOG: '${h}' does not match the h2 format, expected: ## <version> (<yyyy-mm-dd> | WIP)\n`);
492
+ if (!regex.test(h)) warnings.push(`CHANGELOG: '${h}' does not match the h2 format, expected: ## <version> (<yyyy-mm-dd> | WIP) or ## v.next\n`);
481
493
  }
482
494
  regex = /^## (\d+\.\d+\.\d+)/;
483
495
  const v1 = h2[0].match(regex)?.[1];
@@ -4,6 +4,8 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
+ exports.BUNDLE_MARKER_NAME = exports.BUNDLE_MARKER = void 0;
8
+ exports.predatesBundleDetection = predatesBundleDetection;
7
9
  exports.processPackage = processPackage;
8
10
  exports.publish = publish;
9
11
  var _archiver = _interopRequireDefault(require("archiver"));
@@ -487,6 +489,21 @@ async function fallbackImage(img, host, devKey, registry, version, contentHash)
487
489
  requestedVersion: img.imageTag
488
490
  };
489
491
  }
492
+ const BUNDLE_MARKER_NAME = exports.BUNDLE_MARKER_NAME = 'webpack.config.js';
493
+ const BUNDLE_MARKER = exports.BUNDLE_MARKER = '// Generated by `grok publish` for servers before 1.28.0, which detect a ' + 'bundled\n// package by this file. The bundle itself is built by @datagrok/build-config.\nmodule.exports = {};\n';
494
+ function predatesBundleDetection(version) {
495
+ const [major, minor, patch] = (version ?? '').split('.').map(Number);
496
+ if ([major, minor, patch].some(isNaN)) return true;
497
+ return major < 1 || major === 1 && minor < 28;
498
+ }
499
+ async function serverPredatesBundleDetection(host) {
500
+ try {
501
+ const resp = await (0, _nodeFetch.default)(`${host}/info/server`);
502
+ return predatesBundleDetection((await resp.json())?.Version);
503
+ } catch {
504
+ return true;
505
+ }
506
+ }
490
507
  async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker, skipDockerRebuild) {
491
508
  // Validate server connectivity and dev key
492
509
  let timestamps = {};
@@ -612,6 +629,13 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
612
629
  if (userInfo) dockerVersion = userInfo.login;
613
630
  }
614
631
  await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug, skipDockerRebuild ?? false, generatedDockerDirs);
632
+ const bundledWithoutMarker = !rebuild && _fs.default.existsSync('dist/package.js') && !_fs.default.existsSync(BUNDLE_MARKER_NAME);
633
+ if (bundledWithoutMarker && (await serverPredatesBundleDetection(host))) {
634
+ zip.append(BUNDLE_MARKER, {
635
+ name: BUNDLE_MARKER_NAME
636
+ });
637
+ localTimestamps[BUNDLE_MARKER_NAME] = new Date().toUTCString();
638
+ }
615
639
  zip.append(JSON.stringify(localTimestamps), {
616
640
  name: 'timestamps.json'
617
641
  });
@@ -35,8 +35,12 @@ function normalize(type, json) {
35
35
  if (copy.package) copy.package = {
36
36
  id: copy.package.id
37
37
  };
38
- // The stamp is written by the pusher, so it must not make an unchanged entity look different.
39
- if (copy.metaParams) delete copy.metaParams.sync_id;
38
+ // The stamps are written by the pusher, so they must not make an unchanged entity look different.
39
+ if (copy.metaParams) {
40
+ delete copy.metaParams.sync_id;
41
+ delete copy.metaParams.migrated_from;
42
+ delete copy.metaParams.migrated_on;
43
+ }
40
44
  _registry.TYPES[type]?.strip?.(copy);
41
45
  return sortKeys(copy);
42
46
  }
@@ -43,9 +43,12 @@ export function normalize(type: string, json: any): any {
43
43
  delete copy[k];
44
44
  if (copy.package)
45
45
  copy.package = {id: copy.package.id};
46
- // The stamp is written by the pusher, so it must not make an unchanged entity look different.
47
- if (copy.metaParams)
46
+ // The stamps are written by the pusher, so they must not make an unchanged entity look different.
47
+ if (copy.metaParams) {
48
48
  delete copy.metaParams.sync_id;
49
+ delete copy.metaParams.migrated_from;
50
+ delete copy.metaParams.migrated_on;
51
+ }
49
52
  TYPES[type]?.strip?.(copy);
50
53
  return sortKeys(copy);
51
54
  }
@@ -433,7 +433,14 @@ async function saveOne(dapi, bundle, op, rows, idmap, deferred, missing) {
433
433
  const spec = _registry.TYPES[op.type];
434
434
  const payload = (0, _bundle.stripPrivate)(JSON.parse(JSON.stringify(op.json)));
435
435
  const targetId = payload.id ?? op.id;
436
- if (payload.metaParams && typeof payload.metaParams === 'object') payload.metaParams.sync_id = op.id;
436
+ if (payload.metaParams && typeof payload.metaParams === 'object') {
437
+ payload.metaParams.sync_id = op.id;
438
+ const from = bundle.manifest?.source?.url;
439
+ if (from) {
440
+ payload.metaParams.migrated_from = from;
441
+ payload.metaParams.migrated_on = new Date().toISOString();
442
+ }
443
+ }
437
444
 
438
445
  // The server encrypts and masks password-class parameters itself, so target-side
439
446
  // secrets go in as plain parameters and never touch the bundle.
@@ -378,8 +378,14 @@ async function saveOne(dapi: NodeDapi, bundle: Bundle, op: Op, rows: Row[], idma
378
378
  const spec = TYPES[op.type];
379
379
  const payload = stripPrivate(JSON.parse(JSON.stringify(op.json)));
380
380
  const targetId = payload.id ?? op.id;
381
- if (payload.metaParams && typeof payload.metaParams === 'object')
381
+ if (payload.metaParams && typeof payload.metaParams === 'object') {
382
382
  payload.metaParams.sync_id = op.id;
383
+ const from = bundle.manifest?.source?.url;
384
+ if (from) {
385
+ payload.metaParams.migrated_from = from;
386
+ payload.metaParams.migrated_on = new Date().toISOString();
387
+ }
388
+ }
383
389
 
384
390
  // The server encrypts and masks password-class parameters itself, so target-side
385
391
  // secrets go in as plain parameters and never touch the bundle.
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.7.1",
7
+ "version": "6.7.3",
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": {
@@ -19,7 +19,7 @@ const {
19
19
  } = require('../bin/utils/func-generation');
20
20
 
21
21
  // Not imported from commands/migrate: that module loads ts-morph (~1.5 s) at require time.
22
- const toCamelCase = (s) => s.split('-').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('');
22
+ const toCamelCase = (s) => s.replace(/[-_ ]+(\w)/g, (_, c) => c.toUpperCase()).replace(/^[A-Z]/, (c) => c.toLowerCase());
23
23
 
24
24
  let _api;
25
25
  const api = (...a) => (_api ??= require('../bin/commands/api').api)(...a);