ic-mops 0.32.2 → 0.33.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/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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "0.32.2",
3
+ "version": "0.33.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});