datagrok-tools 6.7.9 → 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.
@@ -1,4 +1,4 @@
1
- FROM node:22-bookworm-slim
1
+ FROM node:22.23.4-bookworm-slim
2
2
 
3
3
  # System deps + Chrome stable
4
4
  RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -1,5 +1,5 @@
1
1
 
2
- > datagrok-tools@6.7.9 build /home/runner/work/public/public/tools
2
+ > datagrok-tools@6.7.10 build /home/runner/work/public/public/tools
3
3
  > node build.js
4
4
 
5
- Successfully compiled 52 files with Babel.
5
+ Successfully compiled 55 files with Babel.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # Datagrok-tools changelog
2
2
 
3
- ## v.next
3
+ ## 6.7.10 (2026-11-06)
4
+
5
+ * Added `grok src`: git recipes across the repos of the datagrok umbrella (`clone`, `add`, `sparse`, `switch`, `status`, `pull`, `at`, `bump`, `wt add`, `doctor`)
6
+ * Kept positionals, `--url` and `--dir` verbatim (`1.10` no longer becomes 1.1), and an explicit boolean such as `--force` now reaches every command instead of being dropped
7
+ * Added `grok s o groups list|get|add|set|rm|put|reset`, `endpoints list|rm`, `logs match '<filter>'` (with `--space`) and `logs activations [stop]`: log groups and endpoints of Settings > Logger
8
+ * `grok publish`: A local container build that fails for the host platform on Apple Silicon is retried for linux/amd64, so a package whose base image has no arm64 variant (the Chembl ChEMBL database) builds
9
+ * `grok test`: A package built on u2 or ems runs with u2 strict — a u2 misuse throws a `U2UsageError` naming the fix, and one the page caught and logged fails the run when it is the package's own (a stack frame in its bundle, or none to tell; another package's prints as a warning); `U2_STRICT=report` lists them instead without failing
10
+ * `grok check`: A package built on u2 or ems fails on a problem of u2's consumer check whatever its version (a 0.x package only printed it), so a CSS rule over a new `.u2-*` class or `[data-u2…]` attribute fails the check
11
+ * Added `grok s o problems severity <id|kind:key> <info|warning|critical|none>`: pins the severity a problem's raises use (`none` unpins it)
12
+ * Added `grok connect-folder`: connects a local folder as a connection, served from a Docker container; the agent image tag is pulled on every connect, so a moved tag replaces a stale local agent
13
+ * `grok connect-folder`: Runs only the Docker Hub `datagrok/proxy_storage` agent image whatever image the server names; `--image` runs another, such as a copy in a private registry
14
+ * Added `grok s ... --log [<spec>]` on every `grok s` command (`grok s o` included): its requests share a `datlas-log-id`, every server instance keeps what they log at the spec (`verbose`, `standard`, levels, debug flags or `all`) in memory only, and at the end the command gathers it and prints it to stderr
15
+ * Added `grok s functions run ... --debug`: a debug call (`?debug=true`) whose own log, a query's stages as the query debugger shows them, is kept and printed the same way
16
+ * Added `grok s o logs request <logId>`: gathers what the requests of one `--log` command logged
17
+ * `grok s status`: server build, uptime, DB pool and HTTP connections from `/info/server`; for admins also the requests in flight (`?extended=true`)
18
+ * `grok api`: Fixed double plurals in table accessors (`categorieses` → `categories`)
19
+ * Build: Fixed `package-api.ts` of a decorator package lagging one build behind `package.g.ts`
20
+ * Added a `restore` arm per soft-delete table to the `<Schema>TransactionOp` that `grok api` generates, so the typed `transaction()` accepts the restore ops the server runs
21
+ * Made `grok api` type a required column with a `default` as optional on `<Table>Insert`: the server fills it when an insert omits it
22
+ * Fixed `grok api` emitting a choices type that does not compile, or holds a different value, when a choice contains `'` or `\`
23
+
24
+ ## 6.7.9 (2026-10-16)
4
25
 
5
26
  * Upgraded Puppeteer to v25, which drops the vulnerable `extract-zip` and `basic-ftp` transitive dependencies; Node.js 22.12+ is now required
6
27
  * `grok add app --domain`, `grok api --ui`: scaffold against `@datagrok-libraries/ems`
package/CLAUDE.md CHANGED
@@ -33,6 +33,7 @@ The CLI uses a modular command pattern. Each command is a separate module that:
33
33
  - `claude.ts` - Launch a Dockerized dev environment with Datagrok + Claude Code
34
34
  - `migrate.ts` - Update legacy packages
35
35
  - `init.ts` - Apply configuration to existing packages
36
+ - `src.ts` - Git recipes across the repos of the datagrok umbrella (`grok src`)
36
37
 
37
38
  The commands `api`, `check`, `link`, `publish`, and `test` support the `--all` flag to run recursively across all packages in the current directory.
38
39
 
package/GROK_S.md CHANGED
@@ -36,6 +36,7 @@ browser or a logged-in session.
36
36
  | See what fields an entity type has | `grok s describe connections` |
37
37
  | Hit any undocumented endpoint | `grok s raw GET /users/current` / `raw POST <path> --data '{...}'` |
38
38
  | Check server + per-module health | `grok s healthcheck [--module <name>]` |
39
+ | Server build, uptime, DB pool, requests in flight | `grok s status` |
39
40
  | Problems, alerts, problem rules, logger settings, errors, cloud logs | `grok s observe problems list --alert-status open` (alias `grok s o`) |
40
41
  | Bulk operations in one round-trip | `grok s batch <entity> <verb> --json items.json` |
41
42
  | Move entities dev to prod (bundle, or instance to instance) | `grok s pull ... --out ./bundle` / `grok s migrate ... --from dev --to prod` |
@@ -217,6 +218,7 @@ grants inherited through the entity's project links (`inherited: true`).
217
218
  grok s functions run 'Chem:smilesToMw("CCO")' # positional args
218
219
  grok s functions run 'Pkg:fn({smiles:"CCO", radius:2})' # named args
219
220
  grok s functions run Pkg:fn --json params.json # big input from a file
221
+ grok s functions run 'MyPkg:MyQuery()' --debug # a debug call: the query's own log on stderr
220
222
  ```
