datagrok-tools 5.0.0 → 5.1.2

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.
@@ -119,6 +119,7 @@ Options:
119
119
  --build Builds the package
120
120
  --release Publish package as release version
121
121
  --skip-check Skip check stage
122
+ --verbose Show debug information
122
123
 
123
124
  Running \`grok publish\` is the same as running \`grok publish defaultHost --build --debug\`
124
125
  `;
@@ -146,13 +147,13 @@ Options:
146
147
  --csv Save the test report in a CSV file
147
148
  --gui Launch graphical interface (non-headless mode)
148
149
  --debug Enables debug point on tests run (useless without gui mode)
149
- --catchUnhandled Catch unhandled exceptions during test execution (default=true)
150
+ --verbose Show debug information
151
+ --retry --no-retry Enables or disables browser reload after a failed test
150
152
  --report Report failed tests to audit, notifies package author (default=false)
151
153
  --skip-build Skip the package build step
152
154
  --skip-publish Skip the package publication step
153
- --link Link the package to local utils
155
+ --link Link the package to local utils
154
156
  --record Records the test execution process in mp4 format
155
- --verbose Prints detailed information about passed and skipped tests in the console
156
157
  --platform Runs only platform tests (applicable for ApiTests package only)
157
158
  --core Runs package & auto tests & core tests (core tests run only from DevTools package)
158
159
  --benchmark Runs tests in benchmark mode
@@ -35,8 +35,9 @@ let curDir = process.cwd();
35
35
  const packDir = _path.default.join(curDir, 'package.json');
36
36
  const packageFiles = ['src/package.ts', 'src/detectors.ts', 'src/package.js', 'src/detectors.js', 'src/package-test.ts', 'src/package-test.js', 'package.js', 'detectors.js'];
37
37
  let config;
38
- async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix) {
38
+ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, verbose) {
39
39
  // Get the server timestamps
40
+ verbose = verbose || false;
40
41
  let timestamps = {};
41
42
  let url = `${host}/packages/dev/${devKey}/${packageName}`;
42
43
  if (debug) {
@@ -47,13 +48,16 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
47
48
  return 1;
48
49
  }
49
50
  } catch (error) {
50
- console.error(error);
51
+ if (utils.isConnectivityError(error)) color.error(`Server is possibly offline: ${host}`);
52
+ if (verbose) console.error(error);
51
53
  return 1;
52
54
  }
53
55
  }
54
56
  const zip = (0, _archiverPromise.default)('zip', {
55
57
  store: false
56
58
  });
59
+ const chunks = [];
60
+ zip.on('data', chunk => chunks.push(chunk));
57
61
 
58
62
  // Gather the files
59
63
  const localTimestamps = {};
@@ -130,7 +134,6 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
130
134
  const t = _fs.default.statSync(fullPath).mtime.toUTCString();
131
135
  localTimestamps[canonicalRelativePath] = t;
132
136
  if (debug && timestamps[canonicalRelativePath] === t) {
133
- console.log(`Skipping ${canonicalRelativePath}`);
134
137
  return;
135
138
  }
136
139
  if (canonicalRelativePath.startsWith('connections/')) {
@@ -149,13 +152,11 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
149
152
  zip.append(f, {
150
153
  name: relativePath
151
154
  });
152
- console.log(`Adding ${canonicalRelativePath}...`);
153
155
  return;
154
156
  }
155
157
  zip.append(_fs.default.createReadStream(fullPath), {
156
158
  name: relativePath
157
159
  });
158
- console.log(`Adding ${canonicalRelativePath}...`);
159
160
  });
