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.
- package/.npmrc +1 -0
- package/cli.ts +10 -5
- package/commands/add.ts +15 -2
- package/commands/install-all.ts +15 -2
- package/commands/install.ts +27 -52
- package/commands/remove.ts +12 -1
- package/commands/search.ts +1 -2
- package/commands/sync.ts +34 -27
- package/commands/test/test.ts +7 -1
- package/commands/update.ts +10 -1
- package/declarations/main/main.did +40 -0
- package/declarations/main/main.did.d.ts +26 -0
- package/declarations/main/main.did.js +35 -0
- package/dist/cli.js +10 -5
- package/dist/commands/add.d.ts +7 -4
- package/dist/commands/add.js +7 -2
- package/dist/commands/install-all.d.ts +7 -4
- package/dist/commands/install-all.js +9 -2
- package/dist/commands/install.js +23 -46
- package/dist/commands/remove.d.ts +8 -5
- package/dist/commands/remove.js +3 -1
- package/dist/commands/search.js +1 -2
- package/dist/commands/sync.d.ts +5 -1
- package/dist/commands/sync.js +23 -21
- package/dist/commands/test/test.js +7 -1
- package/dist/commands/update.d.ts +7 -1
- package/dist/commands/update.js +3 -1
- package/dist/declarations/main/main.did +40 -0
- package/dist/declarations/main/main.did.d.ts +26 -0
- package/dist/declarations/main/main.did.js +35 -0
- package/dist/helpers/download-package-files.d.ts +12 -0
- package/dist/helpers/download-package-files.js +62 -0
- package/dist/helpers/resolve-version.d.ts +1 -0
- package/dist/helpers/resolve-version.js +11 -0
- package/dist/integrity.d.ts +5 -0
- package/dist/integrity.js +155 -0
- package/dist/mops.d.ts +2 -0
- package/dist/mops.js +2 -0
- package/dist/package.json +2 -1
- package/dist/vessel.js +2 -2
- package/helpers/download-package-files.ts +78 -0
- package/helpers/resolve-version.ts +12 -0
- package/integrity.ts +188 -0
- package/mops.ts +4 -1
- package/package.json +2 -1
- 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.
|
|
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)
|
|
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)
|
|
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.
|
|
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)
|
|
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)
|
|
161
|
+
silent || logUpdate(`${dep ? 'Dependency' : 'Installing'} ${repo} (global cache)`);
|
|
162
162
|
}
|
|
163
163
|
else {
|
|
164
164
|
mkdirSync(dir, {recursive: true});
|