datagrok-tools 6.5.6 → 6.5.8
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/.devcontainer/docker-compose.yaml +68 -24
- package/CHANGELOG.md +41 -6
- package/CLAUDE.md +35 -10
- package/Core.json +1027 -0
- package/GROK_S.md +563 -27
- package/bin/commands/api.js +121 -70
- package/bin/commands/help.js +3 -65
- package/bin/commands/server-domains.js +468 -0
- package/bin/commands/server-migrate.js +392 -0
- package/bin/commands/server.js +455 -92
- package/bin/grok.js +16 -5
- package/bin/utils/migrate/bundle.js +223 -0
- package/bin/utils/migrate/bundle.ts +222 -0
- package/bin/utils/migrate/parts.js +83 -0
- package/bin/utils/migrate/parts.ts +72 -0
- package/bin/utils/migrate/pool.js +17 -0
- package/bin/utils/migrate/pool.ts +13 -0
- package/bin/utils/migrate/pusher.js +980 -0
- package/bin/utils/migrate/pusher.ts +829 -0
- package/bin/utils/migrate/registry.js +349 -0
- package/bin/utils/migrate/registry.ts +255 -0
- package/bin/utils/migrate/rewriter.js +59 -0
- package/bin/utils/migrate/rewriter.ts +59 -0
- package/bin/utils/migrate/walker.js +571 -0
- package/bin/utils/migrate/walker.ts +509 -0
- package/bin/utils/node-dapi.js +787 -141
- package/bin/utils/playwright-runner.js +55 -39
- package/bin/utils/server-client.js +15 -2
- package/bin/utils/server-output.js +65 -4
- package/bin/utils/test-utils.js +1 -1
- package/domain-schema.schema.json +57 -6
- package/package.json +6 -1
- /package/{vitest.config.ts → vitest.config.mts} +0 -0
|
@@ -82,6 +82,49 @@ function rowsToCsv(rows) {
|
|
|
82
82
|
data: rows.map(r => header.map(h => r[h]))
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
+
function writePlaywrightCsv(pkgDir, csv) {
|
|
86
|
+
// Persist a Playwright-only CSV so the pipeline can ship it to the Datlas
|
|
87
|
+
// 'playwright' bucket, separate from the merged Puppeteer+Playwright
|
|
88
|
+
// test-report.csv that feeds the legacy 'package' bucket and JUnit.
|
|
89
|
+
try {
|
|
90
|
+
_fs.default.writeFileSync(_path.default.join(pkgDir, 'test-report-playwright.csv'), csv, 'utf-8');
|
|
91
|
+
} catch (e) {
|
|
92
|
+
color.warn(`Playwright: failed to write test-report-playwright.csv: ${e.message || e}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A pass that dies before running specs still has to report a failure. Returning an empty CSV
|
|
97
|
+
// let the caller drop it whenever the Puppeteer pass had rows, so a suite that never launched
|
|
98
|
+
// (a config that fails to load, an unreachable host) was reported as a clean run.
|
|
99
|
+
function runnerFailure(pkgDir, args, message) {
|
|
100
|
+
const rows = [{
|
|
101
|
+
date: new Date().toISOString(),
|
|
102
|
+
category: 'playwright',
|
|
103
|
+
name: 'Playwright suite',
|
|
104
|
+
success: false,
|
|
105
|
+
result: message,
|
|
106
|
+
ms: 0,
|
|
107
|
+
skipped: false,
|
|
108
|
+
logs: '',
|
|
109
|
+
owner: '',
|
|
110
|
+
package: process.env.TARGET_PACKAGE || args.package || '',
|
|
111
|
+
widgetsDifference: '',
|
|
112
|
+
flaking: false
|
|
113
|
+
}];
|
|
114
|
+
const csv = rowsToCsv(rows);
|
|
115
|
+
writePlaywrightCsv(pkgDir, csv);
|
|
116
|
+
color.error(`Playwright: ${message}`);
|
|
117
|
+
return {
|
|
118
|
+
failed: true,
|
|
119
|
+
passedAmount: 0,
|
|
120
|
+
failedAmount: 1,
|
|
121
|
+
skippedAmount: 0,
|
|
122
|
+
verbosePassed: '',
|
|
123
|
+
verboseSkipped: '',
|
|
124
|
+
verboseFailed: `Playwright: ${message}\n`,
|
|
125
|
+
csv: csv
|
|
126
|
+
};
|
|
127
|
+
}
|
|
85
128
|
async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
86
129
|
const empty = {
|
|
87
130
|
failed: false,
|
|
@@ -101,29 +144,17 @@ async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
|
101
144
|
key
|
|
102
145
|
} = testUtils.getDevKey(hostKey));
|
|
103
146
|
} catch (e) {
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
...empty,
|
|
107
|
-
failed: true,
|
|
108
|
-
failedAmount: 1,
|
|
109
|
-
verboseFailed: `Playwright: ${e.message || e}\n`
|
|
110
|
-
};
|
|
147
|
+
return runnerFailure(pkgDir, args, `cannot resolve host '${hostKey}': ${e.message || e}`);
|
|
111
148
|
}
|
|
112
149
|
let token;
|
|
113
150
|
try {
|
|
114
151
|
token = await testUtils.getToken(url, key);
|
|
115
152
|
} catch (e) {
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
...empty,
|
|
119
|
-
failed: true,
|
|
120
|
-
failedAmount: 1,
|
|
121
|
-
verboseFailed: `Playwright: ${e.message || e}\n`
|
|
122
|
-
};
|
|
153
|
+
return runnerFailure(pkgDir, args, `cannot exchange dev key for token: ${e.message || e}`);
|
|
123
154
|
}
|
|
124
155
|
let webUrl;
|
|
125
156
|
try {
|
|
126
|
-
webUrl = await testUtils.getWebUrl(url, token);
|
|
157
|
+
webUrl = process.env.DATAGROK_WEB_URL || (await testUtils.getWebUrl(url, token));
|
|
127
158
|
if (webUrl.endsWith('/')) webUrl = webUrl.slice(0, -1);
|
|
128
159
|
} catch {
|
|
129
160
|
webUrl = url.replace(/\/api\/?$/, '');
|
|
@@ -137,15 +168,7 @@ async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
|
137
168
|
}
|
|
138
169
|
}
|
|
139
170
|
const configPath = _path.default.join(testDir, 'playwright.config.ts');
|
|
140
|
-
if (!_fs.default.existsSync(configPath)) {
|
|
141
|
-
color.error(`Playwright: ${configPath} not found.`);
|
|
142
|
-
return {
|
|
143
|
-
...empty,
|
|
144
|
-
failed: true,
|
|
145
|
-
failedAmount: 1,
|
|
146
|
-
verboseFailed: 'Playwright: missing playwright.config.ts\n'
|
|
147
|
-
};
|
|
148
|
-
}
|
|
171
|
+
if (!_fs.default.existsSync(configPath)) return runnerFailure(pkgDir, args, `${configPath} not found`);
|
|
149
172
|
const reportFile = _path.default.join(pkgDir, 'test-playwright-report.json');
|
|
150
173
|
if (_fs.default.existsSync(reportFile)) _fs.default.unlinkSync(reportFile);
|
|
151
174
|
const cliArgs = ['--no-install', 'playwright', 'test', `--config=${configPath}`];
|
|
@@ -168,6 +191,8 @@ async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
|
168
191
|
...process.env,
|
|
169
192
|
DATAGROK_URL: webUrl,
|
|
170
193
|
DATAGROK_AUTH_TOKEN: token,
|
|
194
|
+
DATAGROK_API_URL: url,
|
|
195
|
+
DATAGROK_DEV_KEY: key,
|
|
171
196
|
PLAYWRIGHT_JSON_OUTPUT_NAME: reportFile
|
|
172
197
|
};
|
|
173
198
|
if (token2) env.DATAGROK_AUTH_TOKEN_2 = token2;
|
|
@@ -219,20 +244,18 @@ async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
|
219
244
|
} catch {/* ignore */}
|
|
220
245
|
}
|
|
221
246
|
if (!report) {
|
|
222
|
-
color.error('Playwright: no JSON report produced.');
|
|
223
247
|
const tail = Buffer.concat(stderrChunks).toString('utf-8').slice(-2000);
|
|
224
|
-
return {
|
|
225
|
-
...empty,
|
|
226
|
-
failed: true,
|
|
227
|
-
failedAmount: 1,
|
|
228
|
-
verboseFailed: `Playwright: no JSON report. stderr tail:\n${tail}\n`
|
|
229
|
-
};
|
|
248
|
+
return runnerFailure(pkgDir, args, `no JSON report produced — the suite never ran. stderr tail:\n${tail}`);
|
|
230
249
|
}
|
|
231
250
|
const pkgJson = JSON.parse(_fs.default.readFileSync(_path.default.join(pkgDir, 'package.json'), 'utf-8'));
|
|
232
251
|
const owner = pkgJson.author && (pkgJson.author.email || pkgJson.author) || '';
|
|
233
252
|
const pkgName = process.env.TARGET_PACKAGE || args.package || pkgJson.name || '';
|
|
234
253
|
const rows = [];
|
|
235
254
|
flattenSuites(report.suites, testDir, pkgName, typeof owner === 'string' ? owner : '', args.verbose === true, rows);
|
|
255
|
+
|
|
256
|
+
// A report with no specs is the same non-event as no report at all: the config loaded but
|
|
257
|
+
// collection found nothing, or died on a spec that cannot be imported.
|
|
258
|
+
if (rows.length === 0) return runnerFailure(pkgDir, args, 'the run produced a report with no specs — check the spec imports and testMatch');
|
|
236
259
|
let passedAmount = 0;
|
|
237
260
|
let failedAmount = 0;
|
|
238
261
|
let skippedAmount = 0;
|
|
@@ -253,14 +276,7 @@ async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
|
253
276
|
}
|
|
254
277
|
}
|
|
255
278
|
const csv = rowsToCsv(rows);
|
|
256
|
-
|
|
257
|
-
// 'playwright' bucket, separate from the merged Puppeteer+Playwright
|
|
258
|
-
// test-report.csv that feeds the legacy 'package' bucket and JUnit.
|
|
259
|
-
try {
|
|
260
|
-
_fs.default.writeFileSync(_path.default.join(pkgDir, 'test-report-playwright.csv'), csv, 'utf-8');
|
|
261
|
-
} catch (e) {
|
|
262
|
-
color.warn(`Playwright: failed to write test-report-playwright.csv: ${e.message || e}`);
|
|
263
|
-
}
|
|
279
|
+
writePlaywrightCsv(pkgDir, csv);
|
|
264
280
|
return {
|
|
265
281
|
failed: failedAmount > 0,
|
|
266
282
|
passedAmount: passedAmount,
|
|
@@ -6,10 +6,23 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.createClient = createClient;
|
|
7
7
|
var _nodeDapi = require("./node-dapi");
|
|
8
8
|
var _testUtils = require("./test-utils");
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `--admin` asks the server for an admin session, which lifts the permission filter for this run:
|
|
11
|
+
* without it a stand-wide pull sees only what the key's own account can, and content in other
|
|
12
|
+
* people's spaces is invisible. The server authorises it (`START_ADMIN_SESSION`) and signs the
|
|
13
|
+
* flag into the token, and a dev key mints a fresh session per invocation, so it cannot outlive
|
|
14
|
+
* the command or reach another session.
|
|
15
|
+
*/
|
|
16
|
+
async function createClient(hostArg, admin = false) {
|
|
10
17
|
const {
|
|
11
18
|
url,
|
|
12
19
|
key
|
|
13
20
|
} = (0, _testUtils.getDevKey)(hostArg ?? '');
|
|
14
|
-
|
|
21
|
+
const client = await _nodeDapi.NodeApiClient.login(url, key);
|
|
22
|
+
if (!admin) return client;
|
|
23
|
+
const token = (await client.post('/users/sessions/current/admin'))?.token;
|
|
24
|
+
if (!token) throw new Error(`${url} refused an admin session — the account behind this key cannot start one`);
|
|
25
|
+
client.token = token;
|
|
26
|
+
client.adminMode = true;
|
|
27
|
+
return client;
|
|
15
28
|
}
|
|
@@ -9,8 +9,16 @@ exports.getKeys = getKeys;
|
|
|
9
9
|
exports.printBatchOutput = printBatchOutput;
|
|
10
10
|
exports.printError = printError;
|
|
11
11
|
exports.printOutput = printOutput;
|
|
12
|
+
exports.progressReporter = progressReporter;
|
|
13
|
+
exports.setOutputFormat = setOutputFormat;
|
|
12
14
|
/// Docs: [Grok Dapi](/docs/plans/grok-dapi/)
|
|
13
15
|
|
|
16
|
+
let errorFormat = 'table';
|
|
17
|
+
|
|
18
|
+
/** Set once per run so every `printError` call site reports in the requested format. */
|
|
19
|
+
function setOutputFormat(format) {
|
|
20
|
+
errorFormat = format;
|
|
21
|
+
}
|
|
14
22
|
function printOutput(data, format) {
|
|
15
23
|
if (data === null || data === undefined) {
|
|
16
24
|
if (format !== 'quiet') console.log('(empty)');
|
|
@@ -118,10 +126,63 @@ function printBatchOutput(response, format) {
|
|
|
118
126
|
error: r.error
|
|
119
127
|
})), null, 2) + '\n');
|
|
120
128
|
}
|
|
121
|
-
function printError(err) {
|
|
129
|
+
function printError(err, opts = {}) {
|
|
122
130
|
const apiErr = err?.apiError;
|
|
123
|
-
const
|
|
124
|
-
|
|
131
|
+
const status = apiErr?.errorCode;
|
|
132
|
+
const base = apiErr?.error ?? String(err?.message ?? err);
|
|
133
|
+
const message = typeof status === 'number' && status >= 400 && !base.includes(String(status)) ? `${base} (HTTP ${status})` : base;
|
|
134
|
+
if (errorFormat === 'json') {
|
|
135
|
+
process.stderr.write(JSON.stringify({
|
|
136
|
+
...apiErr,
|
|
137
|
+
error: message
|
|
138
|
+
}) + '\n');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
process.stderr.write(`${message}\n`);
|
|
142
|
+
printErrorDetails(apiErr?.body);
|
|
143
|
+
if (opts.verbose && apiErr?.stackTrace) process.stderr.write(apiErr.stackTrace + '\n');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Structured fields of a domain error envelope: per-row validation errors, manifest errors, a plan awaiting confirmation. */
|
|
147
|
+
function printErrorDetails(body) {
|
|
148
|
+
if (!body || typeof body !== 'object') return;
|
|
149
|
+
for (const r of Array.isArray(body.rows) ? body.rows : []) for (const e of Array.isArray(r?.errors) ? r.errors : []) process.stderr.write(` row ${r.index ?? ''}${e?.column ? ` ${e.column}` : ''}: ${e?.message ?? e?.code ?? ''}\n`);
|
|
150
|
+
for (const e of Array.isArray(body.errors) ? body.errors : []) process.stderr.write(` ${typeof e === 'string' ? e : e?.message ?? JSON.stringify(e)}\n`);
|
|
151
|
+
if (body.plan) process.stderr.write(JSON.stringify(body.plan, null, 2) + '\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Progress for the long migration walks. A whole-stand pull is minutes of network waiting with
|
|
156
|
+
* nothing to show for it, and silence is indistinguishable from a hang. Goes to stderr, so it
|
|
157
|
+
* shows even under `--output json` without disturbing the document, and rewrites a single line
|
|
158
|
+
* on a terminal.
|
|
159
|
+
*/
|
|
160
|
+
function progressReporter(quiet = false) {
|
|
161
|
+
if (quiet) return () => {};
|
|
162
|
+
const tty = process.stderr.isTTY;
|
|
163
|
+
let last = '';
|
|
164
|
+
let drawn = 0;
|
|
165
|
+
return (stage, done, total) => {
|
|
166
|
+
const count = done === undefined ? '' : total === undefined ? ` ${done}` : ` ${done}/${total}`;
|
|
167
|
+
const line = `${stage}${count}`;
|
|
168
|
+
if (!tty) {
|
|
169
|
+
// A log cannot be rewritten in place, so it gets a line every so often rather than one per
|
|
170
|
+
// item — enough to show the run is alive, few enough to stay readable.
|
|
171
|
+
if (line !== last && (done === undefined || done === total || done % 250 === 0)) process.stderr.write(`${line}\n`);
|
|
172
|
+
last = line;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Redrawing on every item writes megabytes a minute into anything that is not really a
|
|
176
|
+
// terminal — a detached run produced a 7 GB file of carriage returns. The eye cannot
|
|
177
|
+
// follow faster than this anyway.
|
|
178
|
+
const finished = done !== undefined && done === total;
|
|
179
|
+
if (!finished && Date.now() - drawn < 100) return;
|
|
180
|
+
drawn = Date.now();
|
|
181
|
+
process.stderr.write(`\r${' '.repeat(last.length)}\r${line}`);
|
|
182
|
+
last = line;
|
|
183
|
+
if (finished) {
|
|
184
|
+
process.stderr.write('\n');
|
|
185
|
+
last = '';
|
|
186
|
+
}
|
|
125
187
|
};
|
|
126
|
-
process.stderr.write(JSON.stringify(out, null, 2) + '\n');
|
|
127
188
|
}
|
package/bin/utils/test-utils.js
CHANGED
|
@@ -127,7 +127,7 @@ function getDevKey(hostKey) {
|
|
|
127
127
|
let key = '';
|
|
128
128
|
let url = '';
|
|
129
129
|
try {
|
|
130
|
-
|
|
130
|
+
url = new URL(host).href;
|
|
131
131
|
if (url.endsWith('/')) url = url.slice(0, -1);
|
|
132
132
|
if (url in urls) key = config['servers'][urls[url]]['key'];
|
|
133
133
|
} catch (error) {
|
|
@@ -77,12 +77,12 @@
|
|
|
77
77
|
"promotion": {
|
|
78
78
|
"enum": ["lazy", "eager"],
|
|
79
79
|
"default": "lazy",
|
|
80
|
-
"description": "Row mode only. lazy: entities row created on first share; eager: at insert."
|
|
80
|
+
"description": "Row mode only. lazy: entities row created on first share; eager: at insert, with the author granted View/Edit/Delete/Share."
|
|
81
81
|
},
|
|
82
82
|
"defaultRowVisibility": {
|
|
83
83
|
"enum": ["table", "none"],
|
|
84
84
|
"default": "table",
|
|
85
|
-
"description": "Row/master modes: whether table-level View shows unshared rows."
|
|
85
|
+
"description": "Row/master modes: whether table-level View shows unshared rows. Their author always sees, edits and shares them; none hides them from everyone else."
|
|
86
86
|
},
|
|
87
87
|
"delegate": {
|
|
88
88
|
"$ref": "#/definitions/identifier",
|
|
@@ -138,6 +138,29 @@
|
|
|
138
138
|
"items": {"$ref": "#/definitions/identifier"},
|
|
139
139
|
"description": "Property schemas contributing jsonb keys to this table."
|
|
140
140
|
},
|
|
141
|
+
"constraints": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"description": "Table-level CHECK constraints keyed by constraint name. The expression may reference this table's relational columns, literals, comparison/arithmetic operators and a whitelist of keywords and immutable functions; it is normalized before it reaches the DDL.",
|
|
144
|
+
"propertyNames": {"$ref": "#/definitions/identifier"},
|
|
145
|
+
"additionalProperties": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"required": ["check"],
|
|
148
|
+
"additionalProperties": false,
|
|
149
|
+
"properties": {
|
|
150
|
+
"check": {"type": "string"}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"grants": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"description": "Permissions granted on the table entity at deploy time, keyed by group name (a platform group such as 'All users' or 'Developers'); each lists the permissions to grant.",
|
|
157
|
+
"minProperties": 1,
|
|
158
|
+
"additionalProperties": {
|
|
159
|
+
"type": "array",
|
|
160
|
+
"minItems": 1,
|
|
161
|
+
"items": {"enum": ["view", "edit", "delete"]}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
141
164
|
"relations": {
|
|
142
165
|
"type": "object",
|
|
143
166
|
"description": "Many-to-many relations of this table keyed by relation name: each links the table to a target table through a junction table. The name shares one namespace with the columns (it is what expand and filter paths address), so it may not collide with a column of this table. Declaration order is preserved and is the order every UI surface lays the relations out in.",
|
|
@@ -211,8 +234,8 @@
|
|
|
211
234
|
"additionalProperties": false,
|
|
212
235
|
"properties": {
|
|
213
236
|
"type": {
|
|
214
|
-
"enum": ["string", "int", "float", "bool", "datetime", "string_list", "ref", "
|
|
215
|
-
"description": "Property type; ref = FK to another table in this manifest; user/group =
|
|
237
|
+
"enum": ["string", "int", "float", "bool", "datetime", "string_list", "ref", "file", "json", "user", "group"],
|
|
238
|
+
"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)."
|
|
216
239
|
},
|
|
217
240
|
"required": {"type": "boolean", "default": false},
|
|
218
241
|
"unique": {
|
|
@@ -220,14 +243,42 @@
|
|
|
220
243
|
"default": false,
|
|
221
244
|
"description": "Unique among live (not soft-deleted) rows."
|
|
222
245
|
},
|
|
246
|
+
"immutable": {
|
|
247
|
+
"type": "boolean",
|
|
248
|
+
"default": false,
|
|
249
|
+
"description": "Write-once: the first non-null value wins; a later change is refused."
|
|
250
|
+
},
|
|
251
|
+
"autoNumber": {
|
|
252
|
+
"description": "Auto-numbering (int columns only; implies immutable): a blank value on insert is assigned from a monotonic counter, a supplied value is kept (the counter catches up). true = one counter per table; {scope} = one counter per master row of the named ref column of this table. Changing it on an existing column (on, off, or a scope change) is a physical change that needs a migration.",
|
|
253
|
+
"oneOf": [
|
|
254
|
+
{"enum": [true]},
|
|
255
|
+
{
|
|
256
|
+
"type": "object",
|
|
257
|
+
"additionalProperties": false,
|
|
258
|
+
"properties": {
|
|
259
|
+
"scope": {
|
|
260
|
+
"$ref": "#/definitions/identifier",
|
|
261
|
+
"description": "A required ref column of this table; numbering restarts per referenced row."
|
|
262
|
+
},
|
|
263
|
+
"start": {
|
|
264
|
+
"type": "integer",
|
|
265
|
+
"minimum": 1,
|
|
266
|
+
"default": 1,
|
|
267
|
+
"description": "First number assigned by a fresh counter."
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
},
|
|
223
273
|
"isName": {
|
|
224
274
|
"type": "boolean",
|
|
225
275
|
"default": false,
|
|
226
276
|
"description": "Marks the primary display-name column (string columns only; at most one per table). Its value titles cards, tooltips, and entity views, and becomes the friendly name of promoted rows. Convention fallback: a string column literally named 'name'."
|
|
227
277
|
},
|
|
228
278
|
"ref": {
|
|
229
|
-
"
|
|
230
|
-
"
|
|
279
|
+
"type": "string",
|
|
280
|
+
"pattern": "^([a-z][a-z0-9_]*|[A-Za-z][A-Za-z0-9_]*\\.[a-z][a-z0-9_]*)$",
|
|
281
|
+
"description": "Target table (type = ref only): a table of this schema, or the qualified '<Schema>.<table>' form for any registered table — a Core table such as 'Core.users' or 'Core.queries', or a table of another plugin's schema. The reference is a hard foreign key only for same-schema targets and Core.users/Core.groups; every other target is a soft reference (no FK constraint) that may dangle after the target is hard-deleted."
|
|
231
282
|
},
|
|
232
283
|
"onDelete": {
|
|
233
284
|
"enum": ["cascade", "restrict", "setnull"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datagrok-tools",
|
|
3
|
-
"
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "https://github.com/datagrok-ai/public.git"
|
|
6
|
+
},
|
|
7
|
+
"version": "6.5.8",
|
|
4
8
|
"description": "Utility to upload and publish packages to Datagrok",
|
|
5
9
|
"homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
|
|
6
10
|
"dependencies": {
|
|
@@ -32,6 +36,7 @@
|
|
|
32
36
|
"update:ivp-parser": "esbuild plugins/ivp-parser.entry.mjs --bundle --format=cjs --platform=node --alias:diff-grok=../libraries/compute-utils/node_modules/diff-grok --outfile=plugins/ivp-parser.bundle.cjs",
|
|
33
37
|
"debug-source-map": "node build.js --source-maps",
|
|
34
38
|
"test": "vitest run --project unit",
|
|
39
|
+
"test:server": "vitest run --project unit bin/__tests__/node-dapi bin/__tests__/server bin/__tests__/migrate",
|
|
35
40
|
"test:watch": "vitest --project unit",
|
|
36
41
|
"test:integration": "vitest run --project integration",
|
|
37
42
|
"test:all": "vitest run"
|
|
File without changes
|