datagrok-tools 6.1.1 → 6.1.4

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.
@@ -158,16 +158,17 @@ Uploads a package
158
158
  Checks for errors before publishing — the package won't be published if there are any.
159
159
 
160
160
  Options:
161
- [--all] [--refresh] [--link] [--build] [--release] [--rebuild-docker] [--skip-check] [-v | --verbose]
162
-
163
- --all Publish all available packages (run in packages directory)
164
- --refresh Publish all available already loaded packages (run in packages directory)
165
- --link Link the package to local packages
166
- --build Builds the package
167
- --release Publish package as release version
168
- --rebuild-docker Force rebuild Docker images locally before pushing to registry
169
- --skip-check Skip check stage
170
- --verbose Print detailed output
161
+ [--all] [--refresh] [--link] [--build] [--release] [--rebuild-docker] [--skip-docker-rebuild] [--skip-check] [-v | --verbose]
162
+
163
+ --all Publish all available packages (run in packages directory)
164
+ --refresh Publish all available already loaded packages (run in packages directory)
165
+ --link Link the package to local packages
166
+ --build Builds the package
167
+ --release Publish package as release version
168
+ --rebuild-docker Force rebuild Docker images locally before pushing to registry
169
+ --skip-docker-rebuild Skip auto-rebuild when Dockerfile folder has changed
170
+ --skip-check Skip check stage
171
+ --verbose Print detailed output
171
172
 
172
173
  Running \`grok publish\` is the same as running \`grok publish defaultHost --build --debug\`
173
174
  `;
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
7
7
  exports.processPackage = processPackage;
8
8
  exports.publish = publish;
9
9
  var _archiverPromise = _interopRequireDefault(require("archiver-promise"));
10
+ var _crypto = _interopRequireDefault(require("crypto"));
10
11
  var _fs = _interopRequireDefault(require("fs"));
11
12
  var _nodeFetch = _interopRequireDefault(require("node-fetch"));
12
13
  var _os = _interopRequireDefault(require("os"));
