datagrok-tools 6.1.9 → 6.1.10
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 +10 -0
- package/CLAUDE.md +68 -0
- package/bin/__tests__/build.test.js +116 -0
- package/bin/__tests__/build.test.ts +101 -0
- package/bin/__tests__/node-dapi.connections.test.js +120 -0
- package/bin/__tests__/node-dapi.connections.test.ts +84 -0
- package/bin/__tests__/node-dapi.groups.test.js +467 -0
- package/bin/__tests__/node-dapi.groups.test.ts +298 -0
- package/bin/__tests__/node-dapi.integration.test.js +406 -0
- package/bin/__tests__/node-dapi.integration.test.ts +447 -0
- package/bin/__tests__/node-dapi.shares.test.js +107 -0
- package/bin/__tests__/node-dapi.shares.test.ts +70 -0
- package/bin/__tests__/node-dapi.users.test.js +86 -0
- package/bin/__tests__/node-dapi.users.test.ts +58 -0
- package/bin/__tests__/server-output.test.js +171 -0
- package/bin/__tests__/server-output.test.ts +133 -0
- package/bin/__tests__/server.test.js +277 -0
- package/bin/__tests__/server.test.ts +197 -0
- package/bin/commands/build.js +1 -1
- package/bin/commands/create.js +8 -5
- package/bin/commands/help.js +61 -1
- package/bin/commands/server.js +520 -0
- package/bin/grok.js +3 -1
- package/bin/utils/node-dapi.js +459 -0
- package/bin/utils/server-client.js +15 -0
- package/bin/utils/server-output.js +127 -0
- package/package-template/package.json +1 -1
- package/package.json +8 -3
- package/vitest.config.ts +25 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.buildInlineManifest = buildInlineManifest;
|
|
7
|
+
exports.parseFuncCall = parseFuncCall;
|
|
8
|
+
exports.resolveManifestSources = resolveManifestSources;
|
|
9
|
+
exports.server = server;
|
|
10
|
+
var fs = _interopRequireWildcard(require("fs"));
|
|
11
|
+
var _nodeDapi = require("../utils/node-dapi");
|
|
12
|
+
var _serverClient = require("../utils/server-client");
|
|
13
|
+
var _serverOutput = require("../utils/server-output");
|
|
14
|
+
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); }
|
|
15
|
+
/// Docs: [Grok Dapi](/docs/plans/grok-dapi/)
|
|
16
|
+
|
|
17
|
+
const ENTITIES = ['users', 'groups', 'functions', 'connections', 'queries', 'scripts', 'packages', 'reports', 'files'];
|
|
18
|
+
const VERBS = ['list', 'get', 'delete'];
|
|
19
|
+
async function server(argv) {
|
|
20
|
+
const args = argv['_'].slice(1);
|
|
21
|
+
const entity = args[0];
|
|
22
|
+
const verb = args[1];
|
|
23
|
+
const rest = args.slice(2);
|
|
24
|
+
const output = argv.output ?? argv.o ?? 'table';
|
|
25
|
+
const limit = Number(argv.limit ?? argv.l ?? 50);
|
|
26
|
+
const offset = Number(argv.offset ?? 0);
|
|
27
|
+
const filter = argv.filter ?? argv.f ?? '';
|
|
28
|
+
const host = argv.host;
|
|
29
|
+
const recursive = !!(argv.r ?? argv.recursive);
|
|
30
|
+
if (!entity || argv.help) {
|
|
31
|
+
console.log(HELP_SERVER);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
let client;
|
|
35
|
+
try {
|
|
36
|
+
client = await (0, _serverClient.createClient)(host);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
(0, _serverOutput.printError)(err);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const dapi = new _nodeDapi.NodeDapi(client);
|
|
42
|
+
try {
|
|
43
|
+
if (entity === 'batch') return handleBatch(dapi, argv, verb, rest, output);
|
|
44
|
+
if (entity === 'raw') return handleRaw(dapi, verb, rest, output);
|
|
45
|
+
if (entity === 'describe') return handleDescribe(dapi, verb ?? rest[0], output);
|
|
46
|
+
if (entity === 'functions' && verb === 'run') return handleFuncRun(dapi, rest, argv, output);
|
|
47
|
+
if (entity === 'files' && verb === 'list') {
|
|
48
|
+
const path = rest[0] ?? '';
|
|
49
|
+
const result = await dapi.files.list(path, recursive);
|
|
50
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (entity === 'files' && verb === 'get') {
|
|
54
|
+
const result = await dapi.files.get(rest[0]);
|
|
55
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (entity === 'files' && verb === 'delete') {
|
|
59
|
+
await dapi.files.delete(rest[0]);
|
|
60
|
+
if (output !== 'quiet') console.log(`Deleted ${rest[0]}`);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (entity === 'shares' && verb === 'add') return handleSharesAdd(dapi, rest, argv, output);
|
|
64
|
+
if (entity === 'shares' && verb === 'list') return handleSharesList(dapi, rest, output);
|
|
65
|
+
if (entity === 'users' && verb === 'save') return handleUserSave(dapi, argv, output);
|
|
66
|
+
if (entity === 'groups' && verb === 'save') return handleGroupSave(dapi, argv, output);
|
|
67
|
+
if (entity === 'connections' && verb === 'save') return handleConnSave(dapi, argv, output);
|
|
68
|
+
if (entity === 'connections' && verb === 'test') return handleConnTest(dapi, rest, argv, output);
|
|
69
|
+
if (entity === 'groups' && verb === 'add-members') return handleGroupAddMembers(dapi, rest, argv, output);
|
|
70
|
+
if (entity === 'groups' && verb === 'remove-members') return handleGroupRemoveMembers(dapi, rest, argv, output);
|
|
71
|
+
if (entity === 'groups' && verb === 'list-members') return handleGroupListMembers(dapi, rest, argv, output);
|
|
72
|
+
if (entity === 'groups' && verb === 'list-memberships') return handleGroupListMemberships(dapi, rest, argv, output);
|
|
73
|
+
const source = dapi[entity];
|
|
74
|
+
if (!source || !ENTITIES.includes(entity)) {
|
|
75
|
+
(0, _serverOutput.printError)(new Error(`Unknown entity type: '${entity}'. Valid: ${ENTITIES.join(', ')}`));
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (!verb) {
|
|
79
|
+
console.log(`Usage: grok s ${entity} <${VERBS.join('|')}> [args]`);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (verb === 'list') {
|
|
83
|
+
const page = Math.floor(offset / limit);
|
|
84
|
+
const results = await source.filter(filter).by(limit).page(page).list();
|
|
85
|
+
(0, _serverOutput.printOutput)(results, output);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (verb === 'get') {
|
|
89
|
+
if (!rest[0]) {
|
|
90
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s <entity> get <id>'));
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const result = await source.find(rest[0]);
|
|
94
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (verb === 'delete') {
|
|
98
|
+
if (!rest[0]) {
|
|
99
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s <entity> delete <id>'));
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
await source.delete(rest[0]);
|
|
103
|
+
if (output !== 'quiet') console.log(`Deleted ${entity}/${rest[0]}`);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
(0, _serverOutput.printError)(new Error(`Unknown verb: '${verb}'. Valid: ${VERBS.join(', ')}`));
|
|
107
|
+
return false;
|
|
108
|
+
} catch (err) {
|
|
109
|
+
(0, _serverOutput.printError)(err);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function handleRaw(dapi, method, rest, output) {
|
|
114
|
+
if (!method || !rest[0]) {
|
|
115
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s raw <METHOD> <path>'));
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const path = rest[0];
|
|
119
|
+
const result = await dapi.raw(method, path);
|
|
120
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
async function handleDescribe(dapi, entityType, output) {
|
|
124
|
+
if (!entityType) {
|
|
125
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s describe <entity-type>'));
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const result = await dapi.describe(entityType);
|
|
129
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
async function handleFuncRun(dapi, rest, argv, output) {
|
|
133
|
+
let funcName = rest[0];
|
|
134
|
+
if (!funcName) {
|
|
135
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s functions run <Name:function(args...)> [--json params.json]'));
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
let params = {};
|
|
139
|
+
if (argv.json) {
|
|
140
|
+
const fs = require('fs');
|
|
141
|
+
try {
|
|
142
|
+
params = JSON.parse(fs.readFileSync(argv.json, 'utf8'));
|
|
143
|
+
} catch (err) {
|
|
144
|
+
(0, _serverOutput.printError)(new Error(`Cannot read params file: ${err.message}`));
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
const parsed = parseFuncCall(funcName);
|
|
149
|
+
funcName = parsed.name;
|
|
150
|
+
params = parsed.params;
|
|
151
|
+
}
|
|
152
|
+
const result = await dapi.functions.run(funcName, params);
|
|
153
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
function readJsonFile(jsonPath) {
|
|
157
|
+
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
158
|
+
}
|
|
159
|
+
function readJsonBody(argv, entity) {
|
|
160
|
+
if (!argv.json) throw new Error(`Usage: grok s ${entity} save --json ${entity}.json`);
|
|
161
|
+
try {
|
|
162
|
+
return readJsonFile(argv.json);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
throw new Error(`Cannot read ${entity} file '${argv.json}': ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function handleSharesAdd(dapi, rest, argv, output) {
|
|
168
|
+
const [entity, ...groupArgs] = rest;
|
|
169
|
+
if (!entity || !groupArgs.length) {
|
|
170
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s shares add <entity-id-or-name> <group>[,<group>...] [--access View|Edit]'));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
const groups = groupArgs.flatMap(g => g.split(',')).map(g => g.trim()).filter(Boolean).join(',');
|
|
174
|
+
const access = typeof argv.access === 'string' ? argv.access : 'View';
|
|
175
|
+
if (access !== 'View' && access !== 'Edit') {
|
|
176
|
+
(0, _serverOutput.printError)(new Error(`Invalid --access '${access}'. Use 'View' or 'Edit'.`));
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const result = await dapi.shares.share(entity, groups, access);
|
|
180
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
181
|
+
if (result?.status === 'failed') process.exitCode = 1;
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
async function handleSharesList(dapi, rest, output) {
|
|
185
|
+
const entityId = rest[0];
|
|
186
|
+
if (!entityId) {
|
|
187
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s shares list <entity-id> (entity id must be a UUID)'));
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const perms = await dapi.shares.list(entityId);
|
|
191
|
+
const flat = (Array.isArray(perms) ? perms : []).map(p => ({
|
|
192
|
+
group: p?.userGroup?.friendlyName ?? p?.userGroup?.name ?? p?.userGroup?.id ?? '',
|
|
193
|
+
groupId: p?.userGroup?.id ?? '',
|
|
194
|
+
access: p?.permission?.name ?? p?.permission?.friendlyName ?? '',
|
|
195
|
+
personal: p?.userGroup?.personal ?? false
|
|
196
|
+
}));
|
|
197
|
+
(0, _serverOutput.printOutput)(flat, output);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
async function handleUserSave(dapi, argv, output) {
|
|
201
|
+
let body;
|
|
202
|
+
try {
|
|
203
|
+
body = readJsonBody(argv, 'users');
|
|
204
|
+
} catch (err) {
|
|
205
|
+
(0, _serverOutput.printError)(err);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
const result = await dapi.users.save(body);
|
|
209
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
async function handleGroupSave(dapi, argv, output) {
|
|
213
|
+
let body;
|
|
214
|
+
try {
|
|
215
|
+
body = readJsonBody(argv, 'groups');
|
|
216
|
+
} catch (err) {
|
|
217
|
+
(0, _serverOutput.printError)(err);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const saveRelations = argv['save-relations'] === true || argv.saveRelations === true;
|
|
221
|
+
const result = await dapi.groups.save(body, saveRelations);
|
|
222
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
async function handleConnSave(dapi, argv, output) {
|
|
226
|
+
if (!argv.json) {
|
|
227
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s connections save --json conn.json [--save-credentials]'));
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
let body;
|
|
231
|
+
try {
|
|
232
|
+
body = readJsonFile(argv.json);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
(0, _serverOutput.printError)(new Error(`Cannot read connection file '${argv.json}': ${err.message}`));
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const saveCredentials = argv['save-credentials'] === true || argv.saveCredentials === true;
|
|
238
|
+
const result = await dapi.connections.save(body, saveCredentials);
|
|
239
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
async function handleConnTest(dapi, rest, argv, output) {
|
|
243
|
+
let body;
|
|
244
|
+
if (argv.json) {
|
|
245
|
+
try {
|
|
246
|
+
body = readJsonFile(argv.json);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
(0, _serverOutput.printError)(new Error(`Cannot read connection file '${argv.json}': ${err.message}`));
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
} else if (rest[0]) {
|
|
252
|
+
body = await dapi.connections.find(rest[0]);
|
|
253
|
+
} else {
|
|
254
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s connections test <id-or-name> | --json conn.json'));
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
await dapi.connections.test(body);
|
|
258
|
+
if (output !== 'quiet') console.log('ok');
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
async function handleGroupAddMembers(dapi, rest, argv, output) {
|
|
262
|
+
const [group, ...members] = rest;
|
|
263
|
+
if (!group || !members.length) {
|
|
264
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s groups add-members <group> <member> [<member> ...] [--admin] [--user]'));
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
const isAdmin = argv.admin === true;
|
|
268
|
+
const personalOnly = argv.user === true;
|
|
269
|
+
const results = await dapi.groups.addMembers(group, members, isAdmin, personalOnly);
|
|
270
|
+
(0, _serverOutput.printOutput)(results, output);
|
|
271
|
+
const anyError = results.some(r => r.status === 'error');
|
|
272
|
+
if (anyError) process.exitCode = 1;
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
async function handleGroupRemoveMembers(dapi, rest, argv, output) {
|
|
276
|
+
const [group, ...members] = rest;
|
|
277
|
+
if (!group || !members.length) {
|
|
278
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s groups remove-members <group> <member> [<member> ...] [--user]'));
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
const personalOnly = argv.user === true;
|
|
282
|
+
const results = await dapi.groups.removeMembers(group, members, personalOnly);
|
|
283
|
+
(0, _serverOutput.printOutput)(results, output);
|
|
284
|
+
const anyError = results.some(r => r.status === 'error');
|
|
285
|
+
if (anyError) process.exitCode = 1;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
async function handleGroupListMembers(dapi, rest, argv, output) {
|
|
289
|
+
if (!rest[0]) {
|
|
290
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s groups list-members <group> [--admin | --no-admin]'));
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const admin = typeof argv.admin === 'boolean' ? argv.admin : undefined;
|
|
294
|
+
const result = await dapi.groups.getMembers(rest[0], admin);
|
|
295
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
async function handleGroupListMemberships(dapi, rest, argv, output) {
|
|
299
|
+
if (!rest[0]) {
|
|
300
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s groups list-memberships <group> [--admin | --no-admin]'));
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
const admin = typeof argv.admin === 'boolean' ? argv.admin : undefined;
|
|
304
|
+
const result = await dapi.groups.getMemberships(rest[0], admin);
|
|
305
|
+
(0, _serverOutput.printOutput)(result, output);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
async function handleBatch(dapi, argv, verb, rest, output) {
|
|
309
|
+
let request;
|
|
310
|
+
if (verb?.endsWith('.json') && rest.length === 0) {
|
|
311
|
+
// grok s batch manifest.json
|
|
312
|
+
let raw;
|
|
313
|
+
try {
|
|
314
|
+
raw = JSON.parse(fs.readFileSync(verb, 'utf8'));
|
|
315
|
+
} catch (err) {
|
|
316
|
+
(0, _serverOutput.printError)(new Error(`Cannot read manifest file '${verb}': ${err.message}`));
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
request = resolveManifestSources(raw);
|
|
320
|
+
} else if (verb && ENTITIES.includes(verb)) {
|
|
321
|
+
const batchVerb = rest[0];
|
|
322
|
+
if (!batchVerb) {
|
|
323
|
+
(0, _serverOutput.printError)(new Error(`Usage: grok s batch ${verb} <verb> [args...]`));
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
if (argv.json) {
|
|
327
|
+
// grok s batch users create --json users.json
|
|
328
|
+
let paramsArray;
|
|
329
|
+
try {
|
|
330
|
+
const raw = JSON.parse(fs.readFileSync(argv.json, 'utf8'));
|
|
331
|
+
paramsArray = Array.isArray(raw) ? raw : [raw];
|
|
332
|
+
} catch (err) {
|
|
333
|
+
(0, _serverOutput.printError)(new Error(`Cannot read params file '${argv.json}': ${err.message}`));
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
request = buildInlineManifest(verb, batchVerb, paramsArray);
|
|
337
|
+
} else {
|
|
338
|
+
// grok s batch files delete path1 path2 ...
|
|
339
|
+
const batchArgs = rest.slice(1);
|
|
340
|
+
if (!batchArgs.length) {
|
|
341
|
+
(0, _serverOutput.printError)(new Error(`Usage: grok s batch ${verb} ${batchVerb} <arg1> [arg2 ...]`));
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
request = buildInlineManifest(verb, batchVerb, batchArgs);
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s batch <entity> <verb> [args]\n grok s batch <entity> <verb> --json params.json\n grok s batch manifest.json'));
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
const result = await dapi.batch(request);
|
|
351
|
+
(0, _serverOutput.printBatchOutput)(result, output);
|
|
352
|
+
return result.summary.failed === 0 && result.summary.partial === 0;
|
|
353
|
+
}
|
|
354
|
+
function buildInlineManifest(entity, verb, args) {
|
|
355
|
+
const action = `${entity}.${verb}`;
|
|
356
|
+
let operations;
|
|
357
|
+
if (args.length > 0 && typeof args[0] === 'object') {
|
|
358
|
+
// Array of param objects (from --json array)
|
|
359
|
+
operations = args.map((p, i) => ({
|
|
360
|
+
id: `op${i}`,
|
|
361
|
+
action,
|
|
362
|
+
params: p
|
|
363
|
+
}));
|
|
364
|
+
} else {
|
|
365
|
+
// Array of string args — map to appropriate param key
|
|
366
|
+
const paramKey = entity === 'files' ? 'path' : 'id';
|
|
367
|
+
operations = args.map((arg, i) => ({
|
|
368
|
+
id: `op${i}`,
|
|
369
|
+
action,
|
|
370
|
+
params: {
|
|
371
|
+
[paramKey]: arg
|
|
372
|
+
}
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
operations
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function resolveManifestSources(manifest) {
|
|
380
|
+
if (!manifest.operations) return manifest;
|
|
381
|
+
const operations = manifest.operations.map(op => {
|
|
382
|
+
if (op.action === 'files.put' && op.params?.source) {
|
|
383
|
+
const sourcePath = op.params.source;
|
|
384
|
+
const content = fs.readFileSync(sourcePath).toString('base64');
|
|
385
|
+
const {
|
|
386
|
+
source: _dropped,
|
|
387
|
+
...rest
|
|
388
|
+
} = op.params;
|
|
389
|
+
return {
|
|
390
|
+
...op,
|
|
391
|
+
params: {
|
|
392
|
+
...rest,
|
|
393
|
+
content
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return op;
|
|
398
|
+
});
|
|
399
|
+
return {
|
|
400
|
+
...manifest,
|
|
401
|
+
operations
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function parseFuncCall(expr) {
|
|
405
|
+
const parenIdx = expr.indexOf('(');
|
|
406
|
+
if (parenIdx === -1) return {
|
|
407
|
+
name: expr,
|
|
408
|
+
params: {}
|
|
409
|
+
};
|
|
410
|
+
const name = expr.slice(0, parenIdx);
|
|
411
|
+
const inner = expr.slice(parenIdx + 1, expr.lastIndexOf(')')).trim();
|
|
412
|
+
if (!inner) return {
|
|
413
|
+
name,
|
|
414
|
+
params: {}
|
|
415
|
+
};
|
|
416
|
+
if (inner.startsWith('{')) {
|
|
417
|
+
try {
|
|
418
|
+
const params = JSON.parse(inner.replace(/([{,]\s*)([a-zA-Z_]\w*)(\s*:)/g, '$1"$2"$3'));
|
|
419
|
+
return {
|
|
420
|
+
name,
|
|
421
|
+
params
|
|
422
|
+
};
|
|
423
|
+
} catch {
|
|
424
|
+
return {
|
|
425
|
+
name,
|
|
426
|
+
params: {}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const positional = inner.split(',').map(s => {
|
|
431
|
+
s = s.trim();
|
|
432
|
+
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) return s.slice(1, -1);
|
|
433
|
+
const n = Number(s);
|
|
434
|
+
return isNaN(n) ? s : n;
|
|
435
|
+
});
|
|
436
|
+
return {
|
|
437
|
+
name,
|
|
438
|
+
params: Object.fromEntries(positional.map((v, i) => [String(i), v]))
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const HELP_SERVER = `
|
|
442
|
+
Usage: grok server <entity> <verb> [args] [options]
|
|
443
|
+
grok s <entity> <verb> [args] [options]
|
|
444
|
+
|
|
445
|
+
Manage a Datagrok server from the command line.
|
|
446
|
+
|
|
447
|
+
Entities:
|
|
448
|
+
users, groups, functions, connections, queries, scripts, packages, reports, files
|
|
449
|
+
|
|
450
|
+
Verbs:
|
|
451
|
+
list List entities
|
|
452
|
+
get Get a single entity by ID or name
|
|
453
|
+
delete Delete an entity by ID
|
|
454
|
+
|
|
455
|
+
Special commands:
|
|
456
|
+
grok s functions run <Name:func(args)> Call a function
|
|
457
|
+
grok s files list [path] [-r] List files (recursive with -r)
|
|
458
|
+
grok s raw <METHOD> <path> Hit any API endpoint
|
|
459
|
+
grok s describe <entity-type> Show entity JSON schema
|
|
460
|
+
grok s shares add <entity> <group>[,<group>...] [--access View|Edit]
|
|
461
|
+
Share an entity with one or more groups
|
|
462
|
+
grok s shares list <entity-id> List who an entity (UUID) is shared with
|
|
463
|
+
grok s users save --json user.json Create or update a user from a JSON file
|
|
464
|
+
grok s groups save --json group.json [--save-relations]
|
|
465
|
+
Create or update a group from a JSON file
|
|
466
|
+
grok s connections save --json conn.json [--save-credentials]
|
|
467
|
+
Create or update a connection from a JSON file
|
|
468
|
+
grok s connections test <id-or-name> Test connectivity of an existing connection
|
|
469
|
+
grok s connections test --json conn.json Test connectivity of a connection defined in JSON
|
|
470
|
+
grok s groups add-members <group> <m>... [--admin] Add one or more users/groups as members
|
|
471
|
+
grok s groups remove-members <group> <m>... Remove members (no-op if not a member)
|
|
472
|
+
grok s groups list-members <group> [--admin] List members (optionally filter by admin)
|
|
473
|
+
grok s groups list-memberships <group> [--admin] List parent groups
|
|
474
|
+
grok s batch <entity> <verb> arg1 [arg2 ...] Batch operation (one round-trip)
|
|
475
|
+
grok s batch <entity> <verb> --json params.json Batch from JSON array
|
|
476
|
+
grok s batch manifest.json Run a workflow manifest
|
|
477
|
+
|
|
478
|
+
Options:
|
|
479
|
+
--host <alias|url> Server alias from config or full URL
|
|
480
|
+
--output <format> Output format: table (default), json, csv, quiet
|
|
481
|
+
--filter <text> Smart filter expression
|
|
482
|
+
--limit <n> Page size (default: 50)
|
|
483
|
+
--offset <n> Start offset (default: 0)
|
|
484
|
+
-r, --recursive Recursive (for files list)
|
|
485
|
+
--json <file> Read function parameters or batch params from JSON file
|
|
486
|
+
|
|
487
|
+
Batch manifest options (in manifest.json):
|
|
488
|
+
stopOnError Stop on first failure (default: true)
|
|
489
|
+
transaction Wrap DB ops in transaction (default: false)
|
|
490
|
+
concurrency Accepted (always treated as 1)
|
|
491
|
+
|
|
492
|
+
Examples:
|
|
493
|
+
grok s users list
|
|
494
|
+
grok s users list --output json --limit 10
|
|
495
|
+
grok s users save --json user.json
|
|
496
|
+
grok s groups save --json group.json --save-relations
|
|
497
|
+
grok s shares add "JohnDoe:MyConnection" Chemists,Admins --access Edit
|
|
498
|
+
grok s shares list <entity-uuid>
|
|
499
|
+
grok s connections list --filter "PostgreSQL"
|
|
500
|
+
grok s connections get <id>
|
|
501
|
+
grok s connections delete <id>
|
|
502
|
+
grok s connections save --json conn.json --save-credentials
|
|
503
|
+
grok s connections test "JohnDoe:MyConnection"
|
|
504
|
+
grok s connections test --json conn.json
|
|
505
|
+
grok s functions run 'Chem:smilesToMw("ccc")'
|
|
506
|
+
grok s functions run Chem:test --json params.json
|
|
507
|
+
grok s files list "System:AppData" -r
|
|
508
|
+
grok s raw GET /api/users/current
|
|
509
|
+
grok s describe connections
|
|
510
|
+
grok s users list --host dev
|
|
511
|
+
grok s users list --host "https://mygrok.com/api"
|
|
512
|
+
grok s groups add-members Admins alice bob --admin
|
|
513
|
+
grok s groups remove-members Admins alice
|
|
514
|
+
grok s groups list-members Admins --admin
|
|
515
|
+
grok s groups list-memberships alice
|
|
516
|
+
grok s batch files delete "System:AppData/old.txt" "System:AppData/tmp.txt"
|
|
517
|
+
grok s batch users create --json users.json
|
|
518
|
+
grok s batch manifest.json
|
|
519
|
+
grok s batch files delete "System:AppData/a" "System:AppData/b" --output json
|
|
520
|
+
`;
|
package/bin/grok.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const argv = require('minimist')(process.argv.slice(2), {
|
|
3
|
-
alias: {k: 'key', h: 'help', r: 'recursive'
|
|
3
|
+
alias: {k: 'key', h: 'help', r: 'recursive'},
|
|
4
4
|
boolean: ['dartium'],
|
|
5
5
|
});
|
|
6
6
|
const help = require('./commands/help').help;
|
|
@@ -24,6 +24,8 @@ const commands = {
|
|
|
24
24
|
testall: require('./commands/test-all').testAll,
|
|
25
25
|
stresstest: require('./commands/stress-tests').stressTests,
|
|
26
26
|
migrate: require('./commands/migrate').migrate,
|
|
27
|
+
server: require('./commands/server').server,
|
|
28
|
+
s: require('./commands/server').server,
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const onPackageCommandNames = ['api', 'check', 'link', 'publish', 'test'];
|