ic-mops 0.32.2 → 0.34.0-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.
Files changed (46) hide show
  1. package/.npmrc +1 -0
  2. package/cli.ts +10 -5
  3. package/commands/add.ts +15 -2
  4. package/commands/install-all.ts +15 -2
  5. package/commands/install.ts +27 -52
  6. package/commands/remove.ts +12 -1
  7. package/commands/search.ts +1 -2
  8. package/commands/sync.ts +34 -27
  9. package/commands/test/test.ts +7 -1
  10. package/commands/update.ts +10 -1
  11. package/declarations/main/main.did +40 -0
  12. package/declarations/main/main.did.d.ts +26 -0
  13. package/declarations/main/main.did.js +35 -0
  14. package/dist/cli.js +10 -5
  15. package/dist/commands/add.d.ts +7 -4
  16. package/dist/commands/add.js +7 -2
  17. package/dist/commands/install-all.d.ts +7 -4
  18. package/dist/commands/install-all.js +9 -2
  19. package/dist/commands/install.js +23 -46
  20. package/dist/commands/remove.d.ts +8 -5
  21. package/dist/commands/remove.js +3 -1
  22. package/dist/commands/search.js +1 -2
  23. package/dist/commands/sync.d.ts +5 -1
  24. package/dist/commands/sync.js +23 -21
  25. package/dist/commands/test/test.js +7 -1
  26. package/dist/commands/update.d.ts +7 -1
  27. package/dist/commands/update.js +3 -1
  28. package/dist/declarations/main/main.did +40 -0
  29. package/dist/declarations/main/main.did.d.ts +26 -0
  30. package/dist/declarations/main/main.did.js +35 -0
  31. package/dist/helpers/download-package-files.d.ts +12 -0
  32. package/dist/helpers/download-package-files.js +62 -0
  33. package/dist/helpers/resolve-version.d.ts +1 -0
  34. package/dist/helpers/resolve-version.js +11 -0
  35. package/dist/integrity.d.ts +5 -0
  36. package/dist/integrity.js +155 -0
  37. package/dist/mops.d.ts +2 -0
  38. package/dist/mops.js +2 -0
  39. package/dist/package.json +2 -1
  40. package/dist/vessel.js +2 -2
  41. package/helpers/download-package-files.ts +78 -0
  42. package/helpers/resolve-version.ts +12 -0
  43. package/integrity.ts +188 -0
  44. package/mops.ts +4 -1
  45. package/package.json +2 -1
  46. package/vessel.ts +2 -2
