diff --git a/package.json b/package.json index 260ffeda..9970f156 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "prepack": "./scripts/prepack.sh", - "query-snaps": "ts-node scripts/query/query.ts", + "query": "ts-node scripts/query.ts", "sign": "ts-node scripts/sign-registry.ts", "test": "jest && jest-it-up", "test:watch": "jest --watch", @@ -63,13 +63,15 @@ "image-size": "^1.1.1", "jest": "^28.1.3", "jest-it-up": "^2.0.2", + "ora": "^5.4.1", "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.3.0", "rimraf": "^3.0.2", "semver": "^7.5.4", "ts-jest": "^28.0.7", "ts-node": "^10.7.0", - "typescript": "~4.8.4" + "typescript": "~4.8.4", + "yargs": "^17.7.2" }, "packageManager": "yarn@3.2.1", "engines": { diff --git a/scripts/query.ts b/scripts/query.ts new file mode 100644 index 00000000..c769a96f --- /dev/null +++ b/scripts/query.ts @@ -0,0 +1,242 @@ +import { detectSnapLocation, fetchSnap } from '@metamask/snaps-controllers'; +import type { SnapId } from '@metamask/snaps-sdk'; +import type { SnapManifest } from '@metamask/snaps-utils'; +import type { SemVerRange } from '@metamask/utils'; +import { hasProperty } from '@metamask/utils'; +import ora from 'ora'; +import semver from 'semver'; +import { runInNewContext } from 'vm'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; + +import type { VerifiedSnap } from '../src'; +import database from '../src/registry.json'; + +type Options = { + verbose?: boolean | undefined; + format: string; + fetch?: boolean | undefined; + all?: boolean | undefined; + filter?: string | undefined; + limit?: number | undefined; +}; + +const verifiedSnaps = database.verifiedSnaps as Record; + +/** + * Get a Snap ID from either a Snap object or a Snap manifest. + * + * @param value - The Snap object or Snap manifest. + * @returns The Snap ID. + */ +function getSnapId(value: VerifiedSnap | SnapManifest) { + if (hasProperty(value, 'id')) { + return value.id; + } + + return `npm:${value.source.location.npm.packageName}`; +} + +/** + * Format Snap as string. + * + * @param value - The Snap object. + * @returns The Snap ID. + */ +function formatString(value: VerifiedSnap | SnapManifest) { + return getSnapId(value); +} + +/** + * Format Snap as JSON, optionally with verbose output. + * + * @param value - The Snap object. + * @param verbose - Whether to include verbose output. + * @returns The Snap ID or the full Snap object. + */ +function formatJson(value: VerifiedSnap | SnapManifest, verbose?: boolean) { + if (verbose) { + return JSON.stringify(value, null, 2); + } + + return JSON.stringify({ snapId: getSnapId(value) }, null, 2); +} + +/** + * Format Snap based on the specified format. + * + * @param snap - The Snap object. + * @param options - The command line options. + * @returns The formatted Snap. + */ +function formatSnap(snap: VerifiedSnap | SnapManifest, options: Options) { + switch (options.format) { + case 'json': + return formatJson(snap, options.verbose); + case 'string': + return formatString(snap); + default: + throw new Error(`Unsupported format: "${String(options.format)}"`); + } +} + +/** + * Format Snaps based on the specified format. + * + * @param snaps - The Snap objects. + * @param options - The command line options. + * @returns The formatted Snaps. + */ +function formatSnaps(snaps: VerifiedSnap[] | SnapManifest[], options: Options) { + return snaps + .map((snap) => formatSnap(snap, options)) + .slice(0, options.limit ?? snaps.length) + .join('\n'); +} + +/** + * Get all Snaps on the registry. + * + * If `fetch` is true, this function will fetch the Snap manifests. Otherwise, + * it will use the local registry. + * + * @param fetch - Whether to fetch Snap manifests. + * @returns The Snap objects. + */ +// eslint-disable-next-line @typescript-eslint/no-shadow +async function getAllSnaps(fetch: boolean) { + if (fetch) { + const spinner = ora( + 'Fetching Snap manifests. This may take a while.', + ).start(); + + const promises = Object.values(verifiedSnaps).map(async (snap) => { + const latestVersion = Object.keys(snap.versions).reduce( + (result, version) => { + if (result === null || semver.gt(version, result)) { + return version as SemVerRange; + } + return result; + }, + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + '0.0.0' as SemVerRange, + ); + + const location = detectSnapLocation(snap.id, { + versionRange: latestVersion, + }); + + const { manifest } = await fetchSnap(snap.id as SnapId, location); + return manifest.result; + }); + + const results = await Promise.all(promises); + spinner.stop(); + + return results; + } + + return Object.values(verifiedSnaps); +} + +/** + * Filter Snaps based on custom logic. + * + * @param code - The custom logic. + * @param snaps - The Snap objects. + * @returns The filtered Snaps. + */ +function filter(code: string, snaps: VerifiedSnap[] | SnapManifest[]) { + return runInNewContext( + ` + snaps.filter((snap) => ${code}); + `, + { + snaps, + }, + ); +} + +/** + * Entry point. + */ +async function main() { + const argv = await yargs(hideBin(process.argv)) + .option('verbose', { + alias: 'v', + type: 'boolean', + description: 'Run with verbose output', + }) + .option('format', { + choices: ['json', 'string'], + default: 'string', + description: 'Output format', + }) + .option('fetch', { + type: 'boolean', + description: 'Fetch Snap manifests, instead of using the local registry', + }) + .option('all', { + type: 'boolean', + description: 'List all Snaps', + conflicts: ['filter', 'limit'], + }) + .option('filter', { + type: 'string', + description: 'Filter Snaps based on some custom logic', + conflicts: ['all'], + }) + .option('exclude', { + type: 'string', + description: 'Exclude Snaps based on some custom logic', + conflicts: ['all'], + }) + .option('limit', { + type: 'number', + description: 'Limit the number of Snaps to list', + conflicts: ['all'], + }) + .example([ + ['$0 --all', 'List all Snaps'], + ['$0 --fetch --all', 'Fetch all Snap manifests and list them'], + [ + '$0 --fetch --filter "snap.initialPermissions[\'endowment:transaction-insight\']"', + 'Get all Snaps that request the "endowment:transaction-insight" permission', + ], + [ + '$0 --fetch --filter "snap.initialPermissions[\'endowment:network-access\']" --exclude "snap.initialPermissions[\'endowment:network-access\']"', + 'Get all Snaps that request the "endowment:transaction-insight" permission, but do not request the "endowment:network-access" permission', + ], + [ + '$0 ---filter "\\!Boolean(snap.metadata.screenshots)" --exclude "snap.id.startsWith(\'npm:@metamask\')" --format json', + 'Get all Snaps that do not have screenshots and are not published by MetaMask, as JSON', + ], + ['$0 --all --format json', 'Output Snaps as JSON'], + ['$0 --all --limit 5', 'Limit the number of Snaps to list'], + ]) + .wrap(yargs.terminalWidth()) + .parseAsync(); + + const snaps = await getAllSnaps(Boolean(argv.fetch)); + if (argv.all) { + return console.log(formatSnaps(snaps, argv)); + } + + if (argv.filter) { + const result = filter(argv.filter, snaps); + + if (argv.exclude) { + const exclude = filter(`!(${argv.exclude})`, result); + return console.log(formatSnaps(exclude, argv)); + } + + return console.log(formatSnaps(result, argv)); + } + + return undefined; +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/query/get-manifests.ts b/scripts/query/get-manifests.ts deleted file mode 100644 index a34cafac..00000000 --- a/scripts/query/get-manifests.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { detectSnapLocation, fetchSnap } from '@metamask/snaps-controllers'; -import type { SnapId } from '@metamask/snaps-sdk'; -import type { SemVerRange } from '@metamask/utils'; -import fs from 'fs'; -import path from 'path'; -import semver from 'semver/preload'; - -import registry from '../../src/registry.json'; - -export const MANIFESTS_FILE_LOCATION = path.join(__dirname, 'data.json'); - -/** - * Function to fetch Snaps manifests and write it to a local JSON file. - */ -export async function getManifests() { - console.log('Fetching Snaps. Please wait ...'); - const allManifests = []; - for (const snap of Object.values(registry.verifiedSnaps)) { - console.log(snap.metadata.name); - const latestVersion = Object.keys(snap.versions).reduce( - (result, version) => { - if (result === null || semver.gt(version, result)) { - return version; - } - return result; - }, - ) as SemVerRange; - - const location = detectSnapLocation(snap.id, { - versionRange: latestVersion, - }); - - const fetchedSnap = await fetchSnap(snap.id as SnapId, location); - allManifests.push(fetchedSnap.manifest.result); - } - - await fs.promises.writeFile( - MANIFESTS_FILE_LOCATION, - JSON.stringify(allManifests), - ); - - console.log('Fetching Snap manifests and writing file - Done'); -} diff --git a/scripts/query/query.ts b/scripts/query/query.ts deleted file mode 100644 index 47a32092..00000000 --- a/scripts/query/query.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { SnapManifest } from '@metamask/snaps-utils'; -import fsPromises from 'fs/promises'; - -import { MANIFESTS_FILE_LOCATION, getManifests } from './get-manifests'; - -/** - * Entry point. - */ -async function main() { - const forceDownload = process.argv.includes('--force-download'); - if (forceDownload) { - await getManifests(); - } else { - try { - // Check if local file exists. - await fsPromises.readFile(MANIFESTS_FILE_LOCATION, 'utf8'); - } catch (eror) { - await getManifests(); - } - } - - const manifests: SnapManifest[] = JSON.parse( - await fsPromises.readFile(MANIFESTS_FILE_LOCATION, 'utf8'), - ); - - /** - * Write your custom code to query the `manifests` array for the data you are interested in. - */ - - // Eg.: Find all Snaps that use `endowment:transaction-insight` - const filteredSnaps = manifests.filter( - (manifest) => manifest.initialPermissions['endowment:transaction-insight'], - ); - console.log('\nQuery results'); - console.log(filteredSnaps.map((snap) => snap.proposedName)); -} - -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/yarn.lock b/yarn.lock index 9c29717a..0e930f10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1264,6 +1264,7 @@ __metadata: image-size: ^1.1.1 jest: ^28.1.3 jest-it-up: ^2.0.2 + ora: ^5.4.1 prettier: ^2.7.1 prettier-plugin-packagejson: ^2.3.0 rimraf: ^3.0.2 @@ -1272,6 +1273,7 @@ __metadata: ts-jest: ^28.0.7 ts-node: ^10.7.0 typescript: ~4.8.4 + yargs: ^17.7.2 languageName: unknown linkType: soft @@ -2348,6 +2350,17 @@ __metadata: languageName: node linkType: hard +"bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + "bn.js@npm:5.2.1, bn.js@npm:^5.2.1": version: 5.2.1 resolution: "bn.js@npm:5.2.1" @@ -2431,6 +2444,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: ^1.3.1 + ieee754: ^1.1.13 + checksum: e2cf8429e1c4c7b8cbd30834ac09bd61da46ce35f5c22a78e6c2f04497d6d25541b16881e30a019c6fd3154150650ccee27a308eff3e26229d788bbdeb08ab84 + languageName: node + linkType: hard + "buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" @@ -2545,7 +2568,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -2590,6 +2613,22 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: ^3.1.0 + checksum: 2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 1bd588289b28432e4676cb5d40505cfe3e53f2e4e10fbe05c8a710a154d6fe0ce7836844b00d6858f740f2ffe67cdc36e0fce9c7b6a8430e80e6388d5aa4956c + languageName: node + linkType: hard + "cli-spinners@npm:^2.6.0": version: 2.6.1 resolution: "cli-spinners@npm:2.6.1" @@ -2615,6 +2654,24 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + "cmd-shim@npm:^6.0.0": version: 6.0.1 resolution: "cmd-shim@npm:6.0.1" @@ -2825,6 +2882,15 @@ __metadata: languageName: node linkType: hard +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: ^1.0.2 + checksum: 3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": version: 1.2.0 resolution: "define-properties@npm:1.2.0" @@ -4100,7 +4166,7 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.2.1": +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e @@ -4185,7 +4251,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -4304,6 +4370,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -4399,6 +4472,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 + languageName: node + linkType: hard + "is-weakref@npm:^1.0.2": version: 1.0.2 resolution: "is-weakref@npm:1.0.2" @@ -5124,6 +5204,16 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: ^4.1.0 + is-unicode-supported: ^0.1.0 + checksum: fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -5656,7 +5746,7 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^5.1.2": +"onetime@npm:^5.1.0, onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" dependencies: @@ -5679,6 +5769,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: ^4.1.0 + chalk: ^4.1.0 + cli-cursor: ^3.1.0 + cli-spinners: ^2.5.0 + is-interactive: ^1.0.0 + is-unicode-supported: ^0.1.0 + log-symbols: ^4.1.0 + strip-ansi: ^6.0.0 + wcwidth: ^1.0.1 + checksum: 28d476ee6c1049d68368c0dc922e7225e3b5600c3ede88fade8052837f9ed342625fdaa84a6209302587c8ddd9b664f71f0759833cbdb3a4cf81344057e63c63 + languageName: node + linkType: hard + "p-limit@npm:^2.2.0": version: 2.3.0 resolution: "p-limit@npm:2.3.0" @@ -5999,7 +6106,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:3.6.2, readable-stream@npm:^3.0.2, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": +"readable-stream@npm:3.6.2, readable-stream@npm:^3.0.2, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -6113,6 +6220,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: ^5.1.0 + signal-exit: ^3.0.2 + checksum: f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -6268,7 +6385,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -7032,6 +7149,15 @@ __metadata: languageName: node linkType: hard +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: ^1.0.3 + checksum: 814e9d1ddcc9798f7377ffa448a5a3892232b9275ebb30a41b529607691c0491de47cba426e917a4d08ded3ee7e9ba2f3fe32e62ee3cd9c7d3bafb7754bd553c + languageName: node + linkType: hard + "webextension-polyfill@npm:>=0.10.0 <1.0, webextension-polyfill@npm:^0.10.0": version: 0.10.0 resolution: "webextension-polyfill@npm:0.10.0" @@ -7187,7 +7313,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1": +"yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -7224,6 +7350,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1"