221
223
 
222
224
  The server binds arguments by parameter name, so positional values are mapped onto the
@@ -497,6 +499,20 @@ module the server does not report exits 1. Requires a valid dev key (standard `g
497
499
  For an anonymous liveness probe — load balancer, k8s readiness — hit `/admin/health` directly;
498
500
  it's on the server's unauthenticated allowlist.
499
501
 
502
+ ### Server status
503
+
504
+ ```bash
505
+ grok s status # build, uptime, DB pool, HTTP connections
506
+ grok s status --output json # {server, extended}
507
+ grok s status --output quiet # just the version
508
+ ```
509
+
510
+ Reads `GET /info/server` and `GET /info/server?extended=true`: `Version`, `Branch`, `Commit`,
511
+ build `Date`, `Uptime`, `Requests` served, `Used Connections` / `Pool Connections` of the DB pool,
512
+ `Isolate`, `Service network`, `webRoot`, and the `Http Server` connection counts. For an admin the
513
+ extended call replaces the active-connection count with the requests in flight per endpoint
514
+ (`{"GET /api/...": n}`), printed as a table; for anyone else the two responses are the same.
515
+
500
516
  ## Problems, logging and errors (`grok s observe`, alias `grok s o`)
501
517
 
502
518
  Thin commands over the server's routes; `grok s observe` with no command prints every verb and option.
@@ -506,6 +522,7 @@ grok s o problems list --alert-status open # the open alerts; --
506
522
  grok s o problems get 'health:Jupyter@<server>' # by id or kind:key; history <id> for its records
507
523
  grok s o problems status connection:<connection id> --data '{"status": "muted", "reason": "maintenance", "until": "2026-10-04T06:00:00Z"}'
508
524
  grok s o problems ack 'health:Jupyter@<server>' --reason x # resolve <id> --reason ... closes the alert
525
+ grok s o problems severity dashboard:<project id> critical --reason 'board meeting' # pin; none unpins
509
526
  grok s o rules get > rules.json # Settings > Alerts problem rules; rules put --json rules.json
510
527
  grok s o rules test # evaluates the saved rules once, now
511
528
  grok s o logger get exportSettings # the logger settings, or one path of them
@@ -525,16 +542,67 @@ grok s o logs archive list --prefix cw/ --since 1d # keys in the log a
525
542
  grok s o logs archive read <key> # one archived object decoded to events
526
543
  grok s o logs container # docker containers and their status
527
544
  grok s o logs container scripting-python_1 --since 1h --grep 'Kernel (started|shutdown)' # one container's log lines
545
+ grok s o logs match 'level in (error, warning) and not text ~ ResizeObserver' --since 2h # what a log filter matches
546
+ grok s o logs match --level error --package Chembl --grep 'time(d )?out' # flags add conditions joined by and; --grep is a regex
547
+ grok s o logs match --level audit --space Chem # audit of entities in the Chem space and its subspaces (case-sensitive)
548
+ grok s o logs match 'space startsWith Chem: or space endsWith :Assays' # space: the entity's space path; = ~ =~ startsWith endsWith
549
+ grok s o groups # log groups (Settings > Logger): name, kind, levels, filter, where to
550
+ grok s o groups add --name Chembl --levels error,warning --filter 'package = Chembl' --to db,grafana:otlp
551
+ grok s o groups set Chembl --when 'level = error and package = Chembl' --scope session --for 15m --before 60s --flags db
552
+ grok s o groups set Chembl --enabled false # only the flags given change; groups rm Chembl deletes it
553
+ grok s o groups put --json groups.json # replace every log group (the GROK_PARAMETERS settings.logger.logGroups shape)
554
+ grok s o groups reset # back to the default groups; the audit group can only be switched off, not removed
555
+ grok s o endpoints # where groups can send records; endpoints rm grafana
556
+ grok s o logs activations # triggered groups on now; logs activations stop <group id> <key>
528
557
  ```
529
558
 
530
559
  Problems need `ManageAlerts`, errors, another session's timeline and `logs cloud`/`logs archive` `ViewTelemetry`,
531
- `rules test` both, logger and rules changes and `logs capture` `EditPluginsSettings`. `capture` is an expiring
560
+ `rules test` both, logger and rules changes, `groups`/`endpoints` changes, `logs activations stop` and `logs capture`
561
+ `EditPluginsSettings`; `logs match` and `logs activations` need `ViewTelemetry`, and changing the audit group or an
562
+ endpoint it sends to also needs `EditAuditLogging`. `logs match` takes the whole filter as one quoted argument; `--grep`
563
+ is a regex and `--space` matches the space and its subspaces, both case-sensitive, as `=~` is. `groups add|set|rm`
564
+ need a server that returns its default log groups; on an older one, replace them all with `groups put`. `capture` is an expiring
532
565
  `userGroupSettings` entry with `saveHttpRequests` on (seeded from the All Users entry, with `usage` saved); on an entry
533
566
  that already expires later it keeps that time. `capture stop` removes only an expiring entry that holds nothing but the
534
567
  capture; for any other entry use `logger set --user <login> --remove`. Read what it recorded with
535
568
  `timeline --session <id>`. `logs container` reads the container's latest `--limit` lines (20000) and only then cuts `--since`/`--from`/`--to` (UTC, matched across the container's own offset) and `--grep`, so a window older than those lines comes back empty: raise `--limit`. `logs archive list --since` filters only the page the server returned (`--limit`), so narrow
