datagrok-tools 6.1.6 → 6.1.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/CLAUDE.md CHANGED
@@ -1,31 +1,12 @@
1
1
  # CLAUDE.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
3
  ## Overview
6
4
 
7
5
  This is **datagrok-tools**, a CLI utility for creating, validating, testing, and publishing packages to Datagrok. The tool is distributed as `grok` command globally via npm.
8
6
 
9
- ## Build & Development Commands
10
-
11
- ### Build the CLI
12
- ```bash
13
- npm run build # Transpile TypeScript to JavaScript using Babel
14
- npm run debug-source-map # Build with source maps for debugging
15
- ```
16
-
17
- The build process uses Babel with `@babel/preset-typescript` to transpile TypeScript files from `bin/` to `bin/` (in-place). **Important:** `.ts` source and `.js` output coexist in the same `bin/` directory — `grok.js` requires the transpiled `.js` files, not the `.ts` sources. After editing any `.ts` file, you must run `npm run build` before testing.
18
-
19
- ### Link for Local Development
20
- ```bash
21
- npm link # Make 'grok' command available globally for testing
22
- ```
7
+ ## Build Notes
23
8
 
24
- ### Testing
25
- While this CLI tool manages testing for Datagrok packages, it doesn't have its own test suite. To test changes:
26
- 1. Build the tool: `npm run build`
27
- 2. Link it: `npm link`
28
- 3. Test commands manually: `grok create test-package`, `grok check`, etc.
9
+ The build process uses Babel with `@babel/preset-typescript` to transpile TypeScript files from `bin/` to `bin/` (in-place). `.ts` source and `.js` output coexist in the same `bin/` directory -- `grok.js` requires the transpiled `.js` files, not the `.ts` sources. After editing any `.ts` file, you must run `npm run build` before testing.
29
10
 
30
11
  ## Code Architecture
31
12
 
@@ -110,14 +91,32 @@ The `publish` command executes these steps:
110
91
  1. Gather files using `ignore-walk` (respects .npmignore/.gitignore/.grokignore)
111
92
  2. Run validation checks (signatures, imports, package.json, changelog)
112
93
  3. Process environment variables in `/connections/*.json` files (replace `${VAR}`)
113
- 4. Create ZIP archive with archiver-promise
114
- 5. Upload to server: `POST ${host}/packages/dev/${devKey}/${packageName}`
94
+ 4. **Process Docker images** (see below)
95
+ 5. Create ZIP archive with archiver-promise (includes `image.json` metadata per container)
96
+ 6. Upload to server: `POST ${host}/packages/dev/${devKey}/${packageName}`
115
97
 
116
98
  **Key flags:**
117
99
  - `--debug` (default) - Package visible only to developer
118
100
  - `--release` - Public package
119
101
  - `--build` / `--rebuild` - Control webpack bundling
120
102
  - `--skip-check` - Skip validation
103
+ - `--rebuild-docker` - Force rebuild Docker images locally before pushing to registry
104
+ - `--skip-docker-rebuild` - Skip auto-rebuild even when dockerfile folder has changed
105
+
106
+ ### Docker Image Handling in `publish`
107
+
108
+ Implemented in `bin/commands/publish.ts` (`processDockerImages()`, `discoverDockerfiles()`).
109
+
110
+ When a package has a `dockerfiles/` directory, `grok publish` automatically manages Docker images:
111
+
112
+ 1. **Discovery** — scans `dockerfiles/` for subdirectories containing a `Dockerfile`. Image name = `<packageName>-<folderName>`.
113
+ 2. **Change detection** — computes SHA256 of the entire dockerfile directory. If the hash differs from what the server has, rebuilds automatically (unless `--skip-docker-rebuild`).
114
+ 3. **Build** — `docker build --platform linux/amd64` (always cross-compiled for linux/amd64).
115
+ 4. **Registry** — tags and pushes to configured Docker registry if one is set.
116
+ 5. **Metadata** — writes `image.json` into the ZIP for each container so the server knows which image to use.
117
+ 6. **Fallback** — if a local build isn't available, queries the server for a compatible pre-built image.
118
+
119
+ **Practical implication:** editing any file inside `dockerfiles/<name>/` will cause `grok publish` to rebuild that container image on the next publish, without needing `--rebuild-docker`.
121
120
 
122
121
  ### Testing Framework
123
122
 
@@ -273,16 +272,6 @@ servers:
273
272
  key: ''