@@ -0,0 +1,12 @@
1
+ import { Principal } from '@dfinity/principal';
2
+ import { Storage } from '../declarations/storage/storage.did.js';
3
+ export declare function downloadPackageFiles(pkg: string, version?: string, threads?: number, onLoad?: (_fileIds: string[], _fileId: string) => void): Promise<Map<string, string>>;
4
+ export declare function getPackageFilesInfo(pkg: string, version: string): Promise<{
5
+ storageId: Principal;
6
+ fileIds: string[];
7
+ }>;
8
+ export declare function getFileIds(pkg: string, version: string): Promise<string[]>;
9
+ export declare function downloadFile(storage: Storage, fileId: string): Promise<{
10
+ path: string;
11
+ data: Array<number>;
12
+ }>;
@@ -0,0 +1,62 @@
1
+ import { mainActor, storageActor } from '../mops.js';
2
+ import { parallel } from '../parallel.js';
3
+ import { resolveVersion } from './resolve-version.js';
4
+ export async function downloadPackageFiles(pkg, version = '', threads = 8, onLoad = (_fileIds, _fileId) => { }) {
5
+ version = await resolveVersion(pkg, version);
6
+ let { storageId, fileIds } = await getPackageFilesInfo(pkg, version);
7
+ let storage = await storageActor(storageId);
8
+ let filesData = new Map;
9
+ await parallel(threads, fileIds, async (fileId) => {
10
+ let { path, data } = await downloadFile(storage, fileId);
11
+ filesData.set(path, data);
12
+ onLoad(fileIds, fileId);
13
+ });
14
+ return filesData;
15
+ }
16
+ // get package files meta
17
+ export async function getPackageFilesInfo(pkg, version) {
18
+ let actor = await mainActor();
19
+ let [packageDetailsRes, fileIds] = await Promise.all([
20
+ actor.getPackageDetails(pkg, version),
21
+ getFileIds(pkg, version),
22
+ ]);
23
+ if ('err' in packageDetailsRes) {
24
+ throw packageDetailsRes.err;
25
+ }
26
+ let packageDetails = packageDetailsRes.ok;
27
+ return {
28
+ storageId: packageDetails.publication.storage,
29
+ fileIds,
30
+ };
31
+ }
32
+ // get package files ids
33
+ export async function getFileIds(pkg, version) {
34
+ let actor = await mainActor();
35
+ let fileIdsRes = await actor.getFileIds(pkg, version);
36
+ if ('err' in fileIdsRes) {
37
+ throw fileIdsRes.err;
38
+ }
39
+ let filesIds = fileIdsRes.ok;
40
+ return filesIds;
41
+ }
42
+ // download single file
43
+ export async function downloadFile(storage, fileId) {
44
+ let fileMetaRes = await storage.getFileMeta(fileId);
45
+ if ('err' in fileMetaRes) {
46
+ throw fileMetaRes.err;
47
+ }
48
+ let fileMeta = fileMetaRes.ok;
49
+ let data = [];
50
+ for (let i = 0n; i < fileMeta.chunkCount; i++) {
51
+ let chunkRes = await storage.downloadChunk(fileId, i);
52
+ if ('err' in chunkRes) {
53
+ throw chunkRes.err;
54
+ }
55
+ let chunk = chunkRes.ok;
56
+ data = [...data, ...chunk];
57
+ }
58
+ return {
59
+ path: fileMeta.path,
60
+ data: data,
61
+ };
62
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveVersion(pkg: string, version?: string): Promise<string>;
@@ -0,0 +1,11 @@
1
+ import { getHighestVersion } from '../mops.js';
2
+ export async function resolveVersion(pkg, version = '') {
3
+ if (!version) {
4
+ let versionRes = await getHighestVersion(pkg);
5
+ if ('err' in versionRes) {
6
+ throw versionRes.err;
7
+ }
8
+ version = versionRes.ok;
9
+ }
10
+ return version;
11
+ }
@@ -0,0 +1,5 @@
1
+ export declare function checkIntegrity(lock?: 'save' | 'check' | 'ignore'): Promise<void>;
2
+ export declare function getLocalFileHash(fileId: string): string;
3
+ export declare function checkRemote(): Promise<void>;
4
+ export declare function saveLockFile(): Promise<void>;
5
+ export declare function checkLockFile(force?: boolean): Promise<void>;
@@ -0,0 +1,155 @@
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 checkIntegrity(lock) {
8
+ let force = !!lock;
9
+ if (!lock) {
10
+ lock = process.env['CI'] ? 'check' : 'ignore';
11
+ }
12
+ if (lock === 'save') {
13
+ await saveLockFile();
14
+ await checkLockFile(force);
15
+ }
16
+ else if (lock === 'check') {
17
+ await checkLockFile(force);
18
+ }
19
+ }
20
+ async function getFileHashesFromRegistry() {
21
+ let packageIds = await getResolvedMopsPackageIds();
22
+ let actor = await mainActor();
23
+ let fileHashesByPackageIds = await actor.getFileHashesByPackageIds(packageIds);
24
+ return fileHashesByPackageIds;
25
+ }
26
+ async function getResolvedMopsPackageIds() {
27
+ let resolvedPackages = await resolvePackages();
28
+ let packageIds = Object.entries(resolvedPackages)
29
+ .filter(([_, version]) => getDependencyType(version) === 'mops')
30
+ .map(([name, version]) => `${name}@${version}`);
31
+ return packageIds;
32
+ }
33
+ // get hash of local file from '.mops' dir by fileId
34
+ export function getLocalFileHash(fileId) {
35
+ let rootDir = getRootDir();
36
+ let file = path.join(rootDir, '.mops', fileId);
37
+ if (!fs.existsSync(file)) {
38
+ console.error(`Missing file ${fileId} in .mops dir`);
39
+ process.exit(1);
40
+ }
41
+ let fileData = fs.readFileSync(file);
42
+ return bytesToHex(sha256(fileData));
43
+ }
44
+ function getMopsTomlHash() {
45
+ return bytesToHex(sha256(fs.readFileSync(getRootDir() + '/mops.toml')));
46
+ }
47
+ // compare hashes of local files with hashes from the registry
48
+ export async function checkRemote() {
49
+ let fileHashesFromRegistry = await getFileHashesFromRegistry();
50
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
+ for (let [_packageId, fileHashes] of fileHashesFromRegistry) {
52
+ for (let [fileId, hash] of fileHashes) {
53
+ let remoteHash = new Uint8Array(hash);
54
+ let localHash = getLocalFileHash(fileId);
55
+ if (localHash !== bytesToHex(remoteHash)) {
56
+ console.error('Integrity check failed.');
57
+ console.error(`Mismatched hash for ${fileId}: ${localHash} vs ${bytesToHex(remoteHash)}`);
58
+ process.exit(1);
59
+ }
60
+ }
61
+ }
62
+ }
63
+ export async function saveLockFile() {
64
+ let rootDir = getRootDir();
65
+ let lockFile = path.join(rootDir, 'mops.lock');
66
+ // if lock file exists and mops.toml hasn't changed, don't update it
67
+ if (fs.existsSync(lockFile)) {
68
+ let lockFileJson = JSON.parse(fs.readFileSync(lockFile).toString());
69
+ let mopsTomlHash = getMopsTomlHash();
70
+ if (mopsTomlHash === lockFileJson.mopsTomlHash) {
71
+ return;
72
+ }
73
+ }
74
+ let fileHashes = await getFileHashesFromRegistry();
75
+ let lockFileJson = {
76
+ version: 1,
77
+ mopsTomlHash: getMopsTomlHash(),
78
+ hashes: fileHashes.reduce((acc, [packageId, fileHashes]) => {
79
+ acc[packageId] = fileHashes.reduce((acc, [fileId, hash]) => {
80
+ acc[fileId] = bytesToHex(new Uint8Array(hash));
81
+ return acc;
82
+ }, {});
83
+ return acc;
84
+ }, {}),
85
+ };
86
+ fs.writeFileSync(lockFile, JSON.stringify(lockFileJson, null, 2));
87
+ }
88
+ // compare hashes of local files with hashes from the lock file
89
+ export async function checkLockFile(force = false) {
90
+ let rootDir = getRootDir();
91
+ let lockFile = path.join(rootDir, 'mops.lock');
92
+ // check if lock file exists
93
+ if (!fs.existsSync(lockFile)) {
94
+ if (force) {
95
+ console.error('Missing lock file. Run `mops install` to generate it.');
96
+ process.exit(1);
97
+ }
98
+ return;
99
+ }
100
+ let lockFileJson = JSON.parse(fs.readFileSync(lockFile).toString());
101
+ let packageIds = await getResolvedMopsPackageIds();
102
+ // check lock file version
103
+ if (lockFileJson.version !== 1) {
104
+ console.error('Integrity check failed');
105
+ console.error(`Invalid lock file version: ${lockFileJson.version}. Supported versions: 1`);
106
+ process.exit(1);
107
+ }
108
+ // check mops.toml hash
109
+ if (lockFileJson.mopsTomlHash !== getMopsTomlHash()) {
110
+ console.error('Integrity check failed');
111
+ console.error('Mismatched mops.toml hash');
112
+ console.error(`Locked hash: ${lockFileJson.mopsTomlHash}`);
113
+ console.error(`Actual hash: ${getMopsTomlHash()}`);
114
+ process.exit(1);
115
+ }
116
+ // check number of packages
117
+ if (Object.keys(lockFileJson.hashes).length !== packageIds.length) {
118
+ console.error('Integrity check failed');
119
+ console.error(`Mismatched number of resolved packages: ${JSON.stringify(Object.keys(lockFileJson.hashes).length)} vs ${JSON.stringify(packageIds.length)}`);
120
+ process.exit(1);
121
+ }
122
+ // check if resolved packages are in the lock file
123
+ for (let packageId of packageIds) {
124
+ if (!(packageId in lockFileJson.hashes)) {
125
+ console.error('Integrity check failed');
126
+ console.error(`Missing package ${packageId} in lock file`);
127
+ process.exit(1);
128
+ }
129
+ }
130
+ for (let [packageId, hashes] of Object.entries(lockFileJson.hashes)) {
131
+ // check if package is in resolved packages
132
+ if (!packageIds.includes(packageId)) {
133
+ console.error('Integrity check failed');
134
+ console.error(`Package ${packageId} in lock file but not in resolved packages`);
135
+ process.exit(1);
136
+ }
137
+ for (let [fileId, lockedHash] of Object.entries(hashes)) {
138
+ // check if file belongs to package
139
+ if (!fileId.startsWith(packageId)) {
140
+ console.error('Integrity check failed');
141
+ console.error(`File ${fileId} in lock file does not belong to package ${packageId}`);
142
+ process.exit(1);
143
+ }
144
+ // local file hash vs hash from lock file
145
+ let localHash = getLocalFileHash(fileId);
146
+ if (lockedHash !== localHash) {
147
+ console.error('Integrity check failed');
148
+ console.error(`Mismatched hash for ${fileId}`);
149
+ console.error(`Locked hash: ${lockedHash}`);
150
+ console.error(`Actual hash: ${localHash}`);
151
+ process.exit(1);
152
+ }
153
+ }
154
+ }
155
+ }
package/dist/mops.d.ts CHANGED
@@ -3,6 +3,7 @@ import { _SERVICE } from './declarations/main/main.did.js';
3
3
  import { _SERVICE as _STORAGE_SERVICE } from './declarations/storage/storage.did.js';
4
4
  import { Config } from './types.js';
5
5
  import { Principal } from '@dfinity/principal';
6
+ import { downloadPackageFiles } from './helpers/download-package-files.js';
6
7
  export declare let apiVersion: string;
7
8
  export declare let globalConfigDir: string;
8
9
  export declare let globalCacheDir: string;
@@ -34,3 +35,4 @@ export declare function formatDir(name: string, version: string): string;
34
35
  export declare function formatGithubDir(name: string, repo: string): string;
35
36
  export declare function readDfxJson(): any;
36
37
  export declare function checkApiCompatibility(): Promise<boolean>;
38
+ export { downloadPackageFiles };
package/dist/mops.js CHANGED
@@ -9,6 +9,7 @@ import fetch from 'node-fetch';
9
9
  import { idlFactory } from './declarations/main/index.js';
10
10
  import { idlFactory as storageIdlFactory } from './declarations/storage/index.js';
11
11
  import { decodeFile } from './pem.js';
12
+ import { downloadPackageFiles } from './helpers/download-package-files.js';
12
13
  if (!global.fetch) {
13
14
  global.fetch = fetch;
14
15
  }
@@ -292,3 +293,4 @@ export async function checkApiCompatibility() {
292
293
  }
293
294
  return true;
294
295
  }