536
569
  the listing with `--prefix` first. `logs` read with the server's AWS role, or with `--connection <id>`. In durations `m` means minutes (in `grok s pull --since` it means months).
537
570
 
571
+ ### Logging one command (`--log`)
572
+
573
+ `logger set --user` changes what a user logs for a while; `--log` makes the server keep what the
574
+ requests of one command log, and prints it at the end. It works on every `grok s` command, `grok s o`
575
+ included:
576
+
577
+ ```bash
578
+ grok s functions run UsageAnalysis:Groups --log # verbose: every level
579
+ grok s functions run UsageAnalysis:Groups --log verbose,db # and every SQL statement (the db debug flag)
580
+ grok s users list --log debug,storage,credentials # levels and flags; flags alone add debug
581
+ grok s functions run 'MyPkg:MyQuery()' --debug # the query's own debug log, plus errors and warnings
582
+ grok s users list --log --output json 2>log.jsonl # one JSON object per record
583
+ ```
584
+
585
+ Every request of the command carries one `datlas-log-id` (a new UUID per command) and the spec in
586
+ `datlas-log`. A command is often several requests, and each can land on a different isolate and
587
+ server instance: each keeps what its request logged at the spec in memory, in a buffer for that id.
588
+ Those records never reach the database, the console or a sync destination, and what the server logs
589
+ the usual way does not change. When the command ends, `GET /log/requests/<logId>` gathers the
590
+ buffers from every isolate of every instance over the message queue, hands them over and forgets
591
+ them; the CLI prints them to stderr, so stdout keeps the result. The gather also adds what the
592
+ server saved the usual way for the command's session at the levels of the spec (errors, warnings, audit),
593
+ skipping copies of records it already has, so an instance that died before it could answer still
594
+ shows its saved errors. A buffer nobody gathers is dropped after an hour. No permission is needed; another user's id takes `ViewTelemetry`.
595
+ `grok s o logs request <logId>` gathers one id by hand.
596
+
597
+ The spec is comma-separated: `verbose`, `standard`, the levels (`error`, `warning`, `info`, `debug`,
598
+ `audit`, `usage`) and the debug flags of the logger settings (`db`, `socket`, `storage`, `credentials`,
599
+ `docker`, `auth`, ... or `all`).
600
+
601
+ `functions run --debug` sends `?debug=true`: the call runs as a debug call (not cached), the way the
602
+ query debugger in the UI runs it, and its own log (connection, the query's stages) is kept with the
603
+ rest; without `--log` the spec is `error,warning`. Only what runs in Datlas is kept: a script that
604
+ runs on a queue worker adds nothing here.
605
+
538
606
  ## Describing an entity type
539
607
 
540
608
  ```bash
package/README.md CHANGED
@@ -151,3 +151,17 @@ For more information on configuring connections, refer to the [Connections](http
151
151
  | debug | Sets a debug breakpoint before each test execution, allowing you to debug the test. Only works in `gui` mode |
152
152
 
153
153
  - `link` command is used for public plugins development to link `datagrok-api` and libraries.
154
+ - `connect-folder` makes a local folder a Datagrok connection. A folder agent in Docker dials out to the server (no
155
+ inbound port) with a key of its own that can only serve this folder; your keypair (`grok login <server>` first)
156
+ never leaves the machine. Connecting the same folder again resumes the same connection. Requires Docker and the
157
+ "Connect local folders" permission.
158
+
159
+ ```shell
160
+ grok connect-folder <folder> [--host <alias>] [--read-only] [--foreground] [--image <ref>] [--force]
161
+ grok connect-folder list [--host <alias>] [--output json]
162
+ grok connect-folder start|stop|logs <connection> [-f]
163
+ grok connect-folder rm <connection>
164
+ ```
165
+
166
+ `<connection>` is the connection name, a connection id prefix (8+ characters), or the folder. `stop` keeps the
167
+ folder disconnected after a reboot; `rm` removes the container, the key and the connection.
@@ -26,18 +26,28 @@ const dbUiFile = 'db-ui.ts';
26
26
  * (u2 is a relative-path dependency without a `dg` subpath export). */
27
27
  const emsDgModule = '@datagrok-libraries/ems/src/dg';
28
28
 
29
- /** Naive English plural of a (snake_case) table name — `issue` → `issues`, `box` →
30
- * `boxes`, `category` → `categories`. */
29
+ /** The platform's `StringUtils.toSingular` (ddt utils.dart): regular forms only; a noun already
30
+ * singular in `-is`/`-us`/`-ss` passes through. */
31
+ function singularizeTableName(name) {
32
+ if (name.length <= 2 || !name.endsWith('s') || /(ss|is|us)$/.test(name)) return name;
33
+ if (name.endsWith('ies') && name.length > 3) return name.slice(0, -3) + 'y';
34
+ return /(xes|zzes|ches|shes|sses|tuses)$/.test(name) ? name.slice(0, -2) : name.slice(0, -1);
35
+ }
36
+
37
+ /** The platform's `StringUtils.toPlural` (ddt utils.dart) — `issue` → `issues`, `box` →
38
+ * `boxes`, `category` → `categories`, `analysis` → `analyses`. */
31
39
  function pluralizeTableName(name) {
40
+ if (name.endsWith('is')) return name.slice(0, -2) + 'es';
32
41
  if (/(s|x|z|ch|sh)$/.test(name)) return name + 'es';
33
- if (/[^aeiou_]y$/.test(name)) return name.slice(0, -1) + 'ies';
42
+ if (/[^aeiou]y$/.test(name)) return name.slice(0, -1) + 'ies';
34
43
  return name + 's';
35
44
  }
36
45
 
37
46
  /** The camelCase plural property name a table gets on the schema client and under the
38
- * schema handle's `tables` (`issue_label` → `issueLabels`). */
47
+ * schema handle's `tables` (`issue_label` → `issueLabels`, `categories` → `categories`): the
48
+ * plural of the singular, as the platform derives a table's plural name. */
39
49
  function tableProp(tableName) {
40
- return utils.snakeToCamelCase(pluralizeTableName(tableName), false);
50
+ return utils.snakeToCamelCase(pluralizeTableName(singularizeTableName(tableName)), false);
41
51
  }
42
52
  const domainSchemaPath = _path.default.join(_path.default.dirname(_path.default.dirname(__dirname)), 'domain-schema.schema.json');
43
53
  /** The platform's own schema, whose tables a qualified ref may target (`Core.users`, ...). */
@@ -277,7 +287,9 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
277
287
  // different sets fall back to a `<alias><PascalTable>` name (deterministic).
278
288
  const choicesAliases = {};
279
289
  const choicesAlias = (tableName, columnName, values) => {
280
- const valueSet = values.map(v => `'${v}'`).join(' | ');
290
+ // JSON.stringify escapes every special character; inside single quotes only ' needs one more
291
+ const literals = values.map(v => `'${JSON.stringify(v).slice(1, -1).replace(/'/g, `\\'`)}'`);
292
+ const valueSet = literals.join(' | ');
281
293
  let alias = utils.snakeToCamelCase(tableName) + utils.snakeToCamelCase(columnName);
