datagrok-tools 6.0.1 → 6.0.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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(find /c/repos/Datagrok -type f \\\\\\( -name \"Dockerfile*\" -o -name \"docker-compose*\" \\\\\\) 2>/dev/null | grep -v node_modules | head -20)",
5
+ "Bash(npm run:*)",
6
+ "Bash(docker build:*)",
7
+ "Bash(cd C:/repos/awesome && node C:/repos/Datagrok/public/tools/bin/grok.js claude destroy GROK-PLAYGROUND 2>&1)",
8
+ "Bash(cd C:/repos/awesome && node C:/repos/Datagrok/public/tools/bin/grok.js claude GROK-PLAYGROUND --in-place --keep --prompt \"echo hello and exit\" 2>&1)",
9
+ "Bash(cd C:/repos/awesome && node C:/repos/Datagrok/public/tools/bin/grok.js claude GROK-PLAYGROUND --in-place --keep --prompt \"echo done\" 2>&1)",
10
+ "Bash(git -C C:/repos/awesome rev-parse --show-toplevel 2>&1)",
11
+ "Bash(cd C:/repos/awesome && node C:/repos/Datagrok/public/tools/bin/grok.js claude GROK-PLAYGROUND 2>&1)",
12
+ "Bash(cd C:/repos/awesome && node C:/repos/Datagrok/public/tools/bin/grok.js claude GROK-PLAYGROUND --keep 2>&1)",
13
+ "Bash(MSYS_NO_PATHCONV=1 docker exec dg-pkg-grok-playground-tools-dev-1 ls /workspace/datagrok/ 2>&1)",
14
+ "Bash(MSYS_NO_PATHCONV=1 docker exec dg-pkg-grok-playground-tools-dev-1 ls -la /workspace/datagrok/packages/awesome 2>&1)",
15
+ "Bash(MSYS_NO_PATHCONV=1 docker exec dg-pkg-grok-playground-tools-dev-1 ls /workspace/datagrok/packages/awesome/ 2>&1)",
16
+ "Bash(MSYS_NO_PATHCONV=1 docker exec dg-pkg-grok-playground-tools-dev-1 bash -c 'echo \"=== Symlink ===\" && ls -la /workspace/datagrok/packages/awesome && echo \"=== Package contents ===\" && ls /workspace/datagrok/packages/awesome/ && echo \"=== Public repo root ===\" && ls /workspace/datagrok/js-api /workspace/datagrok/packages /workspace/datagrok/CLAUDE.md 2>&1 | head -5' 2>&1)",
17
+ "Bash(grep -r \"tools-dev\" /c/repos/Datagrok --include=\"*\" 2>/dev/null | head -20)"
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1 @@
1
+ *.sh text eol=lf
@@ -1,39 +1,23 @@
1
1
  #!/bin/bash
2
2
  set -e
3
3
 
4
- # Resolve the actual version when DG_VERSION is "latest" needed for git branch matching.
5
- resolve_version() {
4
+ # Map DG_VERSION to a git branch name for cloning the public repo.
5
+ # Docker tags like "latest" or "bleeding-edge" aren't git branches — use master.
6
+ # Explicit version numbers (e.g. "0.151.9") are tried as-is with a master fallback.
7
+ resolve_branch() {
6
8
  local ver="${1:-latest}"
7
- if [ "$ver" = "latest" ]; then
8
- echo "[tools-dev] Resolving latest Datagrok version from Docker Hub..." >&2
9
- local resolved
10
- resolved=$(curl -sf "https://hub.docker.com/v2/repositories/datagrok/datagrok/tags?page_size=100&ordering=-name" \
11
- | node -e "
12
- const data = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
13
- const tags = (data.results||[]).map(t=>t.name).filter(n=>/^\d+\.\d+\.\d+$/.test(n));
14
- tags.sort((a,b)=>{
15
- const pa=a.split('.').map(Number), pb=b.split('.').map(Number);
16
- return pb[0]-pa[0]||pb[1]-pa[1]||pb[2]-pa[2];
17
- });
18
- if(tags[0]) process.stdout.write(tags[0]);
19
- " 2>/dev/null) || true
20
- if [ -n "$resolved" ]; then
21
- echo "[tools-dev] Resolved latest version: $resolved" >&2
22
- echo "$resolved"
23
- else
24
- echo "[tools-dev] Could not resolve latest version, falling back to master" >&2
25
- echo "master"
26
- fi
27
- else
28
- echo "$ver"
29
- fi
9
+ case "$ver" in
10
+ latest|bleeding-edge) echo "master" ;;
11
+ *) echo "$ver" ;;
12
+ esac
30
13
  }
31
14
 
32
- PUBLIC_DIR="/workspace/public"
33
- # Detect if /workspace/repo IS the public repo (has js-api/ at root)
34
- if [ -d "/workspace/repo/js-api" ]; then
15
+ PUBLIC_DIR="${DG_PUBLIC_DIR:-/workspace/datagrok}"
16
+ # Detect if /workspace/repo IS the public repo.
17
+ # Require .git to avoid false positives from packages that have public/js-api via npm link.
18
+ if [ -e "/workspace/repo/.git" ] && [ -d "/workspace/repo/js-api" ]; then
35
19
  echo "[tools-dev] Public repo detected at /workspace/repo — skipping clone."
36
- elif [ -d "/workspace/repo/public/js-api" ]; then
20
+ elif [ -e "/workspace/repo/.git" ] && [ -d "/workspace/repo/public/js-api" ]; then
37
21
  echo "[tools-dev] Monorepo detected — public repo at /workspace/repo/public."
38
22
  elif [ -d "$PUBLIC_DIR/js-api" ]; then
39
23
  echo "[tools-dev] Public repo already at $PUBLIC_DIR."
@@ -43,31 +27,45 @@ else
43
27
  if [ -n "$DG_PUBLIC_BRANCH" ]; then
44
28
  BRANCH="$DG_PUBLIC_BRANCH"
45
29
  else
46
- BRANCH=$(resolve_version "${DG_VERSION:-latest}")
30
+ BRANCH=$(resolve_branch "${DG_VERSION:-latest}")
47
31
  fi
48
- echo "[tools-dev] Cloning public repo ($BRANCH) into $PUBLIC_DIR..."
49
- git clone --depth 1 --branch "$BRANCH" "$REPO" "$PUBLIC_DIR" 2>/dev/null \
32
+ # Sparse checkout: only fetch dirs needed for package context (js-api, libraries, help, packages)
33
+ # plus root files (CLAUDE.md, .claude/, etc.). Much faster than a full clone.
34
+ sparse_clone() {
35
+ local branch="$1"
36
+ git clone --depth 1 --branch "$branch" --filter=blob:none --no-checkout "$REPO" "$PUBLIC_DIR" \
37
+ && git -C "$PUBLIC_DIR" sparse-checkout set --no-cone \
38
+ '/*' '!connectors/' '!docker/' '!docusaurus/' '!docusaurus-static/' \
39
+ '!environments/' '!hooks/' '!misc/' '!python-api/' '!datagrok-celery-task/' \
40
+ '/js-api/**' '/libraries/**' '/help/**' '/packages/**' '/tools/**' \
41
+ && git -C "$PUBLIC_DIR" checkout
42
+ }
43
+ echo "[tools-dev] Cloning public repo ($BRANCH, sparse) into $PUBLIC_DIR..."
44
+ sparse_clone "$BRANCH" \
50
45
  || { echo "[tools-dev] Branch '$BRANCH' not found, falling back to master."
51
- git clone --depth 1 --branch master "$REPO" "$PUBLIC_DIR"; }
46
+ rm -rf "$PUBLIC_DIR"
47
+ sparse_clone "master"; }
52
48
  echo "[tools-dev] Public repo ready at $PUBLIC_DIR (branch: $(git -C "$PUBLIC_DIR" branch --show-current))."
53
49
  fi
54
50
 
55
51
  # ── Mount workspace inside public repo for non-public workspaces ──
56
52
  # When TASK_KEY is set and workspace is not the public repo, link it into packages/
57
53
  # so Claude Code walks up to find all public repo context (CLAUDE.md, .claude/, js-api/, etc.)
58
- if [ -d "/workspace/repo/js-api" ]; then
54
+ if [ -e "/workspace/repo/.git" ] && [ -d "/workspace/repo/js-api" ]; then
59
55
  echo "[tools-dev] Public repo IS the workspace — no linking needed."
60
- elif [ -d "/workspace/repo/public/js-api" ]; then
56
+ elif [ -e "/workspace/repo/.git" ] && [ -d "/workspace/repo/public/js-api" ]; then
61
57
  echo "[tools-dev] Monorepo detected — public context at /workspace/repo/public/."
62
58
  elif [ -d "$PUBLIC_DIR/js-api" ]; then
63
59
  # Cloned public repo — link workspace into packages/ and expose context at /workspace/
64
- if [ -n "$TASK_KEY" ]; then
60
+ LINK_NAME="${FOLDER_NAME:-$TASK_KEY}"
61
+ if [ -n "$LINK_NAME" ]; then
65
62
  mkdir -p "$PUBLIC_DIR/packages" 2>/dev/null || true
66
- [ ! -e "$PUBLIC_DIR/packages/$TASK_KEY" ] && ln -s /workspace/repo "$PUBLIC_DIR/packages/$TASK_KEY"
67
- echo "[tools-dev] Workspace linked at $PUBLIC_DIR/packages/$TASK_KEY"
63
+ [ ! -e "$PUBLIC_DIR/packages/$LINK_NAME" ] && ln -s /workspace/repo "$PUBLIC_DIR/packages/$LINK_NAME"
64
+ echo "[tools-dev] Workspace linked at $PUBLIC_DIR/packages/$LINK_NAME"
68
65
  fi
69
- [ ! -e /workspace/.claude ] && ln -s public/.claude /workspace/.claude
70
- [ ! -e /workspace/CLAUDE.md ] && ln -s public/CLAUDE.md /workspace/CLAUDE.md
66
+ PUBLIC_BASENAME=$(basename "$PUBLIC_DIR")
67
+ [ ! -e /workspace/.claude ] && ln -s "$PUBLIC_BASENAME/.claude" /workspace/.claude
68
+ [ ! -e /workspace/CLAUDE.md ] && ln -s "$PUBLIC_BASENAME/CLAUDE.md" /workspace/CLAUDE.md
71
69
  echo "[tools-dev] Linked public repo context at /workspace/"
72
70
  fi
73
71
 
package/CLAUDE.md CHANGED
@@ -14,7 +14,7 @@ npm run build # Transpile TypeScript to JavaScript using Babe
14
14
  npm run debug-source-map # Build with source maps for debugging
15
15
  ```
16
16
 
17
- The build process uses Babel with `@babel/preset-typescript` to transpile TypeScript files from `bin/` (TypeScript source) to `bin/` (JavaScript output). The source TypeScript files are transpiled in-place.
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
18
 
19
19
  ### Link for Local Development
20
20
  ```bash
@@ -42,13 +42,18 @@ The CLI uses a modular command pattern. Each command is a separate module that:
42
42
  - `add.ts` - Add entities (functions, scripts, queries, etc.) to packages
43
43
  - `publish.ts` - Upload and deploy packages to Datagrok servers
44
44
  - `check.ts` - Validate package structure, signatures, imports
45
+ - `build.ts` - Build one package or recursively build all packages in a directory
45
46
  - `test.ts` - Run Puppeteer-based tests for a single package
46
47
  - `test-all.ts` - Run tests across multiple packages
48
+ - `stress-tests.ts` - Run stress tests (must be run from ApiTests package)
47
49
  - `api.ts` - Auto-generate TypeScript wrappers for scripts/queries
48
50
  - `link.ts` - Link libraries for plugin development
51
+ - `claude.ts` - Launch a Dockerized dev environment with Datagrok + Claude Code
49
52
  - `migrate.ts` - Update legacy packages
50
53
  - `init.ts` - Apply configuration to existing packages
51
54
 
55
+ The commands `api`, `check`, `link`, `publish`, and `test` support the `--all` flag to run recursively across all packages in the current directory.
56
+
52
57
  ### Template System
53
58
 
54
59
  **Package Templates** (`package-template/`)
@@ -132,6 +137,29 @@ Tests use Puppeteer for headless browser automation:
132
137
  - `--catchUnhandled` - Catch unhandled exceptions
133
138
  - `--debug` - Debug breakpoints (requires `--gui`)
134
139
 
140
+ ### `grok build` Command
141
+
142
+ Builds packages with `npm install` + `npm run build`. Supports:
143
+ - Single package: `grok build` (from package directory)
144
+ - Recursive: `grok build --recursive` (discovers and builds all packages in subdirectories)
145
+ - `--filter "name:Chem"` - Filter packages by package.json fields (supports regex, `&&` for multiple conditions)
146
+ - `--parallel N` - Max parallel build jobs (default 4)
147
+ - `--no-incremental` - Force full rebuild (default uses `--env incremental`)
148
+
149
+ ### `grok claude` Command
150
+
151
+ Launches a full Dockerized development environment (Datagrok + PostgreSQL + RabbitMQ + tools-dev container with Claude Code):
152
+ ```bash
153
+ grok claude <project-name> # Create worktree + start containers + launch Claude
154
+ grok claude <project-name> --in-place # Use current directory (no worktree)
155
+ grok claude <project-name> --keep # Leave containers running on exit
156
+ grok claude <project-name> --profile full # Include spawner, JKG, demo DBs
157
+ grok claude destroy <project-name> # Tear down containers + worktree
158
+ grok claude destroy-all # Destroy all known projects
159
+ ```
160
+
161
+ Creates a git worktree at `~/pkg-worktrees/<project-name>`, writes Docker Compose files to `$TMPDIR/dg-pkg-<project-name>`, and auto-detects the Datagrok version (`bleeding-edge` for public repo, `latest` otherwise). Mounts `~/.claude` into the container for credentials.
162
+
135
163
  ## Key Patterns and Conventions
136
164
 
137
165
  ### Naming Conventions
@@ -248,11 +248,13 @@ services:
248
248
  DG_VERSION: \${DG_VERSION:-latest}
249
249
  DG_PUBLIC_REPO: \${DG_PUBLIC_REPO:-https://github.com/datagrok-ai/public.git}
250
250
  DG_PUBLIC_BRANCH: \${DG_PUBLIC_BRANCH:-}
251
+ DG_PUBLIC_DIR: \${DG_PUBLIC_DIR:-/workspace/datagrok}
251
252
  JIRA_URL: \${JIRA_URL:-https://reddata.atlassian.net}
252
253
  JIRA_USERNAME: \${JIRA_USERNAME:-}
253
254
  JIRA_TOKEN: \${JIRA_TOKEN:-}
254
255
  GITHUB_TOKEN: \${GITHUB_TOKEN:-}
255
256
  TASK_KEY: \${TASK_KEY:-}
257
+ FOLDER_NAME: \${FOLDER_NAME:-}
256
258
  working_dir: /workspace/repo
257
259
  networks:
258
260
  dg:
@@ -332,7 +334,7 @@ function writeProjectFiles(taskKey, args, worktreeRoot, dgPort) {
332
334
  const dockerSock = process.platform === 'win32' ? '//var/run/docker.sock' : '/var/run/docker.sock';
333
335
 
334
336
  // Generate .env — all paths use forward slashes for Docker compatibility
335
- const envLines = [`WORKTREE_PATH=${toDockerPath(worktreeRoot)}`, `DG_PORT=${dgPort}`, `TASK_KEY=${taskKey.toLowerCase()}`, `DOCKER_SOCK=${dockerSock}`, `DATAGROK_VERSION=${args.version || 'latest'}`, `DG_VERSION=${args.version || 'latest'}`, `GROK_CONNECT_VERSION=${args['grok-connect-version'] || 'latest'}`, `GROK_SPAWNER_VERSION=${args['grok-spawner-version'] || 'latest'}`, `JKG_VERSION=${args['jkg-version'] || 'latest'}`, `TOOLS_DEV_VERSION=${args['tools-dev-version'] || 'latest'}`];
337
+ const envLines = [`WORKTREE_PATH=${toDockerPath(worktreeRoot)}`, `DG_PORT=${dgPort}`, `TASK_KEY=${taskKey.toLowerCase()}`, `DOCKER_SOCK=${dockerSock}`, `DATAGROK_VERSION=${args.version || 'latest'}`, `DG_VERSION=${args.version || 'latest'}`, `GROK_CONNECT_VERSION=${args['grok-connect-version'] || 'latest'}`, `GROK_SPAWNER_VERSION=${args['grok-spawner-version'] || 'latest'}`, `JKG_VERSION=${args['jkg-version'] || 'latest'}`, `TOOLS_DEV_VERSION=${args['tools-dev-version'] || 'latest'}`, `FOLDER_NAME=${_path.default.basename(worktreeRoot)}`];
336
338
  for (const env of ['ANTHROPIC_API_KEY', 'DG_PUBLIC_BRANCH', 'JIRA_URL', 'JIRA_USERNAME', 'JIRA_TOKEN', 'GITHUB_TOKEN']) if (process.env[env]) envLines.push(`${env}=${process.env[env]}`);
337
339
  _fs.default.writeFileSync(_path.default.join(projectDir, '.env'), envLines.join('\n') + '\n');
338
340
 
@@ -540,61 +542,37 @@ async function claude(args) {
540
542
  return false;
541
543
  }
542
544
 
543
- // Wait for Datagrok to be healthy
544
- const healthy = await waitForDatagrok(dgPort);
545
- if (!healthy) color.warn('Datagrok may not be ready. Proceeding anyway...');
546
-
547
- // Determine whether this is a public repo and compute Claude working directory
548
- const isPublic = repoRoot ? isPublicRepo(repoRoot) : false;
549
- const claudeWorkDir = isPublic ? '/workspace/repo/public' : `/workspace/public/packages/${taskKey}`;
550
-
551
- // Fix ownership on bind-mounted directories (host UID may differ from container node user)
545
+ // Determine Claude working directory based on repo type
546
+ // Only trust public-repo markers when we are inside a real git repo — packages can have
547
+ // public/js-api via node_modules symlinks, which would cause a false positive.
552
548
  const containerName = `dg-pkg-${taskKey.toLowerCase()}-tools-dev-1`;
553
- (0, _child_process.spawnSync)('docker', ['exec', '-u', 'root', containerName, 'bash', '-c', 'chown node:node /workspace 2>/dev/null; ' + 'for d in /home/node/.claude /home/node/.npm; do ' + 'mkdir -p "$d" 2>/dev/null; chown -R node:node "$d" 2>/dev/null; done; ' + '[ -f /home/node/.claude.json ] && chown node:node /home/node/.claude.json 2>/dev/null; true'], {
554
- stdio: 'inherit'
555
- });
556
-
557
- // Copy host grok config into container and ensure 'local' server exists
558
- (0, _child_process.spawnSync)('docker', ['exec', '-u', 'root', containerName, 'bash', '-c', 'mkdir -p /home/node/.grok && chown node:node /home/node/.grok'], {
559
- stdio: 'inherit'
560
- });
561
- const grokConfigPath = _path.default.join(_os.default.homedir(), '.grok', 'config.yaml');
562
- if (_fs.default.existsSync(grokConfigPath)) {
563
- (0, _child_process.spawnSync)('docker', ['cp', grokConfigPath, `${containerName}:/home/node/.grok/config.yaml`], {
564
- stdio: 'inherit'
565
- });
566
- (0, _child_process.spawnSync)('docker', ['exec', '-u', 'root', containerName, 'chown', 'node:node', '/home/node/.grok/config.yaml'], {
549
+ let claudeWorkDir;
550
+ if (repoRoot && _fs.default.existsSync(_path.default.join(repoRoot, 'js-api'))) claudeWorkDir = '/workspace/repo';else if (repoRoot && _fs.default.existsSync(_path.default.join(repoRoot, 'public', 'js-api'))) claudeWorkDir = '/workspace/repo/public';else {
551
+ // External repo: entrypoint clones public repo and creates symlink — wait for it
552
+ const folderName = _path.default.basename(worktreeRoot);
553
+ claudeWorkDir = `/workspace/datagrok/packages/${folderName}`;
554
+ color.info(`Waiting for workspace at ${claudeWorkDir} (streaming container logs)...`);
555
+
556
+ // Stream container logs so the user can see clone progress
557
+ const logsProc = (0, _child_process.spawn)('docker', ['logs', '-f', containerName], {
567
558
  stdio: 'inherit'
568
559
  });
569
- color.info('Copied grok config into container');
570
- }
571
-
572
- // Add/ensure 'local' server pointing to compose datagrok (key=admin matches GROK_PARAMETERS)
573
- (0, _child_process.spawnSync)('docker', ['exec', containerName, 'node', '-e', `
574
- const fs = require('fs');
575
- const p = '/home/node/.grok/config.yaml';
576
- let t = '';
577
- try { t = fs.readFileSync(p, 'utf8'); } catch {}
578
- if (!t.trim()) {
579
- t = 'default: local\\nservers:\\n local:\\n url: http://datagrok:8080/api\\n key: admin\\n';
580
- } else if (!t.includes('datagrok:8080')) {
581
- t = t.replace(/^(servers:)/m, '\\$1\\n local:\\n url: http://datagrok:8080/api\\n key: admin');
582
- t = t.replace(/^default:.*/m, 'default: local');
583
- } else {
584
- t = t.replace(/^default:.*/m, 'default: local');
560
+ let ready = false;
561
+ for (let i = 0; i < 120; i++) {
562
+ const check = (0, _child_process.spawnSync)('docker', ['exec', containerName, 'test', '-d', claudeWorkDir]);
563
+ if (check.status === 0) {
564
+ ready = true;
565
+ break;
566
+ }
567
+ await new Promise(r => setTimeout(r, 5000));
585
568
  }
586
- fs.writeFileSync(p, t);
587
- `], {
588
- stdio: 'inherit'
589
- });
590
569
 
591
- // Set up workspace context: for non-public repos, bind-mount workspace into public/packages/
592
- // so that process.cwd() returns the mount-point path (not the resolved symlink target)
593
- // and relative paths like ../../js-api resolve correctly inside the public repo tree.
594
- if (!isPublic) {
595
- (0, _child_process.spawnSync)('docker', ['exec', '-u', 'root', containerName, 'bash', '-c', 'for i in $(seq 1 60); do [ -d /workspace/public/js-api ] && break; sleep 3; done; ' + 'if [ -d /workspace/public/js-api ]; then ' + ' mkdir -p /workspace/public/packages 2>/dev/null; ' + ` rm -f /workspace/public/packages/${taskKey} 2>/dev/null; ` + ` if ! mountpoint -q /workspace/public/packages/${taskKey} 2>/dev/null; then ` + ` mkdir -p /workspace/public/packages/${taskKey} && ` + ` mount --bind /workspace/repo /workspace/public/packages/${taskKey}; ` + ' fi; ' + ` echo "[grok claude] Workspace at /workspace/public/packages/${taskKey}"; ` + 'else ' + ' echo "[grok claude] Warning: public repo clone not ready"; ' + 'fi'], {
596
- stdio: 'inherit'
597
- });
570
+ // Stop streaming logs
571
+ logsProc.kill();
572
+ if (!ready) {
573
+ color.warn(`${claudeWorkDir} not available after 600s — falling back to /workspace/repo`);
574
+ claudeWorkDir = '/workspace/repo';
575
+ }
598
576
  }
599
577
  const claudeArgs = ['--dangerously-skip-permissions'];
600
578
  if (args.prompt) claudeArgs.push('-p', args.prompt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
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": {