datagrok-tools 6.5.8 → 6.5.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
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Datagrok-tools changelog
|
|
2
2
|
|
|
3
|
+
## 6.5.10 (2026-09-11)
|
|
4
|
+
|
|
5
|
+
* `grok s pull` — a bundle file name is capped at 200 bytes plus a digest of the full name, so it stays unique and stable across pulls. A nested view under a long space spells a longer name than a path component may hold (255 bytes on ext4 and NTFS): one TWIG snapshot reached 268 and the write failed with `ENAMETOOLONG`, taking down the whole part rather than the entity — a `--by-namespace` part that only touched the space as a dependency died with it.
|
|
6
|
+
|
|
7
|
+
## 6.5.9 (2026-09-11)
|
|
8
|
+
|
|
9
|
+
* `grok s push/migrate` — placement no longer claims an entity the walk refused. Containment is exclusive, so asserting a project's relation to a platform entity takes it away from whatever holds it on the target: migrating a dashboard built on demo data moved `System:DemoFiles` into the migrated space, and every reference to it by name broke stand-wide until its `System` row was restored by hand. The guard covered a space's own `Files` connection but not `System:` connections, personal `Home` shares, package projects or built-in groups; it now defers to the same `untransferableReason` the walk uses.
|
|
10
|
+
|
|
3
11
|
## 6.5.8 (2026-09-10)
|
|
4
12
|
|
|
5
13
|
* `grok s` — correctness pass from the CLI audit: every server failure now exits 1 with one line on stderr (a 200 carrying an `ApiError` — missing entity, duplicate login or group name, unknown function — used to print as data with exit 0); `--limit`/`--offset` are honoured for users, groups, connections and functions (their public routes ignore paging, so `list` and the new `count` verb go through the internal routers); `files list` works (the public files route only downloads; listing goes through the connection's file route) and prints `path, kind, size`; `tables list|get|delete` no longer throw, and `tables download|get|delete` accept the bare table name, the full `Project:Table` name or the id; `describe <entity|type>` works (registry record plus the fields of a live sample; it called a nonexistent endpoint before); positional `functions run 'F(1, 2)'` maps the values onto the function's inputs instead of silently sending `{"0": 1}` (which ran the function with nulls); `raw` takes API-relative paths (a leading `/api` is accepted), sends a body from `--json <file>` or `--data '<json>'`, and fails on non-2xx — so it, and `healthcheck`, now work against a bare-Datlas `--host` too; `shares list` accepts a name and shows the grants inherited through project links; `groups` name resolution prefers an exact match (`admin` no longer collides with `Administrators`) and `--user` works on `list-members`/`list-memberships`; `connections delete` of an unknown id fails instead of reporting success; a bad `--host` URL or alias reports the reason without the help dump. `users delete` refuses with a pointer at `users block`: the platform has no user deletion, and removing the entity record leaves the login unusable. `functions delete` covers scripts and queries.
|
|
@@ -595,11 +595,12 @@ async function pushRelations(dapi, effective, planned, rows, progress = () => {}
|
|
|
595
595
|
let added = 0;
|
|
596
596
|
for (const rel of json.relations) {
|
|
597
597
|
if (linked.has(rel.entity.id)) continue;
|
|
598
|
-
//
|
|
599
|
-
//
|
|
598
|
+
// Placement is exclusive, so claiming an entity the walk refused takes it away from
|
|
599
|
+
// whatever holds it on the target: a space's own Files connection, or `System:DemoFiles`,
|
|
600
|
+
// which moved into a migrated space and broke every reference to it by name.
|
|
600
601
|
if (!landed.has(rel.entity.id)) {
|
|
601
602
|
const existing = await (0, _walker.findEntity)(dapi, rel.entity.id);
|
|
602
|
-
if (!existing || existing
|
|
603
|
+
if (!existing || (0, _registry.untransferableReason)(existing['#type'], existing)) continue;
|
|
603
604
|
}
|
|
604
605
|
wanted.push(contains(rel.entity.id) ? rel : {
|
|
605
606
|
...rel,
|
|
@@ -522,11 +522,12 @@ async function pushRelations(dapi: NodeDapi, effective: Map<string, BundleEntity
|
|
|
522
522
|
let added = 0;
|
|
523
523
|
for (const rel of json.relations) {
|
|
524
524
|
if (linked.has(rel.entity.id)) continue;
|
|
525
|
-
//
|
|
526
|
-
//
|
|
525
|
+
// Placement is exclusive, so claiming an entity the walk refused takes it away from
|
|
526
|
+
// whatever holds it on the target: a space's own Files connection, or `System:DemoFiles`,
|
|
527
|
+
// which moved into a migrated space and broke every reference to it by name.
|
|
527
528
|
if (!landed.has(rel.entity.id)) {
|
|
528
529
|
const existing = await findEntity(dapi, rel.entity.id);
|
|
529
|
-
if (!existing || existing
|
|
530
|
+
if (!existing || untransferableReason(existing['#type'], existing)) continue;
|
|
530
531
|
}
|
|
531
532
|
wanted.push(contains(rel.entity.id) ? rel : {...rel, isLink: true});
|
|
532
533
|
linked.add(rel.entity.id);
|
|
@@ -16,6 +16,7 @@ exports.resolveTypes = resolveTypes;
|
|
|
16
16
|
exports.stripCredentials = stripCredentials;
|
|
17
17
|
exports.typeOptionsOf = typeOptionsOf;
|
|
18
18
|
exports.untransferableReason = untransferableReason;
|
|
19
|
+
var _crypto = require("crypto");
|
|
19
20
|
/// Docs: [Entity export / import](/docs/features/grok-tool/export-import/DESIGN.md)
|
|
20
21
|
|
|
21
22
|
/** Extra listing rules a `--type` alias adds on top of its entity type. */
|
|
@@ -344,6 +345,20 @@ function fileNameFor(json) {
|
|
|
344
345
|
const cleaned = String(nqName).replace(/[:/\\?*"<>|]+/g, '.').replace(/^\.+|\.+$/g, '');
|
|
345
346
|
// Windows resolves `NUL.json` (and `COM1.csv.json`) to a device, whatever the extension.
|
|
346
347
|
const head = cleaned.split('.')[0];
|
|
347
|
-
return RESERVED_RE.test(head) ? `${head}-${json.id ?? 'entity'}${cleaned.slice(head.length)}` : cleaned;
|
|
348
|
+
return shorten(RESERVED_RE.test(head) ? `${head}-${json.id ?? 'entity'}${cleaned.slice(head.length)}` : cleaned);
|
|
348
349
|
}
|
|
349
|
-
const RESERVED_RE = /^(NUL|CON|PRN|AUX|COM[1-9]|LPT[1-9])$/i;
|
|
350
|
+
const RESERVED_RE = /^(NUL|CON|PRN|AUX|COM[1-9]|LPT[1-9])$/i;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* A path component caps at 255 bytes on ext4 and NTFS, and a nested view under a long space
|
|
354
|
+
* reaches that: one TWIG snapshot spelled 268. The write is what fails, so the whole part dies
|
|
355
|
+
* rather than the entity. The digest keeps a truncated name unique and stable across pulls.
|
|
356
|
+
*/
|
|
357
|
+
const MAX_FILE_NAME = 200;
|
|
358
|
+
function shorten(name) {
|
|
359
|
+
if (Buffer.byteLength(name) <= MAX_FILE_NAME) return name;
|
|
360
|
+
const digest = (0, _crypto.createHash)('sha1').update(name).digest('hex').slice(0, 8);
|
|
361
|
+
let head = name;
|
|
362
|
+
while (Buffer.byteLength(head) > MAX_FILE_NAME - 9) head = head.slice(0, -1);
|
|
363
|
+
return `${head}-${digest}`;
|
|
364
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// Docs: [Entity export / import](/docs/features/grok-tool/export-import/DESIGN.md)
|
|
2
|
+
import {createHash} from 'crypto';
|
|
2
3
|
|
|
3
4
|
export type BytesKind = 'tables' | 'files';
|
|
4
5
|
|
|
@@ -249,7 +250,22 @@ export function fileNameFor(json: any): string {
|
|
|
249
250
|
const cleaned = String(nqName).replace(/[:/\\?*"<>|]+/g, '.').replace(/^\.+|\.+$/g, '');
|
|
250
251
|
// Windows resolves `NUL.json` (and `COM1.csv.json`) to a device, whatever the extension.
|
|
251
252
|
const head = cleaned.split('.')[0];
|
|
252
|
-
return RESERVED_RE.test(head) ? `${head}-${json.id ?? 'entity'}${cleaned.slice(head.length)}` : cleaned;
|
|
253
|
+
return shorten(RESERVED_RE.test(head) ? `${head}-${json.id ?? 'entity'}${cleaned.slice(head.length)}` : cleaned);
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
const RESERVED_RE = /^(NUL|CON|PRN|AUX|COM[1-9]|LPT[1-9])$/i;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* A path component caps at 255 bytes on ext4 and NTFS, and a nested view under a long space
|
|
260
|
+
* reaches that: one TWIG snapshot spelled 268. The write is what fails, so the whole part dies
|
|
261
|
+
* rather than the entity. The digest keeps a truncated name unique and stable across pulls.
|
|
262
|
+
*/
|
|
263
|
+
const MAX_FILE_NAME = 200;
|
|
264
|
+
|
|
265
|
+
function shorten(name: string): string {
|
|
266
|
+
if (Buffer.byteLength(name) <= MAX_FILE_NAME) return name;
|
|
267
|
+
const digest = createHash('sha1').update(name).digest('hex').slice(0, 8);
|
|
268
|
+
let head = name;
|
|
269
|
+
while (Buffer.byteLength(head) > MAX_FILE_NAME - 9) head = head.slice(0, -1);
|
|
270
|
+
return `${head}-${digest}`;
|
|
271
|
+
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "https://github.com/datagrok-ai/public.git"
|
|
6
6
|
},
|
|
7
|
-
"version": "6.5.
|
|
7
|
+
"version": "6.5.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
10
|
"dependencies": {
|