ic-mops 0.6.6 → 0.7.1

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/cli.js CHANGED
@@ -39,6 +39,7 @@ program
39
39
  program
40
40
  .command('add <pkg>')
41
41
  .description('Install the package and save it to mops.toml')
42
+ .option('--dev')
42
43
  .option('--verbose')
43
44
  .action(async (pkg, options) => {
44
45
  await add(pkg, options);
@@ -114,7 +115,8 @@ program
114
115
  .option('--verbose')
115
116
  .action(async (options) => {
116
117
  await installAll({silent: true});
117
- await sources(options);
118
+ let sourcesArr = await sources(options);
119
+ console.log(sourcesArr.join('\n'));
118
120
  });
119
121
 
120
122
  // whoami
@@ -155,8 +157,9 @@ program
155
157
  program
156
158
  .command('test')
157
159
  .description('Run tests')
158
- .action(async () => {
159
- await test();
160
+ .option('--watch', 'Enable watch mode')
161
+ .action(async (options) => {
162
+ await test(options);
160
163
  });
161
164
 
162
165
  // // upgrade
package/commands/add.js CHANGED
@@ -5,14 +5,21 @@ import {checkConfigFile, getHighestVersion, parseGithubURL, readConfig, writeCon
5
5
  import {installFromGithub} from '../vessel.js';
6
6
  import {install} from './install.js';
7
7
 
8
- export async function add(name, {verbose} = {}) {
8
+ export async function add(name, {verbose, dev} = {}) {
9
9
  if (!checkConfigFile()) {
10
10
  return false;
11
11
  }
12
12
 
13
13
  let config = readConfig();
14
- if (!config.dependencies) {
15
- config.dependencies = {};
14
+ if (dev) {
15
+ if (!config['dev-dependencies']) {
16
+ config['dev-dependencies'] = {};
17
+ }
18
+ }
19
+ else {
20
+ if (!config.dependencies) {
21
+ config.dependencies = {};
22
+ }
16
23
  }
17
24
 
18
25
  let pkgDetails;
@@ -68,7 +75,7 @@ export async function add(name, {verbose} = {}) {
68
75
  }
69
76
  }
70
77
 
71
- config.dependencies[pkgDetails.name] = pkgDetails;
78
+ config[dev ? 'dev-dependencies' : 'dependencies'][pkgDetails.name] = pkgDetails;
72
79
  writeConfig(config);
73
80
 
74
81
  logUpdate.clear();
@@ -10,9 +10,11 @@ export async function installAll({verbose, silent} = {}) {
10
10
  }
11
11
 
12
12
  let config = readConfig();
13
- const deps = Object.values(config.dependencies || {});
13
+ let deps = Object.values(config.dependencies || {});
14
+ let devDeps = Object.values(config['dev-dependencies'] || {});
15
+ let allDeps = [...deps, ...devDeps];
14
16
 
15
- for (let {name, repo, path, version} of deps) {
17
+ for (let {name, repo, path, version} of allDeps) {
16
18
  if (repo) {
17
19
  await installFromGithub(name, repo, {verbose, silent});
18
20
  }
@@ -110,7 +110,8 @@ export async function install(pkg, version = '', {verbose, silent, dep} = {}) {
110
110
  // install dependencies
111
111
  let ok = true;
112
112
  let config = readConfig(path.join(dir, 'mops.toml'));
113
- for (const {name, repo, version} of Object.values(config.dependencies || {})) {
113
+ let deps = Object.values(config.dependencies || {});
114
+ for (const {name, repo, version} of deps) {
114
115
  if (repo) {
115
116
  await installFromGithub(name, repo, {silent, verbose});
116
117
  }
@@ -0,0 +1,83 @@
1
+ // mops message format v1
2
+ // mops:1:start
3
+ // mops:1:end
4
+ // mops:1:skip
5
+ import chalk from 'chalk';
6
+
7
+ export class MMF1 {
8
+ stack = [];
9
+ currSuite = '';
10
+ failed = 0;
11
+ passed = 0;
12
+ skipped = 0;
13
+
14
+ parseLine(line) {
15
+ if (line.startsWith('mops:1:start ')) {
16
+ this._testStart(line.split('mops:1:start ')[1]);
17
+ }
18
+ else if (line.startsWith('mops:1:end ')) {
19
+ this._testEnd(line.split('mops:1:end ')[1]);
20
+ }
21
+ else if (line.startsWith('mops:1:skip ')) {
22
+ this._testSkip(line.split('mops:1:skip ')[1]);
23
+ }
24
+ else {
25
+ console.log(' '.repeat(this.stack.length * 2), chalk.gray('stdout'), line);
26
+ }
27
+ }
28
+
29
+ _testStart(name) {
30
+ if (this.stack.length) {
31
+ let suite = this.stack.at(-1);
32
+ if (this.currSuite !== suite) {
33
+ this.currSuite = suite;
34
+ console.log(' '.repeat((this.stack.length - 1) * 2), (chalk.gray('•')) + '', this.stack.at(-1));
35
+ }
36
+ }
37
+ this.stack.push(name);
38
+ }
39
+
40
+ _testEnd(name) {
41
+ if (name !== this.stack.pop()) {
42
+ throw 'mmf1._testEnd: start and end test mismatch';
43
+ }
44
+ this._status(name, 'pass');
45
+ }
46
+
47
+ _testSkip(name) {
48
+ this._status(name, 'skip');
49
+ }
50
+
51
+ _status(name, status) {
52
+ if (status === 'pass') {
53
+ // do not print suite at the end
54
+ if (name === this.currSuite) {
55
+ return;
56
+ }
57
+ this.passed++;
58
+ console.log(' '.repeat(this.stack.length * 2), chalk.green('✓'), name);
59
+ }
60
+ else if (status === 'fail') {
61
+ this.failed++;
62
+ console.log(' '.repeat(this.stack.length * 2), chalk.red('×'), name);
63
+ }
64
+ else if (status === 'skip') {
65
+ this.skipped++;
66
+ console.log(' '.repeat(this.stack.length * 2), chalk.yellow('−'), name);
67
+ }
68
+ }
69
+
70
+ fail(stderr) {
71
+ let name = this.stack.pop() || '';
72
+ this._status(name, 'fail');
73
+ console.log(' '.repeat(this.stack.length * 2), chalk.red('FAIL'), stderr);
74
+ }
75
+
76
+ pass() {
77
+ let name = this.stack.pop();
78
+ if (name) {
79
+ this._status(name, 'pass');
80
+ }
81
+ console.log(' '.repeat(this.stack.length * 2), chalk.green('PASS'));
82
+ }
83
+ }
@@ -17,7 +17,7 @@ export async function publish() {
17
17
 
18
18
  // validate
19
19
  for (let key of Object.keys(config)) {
20
- if (!['package', 'dependencies', 'scripts'].includes(key)) {
20
+ if (!['package', 'dependencies', 'dev-dependencies', 'scripts'].includes(key)) {
21
21
  console.log(chalk.red('Error: ') + `Unknown config section [${key}]`);
22
22
  return;
23
23
  }
@@ -112,6 +112,21 @@ export async function publish() {
112
112
  }
113
113
  }
114
114
 
115
+ if (config['dev-dependencies']) {
116
+ if (Object.keys(config['dev-dependencies']).length > 100) {
117
+ console.log(chalk.red('Error: ') + 'max dev-dependencies is 100');
118
+ return;
119
+ }
120
+
121
+ for (let dep of Object.values(config['dev-dependencies'])) {
122
+ if (dep.path) {
123
+ console.log(chalk.red('Error: ') + 'you can\'t publish packages with local dev-dependencies');
124
+ return;
125
+ }
126
+ delete dep.path;
127
+ }
128
+ }
129
+
115
130
  if (config.package.keywords) {
116
131
  for (let keyword of config.package.keywords) {
117
132
  if (keyword.length > 20) {
@@ -147,7 +162,7 @@ export async function publish() {
147
162
  moc: config.package.moc || '',
148
163
  donation: config.package.donation || '',
149
164
  dependencies: Object.values(config.dependencies || {}),
150
- devDependencies: [],
165
+ devDependencies: Object.values(config['dev-dependencies'] || {}),
151
166
  scripts: [],
152
167
  };
153
168
 
@@ -59,7 +59,11 @@ export async function sources({verbose} = {}) {
59
59
  };
60
60
 
61
61
  let collectDeps = async (config, isRoot = false) => {
62
- for (const pkgDetails of Object.values(config.dependencies || {})) {
62
+ let allDeps = [...Object.values(config.dependencies || {})];
63
+ if (isRoot) {
64
+ allDeps = [...allDeps, ...Object.values(config['dev-dependencies'] || {})];
65
+ }
66
+ for (const pkgDetails of allDeps) {
63
67
  const {name, repo, version} = pkgDetails;
64
68
 
65
69
  // take root dep version or bigger one
@@ -117,7 +121,7 @@ export async function sources({verbose} = {}) {
117
121
  }
118
122
 
119
123
  // sources
120
- for (let [name, pkg] of Object.entries(packages)) {
124
+ return Object.entries(packages).map(([name, pkg]) => {
121
125
  let pkgDir;
122
126
  if (pkg.path) {
123
127
  pkgDir = path.relative(process.cwd(), path.resolve(pkg.path));
@@ -139,6 +143,6 @@ export async function sources({verbose} = {}) {
139
143
  pkgBaseDir = path.join(pkgDir, 'src');
140
144
  }
141
145
 
142
- console.log(`--package ${name} ${pkgBaseDir}`);
143
- }
146
+ return `--package ${name} ${pkgBaseDir}`;
147
+ });
144
148
  }
package/commands/test.js CHANGED
@@ -1,17 +1,59 @@
1
- import {execSync} from 'child_process';
1
+ import {spawn, execSync} from 'child_process';
2
2
  import chalk from 'chalk';
3
3
  import glob from 'glob';
4
+ import chokidar from 'chokidar';
5
+ import debounce from 'debounce';
6
+ import {MMF1} from './mmf1.js';
7
+ import {sources} from './sources.js';
8
+
9
+ let ignore = [
10
+ '**/node_modules/**',
11
+ '**/.mops/**',
12
+ '**/.vessel/**',
13
+ '**/.git/**',
14
+ ];
4
15
 
5
16
  let globConfig = {
6
17
  nocase: true,
7
- ignore: [
8
- '**/node_modules/**',
9
- '**/.mops/**',
10
- '**/.vessel/**',
11
- ],
18
+ ignore: ignore,
12
19
  };
13
20
 
14
- export async function test() {
21
+ export async function test({watch = false} = {}) {
22
+ if (watch) {
23
+ // todo: run only changed for *.test.mo?
24
+ // todo: run all for *.mo?
25
+ let run = debounce(async () => {
26
+ console.clear();
27
+ process.stdout.write('\x1Bc');
28
+ await runAll();
29
+ console.log('-'.repeat(50));
30
+ console.log('Waiting for file changes...');
31
+ console.log(chalk.gray((`Press ${chalk.gray('Ctrl+C')} to exit.`)));
32
+ }, 200);
33
+
34
+ let watcher = chokidar.watch('**/*.mo', {
35
+ ignored: ignore,
36
+ ignoreInitial: true,
37
+ });
38
+
39
+ watcher.on('all', () => {
40
+ run();
41
+ });
42
+ run();
43
+ }
44
+ else {
45
+ let failed = await runAll();
46
+ if (failed) {
47
+ process.exit(1);
48
+ }
49
+ }
50
+ }
51
+
52
+ let dfxCache;
53
+
54
+ export async function runAll() {
55
+ let start = Date.now();
56
+
15
57
  let files = [];
16
58
  let libFiles = glob.sync('**/test?(s)/lib.mo', globConfig);
17
59
  if (libFiles.length) {
@@ -22,6 +64,7 @@ export async function test() {
22
64
  }
23
65
  if (!files.length) {
24
66
  console.log('No test files found');
67
+ console.log('Put your tests in \'test\' directory in *.test.mo files');
25
68
  return;
26
69
  }
27
70
 
@@ -31,31 +74,68 @@ export async function test() {
31
74
  }
32
75
  console.log('-'.repeat(50));
33
76
 
34
- let start = Date.now();
35
77
  let failed = 0;
36
78
  let passed = 0;
37
- let dfxCache = execSync('dfx cache show').toString().trim();
38
- let mopsSources = execSync('mops-local sources').toString().trim().replace(/\n/g, ' ');
79
+ let skipped = 0;
80
+ let sourcesArr = await sources();
81
+ if (!dfxCache) {
82
+ dfxCache = execSync('dfx cache show').toString().trim();
83
+ }
39
84
 
40
85
  for (let file of files) {
41
- try {
86
+ let mmf1 = new MMF1;
87
+
88
+ await new Promise((resolve) => {
42
89
  console.log(`Running ${chalk.gray(file)}`);
43
- execSync(`${dfxCache}/moc -r -wasi-system-api --hide-warnings --error-detail 2 ${mopsSources} ${file}`, {stdio: 'pipe'});
44
- console.log(' ', chalk.green('PASS'));
45
- passed++;
46
- }
47
- catch (err) {
48
- failed++;
49
- if (err.status === 1) {
50
- console.log(' ', chalk.red('FAIL'), err.stderr.toString().trim());
51
- }
52
- else {
53
- console.log(chalk.red('Unknown status:'), err.status);
54
- console.log(err.message);
55
- }
56
- }
57
- }
58
90
 
91
+ let proc = spawn(`${dfxCache}/moc`, ['-r', '-wasi-system-api', '-ref-system-api', '--hide-warnings', '--error-detail=2', ...sourcesArr.join(' ').split(' '), file]);
92
+
93
+ // stdout
94
+ proc.stdout.on('data', (data) => {
95
+ for (let line of data.toString().split('\n')) {
96
+ line = line.trim();
97
+ if (line) {
98
+ mmf1.parseLine(line);
99
+ }
100
+ }
101
+ });
102
+
103
+ // stderr
104
+ proc.stderr.on('data', (data) => {
105
+ let text = data.toString().trim();
106
+ text = text.replace(/:(\d+).(\d+)(-\d+.\d+)/, ':$1:$2');
107
+ mmf1.fail(text);
108
+ });
109
+
110
+ // exit
111
+ proc.on('exit', (code) => {
112
+ if (code === 0) {
113
+ mmf1.pass();
114
+ }
115
+ else if (code !== 1) {
116
+ console.log(chalk.red('unknown code:'), code);
117
+ }
118
+ resolve();
119
+ });
120
+ });
121
+
122
+ passed += mmf1.passed;
123
+ failed += mmf1.failed;
124
+ skipped += mmf1.skipped;
125
+ }
59
126
  console.log('-'.repeat(50));
60
- console.log(`Done in ${chalk.gray(((Date.now() - start) / 1000).toFixed(2) + 's')}, failed ${chalk[failed ? 'redBright' : 'gray'](failed)}, passed ${chalk.greenBright(passed)}`);
127
+ if (failed) {
128
+ console.log(chalk.redBright('Tests failed'));
129
+ }
130
+ else {
131
+ console.log(chalk.greenBright('Tests passed'));
132
+ }
133
+
134
+ console.log(`Done in ${chalk.gray(((Date.now() - start) / 1000).toFixed(2) + 's')}`
135
+ + `, passed ${chalk.greenBright(passed)}`
136
+ + (skipped ? `, skipped ${chalk[skipped ? 'yellowBright' : 'gray'](skipped)}` : '')
137
+ + (failed ? `, failed ${chalk[failed ? 'redBright' : 'gray'](failed)}` : '')
138
+ );
139
+
140
+ return failed === 0;
61
141
  }
package/mops.js CHANGED
@@ -124,33 +124,40 @@ export function readConfig(configFile = path.join(process.cwd(), 'mops.toml')) {
124
124
  let text = fs.readFileSync(configFile).toString();
125
125
  let toml = TOML.parse(text);
126
126
 
127
- const deps = toml.dependencies || {};
128
-
129
- Object.entries(deps).forEach(([name, data]) => {
130
- if (!data || typeof data !== 'string') {
131
- throw Error(`Invalid dependency value ${name} = "${data}"`);
132
- }
133
- if (data.startsWith('https://github.com/')) {
134
- deps[name] = {name, repo: data, version: ''};
135
- }
136
- else if (data.match(/^(\.?\.)?\//)) {
137
- deps[name] = {name, repo: '', path: data, version: ''};
138
- }
139
- else {
140
- deps[name] = {name, repo: '', version: data};
141
- }
142
- });
127
+ let processDeps = (deps) => {
128
+ Object.entries(deps).forEach(([name, data]) => {
129
+ if (!data || typeof data !== 'string') {
130
+ throw Error(`Invalid dependency value ${name} = "${data}"`);
131
+ }
132
+ if (data.startsWith('https://github.com/')) {
133
+ deps[name] = {name, repo: data, version: ''};
134
+ }
135
+ else if (data.match(/^(\.?\.)?\//)) {
136
+ deps[name] = {name, repo: '', path: data, version: ''};
137
+ }
138
+ else {
139
+ deps[name] = {name, repo: '', version: data};
140
+ }
141
+ });
142
+ };
143
+
144
+ processDeps(toml.dependencies || {});
145
+ processDeps(toml['dev-dependencies'] || {});
143
146
 
144
147
  return toml;
145
148
  }
146
149
 
147
150
  export function writeConfig(config, configFile = path.join(process.cwd(), 'mops.toml')) {
148
151
  const deps = config.dependencies || {};
149
-
150
152
  Object.entries(deps).forEach(([name, {repo, path, version}]) => {
151
153
  deps[name] = repo || path || version;
152
154
  });
153
155
 
156
+ const devDeps = config['dev-dependencies'] || {};
157
+ Object.entries(devDeps).forEach(([name, {repo, path, version}]) => {
158
+ devDeps[name] = repo || path || version;
159
+ });
160
+
154
161
  fs.writeFileSync(configFile, TOML.stringify(config).trim());
155
162
  }
156
163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.6.6",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "cli.js"
@@ -11,6 +11,7 @@
11
11
  "url": "https://github.com/ZenVoich/mops.git"
12
12
  },
13
13
  "author": "Zen Voich <zen.voich@gmail.com>",
14
+ "license": "MIT",
14
15
  "dependencies": {
15
16
  "@dfinity/agent": "^0.11.0",
16
17
  "@dfinity/candid": "^0.11.0",
@@ -19,7 +20,9 @@
19
20
  "@iarna/toml": "^2.2.5",
20
21
  "as-table": "^1.0.55",
21
22
  "chalk": "^4.1.2",
23
+ "chokidar": "^3.5.3",
22
24
  "commander": "^9.2.0",
25
+ "debounce": "^1.2.1",
23
26
  "decompress": "^4.2.1",
24
27
  "del": "^6.0.0",
25
28
  "dhall-to-json-cli": "^1.7.6",