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.
@@ -3,13 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.NodeUsersDataSource = exports.NodeTablesDataSource = exports.NodeSharesDataSource = exports.NodePackagesDataSource = exports.NodeHttpDataSource = exports.NodeGroupsDataSource = exports.NodeFuncsDataSource = exports.NodeFilesDataSource = exports.NodeDomainsDataSource = exports.NodeDapi = exports.NodeConnectionsDataSource = exports.NodeApiClient = exports.InternalDataSource = void 0;
6
+ exports.UUID_RE = exports.NodeUsersDataSource = exports.NodeTablesDataSource = exports.NodeSharesDataSource = exports.NodePackagesDataSource = exports.NodeHttpDataSource = exports.NodeGroupsDataSource = exports.NodeFuncsDataSource = exports.NodeFilesDataSource = exports.NodeDomainsDataSource = exports.NodeDapi = exports.NodeConnectionsDataSource = exports.NodeApiClient = exports.InternalDataSource = void 0;
7
7
  exports.apiPath = apiPath;
8
8
  exports.buildQuery = buildQuery;
9
9
  exports.ensureBodyId = ensureBodyId;
10
10
  exports.mapPositionalParams = mapPositionalParams;
11
11
  exports.parseDomainAddress = parseDomainAddress;
12
12
  exports.throwIfApiError = throwIfApiError;
13
+ exports.tryParseJson = tryParseJson;
13
14
  var _crypto = require("crypto");
14
15
  var _keypair = require("./keypair");
15
16
  /// Docs: [Grok Dapi](/docs/plans/grok-dapi/)
@@ -63,6 +64,17 @@ async function fetchOrRetry(url, opts, retriable, timeoutMs = setting('TIMEOUT',
63
64
  class NodeApiClient {
64
65
  /** Set by `createClient` when the run asked for an admin session, so a re-login restores it. */
65
66
  adminMode = false;
67
+ /**
68
+ * `--log`: one id for every request of the command, wherever the server runs it, and the logger settings
69
+ * its records are kept at until `GET /log/requests/<logId>` collects them.
70
+ */
71
+
72
+ get logHeaders() {
73
+ const headers = {};
74
+ if (this.logId) headers['datlas-log-id'] = this.logId;
75
+ if (this.logSpec) headers['datlas-log'] = this.logSpec;
76
+ return headers;
77
+ }
66
78
  constructor(baseUrl, token, devKey, privateKey) {
67
79
  this.baseUrl = baseUrl;
68
80
  this.token = token;
@@ -119,6 +131,7 @@ class NodeApiClient {
119
131
  headers: {
120
132
  'Authorization': this.token,
121
133
  'Content-Type': 'application/json',
134
+ ...this.logHeaders,
122
135
  ...headers
123
136
  }
124
137
  };
@@ -142,7 +155,7 @@ class NodeApiClient {
142
155
  return null;
143
156
  }
144
157
  const ct = res.headers.get('content-type') ?? '';
145
- return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text());
158
+ return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text(), res.headers);
146
159
  }
147
160
  get(path) {
148
161
  return this.request('GET', path);
@@ -164,13 +177,14 @@ class NodeApiClient {
164
177
  method: 'POST',
165
178
  headers: {
166
179
  'Authorization': this.token,
167
- 'Content-Type': contentType
180
+ 'Content-Type': contentType,
181
+ ...this.logHeaders
168
182
  },
169
183
  body: bytes
170
184
  }, false, BYTES_TIMEOUT_MS);
171
185
  if (!res.ok) await throwHttpError(res);
172
186
  const ct = res.headers.get('content-type') ?? '';
173
- return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text());
187
+ return throwIfApiError(ct.includes('application/json') ? await res.json() : await res.text(), res.headers);
174
188
  }
175
189
 
176
190
  /** POST a JSON body and read the response as raw bytes (d42 query results). */
