datagrok-tools 6.1.1 → 6.1.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.
@@ -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,43 @@ 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
+ hash.update(`dir:${entry.relPath}:`);
129
+ } else {
130
+ hash.update(`file:${entry.relPath}:`);
131
+ hash.update(_fs.default.readFileSync(entry.fullPath));
132
+ }
133
+ }
134
+ return hash.digest('hex');
135
+ }
136
+ function listRecursive(basePath, rel) {
137
+ const results = [];
138
+ const fullDir = rel ? _path.default.join(basePath, rel) : basePath;
139
+ for (const entry of _fs.default.readdirSync(fullDir, {
140
+ withFileTypes: true
141
+ })) {
142
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
143
+ const fullPath = _path.default.join(fullDir, entry.name);
144
+ if (entry.isDirectory()) {
145
+ results.push({
146
+ relPath,
147
+ fullPath,
148
+ isDir: true
149
+ });
150
+ results.push(...listRecursive(basePath, relPath));
151
+ } else results.push({
152
+ relPath,
153
+ fullPath,
154
+ isDir: false
155
+ });
156
+ }
157
+ return results;
158
+ }
118
159
  async function getUserLogin(host, devKey) {
119
160
  let loginResp;
120
161
  try {
@@ -144,14 +185,18 @@ async function getUserLogin(host, devKey) {
144
185
  return null;
145
186
  }
146
187
  }
147
- async function resolveLatestCompatible(host, devKey, dockerName) {
188
+ async function resolveLatestCompatible(host, devKey, dockerName, version, contentHash) {
148
189
  const userInfo = await getUserLogin(host, devKey);
149
190
  if (!userInfo) return {
150
191
  found: null,
151
192
  serverError: `Authentication failed. Check your developer key.`
152
193
  };
153
194
  try {
154
- const url = `${host}/docker/images/${encodeURIComponent(dockerName)}/latest-compatible`;
195
+ let url = `${host}/docker/images/${encodeURIComponent(dockerName)}/latest-compatible`;
196
+ const params = [];
197
+ if (version) params.push(`version=${encodeURIComponent(version)}`);
198
+ if (contentHash) params.push(`contentHash=${encodeURIComponent(contentHash)}`);
199
+ if (params.length > 0) url += '?' + params.join('&');
155
200
  const resp = await (0, _nodeFetch.default)(url, {
156
201
  headers: {
157
202
  'Authorization': userInfo.token
@@ -176,29 +221,31 @@ async function resolveLatestCompatible(host, devKey, dockerName) {
176
221
  };
177
222
  }
178
223
  }
179
- async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug) {
224
+ async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug, skipDockerRebuild = false) {
180
225
  const dockerImages = discoverDockerfiles(packageName, version, debug);
181
226
  if (dockerImages.length === 0) return;
182
227
  color.log(`Found ${dockerImages.length} Dockerfile(s)`);
183
228
  if (registry) dockerLogin(registry, devKey);
184
229
  for (const img of dockerImages) {
230
+ color.info(`Processing docker image ${img.fullLocalName}...`);
185
231
  let result;
232
+ const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
233
+ const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
234
+ const contentHash = calculateFolderHash(_path.default.join(curDir, dockerfileDir));
186
235
  if (rebuildDocker) {
187
- const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
188
- const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
189
236
  if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
190
237
  result = pushImage(img, registry);
191
238
  color.success(`Built and tagged ${img.fullLocalName}`);
192
239
  } else {
193
- result = await fallbackImage(img, host, devKey, registry);
240
+ result = await fallbackImage(img, host, devKey, registry, version, contentHash);
194
241
  }
195
242
  } else if (localImageExists(img.fullLocalName)) {
196
- result = pushImage(img, registry);
197
243
  color.success(`Found local image ${img.fullLocalName}`);
244
+ result = pushImage(img, registry);
198
245
  } else {
199
246
  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);
247
+ color.log(` Build it with: docker build -t ${img.fullLocalName} -f ${dockerfilePath} ${dockerfileDir}`);
248
+ const fallback = await fallbackImage(img, host, devKey, registry, version, contentHash);
202
249
  if (fallback.serverError) {
203
250
  color.error(`Cannot resolve fallback: ${fallback.serverError}`);
204
251
  result = {
@@ -206,14 +253,25 @@ async function processDockerImages(packageName, version, registry, devKey, host,
206
253
  fallback: true,
207
254
  requestedVersion: img.imageTag
208
255
  };
209
- } else if (fallback.image) {
256
+ } else if (fallback.image && fallback.hashMatch === true) {
210
257
  result = fallback;
211
- color.warn(`Falling back to ${fallback.image}`);
258
+ color.success(`Falling back to ${fallback.image} (dockerfile unchanged)`);
259
+ } else if (fallback.image && fallback.hashMatch === false && !skipDockerRebuild) {
260
+ color.warn(`Dockerfile folder has changed. Rebuilding image...`);
261
+ if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
262
+ result = pushImage(img, registry);
263
+ color.success(`Built and tagged ${img.fullLocalName}`);
264
+ } else {
265
+ result = {
266
+ image: fallback.image,
267
+ fallback: true,
268
+ requestedVersion: img.imageTag
269
+ };
270
+ color.warn(`Build failed. Falling back to ${fallback.image} (hash mismatch)`);
271
+ }
212
272
  } else {
213
273
  // No fallback and no local image — must build
214
274
  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
275
  if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
218
276
  result = pushImage(img, registry);
219
277
  color.success(`Built and tagged ${img.fullLocalName}`);
@@ -256,11 +314,11 @@ function pushImage(img, registry) {
256
314
  fallback: false
257
315
  };
258
316
  }
259
- async function fallbackImage(img, host, devKey, registry) {
317
+ async function fallbackImage(img, host, devKey, registry, version, contentHash) {
260
318
  const {
261
319
  found,
262
320
  serverError
263
- } = await resolveLatestCompatible(host, devKey, img.imageName);
321
+ } = await resolveLatestCompatible(host, devKey, img.imageName, version, contentHash);
264
322
  if (serverError) return {
265
323
  image: null,
266
324
  fallback: true,
@@ -270,7 +328,8 @@ async function fallbackImage(img, host, devKey, registry) {
270
328
  if (found) return {
271
329
  image: found.image,
272
330
  fallback: true,
273
- requestedVersion: img.imageTag
331
+ requestedVersion: img.imageTag,
332
+ hashMatch: found.hashMatch
274
333
  };
275
334
  return {
276
335
  image: null,
@@ -278,7 +337,7 @@ async function fallbackImage(img, host, devKey, registry) {
278
337
  requestedVersion: img.imageTag
279
338
  };
280
339
  }
281
- async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker) {
340
+ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker, skipDockerRebuild) {
282
341
  // Validate server connectivity and dev key
283
342
  let timestamps = {};
284
343
  let url = `${host}/packages/dev/${devKey}/${packageName}`;
@@ -400,7 +459,7 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
400
459
  const userInfo = await getUserLogin(host, devKey);
401
460
  if (userInfo) dockerVersion = userInfo.login;
402
461
  }
403
- await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug);
462
+ await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug, skipDockerRebuild ?? false);
404
463
  zip.append(JSON.stringify(localTimestamps), {
405
464
  name: 'timestamps.json'
406
465
  });
@@ -545,7 +604,7 @@ async function publishPackage(args) {
545
604
  if (!args.suffix && stdout) args.suffix = stdout.toString().substring(0, 8);
546
605
  });
547
606
  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']);
607
+ 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
608
  } catch (error) {
550
609
  console.error(error);
551
610
  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.3",
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": {