160
161
  if (errs.length) {
161
162
  errs.forEach(e => color.error(e));
@@ -168,37 +169,27 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
168
169
  // Upload
169
170
  url += `?debug=${debug.toString()}&rebuild=${rebuild.toString()}&dropDb=${(dropDb ?? false).toString()}`;
170
171
  if (suffix) url += `&suffix=${suffix.toString()}`;
171
- const uploadPromise = new Promise((resolve, reject) => {
172
- (0, _nodeFetch.default)(url, {
173
- method: 'POST',
174
- body: zip
175
- }).then(async body => {
176
- let response;
177
- try {
178
- response = await body.text();
179
- return JSON.parse(response);
180
- } catch (error) {
181
- console.error(response);
182
- }
183
- }).then(j => resolve(j)).catch(err => {
184
- reject(err);
185
- });
186
- }).catch(error => {
187
- console.error(error);
188
- });
189
172
  await zip.finalize();
173
+ const zipBuffer = Buffer.concat(chunks);
190
174
  try {
191
- const log = await uploadPromise;
175
+ const body = await (0, _nodeFetch.default)(url, {
176
+ method: 'POST',
177
+ body: zipBuffer
178
+ });
179
+ const log = JSON.parse(await body.text());
192
180
  _fs.default.unlinkSync('zip');
193
- if (log['#type'] === 'ApiError') {
194
- color.error(log['message']);
195
- console.error(log['innerMessage']);
196
- console.log(log);
197
- return 1;
198
- } else console.log(log);
199
- // color.warn(contentValidationLog);
181
+ if (log != undefined) {
182
+ if (log['#type'] === 'ApiError') {
183
+ color.error(log['message']);
184
+ console.error(log['innerMessage']);
185
+ console.log(log);
186
+ return 1;
187
+ } else console.log(log);
188
+ // color.warn(contentValidationLog);
189
+ }
200
190
  } catch (error) {
201
- console.error(error);
191
+ if (utils.isConnectivityError(error)) color.error(`Server is possibly offline: ${url}`);
192
+ if (verbose) console.error(error);
202
193
  return 1;
203
194
  }
204
195
  return 0;
@@ -248,15 +239,15 @@ async function publishPackage(args) {
248
239
  if (!args.link) {
249
240
  if (args.build || args.rebuild) {
250
241
  console.log('Building');
251
- utils.runScript('npm install', curDir, false);
252
- utils.runScript('npm run build', curDir, false);
242
+ await utils.runScript('npm install', curDir, false);
243
+ await utils.runScript('npm run build', curDir, false);
253
244
  }
254
245
  }
255
246
  if (args.debug && args.release) {
256
247
  color.error('Incompatible options: --debug and --release');
257
248
  return false;
258
249
  }
259
- console.log('Publishing');
250
+ console.log('Publishing...');
260
251
  // Create `config.yaml` if it doesn't exist yet
261
252
  if (!_fs.default.existsSync(grokDir)) _fs.default.mkdirSync(grokDir);
262
253
  if (!_fs.default.existsSync(confPath)) _fs.default.writeFileSync(confPath, _jsYaml.default.dump(confTemplate));
@@ -305,7 +296,7 @@ async function publishPackage(args) {
305
296
  if (!args.suffix && stdout) args.suffix = stdout.toString().substring(0, 8);
306
297
  });
307
298
  await utils.delay(100);
308
- code = await processPackage(!args.release, Boolean(args.rebuild), url, key, packageName, args.dropDb ?? false, args.suffix);
299
+ code = await processPackage(!args.release, Boolean(args.rebuild), url, key, packageName, args.dropDb ?? false, args.suffix, args.verbose);
309
300
  } catch (error) {
310
301
  console.error(error);
311
302
  code = 1;
@@ -42,7 +42,14 @@ async function test(args) {
42
42
  // color.warn('--core flag can only be used in the DevTools package');
43
43
 
44
44
  if (!args.package) {
45
- await testUtils.loadPackages(packagesDir, packageName, args.host, args['skip-publish'], args['skip-build'], args.link);
45
+ try {
46
+ await testUtils.loadPackages(packagesDir, packageName, args.host, args['skip-publish'], args['skip-build'], args.link);
47
+ } catch (e) {
48
+ console.error('\n');
49
+ // @ts-ignore
50
+ console.error(e.message);
51
+ process.exit(1);
52
+ }
46
53
  }
47
54
  process.env.TARGET_PACKAGE = packageName;
48
55
  const res = await runTesting(args);
@@ -71,6 +78,7 @@ const MAX_RETRIES_PER_SESSION = 10;
71
78
  let retryEnabled = true;
72
79
  async function runTesting(args) {
73
80
  retryEnabled = args['retry'] ?? true;
81
+ if (args.test || args.category) retryEnabled = false;
74
82
  let organized = {
75
83
  package: process.env.TARGET_PACKAGE ?? '',
76
84
  params: {
@@ -47,9 +47,15 @@ const defaultLaunchParameters = exports.defaultLaunchParameters = {
47
47
  protocolTimeout: 0
48
48
  };
49
49
  async function getToken(url, key) {
50
- const response = await fetch(`${url}/users/login/dev/${key}`, {
51
- method: 'POST'
52
- });
50
+ let response;
51
+ try {
52
+ response = await fetch(`${url}/users/login/dev/${key}`, {
53
+ method: 'POST'
54
+ });
55
+ } catch (error) {
56
+ if (utils.isConnectivityError(error)) color.error(`Server is possibly offline: ${url}`);
57
+ throw error;
58
+ }
53
59
  const json = await response.json();
54
60
  if (json.isSuccess == true) return json.token;else throw new Error('Unable to login to server. Check your dev key');
55
61
  }
@@ -190,17 +196,13 @@ const recorderConfig = exports.recorderConfig = {
190
196
  // aspectRatio: '16:9',
191
197
  };
192
198
  async function loadPackage(packageDir, dirName, hostString, skipPublish, skipBuild, linkPackage, release) {
193
- try {
194
- if (skipPublish != true) {
195
- process.stdout.write(`Building and publishing ${dirName}...`);
196
- await utils.runScript(`npm install`, packageDir);
197
- if (linkPackage) await utils.runScript(`grok link`, packageDir);
198
- if (skipBuild != true) await utils.runScript(`npm run build`, packageDir);
199
- await utils.runScript(`grok publish ${hostString}${release ? ' --release' : ''}`, packageDir);
200
- process.stdout.write(` success!\n`);
201
- }
202
- } catch (e) {
203
- process.stdout.write(` failed to load package ${dirName}!\n`);
199
+ if (skipPublish != true) {
200
+ process.stdout.write(`Building and publishing ${dirName}...`);
201
+ await utils.runScript(`npm install`, packageDir);
202
+ if (linkPackage) await utils.runScript(`grok link`, packageDir);
203
+ if (skipBuild != true) await utils.runScript(`npm run build`, packageDir);
204
+ await utils.runScript(`grok publish ${hostString}${release ? ' --release' : ''}`, packageDir);
205
+ process.stdout.write(` success!\n`);
204
206
  }
205
207
  }
206
208
  async function loadPackages(packagesDir, packagesToLoad, host, skipPublish, skipBuild, linkPackage, release) {
@@ -214,18 +216,20 @@ async function loadPackages(packagesDir, packagesToLoad, host, skipPublish, skip
214
216
  for (const dirName of _fs.default.readdirSync(packagesDir)) {
215
217
  const packageDir = _path.default.join(packagesDir, dirName);
216
218
  if (!_fs.default.lstatSync(packageDir).isFile()) {
219
+ let shouldLoad = false;
217
220
  try {
218
221
  const packageJsonData = JSON.parse(_fs.default.readFileSync(_path.default.join(packageDir, 'package.json'), {
219
222
  encoding: 'utf-8'
220
223
  }));
221
224
  const packageFriendlyName = packagesToRun.get((0, _utils.spaceToCamelCase)(packageJsonData['friendlyName'] ?? packageJsonData['name'].split('/')[1] ?? packageJsonData['name'] ?? '').toLocaleLowerCase() ?? '') ?? packagesToRun.get(dirName);
222
- if (utils.isPackageDir(packageDir) && (packageFriendlyName !== undefined || packagesToLoad === 'all')) {
223
- await loadPackage(packageDir, dirName, hostString, skipPublish, skipBuild, linkPackage, release);
224
- packagesToRun.set(dirName, true);
225
- }
225
+ shouldLoad = utils.isPackageDir(packageDir) && (packageFriendlyName !== undefined || packagesToLoad === 'all');
226
226
  } catch (e) {
227
227
  if (utils.isPackageDir(packageDir) && (packagesToRun.get((0, _utils.spaceToCamelCase)(dirName).toLocaleLowerCase()) !== undefined || packagesToLoad === 'all')) console.log(`Couldn't read package.json ${dirName}`);
228
228
  }
229
+ if (shouldLoad) {
230
+ await loadPackage(packageDir, dirName, hostString, skipPublish, skipBuild, linkPackage, release);
231
+ packagesToRun.set(dirName, true);
232
+ }
229
233
  }
230
234
  }
231
235
  console.log();
@@ -332,6 +336,15 @@ function printBrowsersResult(browserResult, verbose = false) {
332
336
  if ((browserResult.failedAmount ?? 0) > 0 && (browserResult.verboseFailed ?? []).length > 0) {
333
337
  console.log('Failed: ');
334
338
  console.log(browserResult.verboseFailed);
339
+ for (const line of browserResult.verboseFailed.split('\n')) {
340
+ const match = line.match(/^(.+?):\s+(.+?)\s+\(/);
341
+ if (match) {
342
+ const category = match[1].trim();
343
+ const testName = match[2].trim();
344
+ if (testName === 'before' || testName === 'after') console.log(`\x1b[33m To run this category separately use --category "${category}" argument\x1b[0m`);else console.log(`\x1b[33m To run this test separately use --category "${category}" --test "${testName}" arguments\x1b[0m`);
345
+ console.log('');
346
+ }
347
+ }
335
348
  }
336
349
  console.log('Passed amount: ' + browserResult?.passedAmount);
337
350
  console.log('Skipped amount: ' + browserResult?.skippedAmount);
@@ -619,39 +632,62 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
619
632
  let currentTestName = null;
620
633
  // Store failed tests with their errors to print after category header
621
634
  let pendingFailures = [];
635
+ let pendingSkipped = [];
622
636
  let pendingBeforeFailure = null;
623
637
  let pendingAfterFailure = null;
624
638
  let modernOutput = false;
625
639
  const printCategorySummary = category => {
626
640
  const results = categoryResults.get(category);
627
641
  if (!results) return;
628
- const formattedCategory = category.replace(/: /g, ', ');
642
+ const formattedCategory = category;
629
643
  const passedCount = results.passed;
644
+ const skippedSuffix = results.skipped > 0 ? `, \x1b[33m${results.skipped} skipped\x1b[0m` : '';
630
645
  if (results.failed > 0 || pendingBeforeFailure || pendingAfterFailure) {
631
- console.log(`\x1b[31m❌ ${formattedCategory} (${passedCount} passed)\x1b[0m`);
646
+ console.log(`\x1b[31m❌ ${formattedCategory}\x1b[31m (\x1b[32m${passedCount} passed${skippedSuffix}\x1b[31m)\x1b[0m`);
632
647
  // Print before() failure first
633
648
  if (pendingBeforeFailure) {
634
649
  console.log(` \x1b[31m❌ before\x1b[0m`);
635
650
  if (pendingBeforeFailure.error) console.log(` \x1b[31m${pendingBeforeFailure.error}\x1b[0m`);
651
+ console.log(` \x1b[33mTo run this category separately use --category "${category}"\x1b[0m`);
636
652
  }
637
653
  // Print after() failure
638
654
  if (pendingAfterFailure) {
639
655
  console.log(` \x1b[31m❌ after\x1b[0m`);
640
656
  if (pendingAfterFailure.error) console.log(` \x1b[31m${pendingAfterFailure.error}\x1b[0m`);
657
+ console.log(` \x1b[33mTo run this category separately use --category "${category}"\x1b[0m`);
641
658
  }
659
+ // Print skipped tests
660
+ for (const skippedName of pendingSkipped) console.log(` \x1b[33m⊘ ${skippedName}\x1b[0m`);
642
661
  // Print test failures
643
662
  for (const failure of pendingFailures) {
644
663
  console.log(` \x1b[31m❌ ${failure.testName}\x1b[0m`);
645
664
  if (failure.error) console.log(` \x1b[31m${failure.error}\x1b[0m`);
665
+ console.log(` \x1b[33mTo run this test separately use --category "${category}" --test "${failure.testName}"\x1b[0m`);
646
666
  }
647
667
  } else {
648
- console.log(`\x1b[32m✅ ${formattedCategory} (${passedCount} passed)\x1b[0m`);
668
+ console.log(`\x1b[32m✅ ${formattedCategory} (${passedCount} passed${skippedSuffix}\x1b[32m)\x1b[0m`);
669
+ // Print skipped tests
670
+ for (const skippedName of pendingSkipped) console.log(` \x1b[33m⊘ ${skippedName}\x1b[0m`);
649
671
  }
650
672
  pendingFailures = [];
673
+ pendingSkipped = [];
651
674
  pendingBeforeFailure = null;
652
675
  pendingAfterFailure = null;
653
676
  };
654
677
 
678
+ // Print all console messages when verbose mode is enabled
679
+ if (browserOptions.verbose) {
680
+ page.on('console', msg => {
681
+ const type = msg.type();
682
+ const text = msg.text();
683
+ if (text.startsWith('Package testing: ')) return;
684
+ if (type === 'error') console.log(`\x1b[31m[console.error] ${text}\x1b[0m`);else if (type === 'warning') console.log(`\x1b[33m[console.warn] ${text}\x1b[0m`);else console.log(`[console.log] ${text}`);
685
+ });
686
+ page.on('pageerror', error => {
687
+ console.log(`\x1b[31m[page error] ${error.message}\x1b[0m`);
688
+ });
689
+ }
690
+
655
691
  // Subscribe to page console events for modern output formatting
656
692
  // On retry, old listeners were removed so we need to re-attach
657
693
  page.on('console', msg => {
@@ -664,21 +700,28 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
664
700
  let match;
665
701
  while ((match = tokenRegex.exec(text)) !== null) tokens.push(match[1]);
666
702
 
667
- // Category start: "Package testing: Started {{Category}}"
668
- if (text.includes('Started') && tokens.length === 1) {
703
+ // Category start: "Package testing: Started {{Category}}" or "Package testing: Started {{Category}} skipped {{N}}"
704
+ if (text.includes('Started') && (tokens.length === 1 || tokens.length === 2 && text.includes('skipped'))) {
669
705
  // Print summary of previous category if exists
670
706
  if (currentCategory && categoryResults.has(currentCategory)) printCategorySummary(currentCategory);
671
707
  currentCategory = tokens[0];
708
+ const skippedCount = tokens.length === 2 ? parseInt(tokens[1]) || 0 : 0;
672
709
  categoryResults.set(currentCategory, {
673
710
  passed: 0,
674
- failed: 0
711
+ failed: 0,
712
+ skipped: skippedCount
675
713
  });
676
714
  pendingFailures = [];
715
+ pendingSkipped = [];
677
716
  pendingBeforeFailure = null;
678
717
  pendingAfterFailure = null;
718
+ } else if (text.includes('Skipped') && tokens.length === 2 && !text.includes('benchmark')) {
719
+ // Individual skipped test: "Package testing: Skipped {{Category}} {{TestName}}"
720
+ // Only collect if the test belongs to the current active category
721
+ if (tokens[0] === currentCategory) pendingSkipped.push(tokens[1]);
679
722
  } else if (text.includes('Started') && tokens.length === 2) {
680
723
  // Test start: "Package testing: Started {{Category}} {{TestName}}"
681
- const category = tokens[0].replace(/: /g, ', ');
724
+ const category = tokens[0];
682
725
  currentTestName = tokens[1];
683
726
  process.stdout.write(`${category}: ${currentTestName}...`);
684
727
  } else if (text.includes('Finished') && tokens.length === 3) {
@@ -689,7 +732,7 @@ async function runBrowser(testExecutionData, browserOptions, browsersId, testInv
689
732
  const results = categoryResults.get(category);
690
733
 
691
734
  // Clear the current test line
692
- process.stdout.write('\r\x1b[K');
735
+ if (!browserOptions.verbose) process.stdout.write('\r\x1b[K');
693
736
  if (status === 'success') {
694
737
  if (results) results.passed++;
695
738
  } else {
@@ -18,6 +18,7 @@ exports.getScriptInputs = getScriptInputs;
18
18
  exports.getScriptName = getScriptName;
19
19
  exports.getScriptOutputType = getScriptOutputType;
20
20
  exports.headerTags = void 0;
21
+ exports.isConnectivityError = isConnectivityError;
21
22
  exports.isEmpty = isEmpty;
22
23
  exports.isPackageDir = isPackageDir;
23
24
  exports.isValidCron = isValidCron;
@@ -260,6 +261,10 @@ const queryWrapperTemplate = exports.queryWrapperTemplate = `#{FUNC_DESCRIPTION}
260
261
  return await grok.data.query('#{PACKAGE_NAMESPACE}:#{FUNC_NAME}', #{PARAMS_OBJECT});
261
262
  }`;
262
263
  const namespaceTemplate = exports.namespaceTemplate = `export namespace #{PACKAGE_NAMESPACE} {\n#{NAME}\n}`;
264
+ function isConnectivityError(error) {
265
+ const msg = (error?.message ?? '').toLowerCase() + ' ' + (error?.code ?? '').toLowerCase();
266
+ return ['econnrefused', 'enotfound', 'etimedout', 'eai_again', 'econnreset', 'fetch failed', 'network error'].some(token => msg.includes(token));
267
+ }
263
268
  async function runScript(script, path, verbose = false) {
264
269
  try {
265
270
  const {
@@ -271,8 +276,8 @@ async function runScript(script, path, verbose = false) {
271
276
  if (stderr && verbose) console.error(`Warning/Error: ${stderr}`);
272
277
  if (stdout && verbose) console.log(`Output: ${stdout}`);
273
278
  } catch (error) {
274
- console.error(`Execution failed: ${error.message}`);
275
- throw new Error(`Error executing '${script}'. Error message: ${error.message}`);
279
+ const output = [error.stdout, error.stderr].filter(Boolean).join('\n');
280
+ throw new Error(output);
276
281
  }
277
282
  }
278
283
  function setHost(host, configFile) {
@@ -15,11 +15,11 @@
15
15
  "webpack-cli": "^5.1.4"
16
16
  },
17
17
  "scripts": {
18
- "link-all": "grok link",
19
18
  "debug-#{PACKAGE_NAME_LOWERCASE}": "webpack && grok publish",
20
19
  "release-#{PACKAGE_NAME_LOWERCASE}": "webpack && grok publish --release",
21
20
  "build-#{PACKAGE_NAME_LOWERCASE}": "webpack",
22
- "build": "grok api && grok check && webpack"
21
+ "build": "grok api && grok check && webpack",
22
+ "test": "grok test"
23
23
  },
24
24
  "canEdit": [
25
25
  "Developers"
@@ -12,6 +12,7 @@ module.exports = {
12
12
  package: './src/package.ts',
13
13
  },
14
14
  resolve: {
15
+ symlinks: false,
15
16
  extensions: ['.wasm', '.mjs', '.ts', '.json', '.js', '.tsx'],
16
17
  },
17
18
  module: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datagrok-tools",
3
- "version": "5.0.0",
3
+ "version": "5.1.2",
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": {