datagrok-tools 6.7.0 → 6.7.2

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.
@@ -13,8 +13,10 @@ Datagrok's package management tool
13
13
  Commands:
14
14
  add Add an object template
15
15
  api Create wrapper functions
16
- build Build a package or multiple packages
17
- check Check package content (function signatures, etgc.)
16
+ setup Get a public/ checkout ready to build (pnpm, install, npm-era clean-up)
17
+ build Build a package and its dependencies (Turborepo), or just this package
18
+ tsc Run the workspace TypeScript compiler
19
+ check Check package content (function signatures, etc.)
18
20
  claude Launch Claude Code in a Datagrok dev container
19
21
  config Create and manage config files
20
22
  create Create a package
@@ -209,7 +211,8 @@ Options:
209
211
  --all Publish all available packages (run in packages directory)
210
212
  --refresh Publish all available already loaded packages (run in packages directory)
211
213
  --link Link the package to local packages
212
- --build Builds the package
214
+ --build Builds the package (the default; kept for compatibility)
215
+ --skip-build Upload the existing dist/ without rebuilding
213
216
  --release Publish package as release version
214
217
  --rebuild-docker Force rebuild Docker images locally before pushing to registry
215
218
  --skip-docker-rebuild Skip auto-rebuild when Dockerfile folder has changed
@@ -308,6 +311,9 @@ https://datagrok.ai/help/develop/how-to/test-packages#local-testing
308
311
  const HELP_LINK = `
309
312
  Usage: grok link
310
313
 
314
+ In a pnpm workspace checkout (public/) this is a no-op: \`pnpm install\` at the repository root
315
+ links every in-repo dependency (workspace:^). The options below apply to standalone checkouts only.
316
+
311
317
  Links \`datagrok-api\`, all necessary libraries and packages for local development.
312
318
  Uses \`npm link\` unless the --path option specified.
313
319
  By default, it links packages from the parent directory of the repository's root.
@@ -341,27 +347,61 @@ Example:
341
347
 
342
348
  meta: { role: 'viewer,ml' }
343
349
  `;
350
+ const HELP_SETUP = `
351
+ Usage: grok setup [--check] [--global]
352
+
353
+ Gets a public/ checkout (or a fresh worktree) ready to build, and keeps it that way after a pull:
354
+ 1. Node 20+ (22 recommended); pnpm through corepack, at the version the workspace pins
355
+ 2. removes per-package node_modules left by the npm era and stray package-lock.json files
356
+ 3. pnpm install at the workspace root
357
+ 4. reports a global grok older than the workspace one
358
+
359
+ --check Report only, change nothing
360
+ --global Also update the global datagrok-tools to the workspace version
361
+
362
+ Examples:
363
+ grok setup After cloning, after a pull that changed dependencies, in a new worktree
364
+ grok setup --check What would change
365
+ `;
366
+ const HELP_TSC = `
367
+ Usage: grok tsc [tsc arguments]
368
+
369
+ Runs the workspace TypeScript compiler (the version @datagrok/build-config pins) with the given arguments,
370
+ so a package never needs its own tsc. After an emitting run in a library, the css/wasm/json assets its
371
+ sources import are mirrored into dist/.
372
+
373
+ Examples:
374
+ grok tsc --noEmit -p tsconfig.json Type-check (what the \`typecheck\` script runs)
375
+ grok tsc -p tsconfig.build.json A library with a custom build config
376
+ `;
344
377
  const HELP_BUILD = `
345
378
  Usage: grok build
346
379
 
347
- Build a package in the current directory, or recursively build multiple packages.
380
+ Inside the public/ workspace: build the package in the current directory and everything it depends
381
+ on, in dependency order, through Turborepo (cached: unchanged packages take milliseconds). Never prompts.
382
+ Inside a Turborepo task, in a standalone package, or with --local: build just this package — an rspack
383
+ bundle (src/package.ts -> dist/package.js, function metadata generated, \`grok check --soft\`) for a
384
+ plugin, a TypeScript emit into dist/ for a library.
348
385
 
349
386
  Options:
350
- [-r | --recursive] [-s | --silent] [--filter] [--no-incremental] [--parallel N] [-v | --verbose]
351
-
352
- --recursive Build all packages in the current directory
353
- --silent Skip confirmation prompt (for recursive builds)
354
- --filter Filter packages by package.json fields (e.g. --filter "category:Cheminformatics")
355
- --no-incremental Run a full build instead of the default incremental build
356
- --parallel N Max parallel builds (default: 4)
357
- --verbose Print detailed output
387
+ [--all] [--affected] [--typecheck] [--filter <turbo filter>] [--parallel N] [--force] [--local] [--skip-check] [-v | --verbose]
388
+
389
+ --all Build the whole workspace
390
+ --affected Build everything the diff against origin/master affects, dependents included
391
+ --typecheck Also run the type-check task
392
+ --filter Extra Turborepo filter (e.g. --filter "@datagrok/chem...")
393
+ --parallel N Max parallel builds (default: 3)
394
+ --force Ignore the build cache
395
+ --local Build only this package, without Turborepo
396
+ --skip-check Local build: skip \`grok check\`
397
+ --verbose Show full build output instead of errors only
358
398
 
359
399
  Examples:
360
- grok build Build the current package
361
- grok build -r Build all packages in the current directory
362
- grok build -r -s Build all packages without confirmation
363
- grok build -r --filter "category:Cheminformatics" Build only matching packages
364
- grok build -r --parallel 8 Build with 8 parallel jobs
400
+ grok build This package and its dependencies
401
+ grok build --all Everything
402
+ grok build --affected What my change touches (CI does the same)
403
+ grok build --typecheck Bundle and type-check
404
+ grok build --local Just this package (what the package's build script runs)
365
405
  `;
