datagrok-tools 6.5.7 → 6.5.9
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/CHANGELOG.md +35 -0
- package/CLAUDE.md +25 -10
- package/Core.json +1027 -0
- package/GROK_S.md +511 -27
- package/bin/commands/api.js +121 -70
- package/bin/commands/help.js +3 -75
- package/bin/commands/server-domains.js +468 -0
- package/bin/commands/server-migrate.js +392 -0
- package/bin/commands/server.js +208 -72
- package/bin/grok.js +14 -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 +981 -0
- package/bin/utils/migrate/pusher.ts +830 -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 +692 -141
- package/bin/utils/playwright-runner.js +3 -1
- 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/bin/commands/server.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.HELP_SERVER = void 0;
|
|
6
7
|
exports.buildInlineManifest = buildInlineManifest;
|
|
7
8
|
exports.parseFuncCall = parseFuncCall;
|
|
8
9
|
exports.resolveManifestSources = resolveManifestSources;
|
|
@@ -12,17 +13,29 @@ var path = _interopRequireWildcard(require("path"));
|
|
|
12
13
|
var _nodeDapi = require("../utils/node-dapi");
|
|
13
14
|
var _serverClient = require("../utils/server-client");
|
|
14
15
|
var _serverOutput = require("../utils/server-output");
|
|
16
|
+
var _serverMigrate = require("./server-migrate");
|
|
17
|
+
var _serverDomains = require("./server-domains");
|
|
18
|
+
var _registry = require("../utils/migrate/registry");
|
|
19
|
+
var _walker = require("../utils/migrate/walker");
|
|
15
20
|
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); }
|
|
16
21
|
/// Docs: [Grok Dapi](/docs/plans/grok-dapi/)
|
|
17
22
|
|
|
23
|
+
/** `queries|scripts get <nqName>` resolves through `/entities`, which needs the entity type. */
|
|
24
|
+
const ENTITY_TYPES = {
|
|
25
|
+
queries: 'DataQuery',
|
|
26
|
+
scripts: 'Script',
|
|
27
|
+
reports: 'UserReport'
|
|
28
|
+
};
|
|
18
29
|
const ENTITIES = ['users', 'groups', 'functions', 'connections', 'queries', 'scripts', 'packages', 'reports', 'files', 'tables'];
|
|
19
|
-
const
|
|
30
|
+
const COMMANDS = ['shares', 'domains', 'raw', 'batch', 'describe', 'healthcheck', 'sync', 'pull', 'push', 'migrate', 'diff', 'bundle'];
|
|
31
|
+
const VERBS = ['list', 'count', 'get', 'delete'];
|
|
20
32
|
async function server(argv) {
|
|
21
33
|
const args = argv['_'].slice(1);
|
|
22
34
|
const entity = args[0];
|
|
23
35
|
const verb = args[1];
|
|
24
36
|
const rest = args.slice(2);
|
|
25
37
|
const output = argv.output ?? argv.o ?? 'table';
|
|
38
|
+
(0, _serverOutput.setOutputFormat)(output);
|
|
26
39
|
const limit = Number(argv.limit ?? argv.l ?? 50);
|
|
27
40
|
const offset = Number(argv.offset ?? 0);
|
|
28
41
|
const filter = argv.filter ?? argv.f ?? '';
|
|
@@ -34,26 +47,40 @@ async function server(argv) {
|
|
|
34
47
|
}
|
|
35
48
|
let client;
|
|
36
49
|
try {
|
|
37
|
-
client = await (0, _serverClient.createClient)(host);
|
|
50
|
+
client = await (0, _serverClient.createClient)(host, !!argv.admin);
|
|
38
51
|
} catch (err) {
|
|
52
|
+
// a bad alias, URL or key is not a usage error: no help dump, just the reason
|
|
39
53
|
(0, _serverOutput.printError)(err);
|
|
40
|
-
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return true;
|
|
41
56
|
}
|
|
42
57
|
const dapi = new _nodeDapi.NodeDapi(client);
|
|
43
58
|
try {
|
|
44
59
|
// await each handler so a rejection lands in the catch below instead of
|
|
45
60
|
// escaping to grok.js as an unhandled rejection (stack trace + exit 255)
|
|
61
|
+
if (['pull', 'push', 'migrate', 'diff', 'bundle'].includes(entity)) return await (0, _serverMigrate.handleMigrate)(dapi, entity, [verb, ...rest].filter(Boolean), argv, output);
|
|
62
|
+
if (entity === 'domains') return await (0, _serverDomains.handleDomains)(dapi, verb, rest, argv, output);
|
|
46
63
|
if (entity === 'batch') return await handleBatch(dapi, argv, verb, rest, output);
|
|
47
|
-
if (entity === 'raw') return await handleRaw(dapi, verb, rest, output);
|
|
64
|
+
if (entity === 'raw') return await handleRaw(dapi, verb, rest, argv, output);
|
|
48
65
|
if (entity === 'describe') return await handleDescribe(dapi, verb ?? rest[0], output);
|
|
49
66
|
if (entity === 'healthcheck') return await handleHealthcheck(dapi, argv, output);
|
|
50
67
|
if (entity === 'sync') return await handleSync(dapi, verb, rest, argv, output);
|
|
51
68
|
if (entity === 'functions' && verb === 'run') return await handleFuncRun(dapi, rest, argv, output);
|
|
52
69
|
if (entity === 'functions' && verb === 'list') return await handleFunctionsList(dapi, argv, limit, offset, filter, output);
|
|
53
70
|
if (entity === 'files' && verb === 'list') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
if (!rest[0]) {
|
|
72
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s files list <connector>[/<path>] [-r]\n e.g. grok s files list "System:AppData" -r'));
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const files = await dapi.files.list(String(rest[0]), recursive);
|
|
76
|
+
if (output === 'json') (0, _serverOutput.printOutput)(files, output);else if (output === 'quiet') {
|
|
77
|
+
for (const f of files) console.log(f.path);
|
|
78
|
+
} else (0, _serverOutput.printOutput)(files.map(f => ({
|
|
79
|
+
path: f.path,
|
|
80
|
+
kind: f.isFile ? 'file' : 'dir',
|
|
81
|
+
size: f.isFile ? f.size ?? '' : '',
|
|
82
|
+
updatedOn: f.updatedOn ?? ''
|
|
83
|
+
})), output);
|
|
57
84
|
return true;
|
|
58
85
|
}
|
|
59
86
|
if (entity === 'files' && verb === 'get') {
|
|
@@ -75,8 +102,7 @@ async function server(argv) {
|
|
|
75
102
|
if (entity === 'connections' && verb === 'test') return await handleConnTest(dapi, rest, argv, output);
|
|
76
103
|
if (entity === 'groups' && verb === 'add-members') return await handleGroupAddMembers(dapi, rest, argv, output);
|
|
77
104
|
if (entity === 'groups' && verb === 'remove-members') return await handleGroupRemoveMembers(dapi, rest, argv, output);
|
|
78
|
-
if (entity === 'groups' && verb === 'list-members') return await handleGroupListMembers(dapi, rest, argv, output);
|
|
79
|
-
if (entity === 'groups' && verb === 'list-memberships') return await handleGroupListMemberships(dapi, rest, argv, output);
|
|
105
|
+
if (entity === 'groups' && (verb === 'list-members' || verb === 'list-memberships')) return await handleGroupListMembers(dapi, verb, rest, argv, output);
|
|
80
106
|
if (entity === 'users' && verb === 'block') return await handleUserBlock(dapi, rest, output);
|
|
81
107
|
if (entity === 'users' && verb === 'unblock') return await handleUserUnblock(dapi, rest, output);
|
|
82
108
|
if (entity === 'tables' && verb === 'download') return await handleTablesDownload(dapi, rest, argv, output);
|
|
@@ -90,7 +116,7 @@ async function server(argv) {
|
|
|
90
116
|
if (entity === 'packages' && verb === 'update') return await handlePackagesUpdate(dapi, rest, argv, output);
|
|
91
117
|
const source = dapi[entity];
|
|
92
118
|
if (!source || !ENTITIES.includes(entity)) {
|
|
93
|
-
(0, _serverOutput.printError)(new Error(`Unknown
|
|
119
|
+
(0, _serverOutput.printError)(new Error(`Unknown command '${entity}'. Entities: ${ENTITIES.join(', ')}. Other commands: ${COMMANDS.join(', ')}. See grok s --help`));
|
|
94
120
|
return false;
|
|
95
121
|
}
|
|
96
122
|
if (!verb) {
|
|
@@ -98,17 +124,30 @@ async function server(argv) {
|
|
|
98
124
|
return true;
|
|
99
125
|
}
|
|
100
126
|
if (verb === 'list') {
|
|
101
|
-
const
|
|
102
|
-
|
|
127
|
+
const results = source instanceof _nodeDapi.InternalDataSource ? await source.list({
|
|
128
|
+
text: filter || undefined,
|
|
129
|
+
limit,
|
|
130
|
+
page: Math.floor(offset / limit) + 1
|
|
131
|
+
}) : await source.filter(filter).by(limit).page(Math.floor(offset / limit)).list();
|
|
103
132
|
(0, _serverOutput.printOutput)(results, output);
|
|
104
133
|
return true;
|
|
105
134
|
}
|
|
135
|
+
if (verb === 'count') {
|
|
136
|
+
if (entity === 'reports') throw new Error('The server has no count endpoint for reports; use `grok s reports list --output quiet | wc -l`');
|
|
137
|
+
const n = source instanceof _nodeDapi.InternalDataSource ? await source.count({
|
|
138
|
+
text: filter || undefined
|
|
139
|
+
}) : await source.filter(filter).count();
|
|
140
|
+
(0, _serverOutput.printOutput)(n, output);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
106
143
|
if (verb === 'get') {
|
|
107
144
|
if (!rest[0]) {
|
|
108
145
|
(0, _serverOutput.printError)(new Error('Usage: grok s <entity> get <id>'));
|
|
109
146
|
return false;
|
|
110
147
|
}
|
|
111
|
-
|
|
148
|
+
// The internal routers find by id only; a name has to be resolved through /entities first.
|
|
149
|
+
const id = source instanceof _nodeDapi.InternalDataSource && !(0, _registry.isUuid)(rest[0]) ? (await (0, _walker.resolveEntity)(dapi, rest[0], ENTITY_TYPES[entity])).id : rest[0];
|
|
150
|
+
const result = await source.find(id);
|
|
112
151
|
(0, _serverOutput.printOutput)(result, output);
|
|
113
152
|
return true;
|
|
114
153
|
}
|
|
@@ -125,8 +164,13 @@ async function server(argv) {
|
|
|
125
164
|
(0, _serverOutput.printError)(new Error(`Unknown verb: '${verb}'. Valid: ${VERBS.join(', ')}${extraVerbs}`));
|
|
126
165
|
return false;
|
|
127
166
|
} catch (err) {
|
|
128
|
-
|
|
129
|
-
|
|
167
|
+
// A runtime failure is not a usage error: report it and exit non-zero without
|
|
168
|
+
// making grok.js dump the help block (which it does for every `false` result).
|
|
169
|
+
(0, _serverOutput.printError)(err, {
|
|
170
|
+
verbose: !!argv.verbose
|
|
171
|
+
});
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
return true;
|
|
130
174
|
}
|
|
131
175
|
}
|
|
132
176
|
async function handleFilesPut(dapi, rest, output) {
|
|
@@ -438,13 +482,20 @@ async function handleFunctionsList(dapi, argv, limit, offset, userFilter, output
|
|
|
438
482
|
(0, _serverOutput.printOutput)(results, output);
|
|
439
483
|
return true;
|
|
440
484
|
}
|
|
441
|
-
async function handleRaw(dapi, method, rest, output) {
|
|
485
|
+
async function handleRaw(dapi, method, rest, argv, output) {
|
|
442
486
|
if (!method || !rest[0]) {
|
|
443
|
-
(0, _serverOutput.printError)(new Error('Usage: grok s raw <METHOD> <path>'));
|
|
487
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s raw <METHOD> <path> [--json body.json | --data \'{"k": 1}\']\n e.g. grok s raw GET /users/current (paths are API-relative; a leading /api is accepted)'));
|
|
444
488
|
return false;
|
|
445
489
|
}
|
|
446
|
-
|
|
447
|
-
|
|
490
|
+
let body;
|
|
491
|
+
if (argv.json) body = readJsonFile(argv.json);else if (argv.data !== undefined) {
|
|
492
|
+
try {
|
|
493
|
+
body = JSON.parse(String(argv.data));
|
|
494
|
+
} catch {
|
|
495
|
+
body = String(argv.data);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const result = await dapi.raw(method, String(rest[0]), body);
|
|
448
499
|
(0, _serverOutput.printOutput)(result, output);
|
|
449
500
|
return true;
|
|
450
501
|
}
|
|
@@ -454,21 +505,15 @@ async function handleSync(dapi, subject, rest, argv, output) {
|
|
|
454
505
|
// runs are read-only here; `run` triggers an actual push and prints
|
|
455
506
|
// the per-item summary. Full reference in
|
|
456
507
|
// core/docs/plans/instance-sync.md (Phase 7 / operational polish).
|
|
457
|
-
//
|
|
458
|
-
// `_callSync` does dual-path routing: tries `/api/sync/...` first
|
|
459
|
-
// (nginx-fronted deployments) and falls back to `/sync/...` (bare
|
|
460
|
-
// datlas on :8082). Same approach as the server-side handshake
|
|
461
|
-
// code so the CLI works against either layout.
|
|
462
508
|
const verb = rest[0];
|
|
509
|
+
// a 1.27 server has no /sync routes at all: report that as "not found", not as a failure
|
|
463
510
|
const callSync = async (method, path, body) => {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (r && (Array.isArray(r) || typeof r === 'object') && r['#type'] !== 'ApiError') return r;
|
|
511
|
+
try {
|
|
512
|
+
return await dapi.raw(method, path, body);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
if (err?.apiError?.errorCode === 404) return null;
|
|
515
|
+
throw err;
|
|
470
516
|
}
|
|
471
|
-
return null;
|
|
472
517
|
};
|
|
473
518
|
if (subject === 'pairs' && verb === 'list') {
|
|
474
519
|
const status = argv.status ? `?status=${encodeURIComponent(argv.status)}` : '';
|
|
@@ -533,8 +578,9 @@ async function handleSync(dapi, subject, rest, argv, output) {
|
|
|
533
578
|
}
|
|
534
579
|
async function handleHealthcheck(dapi, argv, output) {
|
|
535
580
|
const module = argv.module;
|
|
536
|
-
const path = module ? `/
|
|
537
|
-
const result = await dapi.
|
|
581
|
+
const path = module ? `/public/v1/healthcheck?module=${encodeURIComponent(module)}` : '/public/v1/healthcheck';
|
|
582
|
+
const result = await dapi.client.get(path);
|
|
583
|
+
const services = Array.isArray(result?.services) ? result.services : [];
|
|
538
584
|
if (output === 'json') {
|
|
539
585
|
(0, _serverOutput.printOutput)(result, output);
|
|
540
586
|
return true;
|
|
@@ -546,16 +592,25 @@ async function handleHealthcheck(dapi, argv, output) {
|
|
|
546
592
|
console.log(`time: ${result?.time ?? ''}`);
|
|
547
593
|
console.log('');
|
|
548
594
|
}
|
|
549
|
-
(0, _serverOutput.printOutput)(
|
|
595
|
+
if (services.length) (0, _serverOutput.printOutput)(services, output);else if (module) throw new Error(`Module '${module}' is not reported by the server`);else if (output !== 'quiet') console.log('(no services reported)');
|
|
550
596
|
return true;
|
|
551
597
|
}
|
|
552
598
|
async function handleDescribe(dapi, entityType, output) {
|
|
553
599
|
if (!entityType) {
|
|
554
|
-
(0, _serverOutput.printError)(new Error('Usage: grok s describe <entity
|
|
600
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s describe <entity|type> e.g. grok s describe connections, grok s describe Project'));
|
|
555
601
|
return false;
|
|
556
602
|
}
|
|
557
|
-
const result = await dapi.describe(entityType);
|
|
558
|
-
(
|
|
603
|
+
const result = await dapi.describe(String(entityType));
|
|
604
|
+
if (output === 'json') {
|
|
605
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
if (output !== 'quiet' && result.type) console.log(`${result.type.name}${result.type.friendlyName && result.type.friendlyName !== result.type.name ? ` (${result.type.friendlyName})` : ''} id ${result.type.id}${result.type.isPackageEntity ? ' package entity' : ''}\n`);
|
|
609
|
+
if (!result.fields.length) {
|
|
610
|
+
if (output !== 'quiet') console.log('(no entity of this type exists on the server to derive the fields from)');
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
(0, _serverOutput.printOutput)(output === 'quiet' ? result.fields.map(f => f.field) : result.fields, output);
|
|
559
614
|
return true;
|
|
560
615
|
}
|
|
561
616
|
async function handleFuncRun(dapi, rest, argv, output) {
|
|
@@ -577,6 +632,7 @@ async function handleFuncRun(dapi, rest, argv, output) {
|
|
|
577
632
|
const parsed = parseFuncCall(funcName);
|
|
578
633
|
funcName = parsed.name;
|
|
579
634
|
params = parsed.params;
|
|
635
|
+
if (Object.keys(params).some(k => /^\d+$/.test(k))) params = (0, _nodeDapi.mapPositionalParams)(params, (await dapi.functions.find(funcName))?.parameterInfos, funcName);
|
|
580
636
|
}
|
|
581
637
|
const result = await dapi.functions.run(funcName, params);
|
|
582
638
|
(0, _serverOutput.printOutput)(result, output);
|
|
@@ -611,18 +667,25 @@ async function handleSharesAdd(dapi, rest, argv, output) {
|
|
|
611
667
|
return true;
|
|
612
668
|
}
|
|
613
669
|
async function handleSharesList(dapi, rest, output) {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
(0, _serverOutput.printError)(new Error('Usage: grok s shares list <entity-id> (entity id must be a UUID)'));
|
|
670
|
+
if (!rest[0]) {
|
|
671
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s shares list <entity-id-or-name> e.g. grok s shares list "Admin:MyConnection"'));
|
|
617
672
|
return false;
|
|
618
673
|
}
|
|
674
|
+
const entityId = (0, _registry.isUuid)(String(rest[0])) ? String(rest[0]) : (await (0, _walker.resolveEntity)(dapi, String(rest[0]))).id;
|
|
619
675
|
const perms = await dapi.shares.list(entityId);
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
676
|
+
// the permissions route returns group ids only; a share touches a handful of groups
|
|
677
|
+
const groups = new Map();
|
|
678
|
+
for (const id of new Set(perms.map(p => p?.userGroup?.id).filter(Boolean))) groups.set(id, await dapi.groups.find(id));
|
|
679
|
+
const flat = perms.map(p => {
|
|
680
|
+
const g = groups.get(p?.userGroup?.id) ?? p?.userGroup ?? {};
|
|
681
|
+
return {
|
|
682
|
+
group: g.friendlyName ?? g.name ?? g.id ?? '',
|
|
683
|
+
groupId: g.id ?? '',
|
|
684
|
+
access: p?.permission?.name ?? p?.permission?.friendlyName ?? '',
|
|
685
|
+
personal: g.personal ?? false,
|
|
686
|
+
inherited: p?.inheritedByLink === true
|
|
687
|
+
};
|
|
688
|
+
});
|
|
626
689
|
(0, _serverOutput.printOutput)(flat, output);
|
|
627
690
|
return true;
|
|
628
691
|
}
|
|
@@ -714,23 +777,14 @@ async function handleGroupRemoveMembers(dapi, rest, argv, output) {
|
|
|
714
777
|
if (anyError) process.exitCode = 1;
|
|
715
778
|
return true;
|
|
716
779
|
}
|
|
717
|
-
async function handleGroupListMembers(dapi, rest, argv, output) {
|
|
780
|
+
async function handleGroupListMembers(dapi, verb, rest, argv, output) {
|
|
718
781
|
if (!rest[0]) {
|
|
719
|
-
(0, _serverOutput.printError)(new Error(
|
|
782
|
+
(0, _serverOutput.printError)(new Error(`Usage: grok s groups ${verb} <group-or-login> [--admin | --no-admin] [--user]`));
|
|
720
783
|
return false;
|
|
721
784
|
}
|
|
722
785
|
const admin = typeof argv.admin === 'boolean' ? argv.admin : undefined;
|
|
723
|
-
const
|
|
724
|
-
(0,
|
|
725
|
-
return true;
|
|
726
|
-
}
|
|
727
|
-
async function handleGroupListMemberships(dapi, rest, argv, output) {
|
|
728
|
-
if (!rest[0]) {
|
|
729
|
-
(0, _serverOutput.printError)(new Error('Usage: grok s groups list-memberships <group> [--admin | --no-admin]'));
|
|
730
|
-
return false;
|
|
731
|
-
}
|
|
732
|
-
const admin = typeof argv.admin === 'boolean' ? argv.admin : undefined;
|
|
733
|
-
const result = await dapi.groups.getMemberships(rest[0], admin);
|
|
786
|
+
const personalOnly = argv.user === true;
|
|
787
|
+
const result = verb === 'list-members' ? await dapi.groups.getMembers(String(rest[0]), admin, personalOnly) : await dapi.groups.getMemberships(String(rest[0]), admin, personalOnly);
|
|
734
788
|
(0, _serverOutput.printOutput)(result, output);
|
|
735
789
|
return true;
|
|
736
790
|
}
|
|
@@ -867,7 +921,7 @@ function parseFuncCall(expr) {
|
|
|
867
921
|
params: Object.fromEntries(positional.map((v, i) => [String(i), v]))
|
|
868
922
|
};
|
|
869
923
|
}
|
|
870
|
-
const HELP_SERVER = `
|
|
924
|
+
const HELP_SERVER = exports.HELP_SERVER = `
|
|
871
925
|
Usage: grok server <entity> <verb> [args] [options]
|
|
872
926
|
grok s <entity> <verb> [args] [options]
|
|
873
927
|
|
|
@@ -875,27 +929,29 @@ Manage a Datagrok server from the command line.
|
|
|
875
929
|
|
|
876
930
|
Entities:
|
|
877
931
|
users, groups, functions, connections, queries, scripts, packages, reports, files, tables
|
|
932
|
+
(plus domains, shares, batch, raw, describe, healthcheck, sync, pull/push/migrate/diff/bundle below)
|
|
878
933
|
|
|
879
934
|
Verbs:
|
|
880
|
-
list List entities
|
|
935
|
+
list List entities (--filter, --limit, --offset)
|
|
936
|
+
count Count entities (--filter)
|
|
881
937
|
get Get a single entity by ID or name
|
|
882
|
-
delete Delete an entity by ID
|
|
938
|
+
delete Delete an entity by ID or name (users cannot be deleted: block them; functions: scripts and queries only)
|
|
883
939
|
|
|
884
940
|
Special commands:
|
|
885
|
-
grok s functions run <Name:func(args)> Call a function
|
|
941
|
+
grok s functions run <Name:func(args)> Call a function (positional args map onto its inputs)
|
|
886
942
|
grok s functions list [--type <t>] [--language <l>] [--package <p>] [--filter <expr>]
|
|
887
943
|
Type: script|query|function|package
|
|
888
944
|
Language applies to scripts (python, r, julia, nodejs, octave, grok)
|
|
889
|
-
grok s files list <path> [-r]
|
|
945
|
+
grok s files list <connector>[/<path>] [-r] List a share (recursive with -r): path, kind, size
|
|
890
946
|
grok s files get <path> Download a file (returns bytes)
|
|
891
947
|
grok s files delete <path> Delete a file
|
|
892
948
|
grok s files put <local> <remote> Upload a local file
|
|
893
|
-
grok s raw <METHOD> <path>
|
|
894
|
-
grok s describe <entity
|
|
949
|
+
grok s raw <METHOD> <path> [--json f | --data j] Any API endpoint; path is API-relative (/users/current), /api prefix optional
|
|
950
|
+
grok s describe <entity|type> Fields of an entity type (registry record + a live sample)
|
|
895
951
|
grok s healthcheck [--module <name>] Check server + per-module health
|
|
896
952
|
grok s shares add <entity> <group>[,<group>...] [--access View|Edit]
|
|
897
953
|
Share an entity with one or more groups
|
|
898
|
-
grok s shares list <entity-id>
|
|
954
|
+
grok s shares list <entity-id-or-name> List who an entity is shared with
|
|
899
955
|
grok s users save --json user.json Create or update a user from a JSON file
|
|
900
956
|
grok s groups save --json group.json [--save-relations]
|
|
901
957
|
Create or update a group from a JSON file
|
|
@@ -905,12 +961,15 @@ Special commands:
|
|
|
905
961
|
grok s connections test --json conn.json Test connectivity of a connection defined in JSON
|
|
906
962
|
grok s groups add-members <group> <m>... [--admin] Add one or more users/groups as members
|
|
907
963
|
grok s groups remove-members <group> <m>... Remove members (no-op if not a member)
|
|
908
|
-
grok s groups list-members <group> [--admin]
|
|
909
|
-
|
|
964
|
+
grok s groups list-members <group> [--admin] [--user]
|
|
965
|
+
List members (optionally filter by admin; --user: personal group)
|
|
966
|
+
grok s groups list-memberships <group> [--admin] [--user]
|
|
967
|
+
List parent groups
|
|
910
968
|
grok s users block <id-or-login> Block a user from the platform
|
|
911
969
|
grok s users unblock <id-or-login> Unblock a previously blocked user
|
|
912
970
|
grok s tables upload <name> <file.csv|file.d42> Upload a CSV or d42 binary as a Datagrok table
|
|
913
|
-
grok s tables download <name
|
|
971
|
+
grok s tables download <name|Project:Table|id> [-O <file>]
|
|
972
|
+
Download a table as CSV (stdout by default)
|
|
914
973
|
grok s packages install <name>... [--version <v>] Install latest (or pinned) versions from the registry
|
|
915
974
|
grok s packages uninstall <name> Uninstall a package (repository entry is kept)
|
|
916
975
|
grok s packages update <name>... | --all Upgrade to the latest registry version
|
|
@@ -919,6 +978,30 @@ Special commands:
|
|
|
919
978
|
grok s packages set-version <name> <version> Activate a specific published version
|
|
920
979
|
grok s packages share <name> <group>[,...] [--access View|Edit]
|
|
921
980
|
Share a package with one or more groups
|
|
981
|
+
grok s pull [<nqName|id>...] --out <dir> Export entities into a bundle directory
|
|
982
|
+
grok s push <bundle-dir> [--dry-run] Import a bundle into the target server
|
|
983
|
+
grok s migrate <selection> --from <a> --to <b> Pull from one instance and push into another
|
|
984
|
+
grok s diff <bundle-dir> What a push would change (read-only, plans with skip)
|
|
985
|
+
grok s bundle ls <bundle-dir> List what a bundle contains
|
|
986
|
+
grok s domains list [<schema>] [--filter <text>] Domain schemas, or the tables of one schema
|
|
987
|
+
grok s domains get <schema>|<schema.table> [<id>] Manifest, a table's columns, or one row
|
|
988
|
+
grok s domains query <schema.table> [--filter <expr>] [--columns a,b] [--sort 'a,!b'] [--expand x] [--limit n] [--offset n]
|
|
989
|
+
grok s domains count <schema.table> [--filter <expr>]
|
|
990
|
+
grok s domains insert <schema.table> --json rows.json | col=value ... [--error-on-duplicate]
|
|
991
|
+
grok s domains update <schema.table> <id> --json values.json | col=value ... [--version n]
|
|
992
|
+
grok s domains delete <schema.table> <id> | --filter <expr> [--limit n]
|
|
993
|
+
grok s domains delete <schema> --force Purge a user-managed schema with its data
|
|
994
|
+
grok s domains upload <schema.table> <file.csv|.d42|.json> [--upsert] [--no-all-or-nothing] [--error-on-duplicate]
|
|
995
|
+
grok s domains download <schema.table> [-O out.csv|out.d42] [--filter <expr>] [--columns a,b] [--sort s]
|
|
996
|
+
grok s domains aggregate <schema.table> --measures 'count,sum(x) as t' [--group-by a,b] [--filter <expr>]
|
|
997
|
+
grok s domains transaction <schema> --json ops.json Ordered insert/update/delete ops, atomically
|
|
998
|
+
grok s domains audit <schema>|<schema.table> [<id>] [--limit n]
|
|
999
|
+
grok s domains capabilities <schema.table> What the current user may do on the table
|
|
1000
|
+
grok s domains grants <schema>|<schema.table> Direct permission grants on a schema or table
|
|
1001
|
+
grok s domains grant <schema>|<schema.table> <group>[,...] [--access View|Edit|Delete|Share|Extend]
|
|
1002
|
+
grok s domains revoke <schema>|<schema.table> <group>[,...] [--access <permission>]
|
|
1003
|
+
grok s domains create <name> [--friendly-name <t>] [--description <t>]
|
|
1004
|
+
grok s domains apply <schema> --json manifest.json [--dry-run] [--confirm-destructive] [--if-version <v>]
|
|
922
1005
|
grok s batch <entity> <verb> arg1 [arg2 ...] Batch operation (one round-trip)
|
|
923
1006
|
grok s batch <entity> <verb> --json params.json Batch from JSON array
|
|
924
1007
|
grok s batch manifest.json Run a workflow manifest
|
|
@@ -927,14 +1010,47 @@ Special commands:
|
|
|
927
1010
|
grok s sync setup get <setup-id> Inspect a setup (selections, direction, last run)
|
|
928
1011
|
grok s sync run <setup-id> Trigger a push run; prints per-item outcome
|
|
929
1012
|
|
|
1013
|
+
Pull / push / migrate options:
|
|
1014
|
+
--out <dir> Bundle directory to write (pull; merges into an existing bundle)
|
|
1015
|
+
--type <t,t> conn, query, script, project, dashboard, space, view, layout, table,
|
|
1016
|
+
file, group, job, notebook, model
|
|
1017
|
+
--name <glob> Entity name glob: 'Cereal*' matches by prefix, '*demo*' anywhere
|
|
1018
|
+
--namespace <ns> Everything under a namespace, recursively
|
|
1019
|
+
--space <nqName> The space itself plus everything under it
|
|
1020
|
+
--author <login> Entities authored by a login
|
|
1021
|
+
--tag <tag> Entities carrying a tag
|
|
1022
|
+
--since <2w|date> Entities updated since ('2w' means '-2w'; also --since=-2w)
|
|
1023
|
+
--filter <expr> Smart-filter expression ANDed with the other flags
|
|
1024
|
+
--no-deps Do not follow dependencies (never-travels rules still apply)
|
|
1025
|
+
--no-include-data Do not pull table data (.d42) for pulled tables
|
|
1026
|
+
--include-files Also pull the bytes of pulled files
|
|
1027
|
+
--replace Clear the bundle directory before writing
|
|
1028
|
+
--dry-run Push: print the plan and stop
|
|
1029
|
+
--on-conflict <p> Push: what to do when the name is taken on the target by another id —
|
|
1030
|
+
fail (default) aborts before any write, skip leaves the target alone
|
|
1031
|
+
(and fails whatever depends on it), adopt writes into the twin and
|
|
1032
|
+
records idmap.json, duplicate creates the bundle entity under its own
|
|
1033
|
+
id next to the twin (a UserGroup cannot be duplicated — group names
|
|
1034
|
+
are unique, so that row fails)
|
|
1035
|
+
--creds <file.yaml> Push: target-side connection secrets, 'Ns:Conn:' then ' password: \${VAR}'
|
|
1036
|
+
(a covered connection is always written, so a push rotates the secret)
|
|
1037
|
+
--from <alias|url> Migrate: source instance (pulled from, never written to)
|
|
1038
|
+
--to <alias|url> Migrate: target instance
|
|
1039
|
+
--keep Migrate: keep the temporary bundle and print its path on stderr
|
|
1040
|
+
|
|
930
1041
|
Options:
|
|
931
1042
|
--host <alias|url> Server alias from config or full URL
|
|
1043
|
+
--admin Ask the server for an admin session, so the run sees entities the key's
|
|
1044
|
+
own account cannot (other people's spaces). Refused unless the account
|
|
1045
|
+
may start one; lasts for this command only
|
|
932
1046
|
--output <format> Output format: table (default), json, csv, quiet
|
|
933
1047
|
--filter <text> Smart filter expression
|
|
934
1048
|
--limit <n> Page size (default: 50)
|
|
935
1049
|
--offset <n> Start offset (default: 0)
|
|
936
1050
|
-r, --recursive Recursive (for files list)
|
|
937
|
-
--
|
|
1051
|
+
--verbose Print the stack of a runtime failure, not just its message
|
|
1052
|
+
--json <file> Read a JSON body from a file (save, functions run, batch, raw)
|
|
1053
|
+
--data '<json>' Inline JSON body for raw
|
|
938
1054
|
-O, --output-file Write table download to a file instead of stdout
|
|
939
1055
|
--type <t> Function discriminator: script | query | function | package
|
|
940
1056
|
--language <lang> Script language: python, r, julia, nodejs, octave, grok
|
|
@@ -950,6 +1066,7 @@ Batch manifest options (in manifest.json):
|
|
|
950
1066
|
Examples:
|
|
951
1067
|
grok s users list
|
|
952
1068
|
grok s users list --output json --limit 10
|
|
1069
|
+
grok s users count --filter 'status = "active"'
|
|
953
1070
|
grok s users save --json user.json
|
|
954
1071
|
grok s groups save --json group.json --save-relations
|
|
955
1072
|
grok s shares add "JohnDoe:MyConnection" Chemists,Admins --access Edit
|
|
@@ -972,14 +1089,33 @@ Examples:
|
|
|
972
1089
|
grok s packages update --all
|
|
973
1090
|
grok s packages versions Chem
|
|
974
1091
|
grok s packages share Chem Chemists --access View
|
|
975
|
-
grok s raw GET /
|
|
1092
|
+
grok s raw GET /users/current
|
|
1093
|
+
grok s raw POST /public/v1/functions/Sin/call --data '{"x": 1}'
|
|
976
1094
|
grok s describe connections
|
|
1095
|
+
grok s tables download MyTable -O ./my-table.csv
|
|
977
1096
|
grok s users list --host dev
|
|
978
1097
|
grok s users list --host "https://mygrok.com/api"
|
|
979
1098
|
grok s groups add-members Admins alice bob --admin
|
|
980
1099
|
grok s groups remove-members Admins alice
|
|
981
1100
|
grok s groups list-members Admins --admin
|
|
982
1101
|
grok s groups list-memberships alice
|
|
1102
|
+
grok s pull Admin:CerealDemog --out ./bundle --host dev
|
|
1103
|
+
grok s pull Chemists --out ./bundle --host dev
|
|
1104
|
+
grok s pull --type script --author alice --no-deps --out ./bundle
|
|
1105
|
+
grok s bundle ls ./bundle
|
|
1106
|
+
grok s push ./bundle --host local --dry-run
|
|
1107
|
+
grok s push ./bundle --host local --on-conflict adopt
|
|
1108
|
+
grok s pull --type space --namespace MySpace --include-files --out ./bundle
|
|
1109
|
+
grok s push ./bundle --host prod --creds ./creds.yaml
|
|
1110
|
+
grok s migrate Chem:TargetDashboard --from dev --to prod --dry-run
|
|
1111
|
+
grok s diff ./bundle --host local
|
|
1112
|
+
grok s domains list
|
|
1113
|
+
grok s domains get grit.issue
|
|
1114
|
+
grok s domains query grit.issue --filter 'status = "open"' --sort '!created_on' --limit 20
|
|
1115
|
+
grok s domains insert grit.issue title="Crash on save" status=open
|
|
1116
|
+
grok s domains upload grit.issue ./issues.csv --upsert
|
|
1117
|
+
grok s domains download grit.issue -O ./issues.csv
|
|
1118
|
+
grok s domains grant grit.issue Chemists --access Edit
|
|
983
1119
|
grok s batch files delete "System:AppData/old.txt" "System:DemoFiles/tmp.txt"
|
|
984
1120
|
grok s batch users create --json users.json
|
|
985
1121
|
grok s batch manifest.json
|
package/bin/grok.js
CHANGED
|
@@ -36,6 +36,14 @@ const commands = {
|
|
|
36
36
|
|
|
37
37
|
const onPackageCommandNames = ['api', 'check', 'link', 'publish', 'test'];
|
|
38
38
|
|
|
39
|
+
// A machine-readable run prints its error as JSON on stderr (server.ts) and nothing else:
|
|
40
|
+
// a usage dump on stdout would corrupt the output the caller parses.
|
|
41
|
+
const outputFormat = argv.output ?? argv.o;
|
|
42
|
+
function printUsage(command) {
|
|
43
|
+
if (outputFormat !== 'json')
|
|
44
|
+
process.stderr.write(`${help[command]}\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
const command = argv['_'][0];
|
|
40
48
|
if (command !== 'test' && command !== 'stresstest')
|
|
41
49
|
delete argv.dartium;
|
|
@@ -52,23 +60,23 @@ if (command in commands) {
|
|
|
52
60
|
if (result && typeof result.then === 'function') {
|
|
53
61
|
result.then((ok) => {
|
|
54
62
|
if (!ok) {
|
|
55
|
-
|
|
63
|
+
printUsage(command);
|
|
56
64
|
exitWithCode(1);
|
|
57
65
|
}
|
|
58
66
|
}).catch((err) => {
|
|
59
67
|
console.error(err);
|
|
60
|
-
|
|
68
|
+
printUsage(command);
|
|
61
69
|
exitWithCode(255);
|
|
62
70
|
});
|
|
63
71
|
}
|
|
64
72
|
else if (!result) {
|
|
65
|
-
|
|
73
|
+
printUsage(command);
|
|
66
74
|
exitWithCode(1);
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
} catch (err) {
|
|
70
78
|
console.error(err);
|
|
71
|
-
|
|
79
|
+
printUsage(command);
|
|
72
80
|
exitWithCode(255);
|
|
73
81
|
}
|
|
74
82
|
} else
|
|
@@ -76,6 +84,7 @@ if (command in commands) {
|
|
|
76
84
|
|
|
77
85
|
|
|
78
86
|
function exitWithCode(code) {
|
|
79
|
-
|
|
87
|
+
if (outputFormat !== 'json')
|
|
88
|
+
console.log(`Exiting with code ${code}`);
|
|
80
89
|
process.exit(code);
|
|
81
90
|
}
|