datagrok-tools 6.5.6 → 6.5.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/.devcontainer/docker-compose.yaml +68 -24
- package/CHANGELOG.md +41 -6
- package/CLAUDE.md +35 -10
- package/Core.json +1027 -0
- package/GROK_S.md +563 -27
- package/bin/commands/api.js +121 -70
- package/bin/commands/help.js +3 -65
- package/bin/commands/server-domains.js +468 -0
- package/bin/commands/server-migrate.js +392 -0
- package/bin/commands/server.js +455 -92
- package/bin/grok.js +16 -5
- package/bin/utils/migrate/bundle.js +223 -0
- package/bin/utils/migrate/bundle.ts +222 -0
- package/bin/utils/migrate/parts.js +83 -0
- package/bin/utils/migrate/parts.ts +72 -0
- package/bin/utils/migrate/pool.js +17 -0
- package/bin/utils/migrate/pool.ts +13 -0
- package/bin/utils/migrate/pusher.js +980 -0
- package/bin/utils/migrate/pusher.ts +829 -0
- package/bin/utils/migrate/registry.js +349 -0
- package/bin/utils/migrate/registry.ts +255 -0
- package/bin/utils/migrate/rewriter.js +59 -0
- package/bin/utils/migrate/rewriter.ts +59 -0
- package/bin/utils/migrate/walker.js +571 -0
- package/bin/utils/migrate/walker.ts +509 -0
- package/bin/utils/node-dapi.js +787 -141
- package/bin/utils/playwright-runner.js +55 -39
- package/bin/utils/server-client.js +15 -2
- package/bin/utils/server-output.js +65 -4
- package/bin/utils/test-utils.js +1 -1
- package/domain-schema.schema.json +57 -6
- package/package.json +6 -1
- /package/{vitest.config.ts → vitest.config.mts} +0 -0
package/GROK_S.md
CHANGED
|
@@ -21,7 +21,7 @@ browser or a logged-in session.
|
|
|
21
21
|
| List members of a group | `grok s groups list-members <group>` |
|
|
22
22
|
| List the groups a user belongs to | `grok s groups list-memberships <user>` |
|
|
23
23
|
| Share a connection / query / script / project with a group | `grok s shares add <entity> <group> [--access View\|Edit]` |
|
|
24
|
-
| See who an entity is shared with | `grok s shares list <entity-id>`
|
|
24
|
+
| See who an entity is shared with | `grok s shares list <entity-id-or-name>` |
|
|
25
25
|
| Create / update a connection | `grok s connections save --json conn.json` |
|
|
26
26
|
| Test a connection | `grok s connections test <id-or-name>` |
|
|
27
27
|
| Run a registered function | `grok s functions run 'Pkg:fn(arg1,arg2)'` |
|
|
@@ -29,9 +29,16 @@ browser or a logged-in session.
|
|
|
29
29
|
| List / browse files in a file share | `grok s files list "System:AppData" -r` |
|
|
30
30
|
| Upload / download a table (CSV or d42) | `grok s tables upload <name> file.csv\|file.d42` / `tables download <name> -O out.csv` |
|
|
31
31
|
| Check whether a package is deployed | `grok s packages list --filter "MyPlugin"` |
|
|
32
|
-
|
|
|
32
|
+
| Install / update / uninstall server plugins | `grok s packages install Chem Bio` / `packages update --all` / `packages uninstall Chem` |
|
|
33
|
+
| See installed vs latest plugin versions | `grok s packages outdated` / `packages versions Chem` |
|
|
34
|
+
| Count entities | `grok s users count --filter 'status = "active"'` |
|
|
35
|
+
| See what fields an entity type has | `grok s describe connections` |
|
|
36
|
+
| Hit any undocumented endpoint | `grok s raw GET /users/current` / `raw POST <path> --data '{...}'` |
|
|
33
37
|
| Check server + per-module health | `grok s healthcheck [--module <name>]` |
|
|
34
38
|
| Bulk operations in one round-trip | `grok s batch <entity> <verb> --json items.json` |
|
|
39
|
+
| Move entities dev to prod (bundle, or instance to instance) | `grok s pull ... --out ./bundle` / `grok s migrate ... --from dev --to prod` |
|
|
40
|
+
| Browse / query / edit domain-table rows | `grok s domains query grit.issue --filter 'status = "open"'` / `domains insert` / `domains upload` |
|
|
41
|
+
| Manage domain schemas (manifest, apply, grants) | `grok s domains get grit` / `domains apply` / `domains grant grit.issue Chemists --access Edit` |
|
|
35
42
|
|
|
36
43
|
## Configuration
|
|
37
44
|
|
|
@@ -48,21 +55,25 @@ servers:
|
|
|
48
55
|
key: <developer-key>
|
|
49
56
|
```
|
|
50
57
|
|
|
51
|
-
- `grok config
|
|
58
|
+
- `grok config add --alias <name> --server <url> --key <key>` writes a new entry.
|
|
52
59
|
- Add `--default` to make it the active server.
|
|
53
|
-
- Every `grok s ...` command accepts `--host <alias-or-url>` to override the default.
|
|
60
|
+
- Every `grok s ...` command accepts `--host <alias-or-url>` to override the default. The URL
|
|
61
|
+
is the API base (`https://host/api`, or `http://host:8082` for a bare Datlas).
|
|
54
62
|
|
|
55
63
|
## Entity operations
|
|
56
64
|
|
|
57
|
-
### List / get / delete
|
|
65
|
+
### List / count / get / delete
|
|
58
66
|
|
|
59
67
|
```bash
|
|
60
68
|
grok s users list # default: table, 50 rows
|
|
61
69
|
grok s users list --filter "login = 'admin'" # smart filter
|
|
70
|
+
grok s users list --limit 20 --offset 40 # third page of 20
|
|
71
|
+
grok s users count --filter 'status = "active"'
|
|
62
72
|
grok s connections list --output json
|
|
63
73
|
grok s packages list --filter "name:MyPlugin" # table shows friendlyName
|
|
64
74
|
grok s groups get <id-or-name>
|
|
65
|
-
grok s
|
|
75
|
+
grok s users get alice.mendel # users: id or login
|
|
76
|
+
grok s connections delete "Admin:MyConnection" # id or namespace:name
|
|
66
77
|
```
|
|
67
78
|
|
|
68
79
|
Options that work on every entity:
|
|
@@ -75,6 +86,14 @@ Options that work on every entity:
|
|
|
75
86
|
| `--offset <n>` | Skip first `n` rows |
|
|
76
87
|
| `--host <alias\|url>` | Target a specific server from your config |
|
|
77
88
|
|
|
89
|
+
A missing entity, a rejected save (duplicate login or group name), or any other server-side
|
|
90
|
+
failure prints one line on stderr and exits 1 — never an error object on stdout.
|
|
91
|
+
|
|
92
|
+
`delete` is not available for every entity: **users cannot be deleted** (Datagrok has no user
|
|
93
|
+
deletion; `users delete` refuses and points at `users block`), and `functions delete` covers
|
|
94
|
+
scripts and queries only (package functions go away with their package). `count` has no server
|
|
95
|
+
endpoint for `reports`.
|
|
96
|
+
|
|
78
97
|
### Save from JSON
|
|
79
98
|
|
|
80
99
|
`grok s users save` and `grok s groups save` accept the same JSON shape that the
|
|
@@ -125,10 +144,13 @@ grok s groups list-members Chemists # all members
|
|
|
125
144
|
grok s groups list-members Chemists --admin # admin members only
|
|
126
145
|
grok s groups list-members Chemists --no-admin # non-admin members only
|
|
127
146
|
grok s groups list-memberships alice.mendel # groups a user belongs to
|
|
147
|
+
grok s groups list-memberships admin --user # the personal group, not "Administrators"
|
|
128
148
|
```
|
|
129
149
|
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
Names resolve by substring, an exact `name`/`friendlyName` match wins. When a name is still
|
|
151
|
+
ambiguous the command prints every matching group and exits non-zero — pass a UUID to
|
|
152
|
+
disambiguate, or add `--user` (accepted by all four membership verbs) to restrict lookup to
|
|
153
|
+
personal groups.
|
|
132
154
|
|
|
133
155
|
## User administration
|
|
134
156
|
|
|
@@ -145,11 +167,12 @@ flips `status` to `Blocked` and terminates active sessions; unblocking restores
|
|
|
145
167
|
|
|
146
168
|
```bash
|
|
147
169
|
grok s shares add "MyUser:MyConnection" Chemists,Biologists --access Edit
|
|
148
|
-
grok s shares list
|
|
170
|
+
grok s shares list "MyUser:MyConnection"
|
|
149
171
|
```
|
|
150
172
|
|
|
151
173
|
The entity argument accepts either a UUID or an `"author:name"` pair. `--access`
|
|
152
|
-
defaults to `View`.
|
|
174
|
+
defaults to `View`. `shares list` prints one row per group and permission, including the
|
|
175
|
+
grants inherited through the entity's project links (`inherited: true`).
|
|
153
176
|
|
|
154
177
|
## Running functions
|
|
155
178
|
|
|
@@ -159,6 +182,10 @@ grok s functions run 'Pkg:fn({smiles:"CCO", radius:2})' # named args
|
|
|
159
182
|
grok s functions run Pkg:fn --json params.json # big input from a file
|
|
160
183
|
```
|
|
161
184
|
|
|
185
|
+
The server binds arguments by parameter name, so positional values are mapped onto the
|
|
186
|
+
function's inputs in declared order (one extra lookup of the function); more values than inputs
|
|
187
|
+
is an error. Named arguments are sent as they are.
|
|
188
|
+
|
|
162
189
|
## Listing functions with filters
|
|
163
190
|
|
|
164
191
|
`grok s functions list` composes a smart filter from flags so you don't have to hand-roll
|
|
@@ -192,7 +219,8 @@ contain colons (the namespace separator).
|
|
|
192
219
|
|
|
193
220
|
```bash
|
|
194
221
|
grok s files list "System:AppData" -r # recursive
|
|
195
|
-
grok s files list "System:AppData/MyPlugin"
|
|
222
|
+
grok s files list "System:AppData/MyPlugin" # path, kind (file|dir), size, updatedOn
|
|
223
|
+
grok s files list "System:DemoFiles" --output quiet # paths only, pipe-friendly
|
|
196
224
|
grok s files get "System:AppData/MyPlugin/config.json"
|
|
197
225
|
grok s files put ./smiles.csv "System:DemoFiles/smiles.csv" # upload local file
|
|
198
226
|
grok s files delete "System:AppData/MyPlugin/old.csv"
|
|
@@ -214,9 +242,16 @@ grok s tables upload MyTable ./data.d42 # d42 binary →
|
|
|
214
242
|
grok s tables upload MyTable ./data.csv --output json # get ID and markup back
|
|
215
243
|
grok s tables download MyTable # CSV to stdout (pipe-friendly)
|
|
216
244
|
grok s tables download MyTable -O ./data.csv # CSV to a local file
|
|
217
|
-
grok s tables download
|
|
245
|
+
grok s tables download "Admin:MyTable:MyTable" # full name, or the UUID
|
|
246
|
+
grok s tables list --filter MyTable # registered tables
|
|
247
|
+
grok s tables get MyTable # the TableInfo record
|
|
248
|
+
grok s tables delete MyTable
|
|
218
249
|
```
|
|
219
250
|
|
|
251
|
+
A table lives under a project namespace (the upload prints its full name,
|
|
252
|
+
`Admin:MyTable:MyTable`); the bare name works while only one table carries it, otherwise the
|
|
253
|
+
CLI lists the candidates and asks for the full name or the id.
|
|
254
|
+
|
|
220
255
|
Upload streams raw bytes — `Content-Type: text/csv` for `.csv` and
|
|
221
256
|
`application/octet-stream` for `.d42` (auto-detected from the file extension). Both
|
|
222
257
|
formats handle large tables without loading the whole file into a JSON envelope.
|
|
@@ -226,6 +261,170 @@ table|json|csv|quiet`), which still controls how the upload result is printed.
|
|
|
226
261
|
Download is CSV-only — the server reads the stored d42 blob and converts. If you
|
|
227
262
|
need the raw d42 bytes, hit `/tables/data/<id>` directly via `grok s raw`.
|
|
228
263
|
|
|
264
|
+
## Packages
|
|
265
|
+
|
|
266
|
+
Manage server plugins the same way the Package Manager UI does — the server pulls
|
|
267
|
+
published packages from its configured package repository (npm). This is for
|
|
268
|
+
installing **released** packages on a running server; publishing your own local
|
|
269
|
+
package sources is still `grok publish`.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
grok s packages install Chem Bio PowerGrid # install latest of each, one line
|
|
273
|
+
grok s packages install Chem --version 1.14.0 # pin a specific version (single package only)
|
|
274
|
+
grok s packages outdated # installed vs registry-latest
|
|
275
|
+
grok s packages update Chem Bio # upgrade named packages to latest
|
|
276
|
+
grok s packages update --all # upgrade everything outdated
|
|
277
|
+
grok s packages versions Chem # published versions w/ current/latest/debug flags
|
|
278
|
+
grok s packages set-version Chem 1.13.0 # activate a specific version (pulls it if needed)
|
|
279
|
+
grok s packages uninstall Chem # remove; repository entry stays installable
|
|
280
|
+
grok s packages share Chem Chemists --access View
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Semantics worth knowing:
|
|
284
|
+
|
|
285
|
+
- **`install` without `--version` sets the package to `latest`**, which is an
|
|
286
|
+
auto-update intent: the server re-resolves it against the registry every ~15
|
|
287
|
+
minutes, so the package keeps itself current. A pinned `--version` (and `update`,
|
|
288
|
+
which pins the concrete latest version) stays put until you change it.
|
|
289
|
+
- **Install is synchronous** — the call returns once the version is downloaded,
|
|
290
|
+
published, and made current. A heavy first-time install (e.g. Chem) can take
|
|
291
|
+
minutes; if the HTTP call times out, just re-run it — install is idempotent, and
|
|
292
|
+
a version that finished installing server-side is activated instantly on retry.
|
|
293
|
+
- Multi-package `install`/`update` runs sequentially and prints a per-package
|
|
294
|
+
status table (`installed` / `noop` / `error`); a partial failure sets exit code 1.
|
|
295
|
+
- **`update` preserves auto-update tracking**: a package whose desired version is
|
|
296
|
+
`latest` is updated by re-resolving `latest` (it keeps auto-updating); a pinned
|
|
297
|
+
package gets pinned to the concrete registry-latest version. Note that a
|
|
298
|
+
locally-published dev build counts as outdated once the registry moves past its
|
|
299
|
+
base version, so `update --all` will replace it with the registry release.
|
|
300
|
+
- Status semantics: `noop` means "already at the requested latest"; installing an
|
|
301
|
+
explicit `--version` reports `installed` (with the published-version id) even
|
|
302
|
+
when that version was already current — the server re-activates it.
|
|
303
|
+
- **`uninstall` of a repository-backed package keeps the package entry** (it shows
|
|
304
|
+
up as installable again); only locally-published packages are deleted outright.
|
|
305
|
+
It also clears the desired version, so the package drops out of `outdated` until
|
|
306
|
+
reinstalled. Neither path cleans up package credentials or package DB schemas.
|
|
307
|
+
- `versions`, `outdated`, and name resolution read the repository catalog, so they
|
|
308
|
+
need a configured package repository — check with `grok s raw GET /api/packages/repos`.
|
|
309
|
+
- `share` targets the package's project under the hood (same as sharing from the UI),
|
|
310
|
+
so the whole package — functions, queries, connections — is shared at once. For the
|
|
311
|
+
same reason `grok s shares list <package-uuid>` won't show the grant — it lives on
|
|
312
|
+
the package's project, not the package entity itself.
|
|
313
|
+
|
|
314
|
+
## Domain schemas and rows
|
|
315
|
+
|
|
316
|
+
Entity-mapped domain tables — the schemas plugins declare in `databases/<schema>/schema.json`
|
|
317
|
+
and the user-managed ones created at runtime — are reachable through one entity, `domains`.
|
|
318
|
+
Every verb takes an address: a bare `<schema>` names a schema, `<schema>.<table>` names a
|
|
319
|
+
table, mirroring `grok.dapi.domains.schema('grit')` and `grok.dapi.domains.table('grit.issue')`.
|
|
320
|
+
Reads return only the rows and columns the key's user may see; writes are validated,
|
|
321
|
+
permission-checked and audited by the server exactly as they are from the UI.
|
|
322
|
+
|
|
323
|
+
### Browsing
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
grok s domains list # schemas: name, managedBy, version, table count
|
|
327
|
+
grok s domains list grit # tables of one schema: security mode, business key, ...
|
|
328
|
+
grok s domains get grit # the manifest, as JSON (doubles as an export)
|
|
329
|
+
grok s domains get grit.issue # the table's columns (--output json: its manifest section)
|
|
330
|
+
grok s domains get grit.issue <row-id> # one row
|
|
331
|
+
grok s domains capabilities grit.issue # what the current user may do on the table
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Querying
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
grok s domains query grit.issue --filter 'status = "open"' --sort '!created_on' --limit 20
|
|
338
|
+
grok s domains query grit.issue --columns title,status --expand project_id --offset 100
|
|
339
|
+
grok s domains count grit.issue --filter 'status = "open"'
|
|
340
|
+
grok s domains aggregate grit.issue --measures 'count,avg(estimate) as mean' --group-by status
|
|
341
|
+
grok s domains aggregate grit.issue --json spec.json # any DomainAggregateSpec
|
|
342
|
+
grok s domains download grit.issue -O ./issues.csv --filter 'status = "open"'
|
|
343
|
+
grok s domains download grit.issue -O ./issues.d42 # typed DataFrame, 10M-row cap
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
`--filter` is the domain smart-filter grammar (`status = "open"`, `title contains "crash"`,
|
|
347
|
+
`quantity > 10`); values are bound server-side. `--sort` is a comma list with `!` for
|
|
348
|
+
descending; `--expand` takes `<fk_column>`, `details:<child>` or a relation name. JSON output
|
|
349
|
+
and CSV downloads are capped at 10k rows by the server; a `.d42` download uses the DataFrame
|
|
350
|
+
path and goes up to 10M. `--limit` defaults to 50 for `query` and is unbounded for `download`.
|
|
351
|
+
|
|
352
|
+
### Writing rows
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
grok s domains insert grit.issue title="Crash on save" status=open project_id=<uuid>
|
|
356
|
+
grok s domains insert grit.issue --json rows.json # one object or an array
|
|
357
|
+
grok s domains update grit.issue <row-id> status=closed --version 3 # optimistic concurrency
|
|
358
|
+
grok s domains delete grit.issue <row-id>
|
|
359
|
+
grok s domains delete grit.issue --filter 'status = "closed"' --limit 500 # bulk, oldest first
|
|
360
|
+
grok s domains transaction grit --json ops.json # ordered ops, one transaction
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Inline `col=value` pairs are typed when the value parses as JSON (`quantity=5`,
|
|
364
|
+
`done=true`, `note=null`, `tags=["a","b"]`) and sent as strings otherwise. A business-key
|
|
365
|
+
duplicate is reported as `status: duplicate` with the existing id; add `--error-on-duplicate`
|
|
366
|
+
to fail instead. A validation failure prints one line per offending column and exits 1.
|
|
367
|
+
`delete --filter` removes at most 1000 rows per call and says when more remain.
|
|
368
|
+
`ops.json` is the `DomainsDataSource.transaction` ops list — `{op, table, ref?, values?, id?,
|
|
369
|
+
expectedVersion?}`, with `"$ref"` placeholders for earlier ops' ids.
|
|
370
|
+
|
|
371
|
+
### Bulk upload
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
grok s domains upload grit.issue ./issues.csv # insert
|
|
375
|
+
grok s domains upload grit.issue ./issues.csv --upsert # merge by business key
|
|
376
|
+
grok s domains upload grit.issue ./issues.d42 # d42 DataFrame
|
|
377
|
+
grok s domains upload grit.issue ./issues.json --no-all-or-nothing --error-on-duplicate
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The format follows the extension (`.csv`, `.d42`, `.json` — a row array, bare or under
|
|
381
|
+
`rows`). The whole batch is one transaction by default; `--no-all-or-nothing` applies the good
|
|
382
|
+
rows and reports the bad ones per row. The report prints `inserted / updated / skipped /
|
|
383
|
+
errors` plus a row table for the failures; any error sets exit code 1.
|
|
384
|
+
|
|
385
|
+
### Schema lifecycle (user-managed schemas)
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
grok s domains create inventory --friendly-name "Inventory" --description "Lab stock"
|
|
389
|
+
grok s domains apply inventory --json schema.json --dry-run # the change plan, no writes
|
|
390
|
+
grok s domains apply inventory --json schema.json # create / alter tables
|
|
391
|
+
grok s domains apply inventory --json schema.json --confirm-destructive --if-version 3
|
|
392
|
+
grok s domains audit inventory --limit 50 # DDL and row events, newest first
|
|
393
|
+
grok s domains delete inventory --force # purge: data, audit, registry, grants
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`schema.json` may be a full manifest or a partial apply body — only `tables`, `extend`,
|
|
397
|
+
`propertySchemas` and `dropTables` are sent; `name`, `version` and `description` are dropped.
|
|
398
|
+
Named tables replace their definition wholesale; untouched tables stay as the registry has
|
|
399
|
+
them. A plan that drops or narrows anything is refused until `--confirm-destructive` is
|
|
400
|
+
passed, and the plan is printed with the refusal. `--if-version` fails the apply when the
|
|
401
|
+
schema's apply counter (or `ext_version` on a package schema) has moved. Package-deployed
|
|
402
|
+
schemas cannot be applied to or deleted here — `apply` on one is the user-extension path
|
|
403
|
+
(needs `Extend`), and the manifest is owned by `grok publish`. `delete <schema>` requires
|
|
404
|
+
`--force` because it takes every row with it.
|
|
405
|
+
|
|
406
|
+
### Grants
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
grok s domains grants grit.issue # direct permission rows
|
|
410
|
+
grok s domains grant grit.issue Chemists,Biologists --access Edit
|
|
411
|
+
grok s domains revoke grit.issue Chemists --access Edit # omit --access to revoke all
|
|
412
|
+
grok s domains grant grit Chemists --access Extend # schema-level: may add own tables/columns
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Groups resolve by name the same way `groups add-members` does (a login resolves to the
|
|
416
|
+
personal group; ambiguous names list the candidates). Table grants gate row data; schema
|
|
417
|
+
grants gate schema operations (`Edit` to apply, `Delete` to purge, `Share`, `Extend`) and do
|
|
418
|
+
not reach rows. A grant on a table restricted by per-column sharing does not un-hide the
|
|
419
|
+
column — that path (`/domains/grants/column`) is `grok s raw` territory for now.
|
|
420
|
+
|
|
421
|
+
### Not covered here
|
|
422
|
+
|
|
423
|
+
Watch/subscribe, facets, saved filters, per-column sharing and row promotion have no verb;
|
|
424
|
+
`grok s raw <METHOD> /api/domains/...` reaches them. Domain schemas and their data do not
|
|
425
|
+
take part in `pull` / `push` / `migrate` — install the package (or re-`apply` the manifest)
|
|
426
|
+
on the target and `upload` the rows.
|
|
427
|
+
|
|
229
428
|
## Server health
|
|
230
429
|
|
|
231
430
|
```bash
|
|
@@ -248,26 +447,50 @@ Hits `GET /public/v1/healthcheck`. Response:
|
|
|
248
447
|
}
|
|
249
448
|
```
|
|
250
449
|
|
|
251
|
-
`services` is the same payload as `/admin/health` (per-`GrokServiceInfo` records).
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
450
|
+
`services` is the same payload as `/admin/health` (per-`GrokServiceInfo` records). A server
|
|
451
|
+
that reports no services (a dev stack) prints `(no services reported)`; `--module` for a
|
|
452
|
+
module the server does not report exits 1. Requires a valid dev key (standard `grok s` auth).
|
|
453
|
+
For an anonymous liveness probe — load balancer, k8s readiness — hit `/admin/health` directly;
|
|
454
|
+
it's on the server's unauthenticated allowlist.
|
|
455
|
+
|
|
456
|
+
## Describing an entity type
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
grok s describe connections # fields of a DataConnection, with types and examples
|
|
460
|
+
grok s describe Project --output json # by type name; JSON carries the registry record and a sample
|
|
461
|
+
grok s describe users --output quiet # field names only
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
The server publishes no JSON schema, so `describe` combines the entity-type registry record
|
|
465
|
+
with the top-level fields of one existing entity of that type (`field`, `type`, `example`).
|
|
466
|
+
Aliases: `users groups connections queries scripts functions packages reports tables projects
|
|
467
|
+
files`; any other registered type name (`Project`, `ViewLayout`) works when at least one
|
|
468
|
+
entity exists.
|
|
255
469
|
|
|
256
470
|
## Raw API access
|
|
257
471
|
|
|
258
472
|
When no dedicated subcommand exists, fall through to `grok s raw`:
|
|
259
473
|
|
|
260
474
|
```bash
|
|
261
|
-
grok s raw GET /
|
|
262
|
-
grok s raw GET /
|
|
263
|
-
grok s raw POST /
|
|
475
|
+
grok s raw GET /users/current
|
|
476
|
+
grok s raw GET /packages/dev/MyPlugin
|
|
477
|
+
grok s raw POST /admin/reload-settings
|
|
478
|
+
grok s raw POST /public/v1/functions/Sin/call --data '{"x": 1}'
|
|
479
|
+
grok s raw POST /domains/grants/<id> --json grant.json
|
|
264
480
|
```
|
|
265
481
|
|
|
482
|
+
Paths are relative to the API base of the target server (`/users/current` becomes
|
|
483
|
+
`https://host/api/users/current`, or `http://host:8082/users/current` on a bare Datlas); a
|
|
484
|
+
leading `/api` is accepted and dropped, so old `/api/...` paths keep working on both host
|
|
485
|
+
shapes. A body comes from `--json <file>` or `--data '<json>'`. A non-2xx answer, or a
|
|
486
|
+
200 carrying an `ApiError`, prints the message with the HTTP status on stderr and exits 1;
|
|
487
|
+
under `--output json` the stderr line is `{"error", "errorCode", "body"}`.
|
|
488
|
+
|
|
266
489
|
On **Windows Git Bash**, prefix raw paths with `MSYS_NO_PATHCONV=1` to stop the shell
|
|
267
490
|
from rewriting POSIX paths into Windows paths:
|
|
268
491
|
|
|
269
492
|
```bash
|
|
270
|
-
MSYS_NO_PATHCONV=1 grok s raw GET /
|
|
493
|
+
MSYS_NO_PATHCONV=1 grok s raw GET /users/current
|
|
271
494
|
```
|
|
272
495
|
|
|
273
496
|
## Batch operations
|
|
@@ -292,14 +515,17 @@ Manifest shape:
|
|
|
292
515
|
"stopOnError": true,
|
|
293
516
|
"transaction": false,
|
|
294
517
|
"operations": [
|
|
295
|
-
{ "id": "op1", "action": "users.
|
|
296
|
-
{ "id": "op2", "action": "groups.
|
|
518
|
+
{ "id": "op1", "action": "users.create", "params": {"login": "alice", "firstName": "Alice"} },
|
|
519
|
+
{ "id": "op2", "action": "groups.create", "params": {"name": "Chemists"} }
|
|
297
520
|
]
|
|
298
521
|
}
|
|
299
522
|
```
|
|
300
523
|
|
|
301
|
-
|
|
302
|
-
|
|
524
|
+
Actions the server accepts: `create | get | delete` for `users`, `groups`, `connections`,
|
|
525
|
+
`functions`, `queries`, `scripts` (`get | delete` for `reports`), `functions.run`
|
|
526
|
+
(`{name, params}`), and `files.list | get | put | delete`. For `files.put`, add
|
|
527
|
+
`"source": "<local-path>"` and the CLI base64-encodes the file into `content` before sending.
|
|
528
|
+
`users.delete` removes the entity record only (see "List / count / get / delete").
|
|
303
529
|
|
|
304
530
|
## Scripting pattern
|
|
305
531
|
|
|
@@ -354,13 +580,323 @@ grok s groups add-members Chemists alice.mendeleev bob.curie carol.pauling --use
|
|
|
354
580
|
grok s groups list-members Chemists --no-admin
|
|
355
581
|
```
|
|
356
582
|
|
|
357
|
-
|
|
583
|
+
Steps 1 and 2 create; re-running them fails with "already exists" (exit 1) unless the JSON
|
|
584
|
+
carries the existing `id`. Step 3 is idempotent (`noop` on a re-run).
|
|
585
|
+
|
|
586
|
+
## Migrating entities between instances (pull / push / migrate)
|
|
587
|
+
|
|
588
|
+
Entities built in the UI on one instance — connections, queries, scripts, dashboards,
|
|
589
|
+
spaces, layouts, tables, files, jobs, notebooks, models, and the groups and grants they
|
|
590
|
+
need — are promoted to another instance through a **bundle**: a directory of one JSON file
|
|
591
|
+
per entity that travels by any means (a commit, a PR, a USB stick).
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
grok s pull <selection> --out ./bundle --host dev # instance → directory
|
|
595
|
+
grok s bundle ls ./bundle # what is in it
|
|
596
|
+
grok s diff ./bundle --host prod # what a push would change
|
|
597
|
+
grok s push ./bundle --host prod [--dry-run] # directory → instance
|
|
598
|
+
grok s migrate <selection> --from dev --to prod # pull + push, temp dir in between
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Every entity keeps the **same UUID** on both instances, so a push is idempotent: pushing an
|
|
602
|
+
unchanged bundle a second time writes nothing, and 1.28's built-in server-to-server sync
|
|
603
|
+
recognises what the CLI pushed as its own.
|
|
604
|
+
|
|
605
|
+
Moving a **whole instance** rather than one team's work needs `--admin` on both ends, so the
|
|
606
|
+
run sees and writes what the key's own account cannot: without it a pull collects only the
|
|
607
|
+
entities shared with that account, and the target's own users never get their content back.
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
grok s pull --since 10y --type project,connection,query,script,group,layout --out ./bundle --host dev --admin
|
|
611
|
+
grok s push ./bundle --host prod --admin --on-conflict skip
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Move a whole instance one space at a time.** Placement is exclusive on the server — one project
|
|
615
|
+
holding an entity takes it from every other — so a single bundle carrying every space has its
|
|
616
|
+
projects competing, and a stand-sized push spends its time taking rows back off each other. Scoped
|
|
617
|
+
to one space that contention stays inside it:
|
|
618
|
+
|
|
619
|
+
```bash
|
|
620
|
+
grok s migrate --from dev --to prod --admin --by-namespace # every space, in turn
|
|
621
|
+
grok s migrate --from dev --to prod --admin --by-namespace --only Chem,Bio
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
It checks the instance-level prerequisites once before writing anything — users and packages the
|
|
625
|
+
target lacks — and refuses a whole-instance run until they are fixed (`--force` overrides; a run
|
|
626
|
+
already scoped with `--only` is reported but allowed). `--skip` leaves a space out, and a full run
|
|
627
|
+
ends with an `(unowned)` sweep for leaf entities no space holds — a layout under no namespace would
|
|
628
|
+
otherwise never travel. `--no-sweep` turns that off. Each space is a separate pull and push, so a
|
|
629
|
+
failure costs one space rather than the run, and what finished is recorded in a state file
|
|
630
|
+
(`--state`), letting a re-run continue instead of repeating work.
|
|
631
|
+
|
|
632
|
+
Measured on 1.27.9: three spaces, 1,946 entities, 4m44s, one failure isolated to its space; the
|
|
633
|
+
re-run skipped all three in 8s. The same content as part of a 15,778-entity single bundle did not
|
|
634
|
+
finish at all.
|
|
635
|
+
|
|
636
|
+
**Name the content types; do not pull tables directly.** A long-lived stand accumulates a loose
|
|
637
|
+
`TableInfo` per ad-hoc import — dev holds 1.76 M of them against 21 K projects, and an admin
|
|
638
|
+
session sees every one. Selecting projects instead pulls each project's tables, views and layouts
|
|
639
|
+
as dependencies, so everything a dashboard needs still travels and the scratch rows stay behind.
|
|
640
|
+
Check what a stand is carrying before deciding:
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
grok s tables list --host dev --admin --limit 1 --output json # `count` ignores --filter
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
`--type` takes the aliases `conn`/`connection`, `query`, `script`, `project`/`dashboard`/`space`,
|
|
647
|
+
`view`, `layout`, `table`, `file`, `group`, `job`, `notebook`, `model`, or an exact type name
|
|
648
|
+
(`DataConnection`) — a lowercased type name is not an alias and is rejected.
|
|
649
|
+
|
|
650
|
+
### Preparing the target
|
|
651
|
+
|
|
652
|
+
Four things belong to the instance, not to a bundle, and have to be in place before the push.
|
|
653
|
+
`grok s diff` reports missing users; missing packages are reported by the push itself, since the
|
|
654
|
+
check runs against what the push is about to write.
|
|
655
|
+
|
|
656
|
+
| | Why | How to check |
|
|
657
|
+
|---|---|---|
|
|
658
|
+
| **Users** | Content of a user who does not exist lands under the pushing account | `warn user_missing` |
|
|
659
|
+
| **Packages** | Package functions, scripts and connections carry a name-derived id that only exists once the package is *published* there — installing it from the repository is not enough | `warn package_not_installed` |
|
|
660
|
+
| **Platform shares** | `System:AppData` and `System:DemoFiles` are created by the deployment; entities that read from them need them present | `grok s connections list --host prod` |
|
|
661
|
+
| **Credentials** | Secrets never travel — set them on the target after the push, or pass `--creds` | `needs-credentials` rows |
|
|
662
|
+
|
|
663
|
+
A freshly deployed stand also stops at its setup wizard, and 1.27.x keeps the session token in
|
|
664
|
+
memory rather than a cookie — so that redirect logs you straight back out. Click the wizard
|
|
665
|
+
through once and normal login works.
|
|
666
|
+
|
|
667
|
+
Users are **not** part of a bundle. Create them on the target first — content that belongs to
|
|
668
|
+
a user who does not exist there lands under the pushing account instead of theirs.
|
|
669
|
+
|
|
670
|
+
### Surviving a blip
|
|
671
|
+
|
|
672
|
+
A retriable answer (429, 502, 503, 504) or a dropped socket is retried with exponential backoff —
|
|
673
|
+
five attempts by default, about half a minute. That covers a busy moment, not a stand that steps
|
|
674
|
+
out for a restart, and a whole-instance walk is long enough to meet one:
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
GROK_HTTP_RETRIES=9 grok s pull --out ./bundle --host dev --admin ... # ~90s of tolerance
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
A pull that dies anyway is not wasted — pulls accumulate, so re-running without `--replace`
|
|
681
|
+
merges into the same bundle.
|
|
682
|
+
|
|
683
|
+
### A bundle remembers where it was pushed
|
|
684
|
+
|
|
685
|
+
`--on-conflict adopt` records every adoption in the bundle's own `idmap.json`, so a re-push
|
|
686
|
+
writes into the same target rows instead of adopting again. That is what makes a repeated push
|
|
687
|
+
idempotent — but it also means the bundle is no longer neutral: pushed at a *different* target,
|
|
688
|
+
or re-pushed after an aborted attempt, it resolves entities by the ids it learned last time and
|
|
689
|
+
updates rows it would otherwise have created.
|
|
690
|
+
|
|
691
|
+
Pull into a fresh directory (or delete `idmap.json`) whenever a bundle is aimed at a different
|
|
692
|
+
instance than the one it last adopted against.
|
|
693
|
+
|
|
694
|
+
### Conflict policies
|
|
695
|
+
|
|
696
|
+
Measured against a target holding 924 of the bundle's 1,162 names:
|
|
697
|
+
|
|
698
|
+
| `--on-conflict` | What lands | Cost |
|
|
699
|
+
|---|---|---|
|
|
700
|
+
| `fail` (default) | nothing; every conflict is listed first | safest way to see the damage before writing |
|
|
701
|
+
| `skip` | the non-conflicting entities only | most failures — whatever depended on a skipped entity fails too |
|
|
702
|
+
| `adopt` | conflicting names are written into the target's existing rows | keeps one copy; the usual choice for a real migration |
|
|
703
|
+
| `duplicate` | a second copy of everything that collides | target grows by the whole bundle, tables bring their columns |
|
|
704
|
+
|
|
705
|
+
### References the source has already lost
|
|
706
|
+
|
|
707
|
+
A long-lived instance accumulates entities that point at something deleted out from under them —
|
|
708
|
+
most often a saved view whose table is gone. The pull resolves every outside reference against the
|
|
709
|
+
source, so a bundle records which of them the source itself can no longer answer:
|
|
710
|
+
|
|
711
|
+
```
|
|
712
|
+
Bundle warn source_dangling_refs 50 reference(s) point at entities the source itself no longer has
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
The push then reports whatever depends on one as `skip dead_on_source` rather than as a failure,
|
|
716
|
+
and does not fail the run over it — no target can supply what the source has already lost. A
|
|
717
|
+
reference the source *does* keep but the target lacks stays a real failure (`dependency_missing`),
|
|
718
|
+
because installing the package or widening the pull fixes it.
|
|
719
|
+
|
|
720
|
+
### Bundle layout
|
|
721
|
+
|
|
722
|
+
```
|
|
723
|
+
bundle/
|
|
724
|
+
manifest.json # source url + version, pull history, FK-safe order
|
|
725
|
+
DataConnection/Chem.Chembl.json # <Type>/<nqName with ':' and '/' as '.'>.json
|
|
726
|
+
DataQuery/Chem.CompoundsByTarget.json
|
|
727
|
+
Project/Chem.Dashboard.json
|
|
728
|
+
UserGroup/Chemists.json # bare group + member logins, never member ids
|
|
729
|
+
FileInfo/reports.readme.md.json # a file without a namespace is named by its path
|
|
730
|
+
tables/<id>.d42 # table data (on by default for pulled tables)
|
|
731
|
+
files/<id> # file bytes (only with --include-files)
|
|
732
|
+
shares/<encoded path> # share files a datasync table rebuilds itself from
|
|
733
|
+
idmap.json # sourceId -> targetId, written whenever the push resolves an
|
|
734
|
+
# entity to a different id on the target — an adoption, a
|
|
735
|
+
# package entity found by name, or a save that answered with
|
|
736
|
+
# an existing row's id
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
**Pulls accumulate.** Pulling into an existing bundle merges: entities already there are
|
|
740
|
+
overwritten by id, new ones are added, nothing is removed, and `manifest.pulls[]` keeps one
|
|
741
|
+
entry per invocation. `--replace` clears the directory first.
|
|
742
|
+
|
|
743
|
+
```bash
|
|
744
|
+
grok s pull Chem:TargetDashboard --out ./release --host dev
|
|
745
|
+
grok s pull --type script --author alice --no-deps --out ./release --host dev
|
|
746
|
+
grok s pull Chemists --out ./release --host dev
|
|
747
|
+
grok s push ./release --host prod
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Selection
|
|
751
|
+
|
|
752
|
+
| Flag | Selects |
|
|
753
|
+
|---|---|
|
|
754
|
+
| positional `Chem:Dashboard <uuid> ...` | exactly these entities, by nqName or id |
|
|
755
|
+
| `--type conn,query,script,project,dashboard,space,view,layout,table,file,group,job,notebook,model` | which types to list (default: all of them) |
|
|
756
|
+
| `--name <glob>` | free-text search, then a client-side glob on name and friendly name (`Cereal*`, `*demo*`) |
|
|
757
|
+
| `--namespace Chem` | everything **under** the namespace, recursively, plus the space `Chem` itself — without it nothing selected has anywhere to be placed |
|
|
758
|
+
| `--space Chem:Reports` | the same, named by nqName rather than by namespace |
|
|
759
|
+
| `--author alice` | entities authored by a login |
|
|
760
|
+
| `--tag demo` | entities carrying a tag (types whose router has no `tags` param are skipped with a warning) |
|
|
761
|
+
| `--since 2w` / `--since=-30d` / `--since 2026-08-01` | updated since (bare `2w` means `-2w`; the shell eats a leading `-` unless you use `=`) |
|
|
762
|
+
| `--filter "<expr>"` | a smart-filter expression, ANDed with the rest |
|
|
763
|
+
| `--no-deps` | do not follow dependencies (what never travels is still excluded — see below) |
|
|
764
|
+
| `--no-include-data` | do not pull the `.d42` data of the tables that were pulled (data is on by default) |
|
|
765
|
+
| `--include-files` | also pull the bytes of the files that were pulled (off by default) |
|
|
766
|
+
| `--verbose` | print the stack of a runtime failure instead of the one-line message |
|
|
767
|
+
|
|
768
|
+
`--type space` and `--type dashboard` are both `Project` with a different listing rule, so
|
|
769
|
+
they cannot be combined in one command (neither can `--type project,space`).
|
|
770
|
+
|
|
771
|
+
### What the walker adds, and what never travels
|
|
772
|
+
|
|
773
|
+
A selected entity brings its dependencies with it: a project brings its relation children
|
|
774
|
+
(recursively), their views, layouts and tables; a query brings its connection; a datasync
|
|
775
|
+
table brings the connection its creation script opens; a notebook and a model bring their
|
|
776
|
+
tables; every entity brings the non-personal groups that hold grants on it, and a group
|
|
777
|
+
brings its parents and members. A **job** brings nothing — 1.27 does not persist the link
|
|
778
|
+
between a job and the queries it runs, so pull those explicitly.
|
|
779
|
+
|
|
780
|
+
The table below holds however the entity was chosen: `--no-deps` skips the walk, not these
|
|
781
|
+
rules, and `push` refuses the same connections even if a bundle was hand-edited to carry one
|
|
782
|
+
(`skip(platform_connection)` / `skip(personal_storage)` / `skip(space_files_connection)`).
|
|
783
|
+
|
|
784
|
+
| Never travels | Row on the report |
|
|
785
|
+
|---|---|
|
|
786
|
+
| Passwords and other password-class connection parameters | `needs-credentials` (see `--creds`) |
|
|
787
|
+
| Users — memberships are replayed by login and group name | `warn(member_not_found)` when a login is not on the target |
|
|
788
|
+
| Package-owned entities — install the package instead | `warn(package_entity)`, `warn(package_not_installed)` |
|
|
789
|
+
| `System:` connections, the personal `Home` share, a space's own `Files` connection | `info(platform_connection)` / `warn(personal_storage)` / `info(space_files_connection)` |
|
|
790
|
+
| A trained model blob | `info(model_blob_skipped)` |
|
|
791
|
+
|
|
792
|
+
### Credentials: `--creds`
|
|
793
|
+
|
|
794
|
+
A pushed connection arrives without its secrets. Author a YAML file **for the target** and
|
|
795
|
+
pass it to `push` or `migrate`; the values are merged into the connection's parameters before
|
|
796
|
+
the save, and the server encrypts and masks them itself. Nothing is ever read back into the
|
|
797
|
+
bundle.
|
|
798
|
+
|
|
799
|
+
```yaml
|
|
800
|
+
# creds.yaml — keys are connection nqNames as the bundle spells them
|
|
801
|
+
Chem:Chembl:
|
|
802
|
+
password: ${CHEMBL_PROD_PASSWORD}
|
|
803
|
+
Admin:Northwind:
|
|
804
|
+
password: ${NORTHWIND_PASSWORD}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
```bash
|
|
808
|
+
CHEMBL_PROD_PASSWORD=... grok s push ./release --host prod --creds ./creds.yaml
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
`${VAR}` is resolved from the environment exactly as `grok publish` resolves it in
|
|
812
|
+
`connections/*.json` (the file is parsed as YAML first, so a `${VAR}` written in flow style
|
|
813
|
+
needs quoting: `{password: "${VAR}"}`); a variable that is not set aborts the push before the
|
|
814
|
+
first write. A connection the file covers gets no `needs-credentials` row, and is **always**
|
|
815
|
+
written — a secret is invisible in the payload, so a connection that would otherwise be
|
|
816
|
+
`identical` is planned as `update` with reason `credentials`. That is how a password is
|
|
817
|
+
rotated: re-run the push with a new value.
|
|
818
|
+
|
|
819
|
+
### Conflicts
|
|
820
|
+
|
|
821
|
+
An entity whose id is absent on the target but whose name is taken by another id is a
|
|
822
|
+
conflict. `--on-conflict` decides:
|
|
823
|
+
|
|
824
|
+
| Policy | What happens |
|
|
825
|
+
|---|---|
|
|
826
|
+
| `fail` (default) | nothing is written; every conflict is listed and the command exits 1 |
|
|
827
|
+
| `skip` | the twin is left alone; anything in the bundle that points at it is reported `failed(dependency_skipped)` |
|
|
828
|
+
| `adopt` | the bundle entity is written **into** the twin, and `idmap.json` records `sourceId -> targetId` so every later reference and every later push follows it |
|
|
829
|
+
| `duplicate` | the bundle entity is created under its own id next to the twin (the server renames it `Name_1`). A `UserGroup` cannot be duplicated — group names are unique on 1.27, so that row fails |
|
|
830
|
+
|
|
831
|
+
### Reading the plan
|
|
832
|
+
|
|
833
|
+
`diff` and `--dry-run` print the plan without writing (`diff` plans with `skip`, so a
|
|
834
|
+
conflict does not abort it). Actions: `create`, `update` (with the changed top-level keys as
|
|
835
|
+
the detail), `identical` (no write), `skip`, `failed`, plus the `warn` / `info` /
|
|
836
|
+
`needs-credentials` notes. `--output json` emits the 1.28-compatible shape — the same one for
|
|
837
|
+
`diff`, `push` and `migrate`, with `detail` always present (`""` when there is nothing to
|
|
838
|
+
say):
|
|
839
|
+
|
|
840
|
+
```json
|
|
841
|
+
{
|
|
842
|
+
"items": [{"name": "Chem:Chembl", "entityType": "DataConnection", "action": "create", "reason": "", "detail": ""}],
|
|
843
|
+
"counts": {"create": 1, "identical": 4},
|
|
844
|
+
"status": "ok",
|
|
845
|
+
"remoteUrl": "https://prod.datagrok.ai/api"
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
A push exits 1 if any row is `failed`.
|
|
850
|
+
|
|
851
|
+
### 1.27 limits worth knowing
|
|
852
|
+
|
|
853
|
+
- A **FileInfo that lives in a share** is a dead row on the target — only stand-alone blobs
|
|
854
|
+
migrate (`info(file_in_share_not_migratable)`), the same rule the 1.28 sync applies.
|
|
855
|
+
- `metaParams` do not survive a save on connections and queries, so metadata attached there
|
|
856
|
+
does not travel.
|
|
857
|
+
- `GET /projects/relations` fails on a project that links domain-table rows; the walker falls
|
|
858
|
+
back to the project's own `relations[]` and reports `warn(relations_degraded)`.
|
|
859
|
+
- Relations are **merged**, never replaced: a link that exists only on the target is kept, so
|
|
860
|
+
removing a relation from a bundle never unlinks it there (`info(relation_not_removed)`).
|
|
861
|
+
Unlink it in the UI on the target instead.
|
|
862
|
+
- A space delete leaves its `…:Files` connection behind, and it cannot be deleted through the
|
|
863
|
+
API. Deleting a group that still holds grants fails — revoke the grants first.
|
|
864
|
+
|
|
865
|
+
### dev to prod, end to end
|
|
866
|
+
|
|
867
|
+
```bash
|
|
868
|
+
# 1. see what would come over, from dev, without touching prod
|
|
869
|
+
grok s migrate Chem:TargetDashboard --from dev --to prod --dry-run
|
|
870
|
+
|
|
871
|
+
# 2. promote it, filling in the target's secrets
|
|
872
|
+
CHEMBL_PROD_PASSWORD=... grok s migrate Chem:TargetDashboard \
|
|
873
|
+
--from dev --to prod --creds ./creds.yaml --keep
|
|
874
|
+
|
|
875
|
+
# 3. re-run: everything is `identical`, nothing is written
|
|
876
|
+
grok s migrate Chem:TargetDashboard --from dev --to prod
|
|
877
|
+
|
|
878
|
+
# 4. or keep the bundle under version control instead
|
|
879
|
+
grok s pull Chem:TargetDashboard --out ./release --host dev
|
|
880
|
+
git add release && git commit -m "release: target dashboard"
|
|
881
|
+
grok s push ./release --host prod --creds ./creds.yaml
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
`migrate` pulls into a temporary directory and deletes it afterwards; `--keep` keeps it and
|
|
885
|
+
prints its path on **stderr**, so `--output json` stays one parseable document. `--from` is
|
|
886
|
+
only ever read from.
|
|
358
887
|
|
|
359
888
|
## Implementation notes
|
|
360
889
|
|
|
361
|
-
- Source: `public/tools/bin/commands/server.ts`, `public/tools/bin/utils/node-dapi.ts
|
|
890
|
+
- Source: `public/tools/bin/commands/server.ts`, `public/tools/bin/utils/node-dapi.ts`;
|
|
891
|
+
pull / push / migrate in `bin/commands/server-migrate.ts` + `bin/utils/migrate/`.
|
|
892
|
+
Domain schemas and rows in `bin/commands/server-domains.ts` (`NodeDomainsDataSource` in
|
|
893
|
+
`node-dapi.ts`); they call the internal `/domains/` router the browser uses.
|
|
362
894
|
- The Node client talks directly to `/public/v1/` — no Dart interop, no browser, no
|
|
363
895
|
logged-in session required. Authentication uses the developer key from the config.
|
|
364
896
|
- If `grok s` is not working, start by running `grok s healthcheck` — it verifies the
|
|
365
897
|
URL, the key, and basic connectivity, and returns per-module status if the server is
|
|
366
|
-
reachable. Fall back to `grok s raw GET /
|
|
898
|
+
reachable. Fall back to `grok s raw GET /users/current` to isolate auth issues. A `--host`
|
|
899
|
+
URL that is not the API base fails at login with the reason (`should end with /api`).
|
|
900
|
+
- Cross-instance sync (1.28 servers): `grok s sync pairs list`, `sync setups list --pair <id>`,
|
|
901
|
+
`sync setup get <id>`, `sync run <id>`; on 1.27 the routes do not exist and the commands
|
|
902
|
+
answer "not found".
|