366
406
 
367
407
  // const HELP_MIGRATE = `
@@ -440,9 +480,11 @@ const help = exports.help = {
440
480
  report: HELP_REPORT,
441
481
  run: HELP_RUN,
442
482
  test: HELP_TEST,
483
+ tsc: HELP_TSC,
443
484
  testall: HELP_TESTALL,
444
485
  migrate: HELP_MIGRATE,
445
486
  server: _server.HELP_SERVER,
446
487
  s: _server.HELP_SERVER,
488
+ setup: HELP_SETUP,
447
489
  help: HELP
448
490
  };
@@ -30,11 +30,21 @@ while (_path.default.dirname(dirStep) !== dirStep) {
30
30
  }
31
31
  dirStep = _path.default.dirname(dirStep);
32
32
  }
33
+ function isPnpmWorkspace(dir) {
34
+ for (let d = _path.default.resolve(dir);; d = _path.default.dirname(d)) {
35
+ if (_fs.default.existsSync(_path.default.join(d, 'pnpm-workspace.yaml'))) return true;
36
+ if (_path.default.dirname(d) === d) return false;
37
+ }
38
+ }
33
39
  let verbose = false;
34
40
  let pathMode = false;
35
41
  let devMode = false;
36
42
  let unlink = false;
37
43
  async function link(args) {
44
+ if (isPnpmWorkspace(curDir)) {
45
+ console.log('This checkout is a pnpm workspace: in-repo dependencies are linked by `pnpm install` ' + '(workspace:^), and `grok link` is not needed. Run `pnpm install` at the repository root.');
46
+ return true;
47
+ }
38
48
  verbose = args.verbose ?? false;
39
49
  devMode = args.dev ?? false;
40
50
  pathMode = args.path ?? false;
@@ -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 = {};
@@ -526,7 +543,10 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
526
543
  includeEmpty: false,
527
544
  follow: true
528
545
  });