274
273
  ```
275
274
 
276
- ### Webpack Externals
277
-
278
- The CLI validates that imports match webpack externals to prevent bundling datagrok-api and other provided libraries. Common externals:
279
- - `datagrok-api`
280
- - `rxjs`
281
- - `cash-dom`
282
- - `dayjs`
283
- - `openchemlib/full`
284
- - `wu`
285
-
286
275
  ## File References
287
276
 
288
277
  When working with commands, key entry points are:
@@ -22,6 +22,7 @@ Commands:
22
22
  link Link \`datagrok-api\` and libraries for local development
23
23
  publish Upload a package
24
24
  report Manage user error reports (fetch, resolve, create ticket)
25
+ run Build, publish, and open in browser
25
26
  test Run package tests
26
27
  testall Run packages tests
27
28
  migrate Migrate legacy tags to meta.role
@@ -322,6 +323,25 @@ Examples:
322
323
  // file and converting your scripts in the \`package.json\` file
323
324
  // `;
324
325
 
326
+ const HELP_RUN = `
327
+ Usage: grok run [host]
328
+
329
+ Build, publish, and open the package in the browser.
330
+
331
+ Runs \`grok build\`, publishes the package to the server, then opens the server in the default browser.
332
+
333
+ Options:
334
+ [-k | --key] [--release] [-v | --verbose]
335
+
336
+ --key Developer key (overrides config)
337
+ --release Publish as a release version (default: debug)
338
+ --verbose Print detailed output
339
+
340
+ Examples:
341
+ grok run Build, publish to default server, and open browser
342
+ grok run dev Build, publish to 'dev' server alias, and open browser
343
+ grok run https://my.datagrok.ai/api --key abc123
344
+ `;
325
345
  const HELP_REPORT = `
326
346
  Usage: grok report <subcommand> <instance> <id>
327
347
 
@@ -352,6 +372,7 @@ const help = exports.help = {
352
372
  link: HELP_LINK,
353
373
  publish: HELP_PUBLISH,
354
374
  report: HELP_REPORT,
375
+ run: HELP_RUN,
355
376
  test: HELP_TEST,
356
377
  testall: HELP_TESTALL,
357
378
  migrate: HELP_MIGRATE,
@@ -60,6 +60,18 @@ function discoverDockerfiles(packageName, version, debug) {
60
60
  fullLocalName: `${imageName}:${imageTag}`
61
61
  });
62
62
  }
63
+
64
+ // Handle Dockerfile directly in dockerfiles/ (single-container layout)
65
+ const rootDockerfile = _path.default.join(dockerfilesDir, 'Dockerfile');
66
+ if (_fs.default.existsSync(rootDockerfile)) {
67
+ const cleanName = utils.removeScope(packageName).toLowerCase();
68
+ results.push({
69
+ imageName: cleanName,
70
+ imageTag: version,
71
+ dirName: '.',
72
+ fullLocalName: `${cleanName}:${version}`
73
+ });
74
+ }
63
75
  return results;
64
76
  }
65
77
  function dockerCommand(args) {
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.run = run;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _os = _interopRequireDefault(require("os"));
10
+ var _path = _interopRequireDefault(require("path"));
11
+ var _child_process = require("child_process");
12
+ var _jsYaml = _interopRequireDefault(require("js-yaml"));
13
+ var utils = _interopRequireWildcard(require("../utils/utils"));
14
+ var color = _interopRequireWildcard(require("../utils/color-utils"));
15
+ var _publish = require("./publish");
16
+ 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); }
17
+ const grokDir = _path.default.join(_os.default.homedir(), '.grok');
18
+ const confPath = _path.default.join(grokDir, 'config.yaml');
19
+ function getWebUrl(apiUrl) {
20
+ const u = new URL(apiUrl);
21
+ u.pathname = u.pathname.replace(/\/api\/?$/, '') || '/';
22
+ return u.toString().replace(/\/$/, '');
23
+ }
24
+ function openBrowser(url) {
25
+ let command;
26
+ switch (process.platform) {
27
+ case 'darwin':
28
+ command = `open "${url}"`;
29
+ break;
30
+ case 'win32':
31
+ command = `start "" "${url}"`;
32
+ break;
33
+ default:
34
+ command = `xdg-open "${url}"`;
35
+ }
36
+ (0, _child_process.exec)(command, err => {
37
+ if (err) color.warn(`Could not open browser: ${err.message}`);
38
+ });
39
+ }
40
+ const MISSING_MODULE_PATTERNS = ['cannot find module', 'module not found', 'can\'t resolve'];
41
+ async function buildPackage(dir) {
42
+ const buildCmd = 'npm run build -- --env incremental';
43
+ const packageJson = JSON.parse(_fs.default.readFileSync(_path.default.join(dir, 'package.json'), 'utf-8'));
44
+ const name = packageJson.friendlyName || packageJson.name;
45
+ console.log(`Building ${name}...`);
46
+ try {
47
+ await utils.runScript(buildCmd, dir, color.isVerbose());
48
+ color.success(`Successfully built ${name}`);
49
+ return true;
50
+ } catch (error) {
51
+ const msg = (error?.message ?? '').toLowerCase();
52
+ if (MISSING_MODULE_PATTERNS.some(p => msg.includes(p))) {
53
+ color.warn('Missing modules detected, running npm install...');
54
+ try {
55
+ await utils.runScript('npm install', dir, color.isVerbose());
56
+ await utils.runScript(buildCmd, dir, color.isVerbose());
57
+ color.success(`Successfully built ${name}`);
58
+ return true;
59
+ } catch (retryError) {
60
+ color.error(`Failed to build ${name}`);
61
+ if (retryError.message) color.error(retryError.message);
62
+ return false;
63
+ }
64
+ }
65
+ color.error(`Failed to build ${name}`);
66
+ if (error.message) color.error(error.message);
67
+ return false;
68
+ }
69
+ }
70
+ async function run(args) {
71
+ color.setVerbose(args.verbose || false);
72
+
73
+ // Step 1: Build (skip npm install to preserve npm link; retry with npm install only if needed)
74
+ const built = await buildPackage(process.cwd());
75
+ if (!built) return false;
76
+
77
+ // Step 2: Resolve server URL and key
78
+ if (!_fs.default.existsSync(confPath)) {
79
+ color.error(`Config not found at ${confPath}. Run \`grok config\` first.`);
80
+ return false;
81
+ }
82
+ const config = _jsYaml.default.load(_fs.default.readFileSync(confPath, {
83
+ encoding: 'utf-8'
84
+ }));
85
+ const urls = utils.mapURL(config);
86
+ let host = config.default;
87
+ if (args['_'].length === 2) host = args['_'][1];
88
+ let key = '';
89
+ let url = '';
90
+ let registry;
91
+ try {
92
+ url = new URL(host).href;
93
+ if (url.endsWith('/')) url = url.slice(0, -1);
94
+ if (url in urls) {
95
+ const alias = urls[url];
96
+ key = config['servers'][alias]['key'];
97
+ registry = config['servers'][alias]['registry'];
98
+ }
99
+ } catch (error) {
100
+ if (!(host in config.servers)) {
101
+ color.error(`Unknown server alias. Please add it to ${confPath}`);
102
+ return false;
103
+ }
104
+ url = config['servers'][host]['url'];
105
+ key = config['servers'][host]['key'];
106
+ registry = config['servers'][host]['registry'];
107
+ }
108
+ if (args.key) key = args.key;
109
+ if (key === '') {
110
+ color.warn('Please provide the key with `--key` option or add it by running `grok config`');
111
+ return false;
112
+ }
113
+ const packDir = _path.default.join(process.cwd(), 'package.json');
114
+ if (!_fs.default.existsSync(packDir)) {
115
+ color.error('`package.json` doesn\'t exist');
116
+ return false;
117
+ }
118
+ const packageName = JSON.parse(_fs.default.readFileSync(packDir, {
119
+ encoding: 'utf-8'
120
+ })).name;
121
+
122
+ // Step 3: Publish
123
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
124
+ const code = await (0, _publish.processPackage)(!args.release, false, url, key, packageName, false, undefined, host, registry);
125
+ if (code !== 0) return false;
126
+
127
+ // Step 4: Open browser
128
+ const webUrl = getWebUrl(url);
129
+ color.success(`Opening ${webUrl}`);
130
+ openBrowser(webUrl);
131
+ return true;
132
+ }
package/bin/grok.js CHANGED
@@ -19,6 +19,7 @@ const commands = {
19
19
  link: require('./commands/link').link,
20
20
  publish: require('./commands/publish').publish,
21
21
  report: require('./commands/report').report,
22
+ run: require('./commands/run').run,
22
23
  test: require('./commands/test').test,
23
24
  testall: require('./commands/test-all').testAll,
24
25
  stresstest: require('./commands/stress-tests').stressTests,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "6.1.6",
3
+ "version": "6.1.7",
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": {