datagrok-tools 6.4.1 → 6.4.3
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 +8 -0
- package/bin/commands/publish.js +49 -12
- package/bin/utils/playwright-runner.js +7 -1
- package/package.json +1 -1
- package/plugins/func-gen-plugin.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Datagrok-tools changelog
|
|
2
2
|
|
|
3
|
+
## 6.4.3 (2026-06-22)
|
|
4
|
+
|
|
5
|
+
* `grok api` — numeric IVP model inputs are now generated with `nullable: false`, so an emptied input field fails form validation instead of running the solver with a null.
|
|
6
|
+
|
|
7
|
+
## 6.4.2 (2026-06-18)
|
|
8
|
+
|
|
9
|
+
* `grok publish` — registry-aware Docker fallback: when a package's image isn't built locally and the target server has no compatible record, `grok publish` now checks the configured registry and Docker Hub (`docker manifest inspect`) for the expected `datagrok/<name>:<version>` (and content-hashed) tag and uses it, instead of reporting "No fallback available" and failing. Fixes dependency publishes (e.g. Bio → @datagrok/chem) on CI runners where the image exists in the registry but not locally.
|
|
10
|
+
|
|
3
11
|
## 6.4.1 (2026-06-18)
|
|
4
12
|
|
|
5
13
|
* Fixed `grok` failing with `Cannot find module './commands/build'` — the `.npmignore` `build.js` rule was unanchored and excluded the compiled `bin/commands/build.js` from the published package; anchored it to `/build.js`.
|
package/bin/commands/publish.js
CHANGED
|
@@ -95,6 +95,30 @@ function localImageExists(fullName, checkPlatform = true) {
|
|
|
95
95
|
return false;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
function imageExistsInRegistry(ref) {
|
|
99
|
+
try {
|
|
100
|
+
execSync(`docker manifest inspect ${ref}`, {
|
|
101
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
102
|
+
});
|
|
103
|
+
return true;
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Looks for an already-published image in the configured registry and on Docker Hub,
|
|
110
|
+
// without pulling it. Prefers the content-hashed release tag (exact dockerfile match),
|
|
111
|
+
// then the plain version tag. Returns the canonical `datagrok/<name>:<tag>` reference
|
|
112
|
+
// for image.json, or null when nothing usable is published.
|
|
113
|
+
function resolveRegistryImage(imageName, registryTag, versionTag, registry) {
|
|
114
|
+
const tags = registryTag === versionTag ? [versionTag] : [registryTag, versionTag];
|
|
115
|
+
for (const tag of tags) {
|
|
116
|
+
const canonical = `datagrok/${imageName}:${tag}`;
|
|
117
|
+
if (registry && imageExistsInRegistry(`${registry}/${canonical}`)) return canonical;
|
|
118
|
+
if (imageExistsInRegistry(canonical)) return canonical;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
98
122
|
function dockerLogin(registry, devKey) {
|
|
99
123
|
try {
|
|
100
124
|
dockerCommand(`login ${registry} -u any -p ${devKey}`);
|
|
@@ -333,24 +357,37 @@ async function processDockerImages(packageName, version, registry, devKey, host,
|
|
|
333
357
|
requestedVersion: registryTag
|
|
334
358
|
};
|
|
335
359
|
if (!result || result.fallback) color.warn(`Build failed. Falling back to ${fallback.image} (hash mismatch)`);
|
|
336
|
-
} else if (skipDockerRebuild) {
|
|
337
|
-
color.warn(`No fallback available. Skipping docker build (--skip-docker-rebuild).`);
|
|
338
|
-
result = {
|
|
339
|
-
image: null,
|
|
340
|
-
fallback: true,
|
|
341
|
-
requestedVersion: registryTag
|
|
342
|
-
};
|
|
343
360
|
} else {
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
361
|
+
// The server has no compatible record, but the image may already be
|
|
362
|
+
// published in the configured registry / Docker Hub (e.g. pushed by an
|
|
363
|
+
// earlier CI run). Use it directly rather than failing or rebuilding.
|
|
364
|
+
const registryImage = resolveRegistryImage(img.imageName, registryTag, img.imageTag, registry);
|
|
365
|
+
if (registryImage) {
|
|
366
|
+
result = {
|
|
367
|
+
image: registryImage,
|
|
368
|
+
fallback: true,
|
|
369
|
+
requestedVersion: registryTag
|
|
370
|
+
};
|
|
371
|
+
color.success(`Falling back to registry image ${registryImage}`);
|
|
372
|
+
} else if (skipDockerRebuild) {
|
|
373
|
+
color.warn(`No fallback available. Skipping docker build (--skip-docker-rebuild).`);
|
|
348
374
|
result = {
|
|
349
375
|
image: null,
|
|
350
376
|
fallback: true,
|
|
351
377
|
requestedVersion: registryTag
|
|
352
378
|
};
|
|
353
|
-
|
|
379
|
+
} else {
|
|
380
|
+
// No fallback and no local image — must build
|
|
381
|
+
color.warn(`No fallback available. Building ${img.fullLocalName}...`);
|
|
382
|
+
const built = buildAndPush();
|
|
383
|
+
if (built) result = built;else {
|
|
384
|
+
result = {
|
|
385
|
+
image: null,
|
|
386
|
+
fallback: true,
|
|
387
|
+
requestedVersion: registryTag
|
|
388
|
+
};
|
|
389
|
+
color.error(`Failed to build ${img.fullLocalName}. No container will be available.`);
|
|
390
|
+
}
|
|
354
391
|
}
|
|
355
392
|
}
|
|
356
393
|
}
|
|
@@ -157,7 +157,13 @@ async function runPlaywrightTests(pkgDir, testDir, args, hostKey) {
|
|
|
157
157
|
const candidate = _path.default.join(testDir, args.category);
|
|
158
158
|
if (_fs.default.existsSync(candidate)) testDirFinal = candidate;
|
|
159
159
|
}
|
|
160
|
-
if (testDirFinal !== testDir)
|
|
160
|
+
if (testDirFinal !== testDir)
|
|
161
|
+
// Normalize Windows backslashes to forward slashes — Playwright CLI on Windows
|
|
162
|
+
// treats backslash positional args as regex patterns and emits "No tests found"
|
|
163
|
+
// when the resolved path contains backslashes. Linux path.join already emits
|
|
164
|
+
// forward slashes; this no-ops there. Per
|
|
165
|
+
// .claude/plan/investigator-handback-autocomplete-no-tests-2026-05-24.md.
|
|
166
|
+
cliArgs.push(testDirFinal.replace(/\\/g, '/'));
|
|
161
167
|
const env = {
|
|
162
168
|
...process.env,
|
|
163
169
|
DATAGROK_URL: webUrl,
|
package/package.json
CHANGED
|
@@ -51,9 +51,19 @@ function preparseIvpModel(parser, text) {
|
|
|
51
51
|
// Input names use the script-form names (arg bounds/step `_`-prefixed, loop count `_count`) so the
|
|
52
52
|
// run path, the diff-grok fitting/SA pipeline, and `propagateChoice` lookups all agree on one set
|
|
53
53
|
// of names. `scriptKey` mirrors `name`; it is kept only for the value-forwarding map below.
|
|
54
|
+
// Numeric IVP inputs are always required: mark them `nullable: false` so an emptied field fails
|
|
55
|
+
// form validation instead of running with a null (which throws in the solver).
|
|
56
|
+
const withNullable = (annot) => {
|
|
57
|
+
const a = (annot ?? '').trim();
|
|
58
|
+
if (!a.startsWith('{')) return '{nullable: false}'; // no options block
|
|
59
|
+
if (/[{;\s]nullable\s*:/.test(a)) return a; // already declares nullable (any value)
|
|
60
|
+
if (/^\{\s*\}$/.test(a)) return '{nullable: false}'; // empty {}
|
|
61
|
+
return `{nullable: false; ${a.slice(1).trimStart()}`; // everything after '{' untouched
|
|
62
|
+
};
|
|
63
|
+
|
|
54
64
|
const mk = (type, name, scriptKey, input) =>
|
|
55
65
|
({tsType: 'number', name, scriptKey,
|
|
56
|
-
annotation: `//input: ${type} ${name} = ${input.value} ${input.annot
|
|
66
|
+
annotation: `//input: ${type} ${name} = ${input.value} ${withNullable(input.annot)}`.trim()});
|
|
57
67
|
|
|
58
68
|
const inputs = [];
|
|
59
69
|
const a = ivp.arg.name;
|