282
294
  if (choicesAliases[alias] !== undefined && choicesAliases[alias] !== valueSet) alias += utils.snakeToCamelCase(tableName);
283
295
  if (choicesAliases[alias] !== undefined && choicesAliases[alias] !== valueSet) {
@@ -286,7 +298,7 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
286
298
  }
287
299
  if (choicesAliases[alias] === undefined) {
288
300
  choicesAliases[alias] = valueSet;
289
- decls.push(wrapTokens(`export type ${alias} = `, values.map(v => `'${v}'`), ' | ', ';'));
301
+ decls.push(wrapTokens(`export type ${alias} = `, literals, ' | ', ';'));
290
302
  }
291
303
  return alias;
292
304
  };
@@ -364,6 +376,7 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
364
376
  insertType: column.type === 'datetime' ? 'Dayjs | string' : tsType,
365
377
  required: column.required === true,
366
378
  autoNumber: column.autoNumber != null,
379
+ hasDefault: column.default != null,
367
380
  ref: ref
368
381
  });
369
382
  return true;
@@ -380,6 +393,7 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
380
393
  }
381
394
  return columns;
382
395
  }
396
+ const accessorTables = new Map();
383
397
  for (const tableName of tableNames) {
384
398
  const typeName = utils.snakeToCamelCase(tableName);
385
399
  if (emittedTypes.has(typeName)) {
@@ -387,6 +401,12 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
387
401
  return null;
388
402
  }
389
403
  emittedTypes.add(typeName);
404
+ const accessor = tableProp(tableName);
405
+ if (accessorTables.has(accessor)) {
406
+ color.error(`${manifestPath}: tables '${accessorTables.get(accessor)}' and '${tableName}' ` + `both get the accessor '${accessor}' (rename one of them)`);
407
+ return null;
408
+ }
409
+ accessorTables.set(accessor, tableName);
390
410
  const columns = collectColumns(tableName, manifest, false);
391
411
  if (columns == null) return null;
392
412
  tableColumns[tableName] = columns;
@@ -436,7 +456,7 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
436
456
  rowLines.push('}');
437
457
  decls.push(rowLines.join(sep));
438
458
  const insertLines = [`/** Insert payload for \`${manifest.name}.${tableName}\`. */`, `export interface ${typeName}Insert {`];
439
- for (const c of columns) insertLines.push(` ${c.name}${c.required ? '' : '?'}: ${c.insertType};`);
459
+ for (const c of columns) insertLines.push(` ${c.name}${c.required && !c.hasDefault ? '' : '?'}: ${c.insertType};`);
440
460
  for (const r of relations) insertLines.push(` /** Link set: \`${r.target}\` row ids. */`, ` ${r.name}?: string[];`);
441
461
  if (table.idempotency === true) insertLines.push(' idempotencyKey?: string;');
442
462
  insertLines.push('}');
@@ -476,6 +496,7 @@ function generateDomainSchemaCode(manifest, manifestPath, emittedTypes, packageD
476
496
  }
477
497
  decls.push(expandEntries.length === 0 ? `export type ${typeName}Expand = {};` : [`/** Expand keys of \`${manifest.name}.${tableName}\` → fields each adds to the row ` + `(consumed by query()/builder). */`, `export type ${typeName}Expand = {`, ...expandEntries, '};'].join(sep));
478
498
  if (!external) txArms.push(` {op: 'insert'; table: '${tableName}'; ref?: string; values: DG.DomainTxValues<${typeName}Insert>; ` + `onDuplicate?: 'error'} |`, ` {op: 'update'; table: '${tableName}'; id: string; ` + `values: DG.DomainTxValues<${relations.length > 0 ? `${typeName}Update` : `Partial<${typeName}Row>`}>; ` + `expectedVersion?: number} |`, ` {op: 'delete'; table: '${tableName}'; id: string} |`);
499
+ if (!external && table.softDelete !== false) txArms.push(` {op: 'restore'; table: '${tableName}'; id: string} |`);
479
500
 
480
501
  // No datetime config: the client resolves datetime columns from the domain
481
502
  // registry itself (the Dayjs typing in <Table>Row stays true for every client).
@@ -13,6 +13,7 @@ exports.checkImportStatements = checkImportStatements;
13
13
  exports.checkNpmIgnore = checkNpmIgnore;
14
14
  exports.checkPackageFile = checkPackageFile;
15
15
  exports.checkSourceMap = checkSourceMap;
16
+ exports.checkU2Usage = checkU2Usage;
16
17
  exports.extractExternals = extractExternals;
17
18
  var _fs = _interopRequireDefault(require("fs"));
18
19
  var _path = _interopRequireDefault(require("path"));
@@ -20,6 +21,7 @@ var _ignoreWalk = _interopRequireDefault(require("ignore-walk"));
20
21
  var utils = _interopRequireWildcard(require("../utils/utils"));
21
22
  var color = _interopRequireWildcard(require("../utils/color-utils"));
22
23
  var _const = require("../utils/const");
24
+ var _child_process = require("child_process");
23
25
  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); }
