ic-mops 1.3.0 → 1.4.0

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Mops CLI Changelog
2
2
 
3
+ ## 1.4.0
4
+ - Update `mops bench` command output:
5
+ - Print only final results if benchmarks run in a CI environment or there is no vertical space to progressively print the results
6
+ - Hide "Stable Memory" table if it has no data
7
+ - Hide verbose output when running in a CI environment ("Starting replica...", "Running simple.bench.mo...", etc.)
8
+ - Add LaTeX colors to the diffs when running in a CI environment with `--compare` flag
9
+ - CLI now fails if excess arguments are passed to it
10
+
3
11
  ## 1.3.0
4
12
  - Show error on `mops install <pkg>` command. Use `mops add <pkg>` instead.
5
13
  - Added support for pocket-ic replica that comes with dfx in `mops bench` command. To activate it, remove `pocket-ic` from `mops.toml` and run `mops bench --replica pocket-ic`. Requires dfx 0.24.1 or higher.
package/bundle/cli.tgz CHANGED
Binary file
@@ -22,7 +22,9 @@ export class BenchReplica {
22
22
  }
23
23
 
24
24
  async start({silent = false} = {}) {
25
- silent || console.log(`Starting ${this.type} replica...`);
25
+ if (!process.env.CI && !silent) {
26
+ console.log(`Starting ${this.type} replica...`);
27
+ }
26
28
 
27
29
  if (this.type == 'dfx' || this.type === 'dfx-pocket-ic') {
28
30
  await this.stop();
package/commands/bench.ts CHANGED
@@ -5,9 +5,12 @@ import os from 'node:os';
5
5
  import chalk from 'chalk';
6
6
  import {globSync} from 'glob';
7
7
  import {markdownTable} from 'markdown-table';
8
- import logUpdate from 'log-update';
8
+ import {createLogUpdate} from 'log-update';
9
9
  import {execaCommand} from 'execa';
10
10
  import stringWidth from 'string-width';
11
+ import {filesize} from 'filesize';
12
+ import terminalSize from 'terminal-size';
13
+ import {SemVer} from 'semver';
11
14
 
12
15
  import {getRootDir, readConfig} from '../mops.js';
13
16
  import {parallel} from '../parallel.js';
@@ -20,8 +23,6 @@ import {sources} from './sources.js';
20
23
  import {Benchmark, Benchmarks} from '../declarations/main/main.did.js';
21
24
  import {BenchResult, _SERVICE} from '../declarations/bench/bench.did.js';
22
25
  import {BenchReplica} from './bench-replica.js';
23
- import {filesize} from 'filesize';
24
- import {SemVer} from 'semver';
25
26
 
26
27
  type ReplicaName = 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
27
28
 
@@ -83,6 +84,10 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
83
84
 
84
85
  options.replica = replicaType;
85
86
 
87
+ if (process.env.CI) {
88
+ console.log('# Benchmark Results\n\n');
89
+ }
90
+
86
91
  if (replicaType == 'dfx') {
87
92
  options.replicaVersion = getDfxVersion();
88
93
  }
@@ -123,14 +128,19 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
123
128
  for (let file of files) {
124
129
  console.log(chalk.gray(`• ${absToRel(file)}`));
125
130
  }
126
- console.log('');
127
- console.log('='.repeat(50));
128
- console.log('');
131
+ if (!process.env.CI) {
132
+ console.log('');
133
+ console.log('='.repeat(50));
134
+ console.log('');
135
+ }
129
136
  }
130
137
 
131
138
  await replica.start({silent: options.silent});
132
139
 
133
- options.silent || console.log('Deploying canisters...');
140
+ if (!process.env.CI && !options.silent) {
141
+ console.log('Deploying canisters...');
142
+ }
143
+
134
144
  await parallel(os.cpus().length, files, async (file : string) => {
135
145
  try {
136
146
  await deployBenchFile(file, options, replica);
@@ -145,8 +155,8 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
145
155
  let benchResults : Benchmarks = [];
146
156
 
147
157
  await parallel(1, files, async (file : string) => {
148
- if (!options.silent) {
149
- console.log('\n' + ''.repeat(50));
158
+ if (!options.silent && !process.env.CI) {
159
+ console.log('\n' + '-'.repeat(50));
150
160
  console.log(`\nRunning ${chalk.gray(absToRel(file))}...`);
151
161
  console.log('');
152
162
  }
@@ -161,7 +171,9 @@ export async function bench(filter = '', optionsArg : Partial<BenchOptions> = {}
161
171
  }
162
172
  });
163
173
 
164
- options.silent || console.log('Stopping replica...');
174
+ if (!process.env.CI && !options.silent) {
175
+ console.log('Stopping replica...');
176
+ }
165
177
  await replica.stop();
166
178
 
167
179
  fs.rmSync(benchDir, {recursive: true, force: true});
@@ -251,6 +263,7 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
251
263
 
252
264
  let getTable = (prop : keyof BenchResult) : string => {
253
265
  let resArr = [['', ...schema.cols]];
266
+ let allZero = true;
254
267
 
255
268
  for (let [_rowIndex, row] of schema.rows.entries()) {
256
269
  let curRow = [row];
@@ -258,6 +271,9 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
258
271
  for (let [_colIndex, col] of schema.cols.entries()) {
259
272
  let res = results.get(`${row}:${col}`);
260
273
  if (res) {
274
+ if (res[prop] != 0n) {
275
+ allZero = false;
276
+ }
261
277
 
262
278
  // compare with previous results
263
279
  let diff = '';
@@ -265,10 +281,18 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
265
281
  let prevRes = prevResults.get(`${row}:${col}`);
266
282
  if (prevRes) {
267
283
  let percent = (Number(res[prop]) - Number(prevRes[prop])) / Number(prevRes[prop]) * 100;
284
+ if (Object.is(percent, NaN)) {
285
+ percent = 0;
286
+ }
268
287
  let sign = percent > 0 ? '+' : '';
269
288
  let percentText = percent == 0 ? '0%' : sign + percent.toFixed(2) + '%';
270
289
  let color : keyof typeof chalk = percent == 0 ? 'gray' : (percent > 0 ? 'red' : 'green');
271
- diff = ` (${chalk[color](percentText)})`;
290
+ if (process.env.CI) {
291
+ diff = ` $({\\color{${color}}${percentText.replace('%', '\\\\%')}})$`;
292
+ }
293
+ else {
294
+ diff = ` (${chalk[color](percentText)})`;
295
+ }
272
296
  }
273
297
  else {
274
298
  diff = chalk.yellow(' (no previous results)');
@@ -295,25 +319,47 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
295
319
  resArr.push(curRow);
296
320
  }
297
321
 
322
+ // don't show Stable Memory table if all values are 0
323
+ if (allZero && prop == 'rts_logical_stable_memory_size') {
324
+ return '';
325
+ }
326
+
298
327
  return markdownTable(resArr, {
299
328
  align: ['l', ...'r'.repeat(schema.cols.length)],
300
329
  stringLength: stringWidth,
301
330
  });
302
331
  };
303
332
 
304
- let printResults = () => {
305
- logUpdate(`
306
- \n${chalk.bold(schema.name)}
307
- ${schema.description ? '\n' + chalk.gray(schema.description) : ''}
333
+ let logUpdate = createLogUpdate(process.stdout, {showCursor: true});
334
+
335
+ let getOutput = () => {
336
+ return `
337
+ \n${process.env.CI ? `## ${schema.name}` : chalk.bold(schema.name)}
338
+ ${schema.description ? '\n' + (process.env.CI ? `_${schema.description}_` : chalk.gray(schema.description)) : ''}
308
339
  \n\n${chalk.blue('Instructions')}\n\n${getTable('instructions')}
309
340
  \n\n${chalk.blue('Heap')}\n\n${getTable('rts_heap_size')}
310
- \n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}
311
341
  \n\n${chalk.blue('Garbage Collection')}\n\n${getTable('rts_reclaimed')}
312
- `);
342
+ ${getTable('rts_logical_stable_memory_size') ? `\n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}` : ''}
343
+ `;
313
344
  };
314
345
 
315
- if (!process.env.CI && !options.silent) {
316
- printResults();
346
+ let canUpdateLog = !process.env.CI && !options.silent && terminalSize().rows > getOutput().split('\n').length;
347
+
348
+ let log = () => {
349
+ if (options.silent) {
350
+ return;
351
+ }
352
+ let output = getOutput();
353
+ if (process.env.CI || terminalSize().rows <= output.split('\n').length) {
354
+ console.log(output);
355
+ }
356
+ else {
357
+ logUpdate(output);
358
+ }
359
+ };
360
+
361
+ if (canUpdateLog) {
362
+ log();
317
363
  }
318
364
 
319
365
  // run all cells
@@ -331,16 +377,18 @@ async function runBenchFile(file : string, options : BenchOptions, replica : Ben
331
377
  // @ts-ignore
332
378
  reclaimedCells[rowIndex][colIndex] = res.rts_reclaimed;
333
379
 
334
- if (!process.env.CI && !options.silent) {
335
- printResults();
380
+ if (canUpdateLog) {
381
+ log();
336
382
  }
337
383
  }
338
384
  }
339
385
 
340
- if (process.env.CI) {
341
- printResults();
386
+ if (canUpdateLog) {
387
+ logUpdate.done();
388
+ }
389
+ else {
390
+ log();
342
391
  }
343
- logUpdate.done();
344
392
 
345
393
  // save results
346
394
  if (options.save) {
@@ -16,7 +16,9 @@ export class BenchReplica {
16
16
  this.verbose = verbose;
17
17
  }
18
18
  async start({ silent = false } = {}) {
19
- silent || console.log(`Starting ${this.type} replica...`);
19
+ if (!process.env.CI && !silent) {
20
+ console.log(`Starting ${this.type} replica...`);
21
+ }
20
22
  if (this.type == 'dfx' || this.type === 'dfx-pocket-ic') {
21
23
  await this.stop();
22
24
  let dir = path.join(getRootDir(), '.mops/.bench');
@@ -5,9 +5,12 @@ import os from 'node:os';
5
5
  import chalk from 'chalk';
6
6
  import { globSync } from 'glob';
7
7
  import { markdownTable } from 'markdown-table';
8
- import logUpdate from 'log-update';
8
+ import { createLogUpdate } from 'log-update';
9
9
  import { execaCommand } from 'execa';
10
10
  import stringWidth from 'string-width';
11
+ import { filesize } from 'filesize';
12
+ import terminalSize from 'terminal-size';
13
+ import { SemVer } from 'semver';
11
14
  import { getRootDir, readConfig } from '../mops.js';
12
15
  import { parallel } from '../parallel.js';
13
16
  import { absToRel } from './test/utils.js';
@@ -16,8 +19,6 @@ import { getDfxVersion } from '../helpers/get-dfx-version.js';
16
19
  import { getMocPath } from '../helpers/get-moc-path.js';
17
20
  import { sources } from './sources.js';
18
21
  import { BenchReplica } from './bench-replica.js';
19
- import { filesize } from 'filesize';
20
- import { SemVer } from 'semver';
21
22
  let ignore = [
22
23
  '**/node_modules/**',
23
24
  '**/.mops/**',
@@ -55,6 +56,9 @@ export async function bench(filter = '', optionsArg = {}) {
55
56
  }
56
57
  }
57
58
  options.replica = replicaType;
59
+ if (process.env.CI) {
60
+ console.log('# Benchmark Results\n\n');
61
+ }
58
62
  if (replicaType == 'dfx') {
59
63
  options.replicaVersion = getDfxVersion();
60
64
  }
@@ -89,12 +93,16 @@ export async function bench(filter = '', optionsArg = {}) {
89
93
  for (let file of files) {
90
94
  console.log(chalk.gray(`• ${absToRel(file)}`));
91
95
  }
92
- console.log('');
93
- console.log('='.repeat(50));
94
- console.log('');
96
+ if (!process.env.CI) {
97
+ console.log('');
98
+ console.log('='.repeat(50));
99
+ console.log('');
100
+ }
95
101
  }
96
102
  await replica.start({ silent: options.silent });
97
- options.silent || console.log('Deploying canisters...');
103
+ if (!process.env.CI && !options.silent) {
104
+ console.log('Deploying canisters...');
105
+ }
98
106
  await parallel(os.cpus().length, files, async (file) => {
99
107
  try {
100
108
  await deployBenchFile(file, options, replica);
@@ -107,8 +115,8 @@ export async function bench(filter = '', optionsArg = {}) {
107
115
  });
108
116
  let benchResults = [];
109
117
  await parallel(1, files, async (file) => {
110
- if (!options.silent) {
111
- console.log('\n' + ''.repeat(50));
118
+ if (!options.silent && !process.env.CI) {
119
+ console.log('\n' + '-'.repeat(50));
112
120
  console.log(`\nRunning ${chalk.gray(absToRel(file))}...`);
113
121
  console.log('');
114
122
  }
@@ -122,7 +130,9 @@ export async function bench(filter = '', optionsArg = {}) {
122
130
  throw err;
123
131
  }
124
132
  });
125
- options.silent || console.log('Stopping replica...');
133
+ if (!process.env.CI && !options.silent) {
134
+ console.log('Stopping replica...');
135
+ }
126
136
  await replica.stop();
127
137
  fs.rmSync(benchDir, { recursive: true, force: true });
128
138
  return benchResults;
@@ -195,21 +205,33 @@ async function runBenchFile(file, options, replica) {
195
205
  };
196
206
  let getTable = (prop) => {
197
207
  let resArr = [['', ...schema.cols]];
208
+ let allZero = true;
198
209
  for (let [_rowIndex, row] of schema.rows.entries()) {
199
210
  let curRow = [row];
200
211
  for (let [_colIndex, col] of schema.cols.entries()) {
201
212
  let res = results.get(`${row}:${col}`);
202
213
  if (res) {
214
+ if (res[prop] != 0n) {
215
+ allZero = false;
216
+ }
203
217
  // compare with previous results
204
218
  let diff = '';
205
219
  if (options.compare && prevResults) {
206
220
  let prevRes = prevResults.get(`${row}:${col}`);
207
221
  if (prevRes) {
208
222
  let percent = (Number(res[prop]) - Number(prevRes[prop])) / Number(prevRes[prop]) * 100;
223
+ if (Object.is(percent, NaN)) {
224
+ percent = 0;
225
+ }
209
226
  let sign = percent > 0 ? '+' : '';
210
227
  let percentText = percent == 0 ? '0%' : sign + percent.toFixed(2) + '%';
211
228
  let color = percent == 0 ? 'gray' : (percent > 0 ? 'red' : 'green');
212
- diff = ` (${chalk[color](percentText)})`;
229
+ if (process.env.CI) {
230
+ diff = ` $({\\color{${color}}${percentText.replace('%', '\\\\%')}})$`;
231
+ }
232
+ else {
233
+ diff = ` (${chalk[color](percentText)})`;
234
+ }
213
235
  }
214
236
  else {
215
237
  diff = chalk.yellow(' (no previous results)');
@@ -234,23 +256,41 @@ async function runBenchFile(file, options, replica) {
234
256
  }
235
257
  resArr.push(curRow);
236
258
  }
259
+ // don't show Stable Memory table if all values are 0
260
+ if (allZero && prop == 'rts_logical_stable_memory_size') {
261
+ return '';
262
+ }
237
263
  return markdownTable(resArr, {
238
264
  align: ['l', ...'r'.repeat(schema.cols.length)],
239
265
  stringLength: stringWidth,
240
266
  });
241
267
  };
242
- let printResults = () => {
243
- logUpdate(`
244
- \n${chalk.bold(schema.name)}
245
- ${schema.description ? '\n' + chalk.gray(schema.description) : ''}
268
+ let logUpdate = createLogUpdate(process.stdout, { showCursor: true });
269
+ let getOutput = () => {
270
+ return `
271
+ \n${process.env.CI ? `## ${schema.name}` : chalk.bold(schema.name)}
272
+ ${schema.description ? '\n' + (process.env.CI ? `_${schema.description}_` : chalk.gray(schema.description)) : ''}
246
273
  \n\n${chalk.blue('Instructions')}\n\n${getTable('instructions')}
247
274
  \n\n${chalk.blue('Heap')}\n\n${getTable('rts_heap_size')}
248
- \n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}
249
275
  \n\n${chalk.blue('Garbage Collection')}\n\n${getTable('rts_reclaimed')}
250
- `);
276
+ ${getTable('rts_logical_stable_memory_size') ? `\n\n${chalk.blue('Stable Memory')}\n\n${getTable('rts_logical_stable_memory_size')}` : ''}
277
+ `;
251
278
  };
252
- if (!process.env.CI && !options.silent) {
253
- printResults();
279
+ let canUpdateLog = !process.env.CI && !options.silent && terminalSize().rows > getOutput().split('\n').length;
280
+ let log = () => {
281
+ if (options.silent) {
282
+ return;
283
+ }
284
+ let output = getOutput();
285
+ if (process.env.CI || terminalSize().rows <= output.split('\n').length) {
286
+ console.log(output);
287
+ }
288
+ else {
289
+ logUpdate(output);
290
+ }
291
+ };
292
+ if (canUpdateLog) {
293
+ log();
254
294
  }
255
295
  // run all cells
256
296
  for (let [rowIndex, row] of schema.rows.entries()) {
@@ -265,15 +305,17 @@ async function runBenchFile(file, options, replica) {
265
305
  logicalStableMemoryCells[rowIndex][colIndex] = res.rts_logical_stable_memory_size;
266
306
  // @ts-ignore
267
307
  reclaimedCells[rowIndex][colIndex] = res.rts_reclaimed;
268
- if (!process.env.CI && !options.silent) {
269
- printResults();
308
+ if (canUpdateLog) {
309
+ log();
270
310
  }
271
311
  }
272
312
  }
273
- if (process.env.CI) {
274
- printResults();
313
+ if (canUpdateLog) {
314
+ logUpdate.done();
315
+ }
316
+ else {
317
+ log();
275
318
  }
276
- logUpdate.done();
277
319
  // save results
278
320
  if (options.save) {
279
321
  console.log(`Saving results to ${chalk.gray(absToRel(resultsJsonFile))}`);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
@@ -41,7 +41,7 @@
41
41
  "chalk": "5.4.1",
42
42
  "change-case": "5.4.4",
43
43
  "chokidar": "3.6.0",
44
- "commander": "12.1.0",
44
+ "commander": "13.1.0",
45
45
  "debounce": "2.1.1",
46
46
  "decomp-tarxz": "0.1.1",
47
47
  "decompress": "4.2.1",
@@ -69,7 +69,8 @@
69
69
  "semver": "7.7.1",
70
70
  "stream-to-promise": "3.0.0",
71
71
  "string-width": "7.2.0",
72
- "tar": "7.4.3"
72
+ "tar": "7.4.3",
73
+ "terminal-size": "4.0.0"
73
74
  },
74
75
  "devDependencies": {
75
76
  "@tsconfig/strictest": "2.0.5",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/bin/mops.js",
@@ -57,7 +57,7 @@
57
57
  "chalk": "5.4.1",
58
58
  "change-case": "5.4.4",
59
59
  "chokidar": "3.6.0",
60
- "commander": "12.1.0",
60
+ "commander": "13.1.0",
61
61
  "debounce": "2.1.1",
62
62
  "decomp-tarxz": "0.1.1",
63
63
  "decompress": "4.2.1",
@@ -85,7 +85,8 @@
85
85
  "semver": "7.7.1",
86
86
  "stream-to-promise": "3.0.0",
87
87
  "string-width": "7.2.0",
88
- "tar": "7.4.3"
88
+ "tar": "7.4.3",
89
+ "terminal-size": "4.0.0"
89
90
  },
90
91
  "devDependencies": {
91
92
  "@tsconfig/strictest": "2.0.5",