ic-mops 1.1.0 → 1.1.2-pre.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,9 @@
1
1
  # Mops CLI Changelog
2
2
 
3
+ ## 1.1.1
4
+ - `moc-wrapper` now adds hostname to the moc path cache(`.mops/moc-*` filename) to avoid errors when running in Dev Containers
5
+ - `mops watch` now deploys canisters with the `--yes` flag to skip data loss confirmation
6
+
3
7
  ## 1.1.0
4
8
  - New `mops watch` command to check for syntax errors, show warnings, run tests, generate declarations and deploy canisters ([docs](https://docs.mops.one/cli/mops-watch))
5
9
  - New flag `--no-toolchain` in `mops install` command to skip toolchain installation
@@ -22,7 +22,7 @@ else
22
22
  mopsTomlHash=$(shasum $mopsToml -a 256 | awk -F' ' '{print $1}')
23
23
  fi;
24
24
 
25
- cached="$rootDir/.mops/moc-$mopsTomlHash"
25
+ cached="$rootDir/.mops/moc-$(uname -n)-$mopsTomlHash"
26
26
 
27
27
  if [ -f $cached ]; then
28
28
  mocPath=$(cat $cached)
package/bundle/cli.tgz CHANGED
Binary file
package/cli.ts CHANGED
@@ -214,6 +214,7 @@ program
214
214
  .addOption(new Option('--mode <mode>', 'Test mode').choices(['interpreter', 'wasi', 'replica']).default('interpreter'))
215
215
  .addOption(new Option('--replica <replica>', 'Which replica to use to run tests in replica mode').choices(['dfx', 'pocket-ic']))
216
216
  .option('-w, --watch', 'Enable watch mode')
217
+ .option('--debug', 'Show debug logs')
217
218
  .action(async (filter, options) => {
218
219
  checkConfigFile(true);
219
220
  await installAll({silent: true, lock: 'ignore', installFromLockFile: true});
@@ -8,19 +8,21 @@ import {spawn as spawnAsync} from 'promisify-child-process';
8
8
  import {IDL} from '@dfinity/candid';
9
9
  import {Actor, HttpAgent} from '@dfinity/agent';
10
10
  import {PocketIc, PocketIcServer} from 'pic-ic';
11
+ import chalk from 'chalk';
11
12
 
12
13
  import {readConfig} from '../mops.js';
13
14
  import {toolchain} from './toolchain/index.js';
15
+ import {getDfxVersion} from '../helpers/get-dfx-version.js';
14
16
 
15
17
  type StartOptions = {
16
- type ?: 'dfx' | 'pocket-ic';
18
+ type ?: 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
17
19
  dir ?: string;
18
20
  verbose ?: boolean;
19
21
  silent ?: boolean;
20
22
  };
21
23
 
22
24
  export class Replica {
23
- type : 'dfx' | 'pocket-ic' = 'dfx';
25
+ type : 'dfx' | 'pocket-ic' | 'dfx-pocket-ic' = 'dfx';
24
26
  verbose = false;
25
27
  canisters : Record<string, {cwd : string; canisterId : string; actor : any; stream : PassThrough;}> = {};
26
28
  pocketIcServer ?: PocketIcServer;
@@ -36,20 +38,33 @@ export class Replica {
36
38
 
37
39
  silent || console.log(`Starting ${this.type} replica...`);
38
40
 
39
- if (this.type == 'dfx') {
41
+ if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
40
42
  fs.mkdirSync(this.dir, {recursive: true});
41
43
  fs.writeFileSync(path.join(this.dir, 'dfx.json'), JSON.stringify(this.dfxJson(''), null, 2));
42
44
  fs.writeFileSync(path.join(this.dir, 'canister.did'), 'service : { runTests: () -> (); }');
43
45
 
44
46
  await this.stop();
45
47
 
46
- this.dfxProcess = spawn('dfx', ['start', '--clean', '--artificial-delay', '0', (this.verbose ? '' : '-qqqq')].filter(x => x), {cwd: this.dir});
48
+ this.dfxProcess = spawn('dfx', ['start', this.type === 'dfx-pocket-ic' ? '--pocketic' : '', '--clean', (this.verbose ? '' : '-qqqq'), '--artificial-delay', '0'].filter(x => x).flat(), {cwd: this.dir});
47
49
 
48
50
  // process canister logs
49
51
  this._attachCanisterLogHandler(this.dfxProcess);
50
52
 
51
53
  this.dfxProcess.stdout.on('data', (data) => {
52
- console.log('DFX:', data.toString());
54
+ if (this.verbose) {
55
+ console.log('DFX:', data.toString());
56
+ }
57
+ });
58
+
59
+ this.dfxProcess.stderr.on('data', (data) => {
60
+ if (this.verbose) {
61
+ console.error('DFX:', data.toString());
62
+ }
63
+ if (data.toString().includes('Failed to bind socket to')) {
64
+ console.error(chalk.red(data.toString()));
65
+ console.log('Please run again after some time');
66
+ process.exit(11);
67
+ }
53
68
  });
54
69
 
55
70
  // await for dfx to start
@@ -115,9 +130,22 @@ export class Replica {
115
130
  }
116
131
 
117
132
  async stop(sigint = false) {
118
- if (this.type == 'dfx') {
119
- this.dfxProcess?.kill();
120
- // execSync('dfx stop' + (this.verbose ? '' : ' -qqqq'), {cwd: this.dir, timeout: 10_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
133
+ if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
134
+ if (this.dfxProcess) {
135
+ this.dfxProcess.kill();
136
+ // give replica some time to stop
137
+ await new Promise((resolve) => {
138
+ setTimeout(resolve, 1000);
139
+ });
140
+ }
141
+
142
+ // if (!this.dfxProcess) {
143
+ // try {
144
+ // execSync('dfx killall', {cwd: this.dir, timeout: 3_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
145
+ // execSync('dfx stop' + (this.verbose ? '' : ' -qqqq'), {cwd: this.dir, timeout: 10_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
146
+ // }
147
+ // catch {}
148
+ // }
121
149
  }
122
150
  else if (this.pocketIc && this.pocketIcServer) {
123
151
  if (!sigint) {
@@ -128,7 +156,7 @@ export class Replica {
128
156
  }
129
157
 
130
158
  async deploy(name : string, wasm : string, idlFactory : IDL.InterfaceFactory, cwd : string = process.cwd(), signal ?: AbortSignal) {
131
- if (this.type === 'dfx') {
159
+ if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
132
160
  // prepare dfx.json for current canister
133
161
  let dfxJson = path.join(this.dir, 'dfx.json');
134
162
 
@@ -253,6 +281,7 @@ export class Replica {
253
281
  return {
254
282
  version: 1,
255
283
  canisters,
284
+ dfx: getDfxVersion(),
256
285
  defaults: {
257
286
  build: {
258
287
  packtool: 'mops sources',
@@ -8,6 +8,7 @@ import chalk from 'chalk';
8
8
  import {globSync} from 'glob';
9
9
  import chokidar from 'chokidar';
10
10
  import debounce from 'debounce';
11
+ import {SemVer} from 'semver';
11
12
 
12
13
  import {sources} from '../sources.js';
13
14
  import {getRootDir, readConfig} from '../../mops.js';
@@ -25,6 +26,7 @@ import {Replica} from '../replica.js';
25
26
  import {ActorMethod} from '@dfinity/agent';
26
27
  import {PassThrough, Readable} from 'node:stream';
27
28
  import {TestMode} from '../../types.js';
29
+ import {getDfxVersion} from '../../helpers/get-dfx-version.js';
28
30
 
29
31
  let ignore = [
30
32
  '**/node_modules/**',
@@ -39,13 +41,14 @@ let globConfig = {
39
41
  };
40
42
 
41
43
  type ReporterName = 'verbose' | 'files' | 'compact' | 'silent';
42
- type ReplicaName = 'dfx' | 'pocket-ic';
44
+ type ReplicaName = 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
43
45
 
44
46
  type TestOptions = {
45
47
  watch : boolean;
46
48
  reporter : ReporterName;
47
49
  mode : TestMode;
48
50
  replica : ReplicaName;
51
+ debug : boolean;
49
52
  };
50
53
 
51
54
 
@@ -66,7 +69,20 @@ export async function test(filter = '', options : Partial<TestOptions> = {}) {
66
69
  let rootDir = getRootDir();
67
70
 
68
71
  let replicaType = options.replica ?? (config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx' as ReplicaName);
72
+
73
+ if (replicaType === 'pocket-ic' && !config.toolchain?.['pocket-ic']) {
74
+ let dfxVersion = getDfxVersion();
75
+ if (!dfxVersion || new SemVer(dfxVersion).compare('0.24.1') < 0) {
76
+ console.log(chalk.red('Please update dfx to the version >=0.24.1 or specify pocket-ic version in mops.toml'));
77
+ process.exit(1);
78
+ }
79
+ else {
80
+ replicaType = 'dfx-pocket-ic';
81
+ }
82
+ }
83
+
69
84
  replica.type = replicaType;
85
+ replica.verbose = !!options.debug;
70
86
 
71
87
  if (options.watch) {
72
88
  replica.ttl = 60 * 15; // 15 minutes
@@ -202,6 +218,7 @@ export async function testWithReporter(reporterName : ReporterName | Reporter |
202
218
  let testTempDir = path.join(getRootDir(), '.mops/.test/');
203
219
  replica.dir = testTempDir;
204
220
 
221
+ fs.rmSync(testTempDir, {recursive: true, force: true});
205
222
  fs.mkdirSync(testTempDir, {recursive: true});
206
223
 
207
224
  await parallel(os.cpus().length, files, async (file : string) => {
@@ -113,7 +113,7 @@ export class Deployer {
113
113
  }
114
114
 
115
115
  // install
116
- await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', canister], {cwd: rootDir, signal}).catch((error) => {
116
+ await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', '--yes', canister], {cwd: rootDir, signal}).catch((error) => {
117
117
  if (error.code === 'ABORT_ERR') {
118
118
  return {stderr: ''};
119
119
  }
@@ -22,7 +22,7 @@ else
22
22
  mopsTomlHash=$(shasum $mopsToml -a 256 | awk -F' ' '{print $1}')
23
23
  fi;
24
24
 
25
- cached="$rootDir/.mops/moc-$mopsTomlHash"
25
+ cached="$rootDir/.mops/moc-$(uname -n)-$mopsTomlHash"
26
26
 
27
27
  if [ -f $cached ]; then
28
28
  mocPath=$(cat $cached)
package/dist/cli.js CHANGED
@@ -183,6 +183,7 @@ program
183
183
  .addOption(new Option('--mode <mode>', 'Test mode').choices(['interpreter', 'wasi', 'replica']).default('interpreter'))
184
184
  .addOption(new Option('--replica <replica>', 'Which replica to use to run tests in replica mode').choices(['dfx', 'pocket-ic']))
185
185
  .option('-w, --watch', 'Enable watch mode')
186
+ .option('--debug', 'Show debug logs')
186
187
  .action(async (filter, options) => {
187
188
  checkConfigFile(true);
188
189
  await installAll({ silent: true, lock: 'ignore', installFromLockFile: true });
@@ -3,13 +3,13 @@ import { PassThrough } from 'node:stream';
3
3
  import { IDL } from '@dfinity/candid';
4
4
  import { PocketIc, PocketIcServer } from 'pic-ic';
5
5
  type StartOptions = {
6
- type?: 'dfx' | 'pocket-ic';
6
+ type?: 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
7
7
  dir?: string;
8
8
  verbose?: boolean;
9
9
  silent?: boolean;
10
10
  };
11
11
  export declare class Replica {
12
- type: 'dfx' | 'pocket-ic';
12
+ type: 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
13
13
  verbose: boolean;
14
14
  canisters: Record<string, {
15
15
  cwd: string;
@@ -43,6 +43,7 @@ export declare class Replica {
43
43
  dfxJson(canisterName: string, wasmPath?: string, didPath?: string): {
44
44
  version: number;
45
45
  canisters: Record<string, any>;
46
+ dfx: string;
46
47
  defaults: {
47
48
  build: {
48
49
  packtool: string;
@@ -6,8 +6,10 @@ import { PassThrough } from 'node:stream';
6
6
  import { spawn as spawnAsync } from 'promisify-child-process';
7
7
  import { Actor, HttpAgent } from '@dfinity/agent';
8
8
  import { PocketIc, PocketIcServer } from 'pic-ic';
9
+ import chalk from 'chalk';
9
10
  import { readConfig } from '../mops.js';
10
11
  import { toolchain } from './toolchain/index.js';
12
+ import { getDfxVersion } from '../helpers/get-dfx-version.js';
11
13
  export class Replica {
12
14
  constructor() {
13
15
  this.type = 'dfx';
@@ -21,16 +23,28 @@ export class Replica {
21
23
  this.verbose = verbose ?? this.verbose;
22
24
  this.dir = dir ?? this.dir;
23
25
  silent || console.log(`Starting ${this.type} replica...`);
24
- if (this.type == 'dfx') {
26
+ if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
25
27
  fs.mkdirSync(this.dir, { recursive: true });
26
28
  fs.writeFileSync(path.join(this.dir, 'dfx.json'), JSON.stringify(this.dfxJson(''), null, 2));
27
29
  fs.writeFileSync(path.join(this.dir, 'canister.did'), 'service : { runTests: () -> (); }');
28
30
  await this.stop();
29
- this.dfxProcess = spawn('dfx', ['start', '--clean', '--artificial-delay', '0', (this.verbose ? '' : '-qqqq')].filter(x => x), { cwd: this.dir });
31
+ this.dfxProcess = spawn('dfx', ['start', this.type === 'dfx-pocket-ic' ? '--pocketic' : '', '--clean', (this.verbose ? '' : '-qqqq'), '--artificial-delay', '0'].filter(x => x).flat(), { cwd: this.dir });
30
32
  // process canister logs
31
33
  this._attachCanisterLogHandler(this.dfxProcess);
32
34
  this.dfxProcess.stdout.on('data', (data) => {
33
- console.log('DFX:', data.toString());
35
+ if (this.verbose) {
36
+ console.log('DFX:', data.toString());
37
+ }
38
+ });
39
+ this.dfxProcess.stderr.on('data', (data) => {
40
+ if (this.verbose) {
41
+ console.error('DFX:', data.toString());
42
+ }
43
+ if (data.toString().includes('Failed to bind socket to')) {
44
+ console.error(chalk.red(data.toString()));
45
+ console.log('Please run again after some time');
46
+ process.exit(11);
47
+ }
34
48
  });
35
49
  // await for dfx to start
36
50
  let ok = false;
@@ -87,9 +101,21 @@ export class Replica {
87
101
  });
88
102
  }
89
103
  async stop(sigint = false) {
90
- if (this.type == 'dfx') {
91
- this.dfxProcess?.kill();
92
- // execSync('dfx stop' + (this.verbose ? '' : ' -qqqq'), {cwd: this.dir, timeout: 10_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
104
+ if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
105
+ if (this.dfxProcess) {
106
+ this.dfxProcess.kill();
107
+ // give replica some time to stop
108
+ await new Promise((resolve) => {
109
+ setTimeout(resolve, 1000);
110
+ });
111
+ }
112
+ // if (!this.dfxProcess) {
113
+ // try {
114
+ // execSync('dfx killall', {cwd: this.dir, timeout: 3_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
115
+ // execSync('dfx stop' + (this.verbose ? '' : ' -qqqq'), {cwd: this.dir, timeout: 10_000, stdio: ['pipe', this.verbose ? 'inherit' : 'ignore', 'pipe']});
116
+ // }
117
+ // catch {}
118
+ // }
93
119
  }
94
120
  else if (this.pocketIc && this.pocketIcServer) {
95
121
  if (!sigint) {
@@ -99,7 +125,7 @@ export class Replica {
99
125
  }
100
126
  }
101
127
  async deploy(name, wasm, idlFactory, cwd = process.cwd(), signal) {
102
- if (this.type === 'dfx') {
128
+ if (this.type === 'dfx' || this.type === 'dfx-pocket-ic') {
103
129
  // prepare dfx.json for current canister
104
130
  let dfxJson = path.join(this.dir, 'dfx.json');
105
131
  let oldDfxJsonData;
@@ -202,6 +228,7 @@ export class Replica {
202
228
  return {
203
229
  version: 1,
204
230
  canisters,
231
+ dfx: getDfxVersion(),
205
232
  defaults: {
206
233
  build: {
207
234
  packtool: 'mops sources',
@@ -1,12 +1,13 @@
1
1
  import { Reporter } from './reporters/reporter.js';
2
2
  import { TestMode } from '../../types.js';
3
3
  type ReporterName = 'verbose' | 'files' | 'compact' | 'silent';
4
- type ReplicaName = 'dfx' | 'pocket-ic';
4
+ type ReplicaName = 'dfx' | 'pocket-ic' | 'dfx-pocket-ic';
5
5
  type TestOptions = {
6
6
  watch: boolean;
7
7
  reporter: ReporterName;
8
8
  mode: TestMode;
9
9
  replica: ReplicaName;
10
+ debug: boolean;
10
11
  };
11
12
  export declare function test(filter?: string, options?: Partial<TestOptions>): Promise<void>;
12
13
  export declare function testWithReporter(reporterName: ReporterName | Reporter | undefined, filter: string | undefined, defaultMode: TestMode | undefined, replicaType: ReplicaName, watch?: boolean, signal?: AbortSignal): Promise<boolean>;
@@ -7,6 +7,7 @@ import chalk from 'chalk';
7
7
  import { globSync } from 'glob';
8
8
  import chokidar from 'chokidar';
9
9
  import debounce from 'debounce';
10
+ import { SemVer } from 'semver';
10
11
  import { sources } from '../sources.js';
11
12
  import { getRootDir, readConfig } from '../../mops.js';
12
13
  import { parallel } from '../../parallel.js';
@@ -19,6 +20,7 @@ import { SilentReporter } from './reporters/silent-reporter.js';
19
20
  import { toolchain } from '../toolchain/index.js';
20
21
  import { Replica } from '../replica.js';
21
22
  import { PassThrough } from 'node:stream';
23
+ import { getDfxVersion } from '../../helpers/get-dfx-version.js';
22
24
  let ignore = [
23
25
  '**/node_modules/**',
24
26
  '**/.mops/**',
@@ -43,7 +45,18 @@ export async function test(filter = '', options = {}) {
43
45
  let config = readConfig();
44
46
  let rootDir = getRootDir();
45
47
  let replicaType = options.replica ?? (config.toolchain?.['pocket-ic'] ? 'pocket-ic' : 'dfx');
48
+ if (replicaType === 'pocket-ic' && !config.toolchain?.['pocket-ic']) {
49
+ let dfxVersion = getDfxVersion();
50
+ if (!dfxVersion || new SemVer(dfxVersion).compare('0.24.1') < 0) {
51
+ console.log(chalk.red('Please update dfx to the version >=0.24.1 or specify pocket-ic version in mops.toml'));
52
+ process.exit(1);
53
+ }
54
+ else {
55
+ replicaType = 'dfx-pocket-ic';
56
+ }
57
+ }
46
58
  replica.type = replicaType;
59
+ replica.verbose = !!options.debug;
47
60
  if (options.watch) {
48
61
  replica.ttl = 60 * 15; // 15 minutes
49
62
  let sigint = false;
@@ -156,6 +169,7 @@ export async function testWithReporter(reporterName, filter = '', defaultMode =
156
169
  }
157
170
  let testTempDir = path.join(getRootDir(), '.mops/.test/');
158
171
  replica.dir = testTempDir;
172
+ fs.rmSync(testTempDir, { recursive: true, force: true });
159
173
  fs.mkdirSync(testTempDir, { recursive: true });
160
174
  await parallel(os.cpus().length, files, async (file) => {
161
175
  if (signal?.aborted) {
@@ -89,7 +89,7 @@ export class Deployer {
89
89
  });
90
90
  }
91
91
  // install
92
- await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', canister], { cwd: rootDir, signal }).catch((error) => {
92
+ await promisify(execFile)('dfx', ['canister', 'install', '--mode=auto', '--yes', canister], { cwd: rootDir, signal }).catch((error) => {
93
93
  if (error.code === 'ABORT_ERR') {
94
94
  return { stderr: '' };
95
95
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "1.1.0",
3
+ "version": "1.1.2-pre.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "1.1.0",
3
+ "version": "1.1.2-pre.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/bin/mops.js",