529
- const isWebpack = _fs.default.existsSync('webpack.config.js');
546
+
547
+ // A bundled package ships dist/package.js: built by webpack (legacy) or by `grok build`
548
+ // (rspack via @datagrok/build-config, which needs no config file at all).
549
+ const isWebpack = _fs.default.existsSync('webpack.config.js') || _fs.default.existsSync('rspack.config.js') || _fs.default.existsSync('dist/package.js') || _fs.default.existsSync(_path.default.join(curDir, 'src', 'package.ts'));
530
550
  if (!rebuild && isWebpack) {
531
551
  if (_fs.default.existsSync('dist/package.js')) {
532
552
  const distFiles = await (0, _ignoreWalk.default)({
@@ -550,12 +570,6 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
550
570
  const json = JSON.parse(_fs.default.readFileSync(packageFilePath, {
551
571
  encoding: 'utf-8'
552
572
  }));
553
- if (isWebpack) {
554
- const webpackConfigPath = _path.default.join(curDir, 'webpack.config.js');
555
- const content = _fs.default.readFileSync(webpackConfigPath, {
556
- encoding: 'utf-8'
557
- });
558
- }
559
573
  const funcFiles = jsTsFiles.filter(f => packageFiles.includes(f));
560
574
  color.log(`Checks finished in ${Date.now() - checkStart} ms`);
561
575
  const reg = new RegExp(/\${(\w*)}/g);
@@ -615,6 +629,13 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
615
629
  if (userInfo) dockerVersion = userInfo.login;
616
630
  }
617
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
+ }
618
639
  zip.append(JSON.stringify(localTimestamps), {
619
640
  name: 'timestamps.json'
620
641
  });
@@ -692,12 +713,14 @@ async function publish(args) {
692
713
  }
693
714
  async function publishPackage(args) {
694
715
  const nArgs = args['_'].length;
695
- if (!args.link) {
696
- if (args.build || args.rebuild) {
697
- color.log('Building');
698
- await utils.runScript('npm install', curDir, false);
699
- await utils.runScript('npm run build', curDir, false);
700
- }
716
+
717
+ // A bundled package is built locally before upload unless --skip-build. Inside the pnpm
718
+ // workspace the install already happened at the root; standalone packages install first.
719
+ if (!args.link && !args['skip-build'] && !args.rebuild && _fs.default.existsSync(_path.default.join(curDir, 'src'))) {
720
+ color.log('Building');
721
+ const workspace = utils.isPnpmWorkspace(curDir);
722
+ if (!workspace && !_fs.default.existsSync(_path.default.join(curDir, 'node_modules'))) await utils.runScript('npm install', curDir, false);
723
+ await utils.runScript(workspace ? 'pnpm run build' : 'npm run build', curDir, false);
701
724
  }
702
725
  if (args.debug && args.release) {
703
726
  color.error('Incompatible options: --debug and --release');
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.setup = setup;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _child_process = require("child_process");
11
+ var color = _interopRequireWildcard(require("../utils/color-utils"));
12
+ var _build = require("./build");
13
+ 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); }
14
+ /**
15
+ * `grok setup`: one command to get (or keep) a public/ checkout ready to build.
16
+ * 1. Node 20+ (22 recommended); pnpm through corepack, at the version package.json pins.
17
+ * 2. Removes per-package node_modules left by the npm era and stray package-lock.json files.
18
+ * 3. `pnpm install` at the workspace root.
19
+ * 4. Reports a global `grok` older than the workspace one (`--global` updates it).
20
+ * `--check` only reports.
21
+ */
22
+ async function setup(args) {
23
+ const root = (0, _build.findWorkspaceRoot)(process.cwd());
24
+ if (!root) {
25
+ color.error('Not inside the public/ workspace (no pnpm-workspace.yaml above the current directory).');
26
+ return false;
27
+ }
28
+ const check = !!args.check;
29
+ const rootPkg = JSON.parse(_fs.default.readFileSync(_path.default.join(root, 'package.json'), 'utf8'));
30
+ const pinnedPnpm = (rootPkg.packageManager || 'pnpm@10').split('@')[1];
31
+ let ok = true;
32
+
33
+ // 1. Node and pnpm
34
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
35
+ if (nodeMajor < 20) {
36
+ color.error(`Node ${process.versions.node}: 20 or later is required (22 recommended).`);
37
+ return false;
38
+ }
39
+ color.info(`Node ${process.versions.node}`);
40
+ let pnpmVersion = run('pnpm', ['--version']);
41
+ if (pnpmVersion !== pinnedPnpm) {
42
+ color.warn(`pnpm ${pnpmVersion || 'not found'}; the workspace pins ${pinnedPnpm}.`);
43
+ if (!check) {
44
+ if (run('corepack', ['--version']) === null) color.warn('corepack not found: `npm install -g corepack`, then rerun `grok setup`.');else {
45
+ (0, _child_process.spawnSync)('corepack', ['enable'], {
46
+ stdio: 'inherit',
47
+ shell: true
48
+ });
49
+ (0, _child_process.spawnSync)('corepack', ['prepare', `pnpm@${pinnedPnpm}`, '--activate'], {
50
+ stdio: 'inherit',
51
+ shell: true
52
+ });
53
+ pnpmVersion = run('pnpm', ['--version']);
54
+ }
55
+ }
56
+ }
57
+ if (pnpmVersion === pinnedPnpm) color.info(`pnpm ${pnpmVersion}`);else ok = false;
58
+
59
+ // 2. leftovers from the npm era
60
+ const projectDirs = ['js-api', 'tools', 'build-config'].map(d => _path.default.join(root, d));
61
+ for (const group of ['packages', 'libraries']) for (const d of _fs.default.readdirSync(_path.default.join(root, group))) projectDirs.push(_path.default.join(root, group, d));
62
+ const staleModules = projectDirs.filter(d => isLegacyNodeModules(_path.default.join(d, 'node_modules')));
63
+ const staleLocks = projectDirs.map(d => _path.default.join(d, 'package-lock.json')).filter(f => _fs.default.existsSync(f));
64
+ if (staleModules.length || staleLocks.length) {
65
+ color.warn(`${staleModules.length} per-package node_modules from npm and ${staleLocks.length} package-lock.json file(s)` + (check ? ' would be removed' : ': removing'));
66
+ if (!check) {
67
+ for (const d of staleModules) _fs.default.rmSync(_path.default.join(d, 'node_modules'), {
68
+ recursive: true,
69
+ force: true
70
+ });
71
+ for (const f of staleLocks) _fs.default.rmSync(f, {
72
+ force: true
73
+ });
74
+ }
75
+ } else color.info('no npm-era leftovers');
76
+
77
+ // 3. install
78
+ if (!check && pnpmVersion === pinnedPnpm) {
79
+ color.log('pnpm install ...');
80
+ const r = (0, _child_process.spawnSync)('pnpm', ['install', '--frozen-lockfile'], {
81
+ stdio: 'inherit',
82
+ cwd: root,
83
+ shell: true
84
+ });
85
+ if (r.status !== 0) {
86
+ color.warn('frozen install failed (lockfile out of date?): retrying without --frozen-lockfile');
87
+ if ((0, _child_process.spawnSync)('pnpm', ['install'], {
88
+ stdio: 'inherit',
89
+ cwd: root,
90
+ shell: true
91
+ }).status !== 0) {
92
+ color.error('pnpm install failed');
93
+ return false;
94
+ }
95
+ }
96
+ }
97
+
98
+ // 4. the global grok
99
+ const workspaceGrok = JSON.parse(_fs.default.readFileSync(_path.default.join(root, 'tools', 'package.json'), 'utf8')).version;
100
+ // datagrok-tools before 6.6 has no --version and prints its usage instead
101
+ const raw = run('grok', ['--version']);
102
+ const globalGrok = raw === null ? null : /^\d+\.\d+\.\d+/.test(raw) ? raw : 'older than 6.6';
103
+ if (globalGrok && globalGrok !== workspaceGrok) {
104
+ color.warn(`global grok ${globalGrok}; the workspace has ${workspaceGrok}` + (args.global && !check ? ': updating' : ' (run `npm install -g datagrok-tools@' + workspaceGrok + '`, or `grok setup --global`)'));
105
+ if (args.global && !check) (0, _child_process.spawnSync)('npm', ['install', '-g', `datagrok-tools@${workspaceGrok}`], {
106
+ stdio: 'inherit',
107
+ shell: true
108
+ });
109
+ } else if (globalGrok) color.info(`grok ${globalGrok}`);
110
+ if (ok && !check) color.success('Ready: `grok build` in any package, `grok build --all` for everything.');
111
+ return ok;
112
+ }
113
+
114
+ /** npm's node_modules holds real directories; pnpm's per-package one holds only symlinks (and .bin). */
115
+ function isLegacyNodeModules(dir) {
116
+ if (!_fs.default.existsSync(dir)) return false;
117
+ for (const e of _fs.default.readdirSync(dir, {
118
+ withFileTypes: true
119
+ })) {
120
+ if (e.name === '.bin' || e.name === '.cache' || e.name.startsWith('.')) continue;
121
+ const p = _path.default.join(dir, e.name);
122
+ if (e.isSymbolicLink()) continue;
123
+ if (e.isDirectory() && e.name.startsWith('@')) {
124
+ if (_fs.default.readdirSync(p, {
125
+ withFileTypes: true
126
+ }).some(s => s.isDirectory() && !s.isSymbolicLink())) return true;
127
+ continue;
128
+ }
129
+ if (e.isDirectory()) return true;
130
+ }
131
+ return false;
132
+ }
133
+ function run(cmd, args) {
134
+ const r = (0, _child_process.spawnSync)(cmd, args, {
135
+ encoding: 'utf8',
136
+ shell: true
137
+ });
138
+ return r.status === 0 ? r.stdout.trim().split(/\r?\n/).pop() || '' : null;
139
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.tsc = tsc;
7
+ var _child_process = require("child_process");
8
+ var _toolchain = require("../utils/toolchain");
9
+ /**
10
+ * `grok tsc ...` runs the workspace TypeScript compiler (the one `@datagrok/build-config` pins) with the
11
+ * given arguments, so package scripts never depend on a locally installed `tsc`. After an emitting run
12
+ * in a library, the assets its sources import are mirrored into dist/.
13
+ */
14
+ async function tsc(args) {
15
+ const cwd = process.cwd();
16
+ const extra = process.argv.slice(process.argv.indexOf('tsc') + 1);
17
+ const emitting = !extra.includes('--noEmit');
18
+ if (emitting) (0, _toolchain.ensureLibraryExports)(cwd);
19
+ const r = (0, _child_process.spawnSync)(process.execPath, [(0, _toolchain.toolchain)(cwd).tsc, ...extra], {
20
+ stdio: 'inherit',
21
+ cwd
22
+ });
23
+ if (r.status !== 0) return false;
24
+ if (emitting) (0, _toolchain.copyLibraryAssets)(cwd);
25
+ return true;
26
+ }
package/bin/grok.js CHANGED
@@ -9,32 +9,45 @@ const argv = require('minimist')(process.argv.slice(2), {
9
9
  // test.ts / playwright-runner.ts never fired and `--no-retry` was silently ignored
10
10
  // (Playwright kept retrying failed specs). Normalize back to the flag the commands read.
11
11
  if (argv.retry === false) argv['no-retry'] = true;
12
- const help = require('./commands/help').help;
12
+ // The help texts are a large module; load them only when one is printed.
13
+ let _help;
14
+ const help = new Proxy({}, {get: (_, key) => (_help ??= require('./commands/help').help)[key]});
13
15
  const runAllCommand = require('./utils/utils').runAll;
14
16
 
17
+ // Each command module is loaded only when invoked: loading all of them (puppeteer, ts-morph,
18
+ // archiver, inquirer) used to cost ~6 s of start-up on every `grok api` / `grok check`.
19
+ const lazy = (file, name) => (args) => require(`./commands/${file}`)[name](args);
15
20
  const commands = {
16
- add: require('./commands/add').add,
17
- api: require('./commands/api').api,
18
- build: require('./commands/build').build,
19
- check: require('./commands/check').check,
20
- claude: require('./commands/claude').claude,
21
- config: require('./commands/config').config,
22
- create: require('./commands/create').create,
23
- 'docker-gen': require('./commands/docker-gen').dockerGen,
24
- init: require('./commands/init').init,
25
- link: require('./commands/link').link,
26
- login: require('./commands/login').login,
27
- publish: require('./commands/publish').publish,
28
- report: require('./commands/report').report,
29
- run: require('./commands/run').run,
30
- test: require('./commands/test').test,
31
- testall: require('./commands/test-all').testAll,
32
- stresstest: require('./commands/stress-tests').stressTests,
33
- migrate: require('./commands/migrate').migrate,
34
- server: require('./commands/server').server,
35
- s: require('./commands/server').server,
21
+ add: lazy('add', 'add'),
22
+ api: lazy('api', 'api'),
23
+ build: lazy('build', 'build'),
24
+ check: lazy('check', 'check'),
25
+ claude: lazy('claude', 'claude'),
26
+ config: lazy('config', 'config'),
27
+ create: lazy('create', 'create'),
28
+ 'docker-gen': lazy('docker-gen', 'dockerGen'),
29
+ init: lazy('init', 'init'),
30
+ link: lazy('link', 'link'),
31
+ login: lazy('login', 'login'),
32
+ publish: lazy('publish', 'publish'),
33
+ report: lazy('report', 'report'),
34
+ run: lazy('run', 'run'),
35
+ test: lazy('test', 'test'),
36
+ tsc: lazy('tsc', 'tsc'),
37
+ testall: lazy('test-all', 'testAll'),
38
+ stresstest: lazy('stress-tests', 'stressTests'),
39
+ migrate: lazy('migrate', 'migrate'),
40
+ server: lazy('server', 'server'),
41
+ s: lazy('server', 'server'),
42
+ setup: lazy('setup', 'setup'),
36
43
  };
37
44
 
45
+ // `--version` is a string option (grok publish --version 1.10), so a bare `grok --version` parses as ''.
46
+ if (argv._.length === 0 && ('version' in argv && argv.version === '' || argv.v === true)) {
47
+ console.log(require('../package.json').version);
48
+ process.exit(0);
49
+ }
50
+
38
51
  const onPackageCommandNames = ['api', 'check', 'link', 'publish', 'test'];
39
52
 
40
53
  // A machine-readable run prints its error as JSON on stderr (server.ts) and nothing else:
@@ -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.
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.copyLibraryAssets = copyLibraryAssets;
8
+ exports.ensureLibraryExports = ensureLibraryExports;
9
+ exports.toolchain = toolchain;
10
+ var _fs = _interopRequireDefault(require("fs"));
11
+ var _path = _interopRequireDefault(require("path"));
12
+ /**
13
+ * The shared toolchain (`@datagrok/build-config`: rspack factory, TypeScript) as seen from `dir`:
14
+ * the workspace root's copy inside public/, the package's own devDependency in a standalone package.
15
+ */
16
+ function toolchain(dir) {
17
+ let pkgJson;
18
+ try {
19
+ pkgJson = require.resolve('@datagrok/build-config/package.json', {
20
+ paths: [dir]
21
+ });
22
+ } catch {
23
+ throw new Error('@datagrok/build-config is not installed: run `pnpm install` at the root of public/, ' + 'or add it as a devDependency of a standalone package');
24
+ }
25
+ const buildConfigDir = _path.default.dirname(pkgJson);
26
+ const tsPkg = require.resolve('typescript/package.json', {
27
+ paths: [buildConfigDir]
28
+ });
29
+ return {
30
+ buildConfig: require(buildConfigDir),
31
+ buildConfigDir,
32
+ tsc: _path.default.join(_path.default.dirname(tsPkg), 'bin', 'tsc')
33
+ };
34
+ }
35
+ const ASSET = /\.(css|scss|wasm|js|mjs|cjs|json|png|jpe?g|gif|svg|txt|csv|md|html|sdf|mol)$/i;
36
+
37
+ /**
38
+ * A library's dist/ must be self-contained: the css, wasm, json and plain-js files its sources
39
+ * import relative to themselves are mirrored next to the compiled output. A .js next to a .ts of
40
+ * the same name is a stale in-place compile, not an asset.
41
+ */
42
+ function copyLibraryAssets(dir) {
43
+ if (!_fs.default.existsSync(_path.default.join(dir, 'dist'))) return 0;
44
+ let n = 0;
45
+ const walk = rel => {
46
+ for (const e of _fs.default.readdirSync(_path.default.join(dir, rel), {
47
+ withFileTypes: true
48
+ })) {
49
+ if (e.name === 'node_modules' || e.name === 'dist' || e.name.startsWith('.')) continue;
50
+ const r = rel ? `${rel}/${e.name}` : e.name;
51
+ if (e.isDirectory()) walk(r);else if (rel && ASSET.test(e.name) && e.name !== 'package.json' && !isCompiledSibling(dir, r)) {
52
+ const to = _path.default.join(dir, 'dist', r);
53
+ _fs.default.mkdirSync(_path.default.dirname(to), {
54
+ recursive: true
55
+ });
56
+ const from = _path.default.join(dir, r);
57
+ if (!_fs.default.existsSync(to) || _fs.default.statSync(to).mtimeMs < _fs.default.statSync(from).mtimeMs) {
58
+ _fs.default.copyFileSync(from, to);
59
+ n++;
60
+ }
61
+ }
62
+ }
63
+ };
64
+ walk('');
65
+ if (n) console.log(`copied ${n} asset file(s) into dist/`);
66
+ return n;
67
+ }
68
+
69
+ /**
70
+ * Keeps a library's package.json `exports` in step with its source tree. A library that ships dist/
71
+ * has an `exports` map ending in the `./*` wildcard; the wildcard cannot resolve a directory import
72
+ * (`@datagrok-libraries/bio/src/trees`), so every directory with an index.ts needs an explicit entry.
73
+ * Adds the missing ones and leaves everything else (hand-written entries, the wildcards, entries for
74
+ * directories that no longer exist) untouched. Packages without the wildcard are not managed.
75
+ */
76
+ function ensureLibraryExports(dir) {
77
+ const pj = _path.default.join(dir, 'package.json');
78
+ const p = JSON.parse(_fs.default.readFileSync(pj, 'utf8'));
79
+ const ex = p.exports;
80
+ if (!ex || typeof ex !== 'object' || !ex['./*']) return false;
81
+ const indexDirs = new Set();
82
+ const walk = (d, rel) => {
83
+ for (const e of _fs.default.readdirSync(d, {
84
+ withFileTypes: true
85
+ })) {
86
+ if (e.name === 'node_modules' || e.name === 'dist' || e.name.startsWith('.')) continue;
87
+ const r = rel ? `${rel}/${e.name}` : e.name;
88
+ if (e.isDirectory()) walk(_path.default.join(d, e.name), r);else if (e.name === 'index.ts' && rel) indexDirs.add(rel);
89
+ }
90
+ };
91
+ walk(dir, '');
92
+ // Add-only: hand-written entries (aliases such as js-api's "./u2core") are never touched.
93
+ const next = {
94
+ ...ex
95
+ };
96
+ let changed = false;
97
+ for (const rel of [...indexDirs].sort()) {
98
+ const key = `./${rel}`;
99
+ if (!(key in next)) {
100
+ next[key] = {
101
+ types: `./dist/${rel}/index.d.ts`,
102
+ default: `./dist/${rel}/index.js`
103
+ };
104
+ changed = true;
105
+ }
106
+ }
107
+ if (!changed) return false;
108
+ // wildcards last: exports are matched by specificity, but keeping them last reads better
109
+ const wild = ['./*.js', './*'].filter(k => k in next);
110
+ const ordered = {};
111
+ for (const [k, v] of Object.entries(next)) if (!wild.includes(k)) ordered[k] = v;
112
+ for (const k of wild) ordered[k] = next[k];
113
+ p.exports = ordered;
114
+ _fs.default.writeFileSync(pj, JSON.stringify(p, null, 2) + '\n');
115
+ console.log(`package.json exports updated for ${_path.default.basename(dir)}`);
116
+ return true;
117
+ }
118
+ function isCompiledSibling(dir, rel) {
119
+ const m = rel.match(/^(.*)\.(js|mjs|cjs)$/);
120
+ return !!m && ['.ts', '.tsx'].some(e => _fs.default.existsSync(_path.default.join(dir, m[1] + e)));
121
+ }