ic-mops 1.1.2 → 1.3.0-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/dist/cli.js CHANGED
@@ -20,11 +20,12 @@ import { sync } from './commands/sync.js';
20
20
  import { outdated } from './commands/outdated.js';
21
21
  import { update } from './commands/update.js';
22
22
  import { bench } from './commands/bench.js';
23
- import { transferOwnership } from './commands/transfer-ownership.js';
24
23
  import { toolchain } from './commands/toolchain/index.js';
25
24
  import * as self from './commands/self.js';
26
25
  import { resolvePackages } from './resolve-packages.js';
27
26
  import { watch } from './commands/watch/watch.js';
27
+ import { addOwner, printOwners, removeOwner } from './commands/owner.js';
28
+ import { addMaintainer, printMaintainers, removeMaintainer } from './commands/maintainer.js';
28
29
  events.setMaxListeners(20);
29
30
  let networkFile = getNetworkFile();
30
31
  if (fs.existsSync(networkFile)) {
@@ -183,6 +184,7 @@ program
183
184
  .addOption(new Option('--mode <mode>', 'Test mode').choices(['interpreter', 'wasi', 'replica']).default('interpreter'))
184
185
  .addOption(new Option('--replica <replica>', 'Which replica to use to run tests in replica mode').choices(['dfx', 'pocket-ic']))
185
186
  .option('-w, --watch', 'Enable watch mode')
187
+ .option('--verbose', 'Verbose output')
186
188
  .action(async (filter, options) => {
187
189
  checkConfigFile(true);
188
190
  await installAll({ silent: true, lock: 'ignore', installFromLockFile: true });
@@ -249,6 +251,58 @@ userCommand
249
251
  await getUserProp(prop);
250
252
  });
251
253
  program.addCommand(userCommand);
254
+ // mops owner *
255
+ const ownerCommand = new Command('owner').description('Package owner management');
256
+ // mops owner list
257
+ ownerCommand
258
+ .command('list')
259
+ .description('List package owners')
260
+ .action(async () => {
261
+ await printOwners();
262
+ });
263
+ // mops owner add
264
+ ownerCommand
265
+ .command('add <principal>')
266
+ .description('Add package owner')
267
+ .addOption(new Option('--yes', 'Do not ask for confirmation'))
268
+ .action(async (data, options) => {
269
+ await addOwner(data, options.yes);
270
+ });
271
+ // mops owner remove
272
+ ownerCommand
273
+ .command('remove <principal>')
274
+ .description('Remove package owner')
275
+ .addOption(new Option('--yes', 'Do not ask for confirmation'))
276
+ .action(async (data, options) => {
277
+ await removeOwner(data, options.yes);
278
+ });
279
+ program.addCommand(ownerCommand);
280
+ // mops maintainer *
281
+ const maintainerCommand = new Command('maintainer').description('Package maintainer management');
282
+ // mops maintainer list
283
+ maintainerCommand
284
+ .command('list')
285
+ .description('List package maintainers')
286
+ .action(async () => {
287
+ await printMaintainers();
288
+ });
289
+ // mops maintainer add
290
+ maintainerCommand
291
+ .command('add <principal>')
292
+ .description('Add package maintainer')
293
+ .addOption(new Option('--yes', 'Do not ask for confirmation'))
294
+ .action(async (data, options) => {
295
+ await addMaintainer(data, options.yes);
296
+ });
297
+ // mops maintainer remove
298
+ maintainerCommand
299
+ .command('remove <principal>')
300
+ .description('Remove package maintainer')
301
+ .addOption(new Option('--yes', 'Do not ask for confirmation'))
302
+ .action(async (data, options) => {
303
+ await removeMaintainer(data, options.yes);
304
+ });
305
+ program.addCommand(maintainerCommand);
252
306
  // bump
253
307
  program
254
308
  .command('bump [major|minor|patch]')
@@ -279,13 +333,6 @@ program
279
333
  .action(async (pkg, options) => {
280
334
  await update(pkg, options);
281
335
  });
282
- // transfer-ownership
283
- program
284
- .command('transfer-ownership [to-principal]')
285
- .description('Transfer ownership of the current package to another principal')
286
- .action(async (toPrincipal) => {
287
- await transferOwnership(toPrincipal);
288
- });
289
336
  // toolchain
290
337
  const toolchainCommand = new Command('toolchain').description('Toolchain management');
291
338
  toolchainCommand
@@ -1,5 +1,6 @@
1
1
  import process from 'node:process';
2
2
  import path from 'node:path';
3
+ import { existsSync } from 'node:fs';
3
4
  import { createLogUpdate } from 'log-update';
4
5
  import { getRootDir, readConfig } from '../../mops.js';
5
6
  import { installDeps } from './install-deps.js';
@@ -19,7 +20,11 @@ export async function installLocalDep(pkg, pkgPath = '', { verbose, silent, igno
19
20
  // install dependencies
20
21
  if (!ignoreTransitive) {
21
22
  let dir = path.resolve(getRootDir(), pkgPath).replaceAll('{MOPS_ENV}', process.env.MOPS_ENV || 'local');
22
- let config = readConfig(path.join(dir, 'mops.toml'));
23
+ let mopsToml = path.join(dir, 'mops.toml');
24
+ if (!existsSync(mopsToml)) {
25
+ return true;
26
+ }
27
+ let config = readConfig(mopsToml);
23
28
  return installDeps(Object.values(config.dependencies || {}), { silent, verbose }, pkgPath);
24
29
  }
25
30
  else {
@@ -0,0 +1,3 @@
1
+ export declare function printMaintainers(): Promise<void>;
2
+ export declare function addMaintainer(maintainer: string, yes?: boolean): Promise<void>;
3
+ export declare function removeMaintainer(maintainer: string, yes?: boolean): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import process from 'node:process';
2
+ import chalk from 'chalk';
3
+ import { checkConfigFile, getIdentity, readConfig } from '../mops.js';
4
+ import { mainActor } from '../api/actors.js';
5
+ import { Principal } from '@dfinity/principal';
6
+ import prompts from 'prompts';
7
+ export async function printMaintainers() {
8
+ if (!checkConfigFile()) {
9
+ return;
10
+ }
11
+ let config = readConfig();
12
+ let actor = await mainActor();
13
+ let maintainers = await actor.getPackageMaintainers(config.package?.name || '');
14
+ console.log(`Maintainers of package ${chalk.bold(config.package?.name)}:`);
15
+ for (let maintainer of maintainers) {
16
+ console.log(maintainer.toText());
17
+ }
18
+ }
19
+ export async function addMaintainer(maintainer, yes = false) {
20
+ if (!checkConfigFile()) {
21
+ return;
22
+ }
23
+ let config = readConfig();
24
+ let principal = Principal.fromText(maintainer);
25
+ if (!yes) {
26
+ let promptsConfig = {
27
+ onCancel() {
28
+ console.log('aborted');
29
+ process.exit(0);
30
+ },
31
+ };
32
+ let { confirm } = await prompts({
33
+ type: 'confirm',
34
+ name: 'confirm',
35
+ message: `Are you sure you want to add maintainer ${chalk.yellow(maintainer)} to ${chalk.yellow(config.package?.name)} package?`,
36
+ initial: true,
37
+ }, promptsConfig);
38
+ if (!confirm) {
39
+ return;
40
+ }
41
+ }
42
+ let identity = await getIdentity();
43
+ let actor = await mainActor(identity);
44
+ let res = await actor.addMaintainer(config.package?.name || '', principal);
45
+ if ('ok' in res) {
46
+ console.log(chalk.green('Success!'));
47
+ console.log(`Added maintainer ${chalk.bold(maintainer)} to package ${chalk.bold(config.package?.name)}`);
48
+ }
49
+ else {
50
+ console.error(chalk.red('Error: ') + res.err);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ export async function removeMaintainer(maintainer, yes = false) {
55
+ if (!checkConfigFile()) {
56
+ return;
57
+ }
58
+ let config = readConfig();
59
+ let principal = Principal.fromText(maintainer);
60
+ if (!yes) {
61
+ let promptsConfig = {
62
+ onCancel() {
63
+ console.log('aborted');
64
+ process.exit(0);
65
+ },
66
+ };
67
+ let { confirm } = await prompts({
68
+ type: 'confirm',
69
+ name: 'confirm',
70
+ message: `Are you sure you want to remove maintainer ${chalk.red(maintainer)} from ${chalk.red(config.package?.name)} package?`,
71
+ initial: true,
72
+ }, promptsConfig);
73
+ if (!confirm) {
74
+ return;
75
+ }
76
+ }
77
+ let identity = await getIdentity();
78
+ let actor = await mainActor(identity);
79
+ let res = await actor.removeMaintainer(config.package?.name || '', principal);
80
+ if ('ok' in res) {
81
+ console.log(chalk.green('Success!'));
82
+ console.log(`Removed maintainer ${chalk.bold(maintainer)} from package ${chalk.bold(config.package?.name)}`);
83
+ }
84
+ else {
85
+ console.error(chalk.red('Error: ') + res.err);
86
+ process.exit(1);
87
+ }
88
+ }
@@ -0,0 +1,3 @@
1
+ export declare function printOwners(): Promise<void>;
2
+ export declare function addOwner(owner: string, yes?: boolean): Promise<void>;
3
+ export declare function removeOwner(owner: string, yes?: boolean): Promise<void>;
@@ -0,0 +1,88 @@
1
+ import process from 'node:process';
2
+ import chalk from 'chalk';
3
+ import { checkConfigFile, getIdentity, readConfig } from '../mops.js';
4
+ import { mainActor } from '../api/actors.js';
5
+ import { Principal } from '@dfinity/principal';
6
+ import prompts from 'prompts';
7
+ export async function printOwners() {
8
+ if (!checkConfigFile()) {
9
+ return;
10
+ }
11
+ let config = readConfig();
12
+ let actor = await mainActor();
13
+ let owners = await actor.getPackageOwners(config.package?.name || '');
14
+ console.log(`Owners of package ${chalk.bold(config.package?.name)}:`);
15
+ for (let owner of owners) {
16
+ console.log(owner.toText());
17
+ }
18
+ }
19
+ export async function addOwner(owner, yes = false) {
20
+ if (!checkConfigFile()) {
21
+ return;
22
+ }
23
+ let config = readConfig();
24
+ let principal = Principal.fromText(owner);
25
+ if (!yes) {
26
+ let promptsConfig = {
27
+ onCancel() {
28
+ console.log('aborted');
29
+ process.exit(0);
30
+ },
31
+ };
32
+ let { confirm } = await prompts({
33
+ type: 'confirm',
34
+ name: 'confirm',
35
+ message: `Are you sure you want to add owner ${chalk.yellow(owner)} to ${chalk.yellow(config.package?.name)} package?`,
36
+ initial: true,
37
+ }, promptsConfig);
38
+ if (!confirm) {
39
+ return;
40
+ }
41
+ }
42
+ let identity = await getIdentity();
43
+ let actor = await mainActor(identity);
44
+ let res = await actor.addOwner(config.package?.name || '', principal);
45
+ if ('ok' in res) {
46
+ console.log(chalk.green('Success!'));
47
+ console.log(`Added owner ${chalk.bold(owner)} to package ${chalk.bold(config.package?.name)}`);
48
+ }
49
+ else {
50
+ console.error(chalk.red('Error: ') + res.err);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ export async function removeOwner(owner, yes = false) {
55
+ if (!checkConfigFile()) {
56
+ return;
57
+ }
58
+ let config = readConfig();
59
+ let principal = Principal.fromText(owner);
60
+ if (!yes) {
61
+ let promptsConfig = {
62
+ onCancel() {
63
+ console.log('aborted');
64
+ process.exit(0);
65
+ },
66
+ };
67
+ let { confirm } = await prompts({
68
+ type: 'confirm',
69
+ name: 'confirm',
70
+ message: `Are you sure you want to remove owner ${chalk.red(owner)} from ${chalk.red(config.package?.name)} package?`,
71
+ initial: true,
72
+ }, promptsConfig);
73
+ if (!confirm) {
74
+ return;
75
+ }
76
+ }
77
+ let identity = await getIdentity();
78
+ let actor = await mainActor(identity);
79
+ let res = await actor.removeOwner(config.package?.name || '', principal);
80
+ if ('ok' in res) {
81
+ console.log(chalk.green('Success!'));
82
+ console.log(`Removed owner ${chalk.bold(owner)} from package ${chalk.bold(config.package?.name)}`);
83
+ }
84
+ else {
85
+ console.error(chalk.red('Error: ') + res.err);
86
+ process.exit(1);
87
+ }
88
+ }
@@ -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 try 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
+ verbose: 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.verbose;
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) {
@@ -10,13 +10,13 @@ function readDfxJson() {
10
10
  }
11
11
  export function getMotokoCanisters() {
12
12
  let dfxJson = readDfxJson();
13
- return Object.fromEntries(Object.entries(dfxJson.canisters)
13
+ return Object.fromEntries(Object.entries(dfxJson.canisters || {})
14
14
  .filter(([_, canister]) => canister.type === 'motoko')
15
15
  .map(([name, canister]) => [name, canister.main ?? '']));
16
16
  }
17
17
  export function getMotokoCanistersWithDeclarations() {
18
18
  let dfxJson = readDfxJson();
19
- return Object.fromEntries(Object.entries(dfxJson.canisters)
19
+ return Object.fromEntries(Object.entries(dfxJson.canisters || {})
20
20
  .filter(([_, canister]) => canister.type === 'motoko' && canister.declarations)
21
21
  .map(([name, canister]) => [name, canister.main ?? '']));
22
22
  }