296
+ export { downloadPackageFiles };
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.32.2",
3
+ "version": "0.34.0-0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/cli.js"
@@ -38,6 +38,7 @@
38
38
  "@dfinity/identity-secp256k1": "^0.18.1",
39
39
  "@dfinity/principal": "^0.18.1",
40
40
  "@iarna/toml": "^2.2.5",
41
+ "@noble/hashes": "1.3.2",
41
42
  "as-table": "^1.0.55",
42
43
  "cacheable-request": "10.2.12",
43
44
  "camelcase": "^7.0.1",
package/dist/vessel.js CHANGED
@@ -120,11 +120,11 @@ export const installFromGithub = async (name, repo, { verbose = false, dep = fal
120
120
  let dir = formatGithubDir(name, repo);
121
121
  let cacheName = `_github/${name}#${branch}` + (commitHash ? `@${commitHash}` : '');
122
122
  if (existsSync(dir)) {
123
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (local cache) from Github`);
123
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (local cache)`);
124
124
  }
125
125
  else if (isCached(cacheName)) {
126
126
  await copyCache(cacheName, dir);
127
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache) from Github`);
127
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache)`);
128
128
  }
129
129
  else {
130
130
  mkdirSync(dir, { recursive: true });
@@ -0,0 +1,78 @@
1
+ import {mainActor, storageActor} from '../mops.js';
2
+ import {parallel} from '../parallel.js';
3
+ import {Principal} from '@dfinity/principal';
4
+ import {resolveVersion} from './resolve-version.js';
5
+ import {Storage} from '../declarations/storage/storage.did.js';
6
+
7
+ export async function downloadPackageFiles(pkg: string, version = '', threads = 8, onLoad = (_fileIds: string[], _fileId: string) => {}): Promise<Map<string, string>> {
8
+ version = await resolveVersion(pkg, version);
9
+
10
+ let {storageId, fileIds} = await getPackageFilesInfo(pkg, version);
11
+ let storage = await storageActor(storageId);
12
+
13
+ let filesData = new Map;
14
+ await parallel(threads, fileIds, async (fileId: string) => {
15
+ let {path, data} = await downloadFile(storage, fileId);
16
+ filesData.set(path, data);
17
+ onLoad(fileIds, fileId);
18
+ });
19
+
20
+ return filesData;
21
+ }
22
+
23
+ // get package files meta
24
+ export async function getPackageFilesInfo(pkg: string, version: string): Promise<{ storageId: Principal, fileIds: string[] }> {
25
+ let actor = await mainActor();
26
+
27
+ let [packageDetailsRes, fileIds] = await Promise.all([
28
+ actor.getPackageDetails(pkg, version),
29
+ getFileIds(pkg, version),
30
+ ]);
31
+
32
+ if ('err' in packageDetailsRes) {
33
+ throw packageDetailsRes.err;
34
+ }
35
+ let packageDetails = packageDetailsRes.ok;
36
+
37
+ return {
38
+ storageId: packageDetails.publication.storage,
39
+ fileIds,
40
+ };
41
+ }
42
+
43
+ // get package files ids
44
+ export async function getFileIds(pkg: string, version: string): Promise<string[]> {
45
+ let actor = await mainActor();
46
+ let fileIdsRes = await actor.getFileIds(pkg, version);
47
+
48
+ if ('err' in fileIdsRes) {
49
+ throw fileIdsRes.err;
50
+ }
51
+ let filesIds = fileIdsRes.ok;
52
+
53
+ return filesIds;
54
+ }
55
+
56
+ // download single file
57
+ export async function downloadFile(storage: Storage, fileId: string): Promise<{ path: string, data: Array<number> }> {
58
+ let fileMetaRes = await storage.getFileMeta(fileId);
59
+ if ('err' in fileMetaRes) {
60
+ throw fileMetaRes.err;
61
+ }
62
+ let fileMeta = fileMetaRes.ok;
63
+
64
+ let data: Array<number> = [];
65
+ for (let i = 0n; i < fileMeta.chunkCount; i++) {
66
+ let chunkRes = await storage.downloadChunk(fileId, i);
67
+ if ('err' in chunkRes) {
68
+ throw chunkRes.err;
69
+ }
70
+ let chunk = chunkRes.ok;
71
+ data = [...data, ...chunk];
72
+ }
73
+
74
+ return {
75
+ path: fileMeta.path,
76
+ data: data,
77
+ };
78
+ }
@@ -0,0 +1,12 @@
1
+ import {getHighestVersion} from '../mops.js';
2
+
3
+ export async function resolveVersion(pkg: string, version = ''): Promise<string> {
4
+ if (!version) {
5
+ let versionRes = await getHighestVersion(pkg);
6
+ if ('err' in versionRes) {
7
+ throw versionRes.err;
8
+ }
9
+ version = versionRes.ok;
10
+ }
11
+ return version;
12
+ }
package/integrity.ts ADDED
@@ -0,0 +1,188 @@
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
+
8
+ type LockFileV1 = {
9
+ version: 1;
10
+ mopsTomlHash: string;
11
+ hashes: Record<string, Record<string, string>>;
12
+ };
13
+
14
+ export async function checkIntegrity(lock?: 'save' | 'check' | 'ignore') {
15
+ let force = !!lock;
16
+
17
+ if (!lock) {
18
+ lock = process.env['CI'] ? 'check' : 'ignore';
19
+ }
20
+
21
+ if (lock === 'save') {
22
+ await saveLockFile();
23
+ await checkLockFile(force);
24
+ }
25
+ else if (lock === 'check') {
26
+ await checkLockFile(force);
27
+ }
28
+ }
29
+
30
+ async function getFileHashesFromRegistry(): Promise<[string, [string, Uint8Array | number[]][]][]> {
31
+ let packageIds = await getResolvedMopsPackageIds();
32
+ let actor = await mainActor();
33
+ let fileHashesByPackageIds = await actor.getFileHashesByPackageIds(packageIds);
34
+ return fileHashesByPackageIds;
35
+ }
36
+
37
+ async function getResolvedMopsPackageIds(): Promise<string[]> {
38
+ let resolvedPackages = await resolvePackages();
39
+ let packageIds = Object.entries(resolvedPackages)
40
+ .filter(([_, version]) => getDependencyType(version) === 'mops')
41
+ .map(([name, version]) => `${name}@${version}`);
42
+ return packageIds;
43
+ }
44
+
45
+ // get hash of local file from '.mops' dir by fileId
46
+ export function getLocalFileHash(fileId: string): string {
47
+ let rootDir = getRootDir();
48
+ let file = path.join(rootDir, '.mops', fileId);
49
+ if (!fs.existsSync(file)) {
50
+ console.error(`Missing file ${fileId} in .mops dir`);
51
+ process.exit(1);
52
+ }
53
+ let fileData = fs.readFileSync(file);
54
+ return bytesToHex(sha256(fileData));
55
+ }
56
+
57
+ function getMopsTomlHash(): string {
58
+ return bytesToHex(sha256(fs.readFileSync(getRootDir() + '/mops.toml')));
59
+ }
60
+
61
+ // compare hashes of local files with hashes from the registry
62
+ export async function checkRemote() {
63
+ let fileHashesFromRegistry = await getFileHashesFromRegistry();
64
+
65
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
66
+ for (let [_packageId, fileHashes] of fileHashesFromRegistry) {
67
+ for (let [fileId, hash] of fileHashes) {
68
+ let remoteHash = new Uint8Array(hash);
69
+ let localHash = getLocalFileHash(fileId);
70
+
71
+ if (localHash !== bytesToHex(remoteHash)) {
72
+ console.error('Integrity check failed.');
73
+ console.error(`Mismatched hash for ${fileId}: ${localHash} vs ${bytesToHex(remoteHash)}`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ export async function saveLockFile() {
81
+ let rootDir = getRootDir();
82
+ let lockFile = path.join(rootDir, 'mops.lock');
83
+
84
+ // if lock file exists and mops.toml hasn't changed, don't update it
85
+ if (fs.existsSync(lockFile)) {
86
+ let lockFileJson: LockFileV1 = JSON.parse(fs.readFileSync(lockFile).toString());
87
+ let mopsTomlHash = getMopsTomlHash();
88
+ if (mopsTomlHash === lockFileJson.mopsTomlHash) {
89
+ return;
90
+ }
91
+ }
92
+
93
+ let fileHashes = await getFileHashesFromRegistry();
94
+
95
+ let lockFileJson: LockFileV1 = {
96
+ version: 1,
97
+ mopsTomlHash: getMopsTomlHash(),
98
+ hashes: fileHashes.reduce((acc, [packageId, fileHashes]) => {
99
+ acc[packageId] = fileHashes.reduce((acc, [fileId, hash]) => {
100
+ acc[fileId] = bytesToHex(new Uint8Array(hash));
101
+ return acc;
102
+ }, {} as Record<string, string>);
103
+ return acc;
104
+ }, {} as Record<string, Record<string, string>>),
105
+ };
106
+
107
+ fs.writeFileSync(lockFile, JSON.stringify(lockFileJson, null, 2));
108
+ }
109
+
110
+ // compare hashes of local files with hashes from the lock file
111
+ export async function checkLockFile(force = false) {
112
+ let rootDir = getRootDir();
113
+ let lockFile = path.join(rootDir, 'mops.lock');
114
+
115
+ // check if lock file exists
116
+ if (!fs.existsSync(lockFile)) {
117
+ if (force) {
118
+ console.error('Missing lock file. Run `mops install` to generate it.');
119
+ process.exit(1);
120
+ }
121
+ return;
122
+ }
123
+
124
+ let lockFileJson: LockFileV1 = JSON.parse(fs.readFileSync(lockFile).toString());
125
+ let packageIds = await getResolvedMopsPackageIds();
126
+
127
+ // check lock file version
128
+ if (lockFileJson.version !== 1) {
129
+ console.error('Integrity check failed');
130
+ console.error(`Invalid lock file version: ${lockFileJson.version}. Supported versions: 1`);
131
+ process.exit(1);
132
+ }
133
+
134
+ // check mops.toml hash
135
+ if (lockFileJson.mopsTomlHash !== getMopsTomlHash()) {
136
+ console.error('Integrity check failed');
137
+ console.error('Mismatched mops.toml hash');
138
+ console.error(`Locked hash: ${lockFileJson.mopsTomlHash}`);
139
+ console.error(`Actual hash: ${getMopsTomlHash()}`);
140
+ process.exit(1);
141
+ }
142
+
143
+ // check number of packages
144
+ if (Object.keys(lockFileJson.hashes).length !== packageIds.length) {
145
+ console.error('Integrity check failed');
146
+ console.error(`Mismatched number of resolved packages: ${JSON.stringify(Object.keys(lockFileJson.hashes).length)} vs ${JSON.stringify(packageIds.length)}`);
147
+ process.exit(1);
148
+ }
149
+
150
+ // check if resolved packages are in the lock file
151
+ for (let packageId of packageIds) {
152
+ if (!(packageId in lockFileJson.hashes)) {
153
+ console.error('Integrity check failed');
154
+ console.error(`Missing package ${packageId} in lock file`);
155
+ process.exit(1);
156
+ }
157
+ }
158
+
159
+ for (let [packageId, hashes] of Object.entries(lockFileJson.hashes)) {
160
+
161
+ // check if package is in resolved packages
162
+ if (!packageIds.includes(packageId)) {
163
+ console.error('Integrity check failed');
164
+ console.error(`Package ${packageId} in lock file but not in resolved packages`);
165
+ process.exit(1);
166
+ }
167
+
168
+ for (let [fileId, lockedHash] of Object.entries(hashes)) {
169
+
170
+ // check if file belongs to package
171
+ if (!fileId.startsWith(packageId)) {
172
+ console.error('Integrity check failed');
173
+ console.error(`File ${fileId} in lock file does not belong to package ${packageId}`);
174
+ process.exit(1);
175
+ }
176
+
177
+ // local file hash vs hash from lock file
178
+ let localHash = getLocalFileHash(fileId);
179
+ if (lockedHash !== localHash) {
180
+ console.error('Integrity check failed');
181
+ console.error(`Mismatched hash for ${fileId}`);
182
+ console.error(`Locked hash: ${lockedHash}`);
183
+ console.error(`Actual hash: ${localHash}`);
184
+ process.exit(1);
185
+ }
186
+ }
187
+ }
188
+ }
package/mops.ts CHANGED
@@ -14,6 +14,7 @@ import {_SERVICE as _STORAGE_SERVICE} from './declarations/storage/storage.did.j
14
14
  import {decodeFile} from './pem.js';
15
15
  import {Config} from './types.js';
16
16
  import {Principal} from '@dfinity/principal';
17
+ import {downloadPackageFiles} from './helpers/download-package-files.js';
17
18
 
18
19
 
19
20
  if (!global.fetch) {
@@ -338,4 +339,6 @@ export async function checkApiCompatibility() {
338
339
  console.log('-'.repeat(50));
339
340
  }
340
341
  return true;
341
- }
342
+ }
343
+
344
+ export {downloadPackageFiles};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.32.2",
3
+ "version": "0.34.0-0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/cli.js"
@@ -38,6 +38,7 @@
38
38
  "@dfinity/identity-secp256k1": "^0.18.1",
39
39
  "@dfinity/principal": "^0.18.1",
40
40
  "@iarna/toml": "^2.2.5",
41
+ "@noble/hashes": "1.3.2",
41
42
  "as-table": "^1.0.55",
42
43
  "cacheable-request": "10.2.12",
43
44
  "camelcase": "^7.0.1",
package/vessel.ts CHANGED
@@ -154,11 +154,11 @@ export const installFromGithub = async (name: string, repo: string, {verbose = f
154
154
  let cacheName = `_github/${name}#${branch}` + (commitHash ? `@${commitHash}` : '');
155
155
 
156
156
  if (existsSync(dir)) {
157
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (local cache) from Github`);
157
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (local cache)`);
158
158
  }
159
159
  else if (isCached(cacheName)) {
160
160
  await copyCache(cacheName, dir);
161
- silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache) from Github`);
161
+ silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache)`);
162
162
  }
163
163
  else {
164
164
  mkdirSync(dir, {recursive: true});