ic-mops 0.29.0 → 0.31.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/cli.ts CHANGED
@@ -135,8 +135,9 @@ program
135
135
  program
136
136
  .command('import-identity <data>')
137
137
  .description('Import .pem file data to use as identity')
138
- .action(async (data) => {
139
- await importPem(data);
138
+ .addOption(new Option('--no-encrypt', 'Do not ask for a password to encrypt identity'))
139
+ .action(async (data, options) => {
140
+ await importPem(data, options);
140
141
  await whoami();
141
142
  });
142
143
 
package/commands/add.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import chalk from 'chalk';
3
3
  import logUpdate from 'log-update';
4
- import {checkConfigFile, getHighestVersion, parseGithubURL, readConfig, writeConfig} from '../mops.js';
4
+ import {checkConfigFile, getGithubCommit, getHighestVersion, parseGithubURL, readConfig, writeConfig} from '../mops.js';
5
5
  import {installFromGithub} from '../vessel.js';
6
6
  import {install} from './install.js';
7
7
  import {notifyInstalls} from '../notify-installs.js';
@@ -36,11 +36,20 @@ export async function add(name: string, {verbose = false, dev = false} = {}) {
36
36
  }
37
37
  // github package
38
38
  else if (name.startsWith('https://github.com') || name.split('/').length > 1) {
39
- const {org, gitName, branch} = parseGithubURL(name);
39
+ let {org, gitName, branch, commitHash} = parseGithubURL(name);
40
+
41
+ // fetch latest commit hash of branch if not specified
42
+ if (!commitHash) {
43
+ let commit = await getGithubCommit(`${org}/${gitName}`, branch);
44
+ if (!commit.sha) {
45
+ throw Error(`Could not find commit hash for ${name}`);
46
+ }
47
+ commitHash = commit.sha;
48
+ }
40
49
 
41
50
  pkgDetails = {
42
51
  name: parseGithubURL(name).gitName,
43
- repo: `https://github.com/${org}/${gitName}#${branch}`,
52
+ repo: `https://github.com/${org}/${gitName}#${branch}@${commitHash}`,
44
53
  version: '',
45
54
  };
46
55
  }
@@ -6,28 +6,36 @@ import {deleteSync} from 'del';
6
6
  import {globalConfigDir} from '../mops.js';
7
7
  import {encrypt} from '../pem.js';
8
8
 
9
- export async function importPem(data: string) {
9
+ type ImportIdentityOptions = {
10
+ encrypt: boolean;
11
+ };
12
+
13
+ export async function importPem(data: string, options: ImportIdentityOptions = {encrypt: true}) {
10
14
  try {
11
15
  if (!fs.existsSync(globalConfigDir)) {
12
16
  fs.mkdirSync(globalConfigDir);
13
17
  }
14
18
 
15
- let res = await prompts({
16
- type: 'password',
17
- name: 'password',
18
- message: 'Enter password to encrypt identity.pem',
19
- });
20
- let password = res.password;
19
+ let password = '';
21
20
 
22
- if (!password) {
21
+ if (options.encrypt) {
23
22
  let res = await prompts({
24
- type: 'confirm',
25
- name: 'ok',
26
- message: 'Are you sure you don\'t want to protect your identity.pem with a password?',
23
+ type: 'invisible',
24
+ name: 'password',
25
+ message: 'Enter password to encrypt identity.pem',
27
26
  });
28
- if (!res.ok) {
29
- console.log('aborted');
30
- return;
27
+ password = res.password;
28
+
29
+ if (!password) {
30
+ let res = await prompts({
31
+ type: 'confirm',
32
+ name: 'ok',
33
+ message: 'Are you sure you don\'t want to protect your identity.pem with a password?',
34
+ });
35
+ if (!res.ok) {
36
+ console.log('aborted');
37
+ return;
38
+ }
31
39
  }
32
40
  }
33
41
 
@@ -36,13 +36,13 @@ export async function install(pkg: string, version = '', {verbose = false, silen
36
36
 
37
37
  // already installed
38
38
  if (fs.existsSync(dir)) {
39
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (already installed)`);
39
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (local cache)`);
40
40
  alreadyInstalled = true;
41
41
  }
42
42
  // copy from cache
43
43
  else if (isCached(`${pkg}@${version}`)) {
44
44
  await copyCache(`${pkg}@${version}`, dir);
45
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (cache)`);
45
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (global cache)`);
46
46
  }
47
47
  // download
48
48
  else {
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import {checkConfigFile, readConfig} from '../mops.js';
2
+ import {checkConfigFile, getGithubCommit, parseGithubURL, readConfig} from '../mops.js';
3
3
  import {add} from './add.js';
4
4
  import {getAvailableUpdates} from './available-updates.js';
5
5
 
@@ -9,10 +9,38 @@ export async function update(pkg?: string) {
9
9
  }
10
10
  let config = readConfig();
11
11
 
12
+ if (pkg && !config.dependencies?.[pkg] && !config['dev-dependencies']?.[pkg]) {
13
+ console.log(chalk.red(`Package "${pkg}" is not installed!`));
14
+ return;
15
+ }
16
+
17
+ // update github packages
18
+ let deps = Object.values(config.dependencies || {});
19
+ let devDeps = Object.values(config['dev-dependencies'] || {});
20
+ let githubDeps = [...deps, ...devDeps].filter((dep) => dep.repo);
21
+ if (pkg) {
22
+ githubDeps = githubDeps.filter((dep) => dep.name === pkg);
23
+ }
24
+
25
+ for (let dep of githubDeps) {
26
+ let {org, gitName, branch, commitHash} = parseGithubURL(dep.repo || '');
27
+ let dev = !!config['dev-dependencies']?.[dep.name];
28
+ let commit = await getGithubCommit(`${org}/${gitName}`, branch);
29
+ if (commit.sha !== commitHash) {
30
+ await add(`https://github.com/${org}/${gitName}#${branch}@${commit.sha}`, {dev});
31
+ }
32
+ }
33
+
34
+ // update mops packages
12
35
  let available = await getAvailableUpdates(config, pkg);
13
36
 
14
37
  if (available.length === 0) {
15
- console.log(chalk.green('All dependencies are up to date!'));
38
+ if (pkg) {
39
+ console.log(chalk.green(`Package "${pkg}" is up to date!`));
40
+ }
41
+ else {
42
+ console.log(chalk.green('All dependencies are up to date!'));
43
+ }
16
44
  }
17
45
  else {
18
46
  for (let dep of available) {
package/dist/cli.js CHANGED
@@ -121,8 +121,9 @@ program
121
121
  program
122
122
  .command('import-identity <data>')
123
123
  .description('Import .pem file data to use as identity')
124
- .action(async (data) => {
125
- await importPem(data);
124
+ .addOption(new Option('--no-encrypt', 'Do not ask for a password to encrypt identity'))
125
+ .action(async (data, options) => {
126
+ await importPem(data, options);
126
127
  await whoami();
127
128
  });
128
129
  // sources
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import chalk from 'chalk';
3
3
  import logUpdate from 'log-update';
4
- import { checkConfigFile, getHighestVersion, parseGithubURL, readConfig, writeConfig } from '../mops.js';
4
+ import { checkConfigFile, getGithubCommit, getHighestVersion, parseGithubURL, readConfig, writeConfig } from '../mops.js';
5
5
  import { installFromGithub } from '../vessel.js';
6
6
  import { install } from './install.js';
7
7
  import { notifyInstalls } from '../notify-installs.js';
@@ -32,10 +32,18 @@ export async function add(name, { verbose = false, dev = false } = {}) {
32
32
  }
33
33
  // github package
34
34
  else if (name.startsWith('https://github.com') || name.split('/').length > 1) {
35
- const { org, gitName, branch } = parseGithubURL(name);
35
+ let { org, gitName, branch, commitHash } = parseGithubURL(name);
36
+ // fetch latest commit hash of branch if not specified
37
+ if (!commitHash) {
38
+ let commit = await getGithubCommit(`${org}/${gitName}`, branch);
39
+ if (!commit.sha) {
40
+ throw Error(`Could not find commit hash for ${name}`);
41
+ }
42
+ commitHash = commit.sha;
43
+ }
36
44
  pkgDetails = {
37
45
  name: parseGithubURL(name).gitName,
38
- repo: `https://github.com/${org}/${gitName}#${branch}`,
46
+ repo: `https://github.com/${org}/${gitName}#${branch}@${commitHash}`,
39
47
  version: '',
40
48
  };
41
49
  }
@@ -82,4 +82,8 @@ actor class() {
82
82
  public func runCellUpdate(rowIndex : Nat, colIndex : Nat) : async Bench.BenchResult {
83
83
  await _runCellAwait(rowIndex, colIndex);
84
84
  };
85
+
86
+ public func runCellUpdateAwait(rowIndex : Nat, colIndex : Nat) : async Bench.BenchResult {
87
+ _runCell(rowIndex, colIndex);
88
+ };
85
89
  };
@@ -1,10 +1,11 @@
1
1
  type BenchOptions = {
2
- verbose?: boolean;
3
- save?: boolean;
4
- compare?: boolean;
5
2
  dfx?: string;
6
3
  moc?: string;
7
- gc?: 'copying' | 'compacting' | 'generational' | 'incremental' | 'none';
4
+ gc?: 'copying' | 'compacting' | 'generational' | 'incremental';
5
+ forceGc?: boolean;
6
+ save?: boolean;
7
+ compare?: boolean;
8
+ verbose?: boolean;
8
9
  };
9
10
  export declare function bench(filter?: string, options?: BenchOptions): Promise<boolean>;
10
11
  export {};
@@ -12,6 +12,8 @@ import { createActor } from '../declarations/bench/index.js';
12
12
  import { absToRel } from './test/utils.js';
13
13
  import { getMocVersion } from '../helpers/get-moc-version.js';
14
14
  import { getDfxVersion } from '../helpers/get-dfx-version.js';
15
+ import { getMocPath } from '../helpers/get-moc-path.js';
16
+ import { sources } from './sources.js';
15
17
  let ignore = [
16
18
  '**/node_modules/**',
17
19
  '**/.mops/**',
@@ -23,6 +25,16 @@ let globConfig = {
23
25
  ignore: ignore,
24
26
  };
25
27
  export async function bench(filter = '', options = {}) {
28
+ let defaultOptions = {
29
+ moc: getMocVersion(),
30
+ dfx: getDfxVersion(),
31
+ gc: 'incremental',
32
+ forceGc: true,
33
+ save: false,
34
+ compare: false,
35
+ verbose: false,
36
+ };
37
+ options = { ...defaultOptions, ...options };
26
38
  let rootDir = getRootDir();
27
39
  let globStr = '**/bench?(mark)/**/*.bench.mo';
28
40
  if (filter) {
@@ -35,7 +47,7 @@ export async function bench(filter = '', options = {}) {
35
47
  return false;
36
48
  }
37
49
  console.log('No *.bench.mo files found');
38
- console.log('Put your benchmark files in \'bench\' directory in *.bench.mo files');
50
+ console.log('Put your benchmark code in \'bench\' directory in *.bench.mo files');
39
51
  return false;
40
52
  }
41
53
  files.sort();
@@ -48,17 +60,16 @@ export async function bench(filter = '', options = {}) {
48
60
  }
49
61
  console.log('');
50
62
  console.log('='.repeat(50));
63
+ console.log('');
51
64
  console.log('Starting dfx replica...');
52
65
  startDfx(options.verbose);
53
- let resultsByName = new Map();
54
66
  // await parallel(os.cpus().length, files, async (file: string) => {
55
67
  await parallel(1, files, async (file) => {
56
68
  console.log('\n' + '—'.repeat(50));
57
69
  console.log(`\nRunning ${chalk.gray(absToRel(file))}...`);
58
70
  console.log('');
59
71
  try {
60
- let { schema, results } = await runBenchFile(file, options);
61
- resultsByName.set(schema.name || absToRel(file), results);
72
+ await runBenchFile(file, options);
62
73
  }
63
74
  catch (err) {
64
75
  console.error('Unexpected error. Stopping dfx replica...');
@@ -66,42 +77,36 @@ export async function bench(filter = '', options = {}) {
66
77
  throw err;
67
78
  }
68
79
  });
69
- if (options.save) {
70
- console.log('Saving results to mops.bench.json...');
71
- let json = {
72
- version: 1,
73
- moc: options.moc || getMocVersion(),
74
- dfx: options.dfx || getDfxVersion(),
75
- gc: options.gc || 'incremental',
76
- results: {},
77
- };
78
- resultsByName.forEach((results, name) => {
79
- json.results[name] = Array.from(results.entries());
80
- });
81
- fs.writeFileSync(path.join(rootDir, 'mops.bench.json'), JSON.stringify(json, (_, val) => {
82
- if (typeof val === 'bigint') {
83
- return Number(val);
84
- }
85
- else {
86
- return val;
87
- }
88
- }, 2));
89
- }
90
80
  console.log('Stopping dfx replica...');
91
81
  stopDfx(options.verbose);
92
82
  fs.rmSync(benchDir, { recursive: true, force: true });
93
83
  return true;
94
84
  }
95
- function dfxJson(canisterName) {
85
+ function getMocArgs(options) {
86
+ let args = '';
87
+ if (options.forceGc) {
88
+ args += ' --force-gc';
89
+ }
90
+ if (options.gc) {
91
+ args += ` --${options.gc}-gc`;
92
+ }
93
+ return args;
94
+ }
95
+ function dfxJson(canisterName, options = {}) {
96
+ options || console.log(options);
97
+ let canisters = {};
98
+ if (canisterName) {
99
+ canisters[canisterName] = {
100
+ // type: 'motoko',
101
+ type: 'custom',
102
+ wasm: 'canister.wasm',
103
+ candid: 'canister.did',
104
+ // args: getMocArgs(options),
105
+ };
106
+ }
96
107
  return {
97
108
  version: 1,
98
- canisters: {
99
- [canisterName]: {
100
- type: 'motoko',
101
- main: 'canister.mo',
102
- args: '--force-gc --incremental-gc',
103
- }
104
- },
109
+ canisters,
105
110
  defaults: {
106
111
  build: {
107
112
  packtool: 'mops sources',
@@ -119,21 +124,34 @@ function startDfx(verbose = false) {
119
124
  stopDfx(verbose);
120
125
  let dir = path.join(getRootDir(), '.mops/.bench');
121
126
  fs.writeFileSync(path.join(dir, 'dfx.json'), JSON.stringify(dfxJson(''), null, 2));
122
- execSync('dfx start --background --clean', { cwd: dir, stdio: ['inherit', verbose ? 'inherit' : 'ignore', 'inherit'] });
127
+ execSync('dfx start --background --clean' + (verbose ? '' : ' -qqqq'), { cwd: dir, stdio: ['inherit', verbose ? 'inherit' : 'ignore', 'inherit'] });
123
128
  }
124
129
  function stopDfx(verbose = false) {
125
130
  let dir = path.join(getRootDir(), '.mops/.bench');
126
- execSync('dfx stop', { cwd: dir, stdio: ['pipe', verbose ? 'inherit' : 'ignore', 'pipe'] });
131
+ execSync('dfx stop' + (verbose ? '' : ' -qqqq'), { cwd: dir, stdio: ['pipe', verbose ? 'inherit' : 'ignore', 'pipe'] });
127
132
  }
128
- async function runBenchFile(file, options = {}) {
133
+ async function buildBenchFile(file, options = {}) {
129
134
  let rootDir = getRootDir();
130
135
  let tempDir = path.join(rootDir, '.mops/.bench/', path.parse(file).name);
131
136
  fs.mkdirSync(tempDir, { recursive: true });
132
- let canisterName = Date.now().toString(36);
137
+ // let canisterName = Date.now().toString(36);
138
+ let canisterName = path.parse(file).name;
133
139
  // prepare temp files
134
- fs.writeFileSync(path.join(tempDir, 'dfx.json'), JSON.stringify(dfxJson(canisterName), null, 2));
140
+ fs.writeFileSync(path.join(tempDir, 'dfx.json'), JSON.stringify(dfxJson(canisterName, options), null, 2));
135
141
  fs.cpSync(new URL('./bench/bench-canister.mo', import.meta.url), path.join(tempDir, 'canister.mo'));
136
142
  fs.cpSync(file, path.join(tempDir, 'user-bench.mo'));
143
+ // build canister
144
+ let mocPath = getMocPath();
145
+ let mocArgs = getMocArgs(options);
146
+ console.log(tempDir);
147
+ console.log(`${mocPath} -c --idl canister.mo ${mocArgs} ${(await sources({ cwd: tempDir })).join(' ')}`);
148
+ execSync(`${mocPath} -c --idl canister.mo ${mocArgs} ${(await sources({ cwd: tempDir })).join(' ')}`, { cwd: tempDir, stdio: options.verbose ? 'pipe' : ['pipe', 'ignore', 'pipe'] });
149
+ }
150
+ async function runBenchFile(file, options = {}) {
151
+ let rootDir = getRootDir();
152
+ await buildBenchFile(file, options);
153
+ let tempDir = path.join(rootDir, '.mops/.bench/', path.parse(file).name);
154
+ let canisterName = path.parse(file).name;
137
155
  // deploy canister
138
156
  execSync(`dfx deploy ${canisterName} --mode reinstall --yes --identity anonymous`, { cwd: tempDir, stdio: options.verbose ? 'pipe' : ['pipe', 'ignore', 'pipe'] });
139
157
  let canisterId = execSync(`dfx canister id ${canisterName}`, { cwd: tempDir }).toString().trim();
@@ -145,13 +163,14 @@ async function runBenchFile(file, options = {}) {
145
163
  let schema = await actor.init();
146
164
  // load previous results
147
165
  let prevResults;
166
+ let resultsJsonFile = path.join(rootDir, '.bench', `${path.parse(file).name}.json`);
148
167
  if (options.compare) {
149
- let prevResultsJson = JSON.parse(fs.readFileSync(path.join(rootDir, 'mops.bench.json')).toString());
150
- if (prevResultsJson.results[schema.name]) {
151
- prevResults = new Map(prevResultsJson.results[schema.name]);
168
+ if (fs.existsSync(resultsJsonFile)) {
169
+ let prevResultsJson = JSON.parse(fs.readFileSync(resultsJsonFile).toString());
170
+ prevResults = new Map(prevResultsJson.results);
152
171
  }
153
172
  else {
154
- console.log(chalk.yellow(`No previous results found for benchmark with name "${schema.name}"`));
173
+ console.log(chalk.yellow(`No previous results found "${resultsJsonFile}"`));
155
174
  }
156
175
  }
157
176
  let results = new Map();
@@ -205,10 +224,32 @@ async function runBenchFile(file, options = {}) {
205
224
  for (let [colIndex, col] of schema.cols.entries()) {
206
225
  // let res = await actor.runCellQuery(BigInt(rowIndex), BigInt(colIndex));
207
226
  let res = await actor.runCellUpdate(BigInt(rowIndex), BigInt(colIndex));
227
+ // let res = await actor.runCellUpdateAwait(BigInt(rowIndex), BigInt(colIndex));
208
228
  results.set(`${row}:${col}`, res);
209
229
  printResults();
210
230
  }
211
231
  }
212
232
  logUpdate.done();
233
+ // save results
234
+ if (options.save) {
235
+ console.log(`Saving results to ${chalk.gray(absToRel(resultsJsonFile))}`);
236
+ let json = {
237
+ version: 1,
238
+ moc: options.moc,
239
+ dfx: options.dfx,
240
+ gc: options.gc,
241
+ forceGc: options.forceGc,
242
+ results: Array.from(results.entries()),
243
+ };
244
+ fs.mkdirSync(path.dirname(resultsJsonFile), { recursive: true });
245
+ fs.writeFileSync(resultsJsonFile, JSON.stringify(json, (_, val) => {
246
+ if (typeof val === 'bigint') {
247
+ return Number(val);
248
+ }
249
+ else {
250
+ return val;
251
+ }
252
+ }, 2));
253
+ }
213
254
  return { schema, results };
214
255
  }
@@ -1 +1,5 @@
1
- export declare function importPem(data: string): Promise<void>;
1
+ type ImportIdentityOptions = {
2
+ encrypt: boolean;
3
+ };
4
+ export declare function importPem(data: string, options?: ImportIdentityOptions): Promise<void>;
5
+ export {};
@@ -5,26 +5,29 @@ import prompts from 'prompts';
5
5
  import { deleteSync } from 'del';
6
6
  import { globalConfigDir } from '../mops.js';
7
7
  import { encrypt } from '../pem.js';
8
- export async function importPem(data) {
8
+ export async function importPem(data, options = { encrypt: true }) {
9
9
  try {
10
10
  if (!fs.existsSync(globalConfigDir)) {
11
11
  fs.mkdirSync(globalConfigDir);
12
12
  }
13
- let res = await prompts({
14
- type: 'password',
15
- name: 'password',
16
- message: 'Enter password to encrypt identity.pem',
17
- });
18
- let password = res.password;
19
- if (!password) {
13
+ let password = '';
14
+ if (options.encrypt) {
20
15
  let res = await prompts({
21
- type: 'confirm',
22
- name: 'ok',
23
- message: 'Are you sure you don\'t want to protect your identity.pem with a password?',
16
+ type: 'invisible',
17
+ name: 'password',
18
+ message: 'Enter password to encrypt identity.pem',
24
19
  });
25
- if (!res.ok) {
26
- console.log('aborted');
27
- return;
20
+ password = res.password;
21
+ if (!password) {
22
+ let res = await prompts({
23
+ type: 'confirm',
24
+ name: 'ok',
25
+ message: 'Are you sure you don\'t want to protect your identity.pem with a password?',
26
+ });
27
+ if (!res.ok) {
28
+ console.log('aborted');
29
+ return;
30
+ }
28
31
  }
29
32
  }
30
33
  let identityPem = path.resolve(globalConfigDir, 'identity.pem');
@@ -31,13 +31,13 @@ export async function install(pkg, version = '', { verbose = false, silent = fal
31
31
  let alreadyInstalled = false;
32
32
  // already installed
33
33
  if (fs.existsSync(dir)) {
34
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (already installed)`);
34
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (local cache)`);
35
35
  alreadyInstalled = true;
36
36
  }
37
37
  // copy from cache
38
38
  else if (isCached(`${pkg}@${version}`)) {
39
39
  await copyCache(`${pkg}@${version}`, dir);
40
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (cache)`);
40
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${pkg}@${version} (global cache)`);
41
41
  }
42
42
  // download
43
43
  else {
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { checkConfigFile, readConfig } from '../mops.js';
2
+ import { checkConfigFile, getGithubCommit, parseGithubURL, readConfig } from '../mops.js';
3
3
  import { add } from './add.js';
4
4
  import { getAvailableUpdates } from './available-updates.js';
5
5
  export async function update(pkg) {
@@ -7,9 +7,34 @@ export async function update(pkg) {
7
7
  return;
8
8
  }
9
9
  let config = readConfig();
10
+ if (pkg && !config.dependencies?.[pkg] && !config['dev-dependencies']?.[pkg]) {
11
+ console.log(chalk.red(`Package "${pkg}" is not installed!`));
12
+ return;
13
+ }
14
+ // update github packages
15
+ let deps = Object.values(config.dependencies || {});
16
+ let devDeps = Object.values(config['dev-dependencies'] || {});
17
+ let githubDeps = [...deps, ...devDeps].filter((dep) => dep.repo);
18
+ if (pkg) {
19
+ githubDeps = githubDeps.filter((dep) => dep.name === pkg);
20
+ }
21
+ for (let dep of githubDeps) {
22
+ let { org, gitName, branch, commitHash } = parseGithubURL(dep.repo || '');
23
+ let dev = !!config['dev-dependencies']?.[dep.name];
24
+ let commit = await getGithubCommit(`${org}/${gitName}`, branch);
25
+ if (commit.sha !== commitHash) {
26
+ await add(`https://github.com/${org}/${gitName}#${branch}@${commit.sha}`, { dev });
27
+ }
28
+ }
29
+ // update mops packages
10
30
  let available = await getAvailableUpdates(config, pkg);
11
31
  if (available.length === 0) {
12
- console.log(chalk.green('All dependencies are up to date!'));
32
+ if (pkg) {
33
+ console.log(chalk.green(`Package "${pkg}" is up to date!`));
34
+ }
35
+ else {
36
+ console.log(chalk.green('All dependencies are up to date!'));
37
+ }
13
38
  }
14
39
  else {
15
40
  for (let dep of available) {
@@ -4,6 +4,7 @@ type anon_class_10_1 =
4
4
  init: () -> (BenchSchema);
5
5
  runCellQuery: (nat, nat) -> (BenchResult) query;
6
6
  runCellUpdate: (nat, nat) -> (BenchResult);
7
+ runCellUpdateAwait: (nat, nat) -> (BenchResult);
7
8
  };
8
9
  type BenchSchema =
9
10
  record {
@@ -20,5 +20,6 @@ export interface anon_class_10_1 {
20
20
  'init' : ActorMethod<[], BenchSchema>,
21
21
  'runCellQuery' : ActorMethod<[bigint, bigint], BenchResult>,
22
22
  'runCellUpdate' : ActorMethod<[bigint, bigint], BenchResult>,
23
+ 'runCellUpdateAwait' : ActorMethod<[bigint, bigint], BenchResult>,
23
24
  }
24
25
  export interface _SERVICE extends anon_class_10_1 {}
@@ -18,6 +18,7 @@ export const idlFactory = ({ IDL }) => {
18
18
  'init' : IDL.Func([], [BenchSchema], []),
19
19
  'runCellQuery' : IDL.Func([IDL.Nat, IDL.Nat], [BenchResult], ['query']),
20
20
  'runCellUpdate' : IDL.Func([IDL.Nat, IDL.Nat], [BenchResult], []),
21
+ 'runCellUpdateAwait' : IDL.Func([IDL.Nat, IDL.Nat], [BenchResult], []),
21
22
  });
22
23
  return anon_class_10_1;
23
24
  };
@@ -0,0 +1,4 @@
1
+ export declare function getFileHashesFromRegistry(packageIds: string[]): Promise<[string, [string, Uint8Array | number[]][]][]>;
2
+ export declare function checkIntegrity(): Promise<void>;
3
+ export declare function saveLockFile(): Promise<void>;
4
+ export declare function checkLockFile(): Promise<void>;
@@ -0,0 +1,92 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { sha256 } from '@noble/hashes/sha256';
4
+ import { bytesToHex } from '@noble/hashes/utils';
5
+ import { getDependencyType, getRootDir, mainActor } from './mops.js';
6
+ import { resolvePackages } from './resolve-packages.js';
7
+ export async function getFileHashesFromRegistry(packageIds) {
8
+ let actor = await mainActor();
9
+ let fileHashesByPackageIds = await actor.getFileHashesByPackageIds(packageIds);
10
+ return fileHashesByPackageIds;
11
+ }
12
+ export async function checkIntegrity() {
13
+ let rootDir = getRootDir();
14
+ let resolvedPackages = await resolvePackages();
15
+ let packageIds = Object.entries(resolvedPackages)
16
+ .filter(([_, version]) => getDependencyType(version) === 'mops')
17
+ .map(([name, version]) => `${name}@${version}`);
18
+ let fileHashesFromRegistry = await getFileHashesFromRegistry(packageIds);
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ for (let [_packageId, fileHashes] of fileHashesFromRegistry) {
21
+ for (let [fileId, hash] of fileHashes) {
22
+ let remoteHash = new Uint8Array(hash);
23
+ let localData = fs.readFileSync(path.join(rootDir, '.mops', fileId));
24
+ let localHash = await sha256(localData);
25
+ if (bytesToHex(localHash) !== bytesToHex(remoteHash)) {
26
+ console.error('Integrity check failed.');
27
+ console.error(`Mismatched hash for ${fileId}: ${bytesToHex(localHash)} vs ${bytesToHex(remoteHash)}`);
28
+ process.exit(1);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ export async function saveLockFile() {
34
+ let rootDir = getRootDir();
35
+ let resolvedPackages = await resolvePackages();
36
+ let packageIds = Object.entries(resolvedPackages)
37
+ .filter(([_, version]) => getDependencyType(version) === 'mops')
38
+ .map(([name, version]) => `${name}@${version}`);
39
+ let fileHashes = await getFileHashesFromRegistry(packageIds);
40
+ let lockFileJson = {
41
+ version: 1,
42
+ hashes: fileHashes.reduce((acc, [packageId, fileHashes]) => {
43
+ acc[packageId] = fileHashes.reduce((acc, [fileId, hash]) => {
44
+ acc[fileId] = bytesToHex(new Uint8Array(hash));
45
+ return acc;
46
+ }, {});
47
+ return acc;
48
+ }, {}),
49
+ };
50
+ fs.writeFileSync(rootDir + '/mops-lock.json', JSON.stringify(lockFileJson, null, 2));
51
+ }
52
+ export async function checkLockFile() {
53
+ let rootDir = getRootDir();
54
+ let resolvedPackages = await resolvePackages();
55
+ let packageIds = Object.entries(resolvedPackages)
56
+ .filter(([_name, version]) => getDependencyType(version) === 'mops')
57
+ .map(([name, version]) => `${name}@${version}`);
58
+ let fileHashesFromRegistry = await getFileHashesFromRegistry(packageIds);
59
+ let lockFileJson = JSON.parse(fs.readFileSync(rootDir + '/mops-lock.json').toString());
60
+ if (lockFileJson.version !== 1) {
61
+ console.error(`Invalid lock file version: ${lockFileJson.version}`);
62
+ process.exit(1);
63
+ }
64
+ // if (lockFileJson.packageIds.length !== packageIds.length) {
65
+ // console.error(`Mismatched packageIds: ${JSON.stringify(lockFileJson.packageIds)} vs ${JSON.stringify(packageIds)}`);
66
+ // process.exit(1);
67
+ // }
68
+ // for (let i = 0; i < packageIds.length; i++) {
69
+ // if (lockFileJson.packageIds[i] !== packageIds[i]) {
70
+ // console.error(`Mismatched packageIds: ${JSON.stringify(lockFileJson.packageIds)} vs ${JSON.stringify(packageIds)}`);
71
+ // process.exit(1);
72
+ // }
73
+ // }
74
+ for (let [packageId, fileHashes] of fileHashesFromRegistry) {
75
+ let hashes = lockFileJson.hashes[packageId];
76
+ if (!hashes) {
77
+ console.error(`Missing packageId ${packageId} in lock file`);
78
+ process.exit(1);
79
+ }
80
+ for (let [fileId, hash] of fileHashes) {
81
+ let lockFileHash = hashes[fileId];
82
+ if (!lockFileHash) {
83
+ console.error(`Missing fileId ${fileId} in lock file`);
84
+ process.exit(1);
85
+ }
86
+ if (lockFileHash !== bytesToHex(new Uint8Array(hash))) {
87
+ console.error(`Mismatched hash for ${fileId}: ${lockFileHash} vs ${bytesToHex(new Uint8Array(hash))}`);
88
+ process.exit(1);
89
+ }
90
+ }
91
+ }
92
+ }
package/dist/mops.d.ts CHANGED
@@ -21,10 +21,12 @@ export declare function checkConfigFile(): boolean;
21
21
  export declare function progressBar(step: number, total: number): string;
22
22
  export declare function getHighestVersion(pkgName: string): Promise<import("./declarations/main/main.did.js").Result_5>;
23
23
  export declare function parseGithubURL(href: string): {
24
- org: string | undefined;
25
- gitName: string | undefined;
24
+ org: string;
25
+ gitName: string;
26
26
  branch: string;
27
+ commitHash: string;
27
28
  };
29
+ export declare function getGithubCommit(repo: string, ref: string): Promise<any>;
28
30
  export declare function getDependencyType(version: string): "mops" | "github" | "local";
29
31
  export declare function readConfig(configFile?: string): Config;
30
32
  export declare function writeConfig(config: Config, configFile?: string): void;
package/dist/mops.js CHANGED
@@ -104,7 +104,7 @@ export let getIdentity = async () => {
104
104
  let identityPemEncrypted = path.resolve(globalConfigDir, 'identity.pem.encrypted');
105
105
  if (fs.existsSync(identityPemEncrypted)) {
106
106
  let res = await prompts({
107
- type: 'password',
107
+ type: 'invisible',
108
108
  name: 'value',
109
109
  message: 'Enter password:'
110
110
  });
@@ -179,12 +179,26 @@ export async function getHighestVersion(pkgName) {
179
179
  }
180
180
  export function parseGithubURL(href) {
181
181
  const url = new URL(href);
182
- const branch = url.hash?.substring(1) || 'master';
182
+ let branchAndSha = url.hash?.substring(1).split('@');
183
+ let branch = branchAndSha[0] || 'master';
184
+ let commitHash = branchAndSha[1] || '';
183
185
  let [org, gitName] = url.pathname.split('/').filter(path => !!path);
186
+ org = org || '';
187
+ gitName = gitName || '';
184
188
  if (gitName?.endsWith('.git')) {
185
189
  gitName = gitName.substring(0, gitName.length - 4);
186
190
  }
187
- return { org, gitName, branch };
191
+ return { org, gitName, branch, commitHash };
192
+ }
193
+ export async function getGithubCommit(repo, ref) {
194
+ let res = await fetch(`https://api.github.com/repos/${repo}/commits/${ref}`);
195
+ let json = await res.json();
196
+ // try on main branch
197
+ if (json.message && ref === 'master') {
198
+ res = await fetch(`https://api.github.com/repos/${repo}/commits/main`);
199
+ json = await res.json();
200
+ }
201
+ return json;
188
202
  }
189
203
  export function getDependencyType(version) {
190
204
  if (!version || typeof version !== 'string') {
@@ -244,8 +258,8 @@ export function formatDir(name, version) {
244
258
  return path.join(getRootDir(), '.mops', `${name}@${version}`);
245
259
  }
246
260
  export function formatGithubDir(name, repo) {
247
- const { branch } = parseGithubURL(repo);
248
- return path.join(getRootDir(), '.mops/_github', `${name}@${branch}`);
261
+ const { branch, commitHash } = parseGithubURL(repo);
262
+ return path.join(getRootDir(), '.mops/_github', `${name}#${branch}` + (commitHash ? `@${commitHash}` : ''));
249
263
  }
250
264
  export function readDfxJson() {
251
265
  let dir = process.cwd();
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/cli.js"
package/dist/vessel.js CHANGED
@@ -59,8 +59,8 @@ export const readVesselConfig = async (dir, { cache = true } = {}) => {
59
59
  return config;
60
60
  };
61
61
  export const downloadFromGithub = async (repo, dest, onProgress) => {
62
- const { branch, org, gitName } = parseGithubURL(repo);
63
- const zipFile = `https://github.com/${org}/${gitName}/archive/${branch}.zip`;
62
+ const { branch, org, gitName, commitHash } = parseGithubURL(repo);
63
+ const zipFile = `https://github.com/${org}/${gitName}/archive/${commitHash || branch}.zip`;
64
64
  const readStream = got.stream(zipFile);
65
65
  const promise = new Promise((resolve, reject) => {
66
66
  readStream.on('error', (err) => {
@@ -71,7 +71,7 @@ export const downloadFromGithub = async (repo, dest, onProgress) => {
71
71
  });
72
72
  readStream.on('response', (response) => {
73
73
  if (response.headers.age > 3600) {
74
- console.log(chalk.red('Error: ') + 'Failure - response too old');
74
+ console.error(chalk.red('Error: ') + 'Failure - response too old');
75
75
  readStream.destroy(); // Destroy the stream to prevent hanging resources.
76
76
  reject();
77
77
  return;
@@ -79,7 +79,7 @@ export const downloadFromGithub = async (repo, dest, onProgress) => {
79
79
  // Prevent `onError` being called twice.
80
80
  readStream.off('error', reject);
81
81
  const tmpDir = path.resolve(process.cwd(), '.mops/_tmp/');
82
- const tmpFile = path.resolve(tmpDir, `${gitName}@${branch}.zip`);
82
+ const tmpFile = path.resolve(tmpDir, `${gitName}@${commitHash || branch}.zip`);
83
83
  try {
84
84
  mkdirSync(tmpDir, { recursive: true });
85
85
  pipeline(readStream, createWriteStream(tmpFile), (err) => {
@@ -114,20 +114,20 @@ export const downloadFromGithub = async (repo, dest, onProgress) => {
114
114
  return promise;
115
115
  };
116
116
  export const installFromGithub = async (name, repo, { verbose = false, dep = false, silent = false } = {}) => {
117
- const { branch } = parseGithubURL(repo);
118
- const dir = formatGithubDir(name, repo);
119
- const cacheName = `github_${name}@${branch}`;
117
+ let { branch, commitHash } = parseGithubURL(repo);
118
+ let dir = formatGithubDir(name, repo);
119
+ let cacheName = `_github/${name}#${branch}` + (commitHash ? `@${commitHash}` : '');
120
120
  if (existsSync(dir)) {
121
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} (already installed) from Github`);
121
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (local cache) from Github`);
122
122
  }
123
123
  else if (isCached(cacheName)) {
124
124
  await copyCache(cacheName, dir);
125
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} (cache) from Github`);
125
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache) from Github`);
126
126
  }
127
127
  else {
128
128
  mkdirSync(dir, { recursive: true });
129
129
  let progress = (step, total) => {
130
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} ${progressBar(step, total)}`);
130
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} ${progressBar(step, total)}`);
131
131
  };
132
132
  progress(0, 2 * (1024 ** 2));
133
133
  try {
package/mops.ts CHANGED
@@ -121,7 +121,7 @@ export let getIdentity = async (): Promise<Identity | undefined> => {
121
121
  let identityPemEncrypted = path.resolve(globalConfigDir, 'identity.pem.encrypted');
122
122
  if (fs.existsSync(identityPemEncrypted)) {
123
123
  let res = await prompts({
124
- type: 'password',
124
+ type: 'invisible',
125
125
  name: 'value',
126
126
  message: 'Enter password:'
127
127
  });
@@ -210,15 +210,29 @@ export async function getHighestVersion(pkgName: string) {
210
210
 
211
211
  export function parseGithubURL(href: string) {
212
212
  const url = new URL(href);
213
- const branch = url.hash?.substring(1) || 'master';
214
-
213
+ let branchAndSha = url.hash?.substring(1).split('@');
214
+ let branch = branchAndSha[0] || 'master';
215
+ let commitHash = branchAndSha[1] || '';
215
216
  let [org, gitName] = url.pathname.split('/').filter(path => !!path);
217
+ org = org || '';
218
+ gitName = gitName || '';
216
219
 
217
220
  if (gitName?.endsWith('.git')) {
218
221
  gitName = gitName.substring(0, gitName.length - 4);
219
222
  }
223
+ return {org, gitName, branch, commitHash};
224
+ }
220
225
 
221
- return {org, gitName, branch};
226
+ export async function getGithubCommit(repo: string, ref: string): Promise<any> {
227
+ let res = await fetch(`https://api.github.com/repos/${repo}/commits/${ref}`);
228
+ let json: any = await res.json();
229
+
230
+ // try on main branch
231
+ if (json.message && ref === 'master') {
232
+ res = await fetch(`https://api.github.com/repos/${repo}/commits/main`);
233
+ json = await res.json();
234
+ }
235
+ return json;
222
236
  }
223
237
 
224
238
  export function getDependencyType(version: string) {
@@ -289,8 +303,8 @@ export function formatDir(name: string, version: string) {
289
303
  }
290
304
 
291
305
  export function formatGithubDir(name: string, repo: string) {
292
- const {branch} = parseGithubURL(repo);
293
- return path.join(getRootDir(), '.mops/_github', `${name}@${branch}`);
306
+ const {branch, commitHash} = parseGithubURL(repo);
307
+ return path.join(getRootDir(), '.mops/_github', `${name}#${branch}` + (commitHash ? `@${commitHash}` : ''));
294
308
  }
295
309
 
296
310
  export function readDfxJson(): any {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/cli.js"
package/vessel.ts CHANGED
@@ -83,9 +83,9 @@ export const readVesselConfig = async (dir: string, {cache = true} = {}): Promis
83
83
  };
84
84
 
85
85
  export const downloadFromGithub = async (repo: string, dest: string, onProgress: any) => {
86
- const {branch, org, gitName} = parseGithubURL(repo);
86
+ const {branch, org, gitName, commitHash} = parseGithubURL(repo);
87
87
 
88
- const zipFile = `https://github.com/${org}/${gitName}/archive/${branch}.zip`;
88
+ const zipFile = `https://github.com/${org}/${gitName}/archive/${commitHash || branch}.zip`;
89
89
  const readStream = got.stream(zipFile);
90
90
 
91
91
  const promise = new Promise((resolve, reject) => {
@@ -99,7 +99,7 @@ export const downloadFromGithub = async (repo: string, dest: string, onProgress:
99
99
 
100
100
  readStream.on('response', (response) => {
101
101
  if (response.headers.age > 3600) {
102
- console.log(chalk.red('Error: ') + 'Failure - response too old');
102
+ console.error(chalk.red('Error: ') + 'Failure - response too old');
103
103
  readStream.destroy(); // Destroy the stream to prevent hanging resources.
104
104
  reject();
105
105
  return;
@@ -108,7 +108,7 @@ export const downloadFromGithub = async (repo: string, dest: string, onProgress:
108
108
  // Prevent `onError` being called twice.
109
109
  readStream.off('error', reject);
110
110
  const tmpDir = path.resolve(process.cwd(), '.mops/_tmp/');
111
- const tmpFile = path.resolve(tmpDir, `${gitName}@${branch}.zip`);
111
+ const tmpFile = path.resolve(tmpDir, `${gitName}@${commitHash || branch}.zip`);
112
112
 
113
113
  try {
114
114
  mkdirSync(tmpDir, {recursive: true});
@@ -147,22 +147,22 @@ export const downloadFromGithub = async (repo: string, dest: string, onProgress:
147
147
  };
148
148
 
149
149
  export const installFromGithub = async (name: string, repo: string, {verbose = false, dep = false, silent = false} = {}) => {
150
- const {branch} = parseGithubURL(repo);
151
- const dir = formatGithubDir(name, repo);
152
- const cacheName = `github_${name}@${branch}`;
150
+ let {branch, commitHash} = parseGithubURL(repo);
151
+ let dir = formatGithubDir(name, repo);
152
+ let cacheName = `_github/${name}#${branch}` + (commitHash ? `@${commitHash}` : '');
153
153
 
154
154
  if (existsSync(dir)) {
155
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} (already installed) from Github`);
155
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (local cache) from Github`);
156
156
  }
157
157
  else if (isCached(cacheName)) {
158
158
  await copyCache(cacheName, dir);
159
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} (cache) from Github`);
159
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache) from Github`);
160
160
  }
161
161
  else {
162
162
  mkdirSync(dir, {recursive: true});
163
163
 
164
164
  let progress = (step: number, total: number) => {
165
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${name}@${branch} ${progressBar(step, total)}`);
165
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} ${progressBar(step, total)}`);
166
166
  };
167
167
 
168
168
  progress(0, 2 * (1024 ** 2));