24
26
  const warns = ['Latest package version', 'Datagrok API version should contain', 'is served by the platform'];
25
27
  const forbiddenNames = ['function', 'class', 'export'];
@@ -91,6 +93,8 @@ function runChecks(packagePath, soft = false, noExit = false) {
91
93
  if (externals) errors.push(...checkImportStatements(packagePath, jsTsFiles, externals));
92
94
  }
93
95
  errors.push(...checkDatagrokApiImports(packagePath, jsTsFiles));
96
+ const u2Errors = checkU2Usage(packagePath, json);
97
+ errors.push(...u2Errors);
94
98
  warnings.push(...checkHeavyImports(packagePath, jsTsFiles, _path.default.basename(packagePath)));
95
99
  if (!soft) errors.push(...checkSourceMap(packagePath));
96
100
  errors.push(...checkNpmIgnore(packagePath));
@@ -111,7 +115,8 @@ function runChecks(packagePath, soft = false, noExit = false) {
111
115
  if (errors.length) {
112
116
  console.log(`Checking package ${_path.default.basename(packagePath)}...`);
113
117
  showError(errors);
114
- if (soft || json.version.startsWith('0') || errors.every(w => warns.some(ww => w.includes(ww)))) return true;
118
+ // the u2 consumer ratchet holds pre-1.0 packages too
119
+ if (soft || u2Errors.length === 0 && (json.version.startsWith('0') || errors.every(w => warns.some(ww => w.includes(ww))))) return true;
115
120
  if (noExit) return false;else process.exit(1);
116
121
  }
117
122
  color.log(`Checking package ${_path.default.basename(packagePath)}...\t\t\t\u2713 OK`);
@@ -213,6 +218,43 @@ function checkDatagrokApiImports(packagePath, files) {
213
218
  }
214
219
  return errors;
215
220
  }
