datagrok-tools 6.7.8 → 6.7.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.
@@ -10,11 +10,13 @@ exports.resolveManifestSources = resolveManifestSources;
10
10
  exports.server = server;
11
11
  var fs = _interopRequireWildcard(require("fs"));
12
12
  var path = _interopRequireWildcard(require("path"));
13
+ var _crypto = require("crypto");
13
14
  var _nodeDapi = require("../utils/node-dapi");
14
15
  var _serverClient = require("../utils/server-client");
15
16
  var _serverOutput = require("../utils/server-output");
16
17
  var _serverMigrate = require("./server-migrate");
17
18
  var _serverDomains = require("./server-domains");
19
+ var _serverObserve = require("./server-observe");
18
20
  var _registry = require("../utils/migrate/registry");
19
21
  var _walker = require("../utils/migrate/walker");
20
22
  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); }
@@ -27,7 +29,7 @@ const ENTITY_TYPES = {
27
29
  reports: 'UserReport'
28
30
  };
29
31
  const ENTITIES = ['users', 'groups', 'functions', 'connections', 'queries', 'scripts', 'packages', 'reports', 'files', 'tables'];
30
- const COMMANDS = ['shares', 'domains', 'raw', 'batch', 'describe', 'healthcheck', 'sync', 'pull', 'push', 'migrate', 'diff', 'bundle', 'token'];
32
+ const COMMANDS = ['shares', 'domains', 'raw', 'batch', 'describe', 'healthcheck', 'status', 'sync', 'pull', 'push', 'migrate', 'diff', 'bundle', 'token', 'observe', 'o'];
31
33
  const VERBS = ['list', 'count', 'get', 'delete'];