@@ -49,7 +50,7 @@ function discoverDockerfiles(packageName, version, debug) {
49
50
  if (!entry.isDirectory()) continue;
50
51
  const dockerfilePath = _path.default.join(dockerfilesDir, entry.name, 'Dockerfile');
51
52
  if (!_fs.default.existsSync(dockerfilePath)) continue;
52
- const cleanName = utils.removeScope(packageName).replace(/-/g, '').toLowerCase();
53
+ const cleanName = utils.removeScope(packageName).toLowerCase();
53
54
  const imageName = `${cleanName}-${entry.name.toLowerCase()}`;
54
55
  const imageTag = debug ? version : `${version}.X`;
55
56
  results.push({
@@ -95,7 +96,10 @@ function dockerTag(source, target) {
95
96
  }
96
97
  function dockerPush(image) {
97
98
  try {
98
- dockerCommand(`push ${image}`);
99
+ execSync(`docker push ${image}`, {
100
+ encoding: 'utf-8',
101
+ stdio: 'inherit'
102
+ });
99
103
  return true;
100
104
  } catch (e) {
101
105
  color.error(`Failed to push ${image}: ${e.message || e}`);
@@ -115,6 +119,50 @@ function dockerBuild(imageName, dockerfilePath, context) {
115
119
  return false;
116
120
  }
117
121
  }
122
+ function calculateFolderHash(dirPath) {
123
+ const hash = _crypto.default.createHash('sha256');
124
+ const entries = listRecursive(dirPath, '');
125
+ entries.sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0);
126
+ for (const entry of entries) {
127
+ if (entry.isDir) {
128
+ color.log(` Hash entry: dir:${entry.relPath}:`);
129
+ hash.update(`dir:${entry.relPath}:`);
130
+ } else {
131
+ // Normalize CRLF to LF to match server-side storage
132
+ const raw = _fs.default.readFileSync(entry.fullPath);
133
+ const content = raw.includes(0x0d) && !raw.includes(0x00) ? Buffer.from(raw.toString('utf8').replace(/\r\n/g, '\n'), 'utf8') : raw;
134
+ color.log(` Hash entry: file:${entry.relPath}: (${content.length} bytes)`);
135
+ hash.update(`file:${entry.relPath}:`);
136
+ hash.update(content);
137
+ }
138
+ }
139
+ const result = hash.digest('hex');
140
+ color.log(` Folder hash: ${result}`);
141
+ return result;
142
+ }
143
+ function listRecursive(basePath, rel) {
144
+ const results = [];
145
+ const fullDir = rel ? _path.default.join(basePath, rel) : basePath;
146
+ for (const entry of _fs.default.readdirSync(fullDir, {
147
+ withFileTypes: true
148
+ })) {
149
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
150
+ const fullPath = _path.default.join(fullDir, entry.name);
151
+ if (entry.isDirectory()) {
152
+ results.push({
153
+ relPath,
154
+ fullPath,
155
+ isDir: true
156
+ });
157
+ results.push(...listRecursive(basePath, relPath));
158
+ } else results.push({
159
+ relPath,
160
+ fullPath,
161
+ isDir: false
162
+ });
163
+ }
164
+ return results;
165
+ }
118
166
  async function getUserLogin(host, devKey) {
119
167
  let loginResp;
120
168
  try {
@@ -144,14 +192,18 @@ async function getUserLogin(host, devKey) {
144
192
  return null;
145
193
  }
146
194
  }
147
- async function resolveLatestCompatible(host, devKey, dockerName) {
195
+ async function resolveLatestCompatible(host, devKey, dockerName, version, contentHash) {
148
196
  const userInfo = await getUserLogin(host, devKey);
149
197
  if (!userInfo) return {
150
198
  found: null,
151
199
  serverError: `Authentication failed. Check your developer key.`
152
200
  };
153
201
  try {
154
- const url = `${host}/docker/images/${encodeURIComponent(dockerName)}/latest-compatible`;
202
+ let url = `${host}/docker/images/${encodeURIComponent(dockerName)}/latest-compatible`;
203
+ const params = [];
204
+ if (version) params.push(`version=${encodeURIComponent(version)}`);
205
+ if (contentHash) params.push(`contentHash=${encodeURIComponent(contentHash)}`);
206
+ if (params.length > 0) url += '?' + params.join('&');
155
207
  const resp = await (0, _nodeFetch.default)(url, {
156
208
  headers: {
157
209
  'Authorization': userInfo.token
@@ -176,29 +228,31 @@ async function resolveLatestCompatible(host, devKey, dockerName) {
176
228
  };
177
229
  }
178
230
  }
179
- async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug) {
231
+ async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug, skipDockerRebuild = false) {
180
232
  const dockerImages = discoverDockerfiles(packageName, version, debug);
181
233
  if (dockerImages.length === 0) return;
182
234
  color.log(`Found ${dockerImages.length} Dockerfile(s)`);
183
235
  if (registry) dockerLogin(registry, devKey);
184
236
  for (const img of dockerImages) {
237
+ color.info(`Processing docker image ${img.fullLocalName}...`);
185
238
  let result;
239
+ const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
240
+ const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
241
+ const contentHash = calculateFolderHash(_path.default.join(curDir, dockerfileDir));
186
242
  if (rebuildDocker) {
187
- const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
188
- const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
189
243
  if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
190
244
  result = pushImage(img, registry);
191
245
  color.success(`Built and tagged ${img.fullLocalName}`);
192
246
  } else {
193
- result = await fallbackImage(img, host, devKey, registry);
247
+ result = await fallbackImage(img, host, devKey, registry, version, contentHash);
194
248
  }
195
249
  } else if (localImageExists(img.fullLocalName)) {
196
- result = pushImage(img, registry);
197
250
  color.success(`Found local image ${img.fullLocalName}`);
251
+ result = pushImage(img, registry);
198
252
  } else {
199
253
  color.warn(`Local image not found. Expected: ${img.fullLocalName}`);
200
- color.log(` Build it with: docker build -t ${img.fullLocalName} -f dockerfiles/${img.dirName}/Dockerfile dockerfiles/${img.dirName}`);
201
- const fallback = await fallbackImage(img, host, devKey, registry);
254
+ color.log(` Build it with: docker build -t ${img.fullLocalName} -f ${dockerfilePath} ${dockerfileDir}`);
255
+ const fallback = await fallbackImage(img, host, devKey, registry, version, contentHash);
202
256
  if (fallback.serverError) {
203
257
  color.error(`Cannot resolve fallback: ${fallback.serverError}`);
204
258
  result = {
@@ -206,14 +260,25 @@ async function processDockerImages(packageName, version, registry, devKey, host,
206
260
  fallback: true,
207
261
  requestedVersion: img.imageTag
208
262
  };
209
- } else if (fallback.image) {
263
+ } else if (fallback.image && fallback.hashMatch === true) {
210
264
  result = fallback;
211
- color.warn(`Falling back to ${fallback.image}`);
265
+ color.success(`Falling back to ${fallback.image} (dockerfile unchanged)`);
266
+ } else if (fallback.image && fallback.hashMatch === false && !skipDockerRebuild) {
267
+ color.warn(`Dockerfile folder has changed. Rebuilding image...`);
268
+ if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
269
+ result = pushImage(img, registry);
270
+ color.success(`Built and tagged ${img.fullLocalName}`);
271
+ } else {
272
+ result = {
273
+ image: fallback.image,
274
+ fallback: true,
275
+ requestedVersion: img.imageTag
276
+ };
277
+ color.warn(`Build failed. Falling back to ${fallback.image} (hash mismatch)`);
278
+ }
212
279
  } else {
213
280
  // No fallback and no local image — must build
214
281
  color.warn(`No fallback available. Building ${img.fullLocalName}...`);
215
- const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
216
- const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
217
282
  if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
218
283
  result = pushImage(img, registry);
219
284
  color.success(`Built and tagged ${img.fullLocalName}`);
@@ -256,11 +321,11 @@ function pushImage(img, registry) {
256
321
  fallback: false
257
322
  };
258
323
  }
259
- async function fallbackImage(img, host, devKey, registry) {
324
+ async function fallbackImage(img, host, devKey, registry, version, contentHash) {
260
325
  const {
261
326
  found,
262
327
  serverError
263
- } = await resolveLatestCompatible(host, devKey, img.imageName);
328
+ } = await resolveLatestCompatible(host, devKey, img.imageName, version, contentHash);
264
329
  if (serverError) return {
265
330
  image: null,
266
331
  fallback: true,
@@ -270,7 +335,8 @@ async function fallbackImage(img, host, devKey, registry) {
270
335
  if (found) return {
271
336
  image: found.image,
272
337
  fallback: true,
273
- requestedVersion: img.imageTag
338
+ requestedVersion: img.imageTag,
339
+ hashMatch: found.hashMatch
274
340
  };
275
341
  return {
276
342
  image: null,
@@ -278,7 +344,7 @@ async function fallbackImage(img, host, devKey, registry) {
278
344
  requestedVersion: img.imageTag
279
345
  };
280
346
  }
281
- async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker) {
347
+ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker, skipDockerRebuild) {
282
348
  // Validate server connectivity and dev key
283
349
  let timestamps = {};
284
350
  let url = `${host}/packages/dev/${devKey}/${packageName}`;
@@ -400,7 +466,7 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
400
466
  const userInfo = await getUserLogin(host, devKey);
401
467
  if (userInfo) dockerVersion = userInfo.login;
402
468
  }
403
- await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug);
469
+ await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug, skipDockerRebuild ?? false);
404
470
  zip.append(JSON.stringify(localTimestamps), {
405
471
  name: 'timestamps.json'
406
472
  });
@@ -545,7 +611,7 @@ async function publishPackage(args) {
545
611
  if (!args.suffix && stdout) args.suffix = stdout.toString().substring(0, 8);
546
612
  });
547
613
  await utils.delay(100);
548
- code = await processPackage(!args.release, Boolean(args.rebuild), url, key, packageName, args.dropDb ?? false, args.suffix, host, registry, args['rebuild-docker']);
614
+ code = await processPackage(!args.release, Boolean(args.rebuild), url, key, packageName, args.dropDb ?? false, args.suffix, host, registry, args['rebuild-docker'], args['skip-docker-rebuild']);
549
615
  } catch (error) {
550
616
  console.error(error);
551
617
  code = 1;
package/bin/grok.js CHANGED
@@ -27,6 +27,8 @@ const commands = {
27
27
  const onPackageCommandNames = ['api', 'check', 'link', 'publish', 'test'];
28
28
 
29
29
  const command = argv['_'][0];
30
+ if (command !== 'test' && command !== 'stresstest')
31
+ delete argv.dartium;
30
32
  if (command in commands) {
31
33
  try {
32
34
  if (argv['help']) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "6.1.1",
3
+ "version": "6.1.4",
4
4
  "description": "Utility to upload and publish packages to Datagrok",
5
5
  "homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
6
6
  "dependencies": {