221
+
222
+ /** u2's consumer check for a package built on u2 or ems: the u2 lint rules and the shrink-only counts of
223
+ * `consumers-baseline.json`. Prints the ratchet line; growth and enforced rules are errors. */
224
+ function checkU2Usage(packagePath, json) {
225
+ const deps = {
226
+ ...json.dependencies,
227
+ ...json.devDependencies
228
+ };
229
+ if (!('@datagrok-libraries/u2' in deps) && !('@datagrok-libraries/ems' in deps)) return [];
230
+ let script;
231
+ try {
232
+ script = _path.default.join(_path.default.dirname(require.resolve('@datagrok-libraries/u2/package.json', {
233
+ paths: [packagePath]
234
+ })), 'scripts', 'check-consumer.mjs');
235
+ } catch {
236
+ return [];
237
+ }
238
+ if (!_fs.default.existsSync(script)) return [];
239
+ const run = (0, _child_process.spawnSync)(process.execPath, [script, packagePath, '--json'], {
240
+ encoding: 'utf-8',
241
+ maxBuffer: 64 * 1024 * 1024,
242
+ timeout: 300000
243
+ });
244
+ let report;
245
+ try {
246
+ report = JSON.parse(run.stdout);
247
+ } catch {
248
+ return [`u2 consumer check could not run: ${(run.stderr || run.stdout || String(run.error)).trim()}`];
249
+ }
250
+ const errors = [];
251
+ for (const tree of report.trees) {
252
+ for (const line of tree.summary) console.log(line);
253
+ for (const note of tree.notes) color.log(` ${note}`, 'warn');
254
+ errors.push(...tree.problems);
255
+ }
256
+ return errors;
257
+ }
216
258
  function checkHeavyImports(packagePath, files, packageName = '') {
217
259
  const warnings = [];
218
260
  const isTestPackage = /tests?$/i.test(packageName);