32
34
  async function server(argv) {
33
35
  const args = argv['_'].slice(1);
@@ -45,9 +47,18 @@ async function server(argv) {
45
47
  console.log(HELP_SERVER);
46
48
  return true;
47
49
  }
50
+ if ((entity === 'observe' || entity === 'o') && !verb) {
51
+ console.log(_serverObserve.OBSERVE_USAGE);
52
+ return true;
53
+ }
48
54
  let client;
55
+ const logSpec = (0, _serverClient.logSpecOf)(argv);
56
+ const logId = logSpec || entity === 'functions' && verb === 'run' && isDebug(argv) ? (0, _crypto.randomUUID)() : undefined;
49
57
  try {
50
- client = await (0, _serverClient.createClient)(host, !!argv.admin);
58
+ client = await (0, _serverClient.createClient)(host, !!argv.admin, logId ? {
59
+ logId,
60
+ spec: logSpec
61
+ } : undefined);
51
62
  } catch (err) {
52
63
  // a bad alias, URL or key is not a usage error: no help dump, just the reason
53
64
  (0, _serverOutput.printError)(err);
@@ -60,6 +71,7 @@ async function server(argv) {
60
71
  // escaping to grok.js as an unhandled rejection (stack trace + exit 255)
61
72
  if (['pull', 'push', 'migrate', 'diff', 'bundle'].includes(entity)) return await (0, _serverMigrate.handleMigrate)(dapi, entity, [verb, ...rest].filter(Boolean), argv, output);
62
73
  if (entity === 'domains') return await (0, _serverDomains.handleDomains)(dapi, verb, rest, argv, output);
74
+ if (entity === 'observe' || entity === 'o') return await (0, _serverObserve.handleObserve)(dapi, verb, rest[0], rest.slice(1), argv, output);
63
75
  if (entity === 'batch') return await handleBatch(dapi, argv, verb, rest, output);
64
76
  // Shell scripts that used to curl /users/login/dev get a token the same way
65
77
  // every other command does, whatever credential the config holds.
@@ -70,6 +82,7 @@ async function server(argv) {
70
82
  if (entity === 'raw') return await handleRaw(dapi, verb, rest, argv, output);
71
83
  if (entity === 'describe') return await handleDescribe(dapi, verb ?? rest[0], output);
72
84
  if (entity === 'healthcheck') return await handleHealthcheck(dapi, argv, output);
85
+ if (entity === 'status') return await handleStatus(dapi, output);
73
86
  if (entity === 'sync') return await handleSync(dapi, verb, rest, argv, output);
74
87
  if (entity === 'functions' && verb === 'run') return await handleFuncRun(dapi, rest, argv, output);
75
88
  if (entity === 'functions' && verb === 'list') return await handleFunctionsList(dapi, argv, limit, offset, filter, output);
@@ -178,8 +191,15 @@ async function server(argv) {
178
191
  });
179
192
  process.exitCode = 1;
180
193
  return true;
194
+ } finally {
195
+ if (logId) await (0, _serverObserve.printRequestLog)(dapi, logId, output);
181
196
  }
182
197
  }
198
+
199
+ /** `--debug` is not a declared boolean (grok publish reads it too), so it can arrive as a string. */
200
+ function isDebug(argv) {
201
+ return argv.debug !== undefined && argv.debug !== false && argv.debug !== 'false';
202
+ }
183
203
  async function handleFilesPut(dapi, rest, output) {
184
204
  const [localPath, remotePath] = rest;
185
205
  if (!localPath || !remotePath) {
@@ -592,6 +612,39 @@ async function handleHealthcheck(dapi, argv, output) {
592
612
  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)');
593
613
  return true;
594
614
  }
615
+ async function handleStatus(dapi, output) {
616
+ const [info, extended] = await Promise.all([dapi.serverStatus(), dapi.serverStatus(true)]);
617
+ if (output === 'json') {
618
+ (0, _serverOutput.printOutput)({
619
+ server: info,
620
+ extended
621
+ }, output);
622
+ return true;
623
+ }
624
+ if (output === 'quiet') {
625
+ console.log(info?.Version ?? '');
626
+ return true;
627
+ }
628
+ const http = info?.['Http Server'];
629
+ const fields = {
630
+ ...info,
631
+ 'Http Server': undefined
632
+ };
633
+ const width = Math.max(...Object.keys(fields).map(k => k.length)) + 2;
634
+ for (const [k, v] of Object.entries(fields)) if (v !== undefined) console.log(`${(k + ':').padEnd(width)}${v ?? ''}`);
635
+ if (http) console.log(`${'Http Server:'.padEnd(width)}total ${http.total}, active ${http.active}, idle ${http.idle}, closing ${http.closing}`);
636
+ // Non-admins get the plain count back; admins get {"METHOD url": inFlight}.
637
+ const active = extended?.['Http Server']?.active;
638
+ if (active && typeof active === 'object') {
639
+ console.log('');
640
+ const rows = Object.entries(active).map(([request, count]) => ({
641
+ request,
642
+ count
643
+ }));
644
+ if (rows.length) (0, _serverOutput.printOutput)(rows, output);else console.log('(no requests in flight)');
645
+ }
646
+ return true;
647
+ }
595
648
  async function handleDescribe(dapi, entityType, output) {
596
649
  if (!entityType) {
597
650
  (0, _serverOutput.printError)(new Error('Usage: grok s describe <entity|type> e.g. grok s describe connections, grok s describe Project'));
@@ -611,9 +664,11 @@ async function handleDescribe(dapi, entityType, output) {
611
664
  return true;
612
665
  }
613
666
  async function handleFuncRun(dapi, rest, argv, output) {
614
- let funcName = rest[0];
667
+ // `--debug 'Pkg:f()'` hands the call to --debug.
668
+ let funcName = rest[0] ?? (typeof argv.debug === 'string' ? argv.debug : undefined);
669
+ const debug = isDebug(argv);
615
670
  if (!funcName) {
616
- (0, _serverOutput.printError)(new Error('Usage: grok s functions run <Name:function(args...)> [--json params.json]'));
671
+ (0, _serverOutput.printError)(new Error('Usage: grok s functions run <Name:function(args...)> [--json params.json] [--debug]'));
617
672
  return false;
618
673
  }
619
674
  let params = {};
@@ -631,7 +686,7 @@ async function handleFuncRun(dapi, rest, argv, output) {
631
686
  params = parsed.params;
632
687
  if (Object.keys(params).some(k => /^\d+$/.test(k))) params = (0, _nodeDapi.mapPositionalParams)(params, (await dapi.functions.find(funcName))?.parameterInfos, funcName);
633
688
  }
634
- const result = await dapi.functions.run(funcName, params);
689
+ const result = await dapi.functions.run(funcName, params, debug);
635
690
  (0, _serverOutput.printOutput)(result, output);
636
691
  return true;
637
692
  }
@@ -927,6 +982,7 @@ Manage a Datagrok server from the command line.
927
982
  Entities:
928
983
  users, groups, functions, connections, queries, scripts, packages, reports, files, tables
929
984
  (plus domains, shares, batch, raw, describe, healthcheck, sync, pull/push/migrate/diff/bundle below)
985
+ observe (alias o): problems, problem rules, logger settings, errors, timeline, cloud logs; grok s observe prints its verbs
930
986
 
931
987
  Verbs:
932
988
  list List entities (--filter, --limit, --offset)
@@ -946,6 +1002,8 @@ Special commands:
946
1002
  grok s raw <METHOD> <path> [--json f | --data j] Any API endpoint; path is API-relative (/users/current), /api prefix optional
947
1003
  grok s describe <entity|type> Fields of an entity type (registry record + a live sample)
948
1004
  grok s healthcheck [--module <name>] Check server + per-module health
1005
+ grok s status Server build, uptime, DB pool and HTTP connections
1006
+ (/info/server); admins also see requests in flight
949
1007
  grok s shares add <entity> <group>[,<group>...] [--access View|Edit]
950
1008
  Share an entity with one or more groups
951
1009
  grok s shares list <entity-id-or-name> List who an entity is shared with
@@ -1050,6 +1108,13 @@ Options:
1050
1108
  --offset <n> Start offset (default: 0)
1051
1109
  -r, --recursive Recursive (for files list)
1052
1110
  --verbose Print the stack of a runtime failure, not just its message
1111
+ --log [<spec>] Keep what this command's requests log at <spec>, then collect it from
1112
+ every server instance and print it to stderr: verbose (default),
1113
+ standard, levels (error, warning, info, debug, audit, usage) and debug
1114
+ flags (db, storage, credentials, ..., or all), comma-separated. Only
1115
+ these requests are affected; no permission is needed
1116
+ --debug functions run: a debug call, as the query debugger runs it; its log (the
1117
+ query's stages) prints the same way
1053
1118
  --json <file> Read a JSON body from a file (save, functions run, batch, raw)
1054
1119
  --data '<json>' Inline JSON body for raw
1055
1120
  -O, --output-file Write table download to a file instead of stdout
@@ -1080,6 +1145,8 @@ Examples:
1080
1145
  grok s connections test --json conn.json
1081
1146
  grok s functions run 'Chem:smilesToMw("ccc")'
1082
1147
  grok s functions run Chem:test --json params.json
1148
+ grok s functions run 'MyPkg:MyQuery()' --debug # the query's own log
1149
+ grok s functions run UsageAnalysis:Groups --log verbose,db # and every SQL statement it ran
1083
1150
  grok s functions list --type script --language python --limit 20
1084
1151
  grok s functions list --package Chem --type query
1085
1152
  grok s functions list --package PowerPack --type package --filter 'name contains "grid"'
@@ -0,0 +1,458 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.src = src;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _child_process = require("child_process");
11
+ var color = _interopRequireWildcard(require("../utils/color-utils"));
12
+ 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); }
13
+ const arity = {
14
+ clone: [0, Infinity],
15
+ add: [1, Infinity],
16
+ sparse: [2, Infinity],
17
+ switch: [0, 0],
18
+ status: [0, 0],
19
+ pull: [0, 0],
20
+ at: [1, 1],
21
+ bump: [0, Infinity],
22
+ wt: [3, Infinity],
23
+ doctor: [0, 0]
24
+ };
25
+ const env = {
26
+ ...process.env,
27
+ ...(process.stdin.isTTY ? {} : {
28
+ GIT_TERMINAL_PROMPT: '0'
29
+ })
30
+ };
31
+ for (const key of ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_PREFIX']) delete env[key];
32
+ let explain = false;
33
+ const quote = s => /^[\w@%+=:,./-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
34
+ function git(dir, args) {
35
+ const r = (0, _child_process.spawnSync)('git', ['-C', dir, ...args], {
36
+ encoding: 'utf-8',
37
+ env,
38
+ maxBuffer: 64 * 1024 * 1024
39
+ });
40
+ return {
41
+ ok: r.status === 0,
42
+ fatal: r.status === 128,
43
+ out: (r.stdout ?? '').trim(),
44
+ err: (r.stderr ?? '').trim()
45
+ };
46
+ }
47
+ function step(dir, args) {
48
+ const rel = _path.default.relative(process.cwd(), dir);
49
+ console.log(`$ git ${rel ? `-C ${quote(rel)} ` : ''}${args.map(quote).join(' ')}`);
50
+ return explain || (0, _child_process.spawnSync)('git', ['-C', dir, ...args], {
51
+ stdio: 'inherit',
52
+ env
53
+ }).status === 0;
54
+ }
55
+ function findRoot() {
56
+ let dir = git(process.cwd(), ['rev-parse', '--show-toplevel']);
57
+ if (!dir.ok) throw new Error(dir.err);
58
+ for (let up; (up = git(dir.out, ['rev-parse', '--show-superproject-working-tree'])).out;) dir = up;
59
+ return git(dir.out, ['ls-files', '--', '.gitmodules']).out ? dir.out : undefined;
60
+ }
61
+ function children(dir) {
62
+ if (!git(dir, ['ls-files', '--', '.gitmodules']).out) return [];
63
+ const file = _fs.default.existsSync(_path.default.join(dir, '.gitmodules')) ? ['-f', '.gitmodules'] : ['--blob', ':./.gitmodules'];
64
+ const r = git(dir, ['config', ...file, '--get-regexp', '^submodule\\..*\\.(path|url|branch)$']);
65
+ if (r.fatal) throw new Error(`${_path.default.join(dir, '.gitmodules')}: ${r.err}`);
66
+ const mods = new Map();
67
+ for (const line of r.out.split('\n')) {
68
+ const m = /^submodule\.(.+)\.(path|url|branch) (.*)$/.exec(line);
69
+ if (m && !m[3].startsWith('-')) mods.set(m[1], {
70
+ ...mods.get(m[1]),
71
+ [m[2]]: m[3]
72
+ });
73
+ }
74
+ const links = new Set(git(dir, ['ls-files', '-z', '--stage']).out.split('\0').filter(l => l.startsWith('160000 ')).map(l => l.slice(l.indexOf('\t') + 1)));
75
+ return [...mods].filter(([, c]) => links.has(c.path) && c.url).map(([name, c]) => ({
76
+ name,
77
+ path: c.path,
78
+ url: c.url,
79
+ branch: c.branch,
80
+ dir: _path.default.join(dir, c.path)
81
+ }));
82
+ }
83
+ const cloned = dir => _fs.default.existsSync(_path.default.join(dir, '.git')) && (_fs.default.statSync(_path.default.join(dir, '.git')).isFile() || !git(dir, ['rev-parse', '--show-prefix']).out);
84
+ const empty = dir => !_fs.default.existsSync(dir) || !_fs.default.readdirSync(dir).length;
85
+ function* repos(dir, c, owner = dir) {
86
+ yield {
87
+ dir,
88
+ branch: c?.branch,
89
+ c,
90
+ owner
91
+ };
92
+ for (const k of children(dir).filter(x => cloned(x.dir))) yield* repos(k.dir, k, dir);
93
+ }
94
+ function state(dir) {
95
+ const r = git(dir, ['status', '--porcelain=v2', '--branch', '--untracked-files=no', '--ignore-submodules=all']);
96
+ const head = /^# branch\.head (.*)$/m.exec(r.out)?.[1] ?? '';
97
+ return {
98
+ error: r.ok ? '' : r.err.split('\n')[0],
99
+ branch: head === '(detached)' ? '' : head,
100
+ upstream: /^# branch\.ab /m.test(r.out),
101
+ ahead: Number(/^# branch\.ab \+(\d+)/m.exec(r.out)?.[1] ?? 0),
102
+ changed: r.out.split('\n').filter(l => l && !l.startsWith('#')).length
103
+ };
104
+ }
105
+ const orphaned = dir => !!git(dir, ['rev-list', '-n1', 'HEAD', '--not', '--branches', '--remotes', '--tags']).out;
106
+ const label = (root, dir) => _path.default.relative(root, dir) || '(umbrella)';
107
+ const gitC = dir => `git -C ${quote(_path.default.relative(process.cwd(), dir) || '.')}`;
108
+ function locate(root, rel) {
109
+ const chain = [];
110
+ for (let dir = root, rest = rel.replace(/\/+$/, '');;) {
111
+ const c = children(dir).find(x => rest === x.path || rest.startsWith(x.path + '/'));
112
+ if (!c) return undefined;
113
+ chain.push(c);
114
+ if (rest === c.path) return chain;
115
+ [dir, rest] = [c.dir, rest.slice(c.path.length + 1)];
116
+ }
117
+ }
118
+ const modules = (owner, c) => _path.default.resolve(owner, git(owner, ['rev-parse', '--git-path', `modules/${c.name}`]).out);
119
+ function rollback(owner, c, made) {
120
+ if (cloned(c.dir) || empty(c.dir)) step(owner, ['submodule', 'deinit', '--force', '--', c.path]);
121
+ step(owner, ['config', `submodule.${c.name}.active`, 'false']);
122
+ if (made) _fs.default.rmSync(made, {
123
+ recursive: true,
124
+ force: true
125
+ });
126
+ }
127
+ function cloneChild(root, owner, c, failed) {
128
+ if (cloned(c.dir)) return true;
129
+ const gitdir = modules(owner, c);
130
+ const made = _fs.default.existsSync(gitdir) ? undefined : gitdir;
131
+ if (!step(owner, ['submodule', 'update', '--init', '--require-init', '--checkout', '--filter=blob:none', '--', c.path])) {
132
+ rollback(owner, c, made);
133
+ failed.push(label(root, c.dir));
134
+ return false;
135
+ }
136
+ if (c.branch && !step(c.dir, ['switch', c.branch])) failed.push(label(root, c.dir));
137
+ if (_fs.default.existsSync(_path.default.join(c.dir, '.gitmodules'))) step(c.dir, ['config', 'push.recurseSubmodules', 'check']);
138
+ return true;
139
+ }
140
+ function cloneTree(root, owner, c, failed) {
141
+ const fresh = !cloned(c.dir);
142
+ if (!cloneChild(root, owner, c, failed) || !git(c.dir, ['ls-files', '--', '.gitmodules']).out) return;
143
+ const cur = fresh ? c.branch : git(c.dir, ['branch', '--show-current']).out;
144
+ if (cur === c.branch || !cur && git(c.dir, ['rev-parse', 'HEAD']).out === git(owner, ['rev-parse', `:${c.path}`]).out) children(c.dir).forEach(k => cloneTree(root, c.dir, k, failed));
145
+ }
146
+ function addSpec(root, dir, spec, failed) {
147
+ const kids = children(dir);
148
+ const parent = kids.find(c => spec.startsWith(c.path + '/'));
149
+ if (parent) {
150
+ return !cloneChild(root, dir, parent, failed) || explain && !cloned(parent.dir) || addSpec(root, parent.dir, spec.slice(parent.path.length + 1), failed);
151
+ }
152
+ const listed = git(dir, ['ls-files', '-z', '--', spec]).out.split('\0');
153
+ const targets = kids.filter(c => listed.includes(c.path));
154
+ targets.forEach(c => cloneTree(root, dir, c, failed));
155
+ return targets.length > 0;
156
+ }
157
+ function add(root, sel) {
158
+ const specs = (sel.length ? sel : ['default']).flatMap(s => {
159
+ const profile = git(root, ['config', '-f', '.gitprofiles', '--get-all', `profile.${s}.path`]);
160
+ if (profile.fatal) throw new Error(`${_path.default.join(root, '.gitprofiles')}: ${profile.err}`);
161
+ return profile.ok ? profile.out.split('\n') : sel.length ? [s] : ['.'];
162
+ });
163
+ const failed = [];
164
+ const unknown = specs.map(s => s.replace(/^\.\//, '').replace(/\/+$/, '')).filter(s => !addSpec(root, root, s, failed));
165
+ unknown.forEach(s => color.error(`${s}: no profile or submodule by that name`));
166
+ if (failed.length) color.error(`Failed (see the git errors above): ${failed.join(', ')}`);
167
+ return !unknown.length && !failed.length;
168
+ }
169
+ function clone(sel, url, dir) {
170
+ if (!step(process.cwd(), ['clone', '-c', 'push.recurseSubmodules=check', '--filter=blob:none', '--', url, dir])) return false;
171
+ if (!explain) return add(dir, sel);
172
+ console.log(`# then, in ${dir}: grok src add ${sel.join(' ') || '(profile "default", else every child)'}`);
173
+ return true;
174
+ }
175
+ function sparse(root, rel, globs) {
176
+ const chain = locate(root, rel);
177
+ if (!chain) return color.error(`${rel}: not a submodule, or its parent is not cloned`), false;
178
+ const c = chain[chain.length - 1];
179
+ const owner = chain.length > 1 ? chain[chain.length - 2].dir : root;
180
+ const absorb = ['submodule', 'absorbgitdirs', '--', c.path];
181
+ const set = ['sparse-checkout', 'set', '--no-cone', ...globs];
182
+ if (cloned(c.dir)) return step(owner, absorb) && step(c.dir, set);
183
+ const gitdir = modules(owner, c);
184
+ if (_fs.default.existsSync(gitdir)) return color.error(`${gitdir} exists from an earlier clone: grok src add ${rel}, then sparse again`), false;
185
+ const ok = step(owner, ['submodule', 'init', '--', c.path]) && step(owner, ['clone', '--filter=blob:none', '--no-checkout', '--', git(owner, ['config', `submodule.${c.name}.url`]).out || '<url>', c.path]) && step(owner, absorb) && step(c.dir, set) && step(c.dir, ['checkout', c.branch ?? git(owner, ['rev-parse', `:${c.path}`]).out, '--']);
186
+ if (!ok) rollback(owner, c, gitdir);
187
+ return ok;
188
+ }
189
+ function switchAll(root) {
190
+ let ok = true;
191
+ for (const {
192
+ dir,
193
+ branch
194
+ } of repos(root)) {
195
+ const s = state(dir);
196
+ if (!branch || s.branch === branch) continue;
197
+ if (s.error || s.changed || !s.branch && orphaned(dir)) {
198
+ color.warn(`${label(root, dir)}: ${s.error || 'uncommitted changes or commits on no branch'}, not switched to ${branch}`);
199
+ ok = false;
200
+ } else ok = step(dir, ['switch', branch]) && ok;
201
+ }
202
+ return ok;
203
+ }
204
+ function status(root) {
205
+ const head = ['REPO', 'BRANCH', 'UNPUSHED', 'CHANGED'];
206
+ const rows = [...repos(root)].map(({
207
+ dir
208
+ }) => {
209
+ const s = state(dir);
210
+ const unpushed = !s.branch ? '' : !s.upstream ? 'no upstream' : s.ahead ? `${s.ahead}` : '';
211
+ return [label(root, dir), s.error ? '(error: grok src doctor)' : s.branch || '(detached)', unpushed, s.changed ? `${s.changed}` : ''];
212
+ });
213
+ const widths = head.map((_, i) => Math.max(...[head, ...rows].map(r => r[i].length)));
214
+ for (const r of [head, ...rows]) console.log(r.map((x, i) => x.padEnd(widths[i])).join(' ').trimEnd());
215
+ return !rows.some(r => r[1].startsWith('(error: '));
216
+ }
217
+ function pull(root) {
218
+ const skipped = [];
219
+ const failed = [];
220
+ for (const {
221
+ dir,
222
+ branch
223
+ } of repos(root)) {
224
+ const s = state(dir);
225
+ if (s.error) failed.push(label(root, dir));else if (!s.branch || !s.upstream) {
226
+ if (branch || dir === root) skipped.push(label(root, dir));
227
+ } else if (!step(dir, ['pull', '--ff-only', '--no-recurse-submodules'])) failed.push(label(root, dir));
228
+ }
229
+ if (skipped.length) color.warn(`Skipped (detached or no upstream): ${skipped.join(', ')}`);
230
+ if (failed.length) color.error(`Not pulled: ${failed.join(', ')}`);
231
+ return !failed.length;
232
+ }
233
+ function at(root, ref) {
234
+ const all = [...repos(root)];
235
+ const loose = all.flatMap(({
236
+ c,
237
+ owner
238
+ }) => c && !git(owner, ['config', `submodule.${c.name}.url`]).ok ? [`${gitC(owner)} submodule init -- ${quote(c.path)}`] : []);
239
+ if (loose.length) return color.error(`Cloned but not initialized, so git would leave them off the pins: ${loose.join('; ')}`), false;
240
+ const blocked = all.filter(({
241
+ dir
242
+ }) => {
243
+ const s = state(dir);
244
+ return s.error || s.changed || !s.branch && orphaned(dir) || !git(dir, ['diff', '--cached', '--quiet', '--ignore-submodules=none']).ok;
245
+ }).map(({
246
+ dir
247
+ }) => label(root, dir));
248
+ if (blocked.length) return color.error(`Uncommitted changes or commits on no branch in ${blocked.join(', ')}: commit, stash or branch first`), false;
249
+ return step(root, ['checkout', ref, '--']) && step(root, ['submodule', 'update', '--recursive', '--checkout']);
250
+ }
251
+ function bump(root, rels) {
252
+ const failed = [];
253
+ const owners = new Map();
254
+ let checked = 0;
255
+ const skipped = [];
256
+ const visit = (c, owner) => {
257
+ let o = owners.get(owner);
258
+ if (!o) {
259
+ const staged = !git(owner, ['diff', '--cached', '--quiet', '--ignore-submodules=none']).ok;
260
+ const detached = !state(owner).branch;
261
+ if (staged || detached && rels.length) failed.push(`${label(root, owner)} (${staged ? 'staged changes' : 'not on a branch'})`);else if (detached) skipped.push(label(root, owner));
262
+ owners.set(owner, o = {
263
+ ok: !staged && !detached,
264
+ links: []
265
+ });
266
+ }
267
+ const name = label(root, c.dir);
268
+ const branch = c.branch === '.' ? state(owner).branch : c.branch;
269
+ if (!o.ok || !branch) {
270
+ if (o.ok && rels.length) failed.push(`${name} (no branch in .gitmodules)`);
271
+ return;
272
+ }
273
+ if (git(c.dir, ['config', 'remote.origin.url']).out !== git(owner, ['config', `submodule.${c.name}.url`]).out) return failed.push(`${name} (origin differs from .gitmodules: grok src doctor --fix)`);
274
+ checked++;
275
+ const ref = `refs/remotes/origin/${branch}`;
276
+ if (!step(c.dir, ['fetch', '--no-recurse-submodules', 'origin', `+refs/heads/${branch}:${ref}`])) return failed.push(name);
277
+ const tip = explain ? git(c.dir, ['ls-remote', 'origin', `refs/heads/${branch}`]).out.split('\t')[0] : git(c.dir, ['rev-parse', '--verify', '-q', `${ref}^{commit}`]).out;
278
+ const pin = git(owner, ['rev-parse', `HEAD:${c.path}`]).out;
279
+ if (!tip) return failed.push(`${name} (origin has no ${branch})`);
280
+ if (tip !== pin && !explain && !git(c.dir, ['merge-base', '--is-ancestor', pin, tip]).ok) return failed.push(`${name} (the pin is not on origin/${branch}; move it by hand)`);
281
+ const s = state(c.dir);
282
+ const head = git(c.dir, ['rev-parse', 'HEAD']).out;
283
+ const moved = head === tip || !s.changed && (s.branch === branch ? step(c.dir, ['merge', '--ff-only', ref]) : !s.branch && git(c.dir, ['merge-base', '--is-ancestor', head, tip]).ok && step(c.dir, ['checkout', '--detach', tip]));
284
+ if (tip === pin) return;
285
+ if (!moved) color.warn(`${name}: checkout left as it is (local changes, or not on ${branch} or the pin); pinned to origin/${branch}`);
286
+ o.links.push({
287
+ path: c.path,
288
+ tip
289
+ });
290
+ };
291
+ const wanted = [...new Set(rels.map(r => r.replace(/\\/g, '/').replace(/^(\.\/)+/, '').replace(/\/+$/, '')))];
292
+ for (const rel of wanted.sort((a, b) => a.split('/').length - b.split('/').length)) {
293
+ const chain = locate(root, rel);
294
+ const c = chain?.[chain.length - 1];
295
+ if (!chain || !c || !cloned(c.dir)) failed.push(chain ? `${rel} (not cloned: grok src add ${rel})` : `${rel} (not a submodule)`);else visit(c, chain.length > 1 ? chain[chain.length - 2].dir : root);
296
+ }
297
+ if (!rels.length) {
298
+ for (const {
299
+ c,
300
+ owner
301
+ } of repos(root)) {
302
+ if (c) visit(c, owner);
303
+ }
304
+ }
305
+ let bumped = false;
306
+ for (const [owner, {
307
+ links
308
+ }] of owners) {
309
+ if (!links.length) continue;
310
+ bumped = true;
311
+ const paths = links.map(l => l.path);
312
+ if (step(owner, ['update-index', ...links.flatMap(l => ['--cacheinfo', `160000,${l.tip},${l.path}`])]) && step(owner, ['commit', '-m', `Bump ${paths.join(', ')}`])) {
313
+ if (owner !== root) console.log(`${label(root, owner)}: push it, then grok src bump ${_path.default.relative(root, owner).split(_path.default.sep).join('/')}`);
314
+ } else {
315
+ if (!explain) git(owner, ['reset', '-q', '--', ...paths]);
316
+ failed.push(label(root, owner));
317
+ }
318
+ }
319
+ if (skipped.length) color.warn(`Not on a branch, so their children were not bumped: ${skipped.join(', ')}`);else if (!bumped && !failed.length) console.log(checked ? 'Every pin is at the tip of its branch.' : 'No child with a branch to bump.');
320
+ if (failed.length) color.error(`Not bumped: ${failed.join(', ')}`);
321
+ return !failed.length;
322
+ }
323
+ function wtAdd(root, name, rels, destArg) {
324
+ const dest = _path.default.resolve(destArg ?? `${root}-worktrees/${name}`);
325
+ const dirs = new Set([root]);
326
+ for (const rel of rels) {
327
+ const chain = locate(root, rel);
328
+ if (!chain || !cloned(chain[chain.length - 1].dir)) return color.error(`${rel}: not a cloned submodule`), false;
329
+ chain.forEach(c => dirs.add(c.dir));
330
+ }
331
+ const todo = [...dirs].map(dir => ({
332
+ dir,
333
+ to: _path.default.join(dest, _path.default.relative(root, dir))
334
+ }));
335
+ for (const {
336
+ dir,
337
+ to
338
+ } of todo) {
339
+ const elsewhere = git(dir, ['for-each-ref', '--format=%(worktreepath)', `refs/heads/${name}`]).out;
340
+ if (cloned(to) ? git(to, ['branch', '--show-current']).out !== name : elsewhere) {
341
+ const where = elsewhere && (git(dir, [`--git-dir=${elsewhere}`, 'rev-parse', '--show-toplevel']).out || elsewhere);
342
+ return color.error(cloned(to) ? `${to} exists and is not on ${name}` : `${label(root, dir)}: ${name} is checked out in ${where} (if that is gone: ${gitC(dir)} worktree prune)`), false;
343
+ }
344
+ }
345
+ for (const {
346
+ dir,
347
+ to
348
+ } of todo.filter(t => !cloned(t.to))) {
349
+ const local = git(dir, ['show-ref', '--verify', '--quiet', `refs/heads/${name}`]).ok;
350
+ const remote = !local && git(dir, ['show-ref', '--verify', '--quiet', `refs/remotes/origin/${name}`]).ok;
351
+ const start = local ? ['--', to, name] : remote ? ['--track', '-b', name, '--', to, `origin/${name}`] : ['-b', name, '--', to];
352
+ if (!step(dir, ['worktree', 'add', ...start])) return false;
353
+ const own = explain ? '<worktree gitdir>/config.worktree' : git(to, ['--work-tree=.', 'rev-parse', '--path-format=absolute', '--git-path', 'config.worktree']).out;
354
+ if (dir !== root && !step(dir, ['config', '-f', own, 'core.worktree', to])) return false;
355
+ }
356
+ console.log(`Worktrees: ${dest}`);
357
+ return true;
358
+ }
359
+ function doctor(root, fix) {
360
+ const problems = [];
361
+ const synced = [];
362
+ if (process.platform === 'win32' && git(root, ['config', '--type=bool', 'core.longpaths']).out !== 'true') problems.push('core.longpaths is not set: git config --global core.longpaths true');
363
+ for (const {
364
+ dir,
365
+ branch,
366
+ c,
367
+ owner
368
+ } of repos(root)) {
369
+ const s = state(dir);
370
+ const name = label(root, dir);
371
+ const at = gitC(dir);
372
+ if (s.error) {
373
+ problems.push(`${name}: ${s.error}`);
374
+ continue;
375
+ }
376
+ const orphan = !s.branch && orphaned(dir);
377
+ if (!s.branch && (branch || orphan)) problems.push(`${name}: detached HEAD: ${orphan ? `${at} switch -c <name>` : 'grok src switch'}`);
378
+ if (s.ahead) problems.push(`${name}: ${s.ahead} unpushed commit(s): ${at} push`);
379
+ const local = s.branch && !s.upstream ? Number(git(dir, ['rev-list', '--count', 'HEAD', '--not', '--remotes']).out) : 0;
380
+ if (local) problems.push(`${name}: ${local} commit(s) on no remote: ${at} push -u origin ${s.branch}`);
381
+ if (!c) continue;
382
+ const base = git(owner, ['config', 'remote.origin.url']).out.replace(/\/+$/, '');
383
+ const url = c.url.startsWith('../') ? base && base.replace(/[^/:]+$/, '') + c.url.slice(3) : c.url;
384
+ if (!url) {
385
+ problems.push(`${name}: ${label(root, owner)} has no origin remote`);
386
+ continue;
387
+ }
388
+ const known = git(owner, ['config', `submodule.${c.name}.url`]);
389
+ if (known.out !== url || git(dir, ['config', 'remote.origin.url']).out !== url) {
390
+ problems.push(`${name}: URL differs from .gitmodules (${url}): grok src doctor --fix`);
391
+ synced.push(!fix || (known.ok || step(owner, ['submodule', 'init', '--', c.path])) && step(owner, ['config', `submodule.${c.name}.url`, url]) && step(dir, ['remote', 'set-url', 'origin', url]));
392
+ }
393
+ }
394
+ if (fix && !explain && synced.length && synced.every(Boolean)) return doctor(root, false);
395
+ problems.forEach(p => console.log(p));
396
+ if (!problems.length) color.success('No problems found.');
397
+ return !problems.length || fix && explain && synced.length > 0;
398
+ }
399
+ function run(cmd, rest, argv) {
400
+ if (cmd === 'clone') {
401
+ const url = argv.url ?? 'git@github.com:datagrok-ai/datagrok.git';
402
+ return clone(rest, url, _path.default.resolve(argv.dir ?? _path.default.basename(url).replace(/\.git$/, '')));
403
+ }
404
+ const root = findRoot();
405
+ if (!root) return color.error('Not inside an umbrella checkout (a repo with .gitmodules)'), false;
406
+ const [gitDir, commonDir] = git(root, ['rev-parse', '--path-format=absolute', '--git-dir', '--git-common-dir']).out.split('\n');
407
+ if (gitDir !== commonDir && (['add', 'sparse', 'switch', 'at'].includes(cmd) || cmd === 'doctor' && argv.fix)) return color.error(`grok src ${cmd} changes what the main checkout shares (config, branches): run it in the main checkout`), false;
408
+ switch (cmd) {
409
+ case 'add':
410
+ return add(root, rest);
411
+ case 'sparse':
412
+ return sparse(root, rest[0], rest.slice(1));
413
+ case 'switch':
414
+ return switchAll(root);
415
+ case 'status':
416
+ return status(root);
417
+ case 'pull':
418
+ return pull(root);
419
+ case 'at':
420
+ return at(root, rest[0]);
421
+ case 'bump':
422
+ return bump(root, rest);
423
+ case 'wt':
424
+ return wtAdd(root, rest[1], rest.slice(2), argv.dir);
425
+ default:
426
+ return doctor(root, !!argv.fix);
427
+ }
428
+ }
429
+ async function src(argv) {
430
+ const [cmd, ...rest] = argv._.slice(1);
431
+ const options = Object.keys(argv).filter(k => !['_', 'explain', 'fix', 'url', 'dir'].includes(k));
432
+ if (options.length || [argv.url, argv.dir].some(v => v !== undefined && (typeof v !== 'string' || !v))) {
433
+ color.error(options.length ? `Unknown option --${options[0]}` : '--url and --dir take one value');
434
+ return false;
435
+ }
436
+ if (!cmd) {
437
+ console.log((await Promise.resolve().then(() => _interopRequireWildcard(require('./help')))).help.src);
438
+ return true;
439
+ }
440
+ if (!Object.hasOwn(arity, cmd) || rest.length < arity[cmd][0] || rest.length > arity[cmd][1] || cmd === 'wt' && rest[0] !== 'add') {
441
+ color.error(`Unknown command or wrong arguments: grok src ${cmd}`);
442
+ return false;
443
+ }
444
+ const [major, minor] = (/(\d+)\.(\d+)/.exec(git('.', ['--version']).out) ?? []).slice(1).map(Number);
445
+ if (!(major * 100 + minor >= 236)) {
446
+ color.error('grok src needs git 2.36 or newer');
447
+ process.exitCode = 1;
448
+ return true;
449
+ }
450
+ explain = !!argv.explain;
451
+ try {
452
+ if (!run(cmd, rest, argv)) process.exitCode = 1;
453
+ } catch (e) {
454
+ color.error(e instanceof Error ? e.message : String(e));
455
+ process.exitCode = 1;
456
+ }
457
+ return true;
458
+ }