@@ -179,7 +193,8 @@ class NodeApiClient {
179
193
  method: 'POST',
180
194
  headers: {
181
195
  'Authorization': this.token,
182
- 'Content-Type': 'application/json'
196
+ 'Content-Type': 'application/json',
197
+ ...this.logHeaders
183
198
  },
184
199
  body: JSON.stringify(body),
185
200
  signal: AbortSignal.timeout(BYTES_TIMEOUT_MS)
@@ -192,7 +207,8 @@ class NodeApiClient {
192
207
  async getBytes(path) {
193
208
  const res = await fetchOrRetry(`${this.baseUrl}${path}`, {
194
209
  headers: {
195
- 'Authorization': this.token
210
+ 'Authorization': this.token,
211
+ ...this.logHeaders
196
212
  }
197
213
  }, true, BYTES_TIMEOUT_MS);
198
214
  if (!res.ok) await throwHttpError(res);
@@ -236,13 +252,15 @@ async function throwHttpError(res) {
236
252
  * a JSON string when the handler returned text. Every response passes through here so a
237
253
  * missing entity, a rejected save or an unknown function throws instead of printing as data.
238
254
  */
239
- function throwIfApiError(payload) {
255
+ /** A user error answers HTTP 200 with the ApiError in the body and its status in the `api-error-code` header. */
256
+ function throwIfApiError(payload, headers) {
240
257
  const parsed = typeof payload === 'string' ? tryParseJson(payload) : payload;
241
258
  if (parsed?.['#type'] === 'ApiError') {
259
+ const code = headers?.get('api-error-code');
242
260
  const err = {
243
261
  error: parsed.message ?? 'Request failed',
244
262
  source: 'Server',
245
- errorCode: parsed.errorCode,
263
+ errorCode: parsed.errorCode ?? (code ? Number(code) : undefined),
246
264
  stackTrace: parsed.stackTrace,
247
265
  body: parsed
248
266
  };
@@ -388,7 +406,7 @@ class InternalDataSource {
388
406
  }
389
407
  }
390
408
  exports.InternalDataSource = InternalDataSource;
391
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
409
+ const UUID_RE = exports.UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
392
410
  class NodeGroupsDataSource extends NodeHttpDataSource {
393
411
  constructor(client) {
394
412
  super(client, 'groups', '/groups');
@@ -638,9 +656,11 @@ class NodeFuncsDataSource extends NodeHttpDataSource {
638
656
  constructor(client) {
639
657
  super(client, 'functions', '/log/funcs');
640
658
  }
641
- async run(name, params) {
659
+
660
+ /** [debug] runs it as a debug call, as the query debugger does; its log is kept with the command's `--log`. */
661
+ async run(name, params, debug = false) {
642
662
  const normalizedName = name.replace(':', '.');
643
- return this.client.post(`/public/v1/functions/${encodeURIComponent(normalizedName)}/call`, params ?? {});
663
+ return this.client.post(`/public/v1/functions/${encodeURIComponent(normalizedName)}/call${debug ? '?debug=true' : ''}`, params ?? {});
644
664
  }
645
665
 
646
666
  /** The public API has no DELETE for functions; scripts and queries go to their own routers. */
@@ -936,6 +956,10 @@ function parseDomainAddress(s, opts = {}) {
936
956
  if (opts.table === false && address.table) throw new Error(`'${str}' names a table; this command needs a schema`);
937
957
  return address;
938
958
  }
959
+ /** Every literal a `/domains/{schema}/{table}/{id}` route registers at the id position, the dot
960
+ * segments a proxy folds away, and the u2 draft sentinel — the Dart `DomainRowKey.reserved` list. */
961
+ const RESERVED_ROW_KEYS = ['access', 'aggregate', 'audit', 'batch', 'delete', 'facets', 'filters', 'query', 'update', 'version', 'watch', 'new', '.', '..'];
962
+
939
963
  /**
940
964
  * Client for entity-mapped domain tables (`/domains/...`, DomainsRouter). Rows are plain
941
965
  * JSON objects; the server validates, permission-checks and audits every write. Errors
@@ -950,6 +974,13 @@ class NodeDomainsDataSource {
950
974
  return `/domains/${encodeURIComponent(schema)}/${encodeURIComponent(table)}`;
951
975
  }
952
976
 
977
+ /** The row-id segment as the Dart `DomainRowKey` spells it (grok_shared domain_manifest_rules.dart):
978
+ * escaped, then percent-encoded once more so an external key's own `%2C` survives the proxy. */
979
+ row(schema, table, id) {
980
+ const escaped = RESERVED_ROW_KEYS.includes(id) || id.startsWith('~') ? `~${id}` : id;
981
+ return `${this.rows(schema, table)}/${encodeURIComponent(escaped)}`;
982
+ }
983
+
953
984
  /** Registered schemas with their tables (`GET /domains/schemas`); [text] is a smart filter. */
954
985
  schemas(text) {
955
986
  return this.client.get(`/domains/schemas${buildQuery({
@@ -1001,7 +1032,7 @@ class NodeDomainsDataSource {
1001
1032
  })}`);
1002
1033
  }
1003
1034
  rowAudit(schema, table, id) {
1004
- return this.client.get(`${this.rows(schema, table)}/${encodeURIComponent(id)}/audit`);
1035
+ return this.client.get(`${this.row(schema, table, id)}/audit`);
1005
1036
  }
1006
1037
  grants(entityId) {
1007
1038
  return this.client.get(`/domains/grants/${encodeURIComponent(entityId)}`);
@@ -1053,7 +1084,7 @@ class NodeDomainsDataSource {
1053
1084
  /** One row, or null when it does not exist or is not visible (the server answers 404). */
1054
1085
  async getRow(schema, table, id) {
1055
1086
  try {
1056
- return await this.client.get(`${this.rows(schema, table)}/${encodeURIComponent(id)}`);
1087
+ return await this.client.get(this.row(schema, table, id));
1057
1088
  } catch (err) {
1058
1089
  if (err?.apiError?.errorCode === 404) return null;
1059
1090
  throw err;
@@ -1073,10 +1104,10 @@ class NodeDomainsDataSource {
1073
1104
  values
1074
1105
  };
1075
1106
  if (version !== undefined) body.version = version;
1076
- return this.client.request('PATCH', `${this.rows(schema, table)}/${encodeURIComponent(id)}`, body);
1107
+ return this.client.request('PATCH', this.row(schema, table, id), body);
1077
1108
  }
1078
1109
  deleteRow(schema, table, id) {
1079
- return this.client.del(`${this.rows(schema, table)}/${encodeURIComponent(id)}`);
1110
+ return this.client.del(this.row(schema, table, id));
1080
1111
  }
1081
1112
 
1082
1113
  /** Soft-deletes up to [limit] (≤1000) matching rows in one transaction; `{deleted, hasMore}`. */
@@ -1138,6 +1169,9 @@ class NodeDapi {
1138
1169
  get reports() {
1139
1170
  return this.internal('/reports');
1140
1171
  }
1172
+ get problems() {
1173
+ return this.internal('/problems');
1174
+ }
1141
1175
  get files() {
1142
1176
  return new NodeFilesDataSource(this.client);
1143
1177
  }
@@ -1153,9 +1187,14 @@ class NodeDapi {
1153
1187
  internal(route) {
1154
1188
  return new InternalDataSource(this.client, route);
1155
1189
  }
1190
+
1191
+ /** `/info/server`; [extended] adds the in-flight requests per endpoint (admins only). */
1192
+ async serverStatus(extended = false) {
1193
+ const raw = await this.client.get(extended ? '/info/server?extended=true' : '/info/server');
1194
+ return typeof raw === 'string' ? JSON.parse(raw) : raw;
1195
+ }
1156
1196
  async serverInfo() {
1157
- const raw = await this.client.get('/info/server');
1158
- const info = typeof raw === 'string' ? JSON.parse(raw) : raw;
1197
+ const info = await this.serverStatus();
1159
1198
  return {
1160
1199
  version: info?.Version ?? '',
1161
1200
  commit: info?.Commit
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.createClient = createClient;
7
+ exports.logSpecOf = logSpecOf;
7
8
  var _nodeDapi = require("./node-dapi");
8
9
  var _keypair = require("./keypair");
9
10
  /**
@@ -13,11 +14,17 @@ var _keypair = require("./keypair");
13
14
  * flag into the token, and a dev key mints a fresh session per invocation, so it cannot outlive
14
15
  * the command or reach another session.
15
16
  */
16
- async function createClient(hostArg, admin = false) {
17
+ /** `--log` alone is `verbose`. */
18
+ function logSpecOf(argv) {
19
+ return argv.log === undefined || argv.log === false ? undefined : argv.log === true ? 'verbose' : String(argv.log);
20
+ }
21
+ async function createClient(hostArg, admin = false, log) {
17
22
  // Resolved from the alias the caller named, not from its URL: two aliases can point at the
18
23
  // same server, and only this side knows which one was asked for.
19
24
  const cred = (0, _keypair.getServerCredentials)(hostArg ?? '');
20
25
  const client = await _nodeDapi.NodeApiClient.login(cred.url, cred.key ?? '', cred.privateKey);
26
+ client.logId = log?.logId;
27
+ client.logSpec = log?.spec;
21
28
  if (!admin) return client;
22
29
  const token = (await client.post('/users/sessions/current/admin'))?.token;
23
30
  if (!token) throw new Error(`${cred.url} refused an admin session — the account behind this key cannot start one`);
@@ -16,6 +16,7 @@ exports.getToken = getToken;
16
16
  exports.getWebUrl = getWebUrl;
17
17
  exports.getWebUrlFromPage = getWebUrlFromPage;
18
18
  exports.isPackageOnServer = isPackageOnServer;
19
+ exports.isPackageU2Usage = isPackageU2Usage;
19
20
  exports.loadPackage = loadPackage;
20
21
  exports.loadPackages = loadPackages;
21
22
  exports.loadTestsList = loadTestsList;
@@ -159,6 +160,36 @@ function getDevKey(hostKey) {
159
160
  key
160
161
  };
161
162
  }
163
+ const U2_USAGE = 'u2 usage:';
164
+ /** The u2 usage mode a package built on u2 or ems is tested in: a misuse throws, unless `U2_STRICT` says otherwise
165
+ * (`report` logs each one as a `u2 usage:` line and the run does not fail on them). */
166
+ const U2_MODE = process.env.U2_STRICT ?? 'throw';
167
+
168
+ /** Whether a `u2 usage:` line the page logged fails the run: the package is tested with u2 strict. */
169
+ function failsOnU2Usage() {
170
+ return U2_MODE !== 'report' && usesU2(process.cwd());
171
+ }
172
+
173
+ /** Whether a `u2 usage:` line is the tested package's: a stack frame it carries is in the package's bundle
174
+ * (`…/files/<package>/…`), or it carries none to tell. A line whose frames are all in other bundles is another
175
+ * package's misuse, reported but not failed on. */
176
+ function isPackageU2Usage(line, packageName) {
177
+ const frames = line.match(/https?:\/\/[^\s()]+/g) ?? [];
178
+ const bundle = `/files/${packageName?.toLowerCase()}/`;
179
+ return packageName === undefined || frames.length === 0 || frames.some(f => f.toLowerCase().includes(bundle));
180
+ }
181
+ function usesU2(packageDir) {
182
+ try {
183
+ const json = JSON.parse(_fs.default.readFileSync(_path.default.join(packageDir, 'package.json'), 'utf-8'));
184
+ const deps = {
185
+ ...json.dependencies,
186
+ ...json.devDependencies
187
+ };
188
+ return '@datagrok-libraries/u2' in deps || '@datagrok-libraries/ems' in deps;
189
+ } catch {
190
+ return false;
191
+ }
192
+ }
162
193
  function appendUrlParams(url, extraParams) {
163
194
  if (!extraParams) return url;
164
195
  return url + (url.includes('?') ? '&' : '?') + extraParams;
@@ -194,6 +225,9 @@ async function getBrowserPage(puppeteer, params = defaultLaunchParameters, urlPa
194
225
  height: 1080
195
226
  });
196
227
  page.setDefaultNavigationTimeout(0);
228
+ if (usesU2(process.cwd())) await page.evaluateOnNewDocument(mode => {
229
+ globalThis.__U2_STRICT__ = mode;
230
+ }, U2_MODE);
197
231
  await page.goto(`${url}/oauth/`);
198
232
  await page.browser().setCookie({
199
233
  name: 'auth',
@@ -537,6 +571,16 @@ function printBrowsersResult(browserResult, verbose = false) {
537
571
  console.log('Skipped tests: ' + (browserResult?.skippedAmount ?? 0));
538
572
  console.log('Failed tests: ' + (browserResult?.failedAmount ?? 0));
539
573
  }
574
+ const own = browserResult.u2Usage?.filter(l => isPackageU2Usage(l, process.env.TARGET_PACKAGE)) ?? [];
575
+ const others = browserResult.u2Usage?.filter(l => !own.includes(l)) ?? [];
576
+ if (own.length) {
577
+ color.warn(failsOnU2Usage() ? `${own.length} u2 usage error(s) caught on the page (the run fails):` : `${own.length} u2 usage report(s), in report mode (the run does not fail on them):`);
578
+ for (const line of own) color.warn(` ${line}`);
579
+ }
580
+ if (others.length) {
581
+ color.warn(`${others.length} u2 usage line(s) from other packages' code (the run does not fail on them):`);
582
+ for (const line of others) color.warn(` ${line}`);
583
+ }
540
584
  if (browserResult.failed) {
541
585
  if (browserResult.verboseFailed === 'Package not found') color.fail('Tests not found');else color.fail('Tests failed.');
542
586
  } else color.success('Tests passed.');
@@ -860,6 +904,11 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
860
904
  // Subscribe to page console events for modern output formatting
861
905
  // On retry, old listeners were removed so we need to re-attach
862
906
  page.on('console', msg => reporter.onLine(msg.text()));
907
+ const u2Usage = [];
908
+ page.on('console', msg => {
909
+ const text = msg.text();
910
+ if (text.includes(U2_USAGE) && !u2Usage.includes(text)) u2Usage.push(text);
911
+ });
863
912
  const testingResults = await page.evaluate((testData, options) => {
864
913
  if (options.benchmark) window.DG.Test.isInBenchmark = true;
865
914
  if (options.reproduce) window.DG.Test.isReproducing = true;
@@ -918,12 +967,14 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
918
967
  // Return browser session for potential reuse
919
968
  return {
920
969
  ...testingResults,
970
+ failed: testingResults.failed || failsOnU2Usage() && u2Usage.some(l => isPackageU2Usage(l, process.env.TARGET_PACKAGE)),
921
971
  browserSession: browserOptions.keepBrowserOpen ? {
922
972
  browser,
923
973
  page,
924
974
  webUrl
925
975
  } : undefined,
926
- modernOutput: reporter.modernOutput
976
+ modernOutput: reporter.modernOutput,
977
+ u2Usage
927
978
  };
928
979
  }, testInvocationTimeout);
929
980
  }
@@ -938,7 +989,8 @@ async function mergeBrowsersResults(browsersResults) {
938
989
  failedAmount: browsersResults[0].failedAmount,
939
990
  csv: browsersResults[0].csv,
940
991
  error: '',
941
- modernOutput: browsersResults.some(r => r.modernOutput)
992
+ modernOutput: browsersResults.some(r => r.modernOutput),
993
+ u2Usage: [...new Set(browsersResults.flatMap(r => r.u2Usage ?? []))]
942
994
  };
943
995
  for (const browsersResult of browsersResults) {
944
996
  if (mergedResult.csv === browsersResult.csv) continue;
@@ -16,6 +16,36 @@
16
16
  "type": "string",
17
17
  "description": "Manifest version (semver recommended). Recorded in the registry on deploy."
18
18
  },
19
+ "storage": {
20
+ "type": "object",
21
+ "description": "Where the rows live. Absent = domain: the platform creates and owns the tables. external: the tables already exist in the database of the named platform connection and are read through it (no row security, soft delete, audit, relations, constraints, property schemas or extensions; read-only unless 'writable'); every table then declares its remote primary key as businessKey, and 'table'/'column' carry the remote names when they differ from the declared ones.",
22
+ "required": ["kind"],
23
+ "additionalProperties": false,
24
+ "properties": {
25
+ "kind": {"enum": ["domain", "external"]},
26
+ "connection": {
27
+ "type": "string",
28
+ "minLength": 1,
29
+ "description": "external: the qualified name of the platform connection to read through ('Package:Connection'); required."
30
+ },
31
+ "schema": {
32
+ "$ref": "#/definitions/remoteIdentifier",
33
+ "description": "external: the schema inside the remote database (e.g. 'public', 'dbo'); required."
34
+ },
35
+ "catalog": {
36
+ "$ref": "#/definitions/remoteIdentifier",
37
+ "description": "external: the catalog/database, for targets that have one."
38
+ },
39
+ "writable": {
40
+ "type": "boolean",
41
+ "default": false,
42
+ "description": "external: rows may be inserted, patched and deleted through the connection (one remote transaction per request, keyed by businessKey; on ClickHouse, which commits every statement, a request is one multi-row INSERT and patch/delete/upsert are refused). The publisher must hold Query, AddRows, ChangeValues and RemoveRows on the connection; a table opts out with its own 'writable': false."
43
+ }
44
+ },
45
+ "if": {"properties": {"kind": {"const": "external"}}},
46
+ "then": {"required": ["connection", "schema"]},
47
+ "else": {"properties": {"connection": false, "schema": false, "catalog": false, "writable": false}}
48
+ },
19
49
  "extensible": {
20
50
  "type": "object",
21
51
  "description": "Opt-in for user extensions of this schema. Users holding the Extend permission on the schema entity may then add their own tables; per-table column extension is opted in with the table's own 'extensible' flag. User objects are retained across plugin upgrades and never appear in generated typed clients.",
@@ -64,11 +94,26 @@
64
94
  {"not": {"type": "string", "pattern": "^x_"}}
65
95
  ]
66
96
  },
97
+ "remoteIdentifier": {
98
+ "description": "A name inside the remote database, verbatim (spaces allowed, the connector quotes it); it may not contain brackets, quotes, backslashes or dots.",
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "maxLength": 256,
102
+ "pattern": "^[^\\[\\]\"'`\\\\.]+$"
103
+ },
67
104
  "table": {
68
105
  "type": "object",
69
106
  "required": ["columns"],
70
107
  "additionalProperties": false,
71
108
  "properties": {
109
+ "table": {
110
+ "$ref": "#/definitions/remoteIdentifier",
111
+ "description": "External storage only: the remote table's name when it differs from the declared one (e.g. 'Order Details')."
112
+ },
113
+ "writable": {
114
+ "type": "boolean",
115
+ "description": "Writable external storage only: false keeps this table read-only while the rest of the binding accepts writes."
116
+ },
72
117
  "securityMode": {
73
118
  "enum": ["table", "master", "row"],
74
119
  "default": "table",
@@ -263,7 +308,11 @@
263
308
  "properties": {
264
309
  "type": {
265
310
  "enum": ["string", "int", "float", "bool", "datetime", "string_list", "ref", "file", "json", "user", "group"],
266
- "description": "Property type; ref = FK to another table in this manifest; user/group = aliases of ref Core.users / ref Core.groups (semantic type User / Group); file = a file://<connection>/<path> string into platform file storage (semantic type File); json = a jsonb-backed opaque object (filters, sorts, facets and aggregates refuse it)."
311
+ "description": "Property type; ref = FK to another table in this manifest; user/group = aliases of ref Core.users / ref Core.groups (semantic type User / Group); file = a file://<connection>/<path> string into platform file storage (semantic type File); json = a jsonb-backed opaque object (filters, sorts, facets and aggregates refuse it). An external storage admits string, int, float, bool, datetime and same-manifest ref only."
312
+ },
313
+ "column": {
314
+ "$ref": "#/definitions/remoteIdentifier",
315
+ "description": "External storage only: the remote column's name when it differs from the declared one (e.g. 'OrderID')."
267
316
  },
268
317
  "required": {"type": "boolean", "default": false},
269
318
  "unique": {
@@ -1,4 +1,4 @@
1
- import {domains} from '@datagrok-libraries/u2/src/dg/index.js';
1
+ import {domains} from '@datagrok-libraries/ems/src/dg';
2
2
 
3
3
  // the skins of everything the u2 domain stack renders
4
- import '@datagrok-libraries/u2/src/dg/domain/styles.js';
4
+ import '@datagrok-libraries/ems/src/dg/styles.js';
package/package.json CHANGED
@@ -4,9 +4,12 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/datagrok-ai/public.git"
6
6
  },
7
- "version": "6.7.8",
7
+ "version": "6.7.10",
8
8
  "description": "Utility to upload and publish packages to Datagrok",
9
9
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
10
+ "engines": {
11
+ "node": ">=22.12.0"
12
+ },
10
13
  "dependencies": {
11
14
  "@babel/parser": "^7.29.7",
12
15
  "@babel/runtime": "^7.29.7",
@@ -24,7 +27,7 @@
24
27
  "minimist": "^1.2.8",
25
28
  "node-fetch": "^2.7.0",
26
29
  "papaparse": "^5.5.3",
27
- "puppeteer": "^24.15.0",
30
+ "puppeteer": "^25.14.0",
28
31
  "ts-morph": "^28.0.0",
29
32
  "@babel/generator": "^7.29.0",
30
33
  "datagrok-api": "^1.27.12"
@@ -202,8 +202,8 @@ class FuncGeneratorPlugin {
202
202
  this._checkPackageFileForDecoratorsExport(packageFilePath);
203
203
  // Uncommment to add obvious import/export
204
204
  // this._writeToPackageFile(packageFilePath, genImports, genExports);
205
+ api({_: ['api']});
205
206
  });
206
- api({_: ['api']});
207
207
  }
208
208
 
209
209
  _addNodeData(
package/vitest.config.mts CHANGED
@@ -1,9 +1,12 @@
1
1
  import {defineConfig} from 'vitest/config';
2
2
 
3
3
  export default defineConfig({
4
+ // Babel writes each .js beside its .ts in bin/: tests must load the sources, and one copy of each module.
5
+ resolve: {extensions: ['.ts', '.mts', '.mjs', '.js', '.jsx', '.tsx', '.json']},
4
6
  test: {
5
7
  projects: [
6
8
  {
9
+ extends: true,
7
10
  test: {
8
11
  name: 'unit',
9
12
  environment: 'node',
@@ -12,6 +15,7 @@ export default defineConfig({
12
15
  },
13
16
  },
14
17
  {
18
+ extends: true,
15
19
  test: {
16
20
  name: 'integration',
17
21
  environment: 'node',