From abdcbead842ae7313bee6e7ef1a287798e702ed0 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Wed, 26 Oct 2022 15:00:28 +0200 Subject: [PATCH] Remove JSON schemas in favour of structs (#862) * Remove JSON schemas in favour of structs * Fix browser export * Fix build script * Fix other packages * Add tests * Fix PR comments * Remove custom minSize struct * Fix export * Update coverage * Remove ajv cli * Remove @json-schema-tools/transpiler --- packages/cli/src/cmds/init/initUtils.test.ts | 5 +- packages/cli/src/cmds/init/initUtils.ts | 7 +- packages/controllers/jest.config.js | 4 +- .../controllers/src/snaps/SnapController.ts | 9 +- packages/utils/jest.config.js | 6 +- packages/utils/package.json | 9 +- packages/utils/scripts/build-ajv.sh | 28 --- .../utils/scripts/transpileSchemaTypes.js | 61 ------- packages/utils/src/fs.test.ts | 3 +- packages/utils/src/index.browser.ts | 1 - packages/utils/src/index.ts | 1 - .../src/json-schemas/NpmSnapPackageJson.ts | 26 --- packages/utils/src/json-schemas/README.md | 4 - .../utils/src/json-schemas/SnapManifest.ts | 106 ------------ packages/utils/src/json-schemas/index.ts | 59 ------- .../npm-snap-package-json.schema.json | 59 ------- .../json-schemas/snap-manifest.schema.json | 131 --------------- .../validateNpmSnapPackageJson.d.ts | 2 - .../validateNpmSnapPackageJson.js | 1 - .../json-schemas/validateSnapManifest.d.ts | 2 - .../src/json-schemas/validateSnapManifest.js | 1 - packages/utils/src/manifest.test.ts | 10 +- packages/utils/src/manifest.ts | 4 +- packages/utils/src/npm.ts | 23 +-- packages/utils/src/snaps.ts | 8 +- packages/utils/src/test-utils/manifest.ts | 14 +- packages/utils/src/test-utils/types.ts | 1 - packages/utils/src/types.test.ts | 107 ++++++++++++ packages/utils/src/types.ts | 159 +++++++++++++++++- yarn.lock | 55 +----- 30 files changed, 308 insertions(+), 598 deletions(-) delete mode 100755 packages/utils/scripts/build-ajv.sh delete mode 100644 packages/utils/scripts/transpileSchemaTypes.js delete mode 100644 packages/utils/src/json-schemas/NpmSnapPackageJson.ts delete mode 100644 packages/utils/src/json-schemas/README.md delete mode 100644 packages/utils/src/json-schemas/SnapManifest.ts delete mode 100644 packages/utils/src/json-schemas/index.ts delete mode 100644 packages/utils/src/json-schemas/npm-snap-package-json.schema.json delete mode 100644 packages/utils/src/json-schemas/snap-manifest.schema.json delete mode 100644 packages/utils/src/json-schemas/validateNpmSnapPackageJson.d.ts delete mode 100644 packages/utils/src/json-schemas/validateNpmSnapPackageJson.js delete mode 100644 packages/utils/src/json-schemas/validateSnapManifest.d.ts delete mode 100644 packages/utils/src/json-schemas/validateSnapManifest.js delete mode 100644 packages/utils/src/test-utils/types.ts create mode 100644 packages/utils/src/types.test.ts diff --git a/packages/cli/src/cmds/init/initUtils.test.ts b/packages/cli/src/cmds/init/initUtils.test.ts index f6f18f5372..9aa82dcb75 100644 --- a/packages/cli/src/cmds/init/initUtils.test.ts +++ b/packages/cli/src/cmds/init/initUtils.test.ts @@ -47,7 +47,8 @@ jest.mock('fs', () => ({ jest.mock('@metamask/snap-utils', () => ({ ...jest.requireActual('@metamask/snap-utils'), readJsonFile: jest.fn(), - validateSnapJsonFile: jest.fn(), + assertIsNpmSnapPackageJson: jest.fn(), + assertIsSnapManifest: jest.fn(), })); jest.mock('init-package-json'); jest.mock('mkdirp'); @@ -75,7 +76,7 @@ describe('initUtils', () => { .mockImplementationOnce(async () => ''); const validateSnapJsonFileMock = jest - .spyOn(snapUtils, 'validateSnapJsonFile') + .spyOn(snapUtils, 'assertIsNpmSnapPackageJson') .mockImplementationOnce(() => true); jest.spyOn(console, 'log').mockImplementation(); diff --git a/packages/cli/src/cmds/init/initUtils.ts b/packages/cli/src/cmds/init/initUtils.ts index 527b40afca..36ee2963ef 100644 --- a/packages/cli/src/cmds/init/initUtils.ts +++ b/packages/cli/src/cmds/init/initUtils.ts @@ -5,9 +5,10 @@ import { NpmSnapFileNames, SnapManifest, NpmSnapPackageJson, - validateSnapJsonFile, + assertIsNpmSnapPackageJson, readJsonFile, deepClone, + assertIsSnapManifest, } from '@metamask/snap-utils'; import initPackageJson from 'init-package-json'; import mkdirp from 'mkdirp'; @@ -48,7 +49,7 @@ export async function asyncPackageInit( try { const packageJson = await readJsonFile(NpmSnapFileNames.PackageJson); - validateSnapJsonFile(NpmSnapFileNames.PackageJson, packageJson); + assertIsNpmSnapPackageJson(packageJson); console.log( `Init: Successfully parsed '${NpmSnapFileNames.PackageJson}'!`, @@ -292,7 +293,7 @@ export async function buildSnapManifest( }; try { - validateSnapJsonFile(NpmSnapFileNames.Manifest, manifest); + assertIsSnapManifest(manifest); } catch (error) { /* istanbul ignore next */ throw new Error( diff --git a/packages/controllers/jest.config.js b/packages/controllers/jest.config.js index 0daf9d7cc5..8a3d658734 100644 --- a/packages/controllers/jest.config.js +++ b/packages/controllers/jest.config.js @@ -10,8 +10,8 @@ module.exports = { global: { branches: 85.74, functions: 95.3, - lines: 94.84, - statements: 94.93, + lines: 94.83, + statements: 94.92, }, }, projects: [ diff --git a/packages/controllers/src/snaps/SnapController.ts b/packages/controllers/src/snaps/SnapController.ts index eaffb8a797..f4e364f2db 100644 --- a/packages/controllers/src/snaps/SnapController.ts +++ b/packages/controllers/src/snaps/SnapController.ts @@ -35,7 +35,6 @@ import { SNAP_PREFIX, ValidatedSnapId, validateSnapId, - validateSnapJsonFile, validateSnapShasum, TruncatedSnapFields, Snap, @@ -51,6 +50,7 @@ import { fromEntries, SnapStatus, SnapStatusEvents, + assertIsSnapManifest, } from '@metamask/snap-utils'; import { Duration, @@ -1904,7 +1904,7 @@ export class SnapController extends BaseController< versionRange = DEFAULT_REQUESTED_SNAP_VERSION, } = args; - validateSnapJsonFile(NpmSnapFileNames.Manifest, manifest); + assertIsSnapManifest(manifest); const { version } = manifest; if (!satisfiesVersionRange(version, versionRange)) { @@ -2044,11 +2044,10 @@ export class SnapController extends BaseController< ); } - const _manifest = await ( + const manifest = await ( await this._fetchFunction(manifestUrl.toString(), fetchOptions) ).json(); - validateSnapJsonFile(NpmSnapFileNames.Manifest, _manifest); - const manifest = _manifest as SnapManifest; + assertIsSnapManifest(manifest); const { source: { diff --git a/packages/utils/jest.config.js b/packages/utils/jest.config.js index dd0f828822..4a42050639 100644 --- a/packages/utils/jest.config.js +++ b/packages/utils/jest.config.js @@ -16,9 +16,9 @@ module.exports = { coverageThreshold: { global: { branches: 86.05, - functions: 97, - lines: 96.68, - statements: 96.76, + functions: 97.14, + lines: 96.79, + statements: 96.86, }, }, moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'], diff --git a/packages/utils/package.json b/packages/utils/package.json index ef1c9b27db..77e419be79 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -44,11 +44,7 @@ "lint": "yarn lint:eslint && yarn lint:misc --check", "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", "lint:changelog": "yarn auto-changelog validate", - "ajv-compile": "ajv compile --strict=true --all-errors", - "build:ajv": "./scripts/build-ajv.sh", - "build:pre-tsc": "yarn build:ajv && node scripts/transpileSchemaTypes.js", - "build:tsc": "tsc --project tsconfig.local.json", - "build": "yarn build:pre-tsc && yarn build:tsc", + "build": "tsc --project tsconfig.local.json", "build:clean": "yarn clean && yarn build", "clean": "rimraf '*.tsbuildinfo' 'dist/*'", "publish:package": "../../scripts/publish-package.sh" @@ -60,7 +56,6 @@ "@metamask/utils": "^3.1.0", "@noble/hashes": "^1.1.3", "@scure/base": "^1.1.1", - "ajv": "^8.11.0", "eth-rpc-errors": "^4.0.3", "fast-deep-equal": "^3.1.3", "rfdc": "^1.3.0", @@ -69,7 +64,6 @@ "superstruct": "^0.16.5" }, "devDependencies": { - "@json-schema-tools/transpiler": "^1.10.2", "@lavamoat/allow-scripts": "^2.0.3", "@metamask/auto-changelog": "^2.6.0", "@metamask/eslint-config": "^9.0.0", @@ -78,7 +72,6 @@ "@metamask/eslint-config-typescript": "^9.0.1", "@types/jest": "^27.5.1", "@types/semver": "^7.3.10", - "ajv-cli": "^5.0.0", "eslint": "^7.30.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.23.4", diff --git a/packages/utils/scripts/build-ajv.sh b/packages/utils/scripts/build-ajv.sh deleted file mode 100755 index d9f8e127ae..0000000000 --- a/packages/utils/scripts/build-ajv.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -x -set -e -set -u -set -o pipefail - -function ajv_compile() { - yarn ajv compile --strict=true --all-errors -s "$1" -o "$2" -} - -# This matches the final return statement of the compiled files, which is the -# return statement of the exported validation function. -# We replace the original boolean return value with the array of all errors -# encountered during validation. -SED_REGEX='s/return errors === 0;\}$/return vErrors;\}/' - -FILE_1='src/json-schemas/validateSnapManifest.js' -FILE_2='src/json-schemas/validateNpmSnapPackageJson.js' - -ajv_compile 'src/json-schemas/snap-manifest.schema.json' "$FILE_1" -ajv_compile 'src/json-schemas/npm-snap-package-json.schema.json' "$FILE_2" - -# Modify the return value of the validation functions. -sed -i'' -e "$SED_REGEX" "$FILE_1" "$FILE_2" - -# Remove sed backup files (created on macOS) -rm -f src/snaps/json-schemas/*.js-e diff --git a/packages/utils/scripts/transpileSchemaTypes.js b/packages/utils/scripts/transpileSchemaTypes.js deleted file mode 100644 index 9b5eaff51a..0000000000 --- a/packages/utils/scripts/transpileSchemaTypes.js +++ /dev/null @@ -1,61 +0,0 @@ -const { writeFileSync } = require('fs'); -const path = require('path'); -const { Transpiler } = require('@json-schema-tools/transpiler'); -const snapManifestSchema = require('../src/json-schemas/snap-manifest.schema.json'); -const npmSnapPackageJsonSchema = require('../src/json-schemas/npm-snap-package-json.schema.json'); - -// A comment warning against modifying generated files. -const FILE_PREFIX = - '// THIS IS A PROGRAMMATICALLY GENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n\n'; - -main(); - -/** - * Main function of the script. - */ -function main() { - [ - [snapManifestSchema, 'SnapManifest'], - [npmSnapPackageJsonSchema, 'NpmSnapPackageJson'], - ].forEach(([schema, primaryExportName]) => { - writeSchema(transpileSchema(schema, primaryExportName), primaryExportName); - }); -} - -/** - * Transpiles a given JSON schema to TypeScript. - * - * @param {object} schema - The schema JSON. - * @param {string} primaryExportName - The name of the primary export for the schema. - * @returns {string} The TypeScript source code transpiled from the schema. - */ -function transpileSchema(schema, primaryExportName) { - return FILE_PREFIX.concat( - new Transpiler(schema) - .toTypescript() - // Get rid of all export statements - .replace(/^export /gmu, '') - // Convert all interfaces to object types for Json type compatibility - .replace(/^interface (\w+) \{/gmu, 'type $1 = {') - // Export the primary schema type only - .replace( - new RegExp(`^type ${primaryExportName}`, 'mu'), - `export type ${primaryExportName}`, - ) - // Add a newline because we like it better - .concat('\n'), - ); -} - -/** - * Writes a transpiled schema to disk. - * - * @param {string} typeSource - The TypeScript source code resulting from the transpilation. - * @param {string} primaryExportName - The name of the primary export for the schema. - */ -function writeSchema(typeSource, primaryExportName) { - writeFileSync( - path.resolve(__dirname, `../src/json-schemas/${primaryExportName}.ts`), - typeSource, - ); -} diff --git a/packages/utils/src/fs.test.ts b/packages/utils/src/fs.test.ts index aecc428dee..1824d420cc 100644 --- a/packages/utils/src/fs.test.ts +++ b/packages/utils/src/fs.test.ts @@ -11,9 +11,8 @@ import { validateFilePath, validateDirPath, } from './fs'; -import { NpmSnapFileNames } from './types'; +import { NpmSnapFileNames, SnapManifest } from './types'; import { DEFAULT_SNAP_BUNDLE, getSnapManifest } from './test-utils'; -import { SnapManifest } from './json-schemas'; jest.mock('fs'); diff --git a/packages/utils/src/index.browser.ts b/packages/utils/src/index.browser.ts index 22f79afb3c..212d387269 100644 --- a/packages/utils/src/index.browser.ts +++ b/packages/utils/src/index.browser.ts @@ -4,7 +4,6 @@ export * from './deep-clone'; export * from './default-endowments'; export * from './flatMap'; export * from './json-rpc'; -export * from './json-schemas'; export * from './namespace'; export * from './notification'; export * from './object'; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 45db8898ea..55fbedda3f 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -6,7 +6,6 @@ export * from './eval'; export * from './flatMap'; export * from './fs'; export * from './json-rpc'; -export * from './json-schemas'; export * from './manifest'; export * from './mock'; export * from './namespace'; diff --git a/packages/utils/src/json-schemas/NpmSnapPackageJson.ts b/packages/utils/src/json-schemas/NpmSnapPackageJson.ts deleted file mode 100644 index 99940d9224..0000000000 --- a/packages/utils/src/json-schemas/NpmSnapPackageJson.ts +++ /dev/null @@ -1,26 +0,0 @@ -// THIS IS A PROGRAMMATICALLY GENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. - -type Version = string; -type PackageName = string; -type Private = boolean; -type Main = string; -type StringDj4X5WuP = string; -type StringXp6NyS6W = "https://registry.npmjs.org" | "https://registry.npmjs.org/"; -type PublishConfig = { - access?: StringDj4X5WuP; - registry: StringXp6NyS6W; - [k: string]: any; -} -type Repository = { - type: StringDj4X5WuP; - url: StringDj4X5WuP; -} -export type NpmSnapPackageJson = { - version: Version; - name: PackageName; - private?: Private; - main?: Main; - publishConfig?: PublishConfig; - repository?: Repository; - [k: string]: any; -} diff --git a/packages/utils/src/json-schemas/README.md b/packages/utils/src/json-schemas/README.md deleted file mode 100644 index 149131fbba..0000000000 --- a/packages/utils/src/json-schemas/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# JSON Schema Files - -The `.js` files and some `.ts` files in this directory are programmatically generated during the -build process for this package. Do not modify them directly. diff --git a/packages/utils/src/json-schemas/SnapManifest.ts b/packages/utils/src/json-schemas/SnapManifest.ts deleted file mode 100644 index f5639d63aa..0000000000 --- a/packages/utils/src/json-schemas/SnapManifest.ts +++ /dev/null @@ -1,106 +0,0 @@ -// THIS IS A PROGRAMMATICALLY GENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. - -/** - * - * MUST be a valid SemVer version string and equal to the corresponding `package.json` field. - * - */ -type Version = string; -/** - * - * MUST be a non-empty string less than or equal to 280 characters. A short description of the Snap. - * - */ -type Description = string; -/** - * - * MUST be a string less than or equal to 214 characters. The Snap author's proposed name for the Snap. The Snap host application may display this name unmodified in its user interface. The proposed name SHOULD be human-readable. - * - */ -type ProposedName = string; -type NullQu0Arl1F = null; -type StringDj4X5WuP = string; -type ObjectOfStringDj4X5WuPStringDj4X5WuPHQwLk7Md = { - type: StringDj4X5WuP; - url: StringDj4X5WuP; -} -/** - * - * MAY be omitted. If present, MUST be equal to the corresponding package.json field. - * - */ -type Repository = NullQu0Arl1F | ObjectOfStringDj4X5WuPStringDj4X5WuPHQwLk7Md; -/** - * - * MUST be the Base64-encoded string representation of the SHA-256 hash of the Snap source file. - * - */ -type StringFpP4DSlq = string; -/** - * - * The path to the Snap bundle file from the project root directory. - * - */ -type FilePath = string; -/** - * - * The path to an .svg file from the project root directory. - * - */ -type IconPath = string; -/** - * - * The Snap's npm package name. - * - */ -type PackageName = string; -/** - * - * The npm registry URL. - * - */ -type NpmRegistry = "https://registry.npmjs.org" | "https://registry.npmjs.org/"; -type Npm = { - filePath: FilePath; - iconPath?: IconPath; - packageName: PackageName; - registry: NpmRegistry; -} -type SourceLocation = { - npm: Npm; -} -/** - * - * Specifies some Snap metadata and where to fetch the Snap during installation. - * - */ -type Source = { - shasum: StringFpP4DSlq; - location: SourceLocation; -} -/** - * - * MUST be a valid EIP-2255 wallet_requestPermissions parameter object, specifying the initial permissions that will be requested when the Snap is added to the host application. - * - */ -type InitialPermissions = { [key: string]: any; } -/** - * - * The Snap manifest specification version targeted by the manifest. - * - */ -type ManifestVersion = "0.1"; -/** - * - * The Snap manifest file MUST be named `snap.manifest.json` and located in the package root directory. - * - */ -export type SnapManifest = { - version: Version; - description: Description; - proposedName: ProposedName; - repository?: Repository; - source: Source; - initialPermissions: InitialPermissions; - manifestVersion: ManifestVersion; -} diff --git a/packages/utils/src/json-schemas/index.ts b/packages/utils/src/json-schemas/index.ts deleted file mode 100644 index 9f69ae11e6..0000000000 --- a/packages/utils/src/json-schemas/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { NpmSnapFileNames } from '../types'; -import validateNpmSnapPackageJson from './validateNpmSnapPackageJson.js'; -import validateSnapManifest from './validateSnapManifest.js'; - -export { NpmSnapPackageJson } from './NpmSnapPackageJson'; -export { SnapManifest } from './SnapManifest'; - -/** - * Validates a Snap JSON file. Throws a human-readable list of errors if - * validation fails. - * - * @param fileName - The name of Snap JSON file to validate. - * @param content - The contents of the file. - */ -export function validateSnapJsonFile( - fileName: NpmSnapFileNames, - content: unknown, -): void { - let errors; - switch (fileName) { - case NpmSnapFileNames.Manifest: - if (content && typeof content === 'object' && !Array.isArray(content)) { - if ((content as any).repository === undefined) { - // We do this to allow consumers to omit this field. We cannot omit - // it internally due to TS@<4.4 limitations. - (content as any).repository = null; - } - } - - errors = validateSnapManifest(content); - break; - case NpmSnapFileNames.PackageJson: - errors = validateNpmSnapPackageJson(content); - break; - default: - throw new Error(`Unrecognized file name "${fileName}".`); - } - - if (errors && errors.length !== 0) { - throw new Error( - `${errors - .reduce( - ( - allErrors, - errorObject: { instancePath?: string; message?: string } = {}, - ) => { - const { instancePath, message = 'unknown error' } = errorObject; - const currentString = instancePath - ? `\t${instancePath}\n\t${message}\n\n` - : `\t${message}\n\n`; - - return `${allErrors}${currentString}`; - }, - '', - ) - .replace(/\n$/u, '')}`, - ); - } -} diff --git a/packages/utils/src/json-schemas/npm-snap-package-json.schema.json b/packages/utils/src/json-schemas/npm-snap-package-json.schema.json deleted file mode 100644 index 7b0c0527fc..0000000000 --- a/packages/utils/src/json-schemas/npm-snap-package-json.schema.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "title": "npm Snap package.json", - "type": "object", - "required": ["version", "name"], - "properties": { - "version": { - "type": "string", - "title": "Version", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" - }, - "name": { - "type": "string", - "title": "Package Name", - "minLength": 1, - "maxLength": 214, - "pattern": "^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$" - }, - "private": { - "type": "boolean", - "title": "Private" - }, - "main": { - "type": "string", - "title": "Main", - "minLength": 1 - }, - "publishConfig": { - "type": "object", - "title": "Publish Config", - "required": ["registry"], - "properties": { - "access": { - "type": "string", - "minLength": 1 - }, - "registry": { - "type": "string", - "enum": ["https://registry.npmjs.org", "https://registry.npmjs.org/"] - } - } - }, - "repository": { - "type": "object", - "title": "Repository", - "additionalProperties": false, - "required": ["type", "url"], - "properties": { - "type": { - "type": "string", - "minLength": 1 - }, - "url": { - "type": "string", - "minLength": 1 - } - } - } - } -} diff --git a/packages/utils/src/json-schemas/snap-manifest.schema.json b/packages/utils/src/json-schemas/snap-manifest.schema.json deleted file mode 100644 index d70d79f8ba..0000000000 --- a/packages/utils/src/json-schemas/snap-manifest.schema.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "title": "Snap Manifest", - "description": "The Snap manifest file MUST be named `snap.manifest.json` and located in the package root directory.", - "type": "object", - "additionalProperties": false, - "required": [ - "version", - "description", - "proposedName", - "source", - "initialPermissions", - "manifestVersion" - ], - "properties": { - "version": { - "type": "string", - "title": "Version", - "description": "MUST be a valid SemVer version string and equal to the corresponding `package.json` field.", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" - }, - "description": { - "type": "string", - "title": "Description", - "description": "MUST be a non-empty string less than or equal to 280 characters. A short description of the Snap.", - "minLength": 1, - "maxLength": 280 - }, - "proposedName": { - "type": "string", - "title": "Proposed Name", - "description": "MUST be a string less than or equal to 214 characters. The Snap author's proposed name for the Snap. The Snap host application may display this name unmodified in its user interface. The proposed name SHOULD be human-readable.", - "minLength": 1, - "maxLength": 214, - "pattern": "^(?:[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*)|(?:(?:@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*/)?[A-Za-z0-9-~][A-Za-z0-9-._~]*)$" - }, - "repository": { - "title": "Repository", - "description": "MAY be omitted. If present, MUST be equal to the corresponding package.json field.", - "oneOf": [ - { - "type": "null" - }, - { - "type": "object", - "additionalProperties": false, - "required": ["type", "url"], - "properties": { - "type": { - "type": "string", - "minLength": 1 - }, - "url": { - "type": "string", - "minLength": 1 - } - } - } - ] - }, - "source": { - "type": "object", - "title": "Source", - "description": "Specifies some Snap metadata and where to fetch the Snap during installation.", - "additionalProperties": false, - "required": ["shasum", "location"], - "properties": { - "shasum": { - "type": "string", - "description": "MUST be the Base64-encoded string representation of the SHA-256 hash of the Snap source file.", - "minLength": 44, - "maxLength": 44, - "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$" - }, - "location": { - "title": "Source Location", - "type": "object", - "additionalProperties": false, - "required": ["npm"], - "properties": { - "npm": { - "title": "npm", - "type": "object", - "additionalProperties": false, - "required": ["filePath", "packageName", "registry"], - "properties": { - "filePath": { - "type": "string", - "title": "File Path", - "description": "The path to the Snap bundle file from the project root directory.", - "minLength": 1 - }, - "iconPath": { - "type": "string", - "title": "Icon Path", - "description": "The path to an .svg file from the project root directory.", - "pattern": "\\w+\\.svg$" - }, - "packageName": { - "type": "string", - "title": "Package Name", - "description": "The Snap's npm package name.", - "minLength": 1 - }, - "registry": { - "type": "string", - "title": "npm Registry", - "description": "The npm registry URL.", - "enum": [ - "https://registry.npmjs.org", - "https://registry.npmjs.org/" - ] - } - } - } - } - } - } - }, - "initialPermissions": { - "type": "object", - "title": "Initial Permissions", - "description": "MUST be a valid EIP-2255 wallet_requestPermissions parameter object, specifying the initial permissions that will be requested when the Snap is added to the host application." - }, - "manifestVersion": { - "type": "string", - "title": "Manifest Version", - "description": "The Snap manifest specification version targeted by the manifest.", - "enum": ["0.1"] - } - } -} diff --git a/packages/utils/src/json-schemas/validateNpmSnapPackageJson.d.ts b/packages/utils/src/json-schemas/validateNpmSnapPackageJson.d.ts deleted file mode 100644 index 2f95b8949d..0000000000 --- a/packages/utils/src/json-schemas/validateNpmSnapPackageJson.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const validate: (manifest: unknown) => { message: string }[]; -export = validate; diff --git a/packages/utils/src/json-schemas/validateNpmSnapPackageJson.js b/packages/utils/src/json-schemas/validateNpmSnapPackageJson.js deleted file mode 100644 index e520f7b156..0000000000 --- a/packages/utils/src/json-schemas/validateNpmSnapPackageJson.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";module.exports = validate20;module.exports.default = validate20;const schema22 = {"title":"npm Snap package.json","type":"object","required":["version","name"],"properties":{"version":{"type":"string","title":"Version","pattern":"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"},"name":{"type":"string","title":"Package Name","minLength":1,"maxLength":214,"pattern":"^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"},"private":{"type":"boolean","title":"Private"},"main":{"type":"string","title":"Main","minLength":1},"publishConfig":{"type":"object","title":"Publish Config","required":["registry"],"properties":{"access":{"type":"string","minLength":1},"registry":{"type":"string","enum":["https://registry.npmjs.org","https://registry.npmjs.org/"]}}},"repository":{"type":"object","title":"Repository","additionalProperties":false,"required":["type","url"],"properties":{"type":{"type":"string","minLength":1},"url":{"type":"string","minLength":1}}}}};const pattern0 = new RegExp("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "u");const pattern1 = new RegExp("^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$", "u");const func4 = require("ajv/dist/runtime/ucs2length").default;function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(data && typeof data == "object" && !Array.isArray(data)){if(data.version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.name === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.version !== undefined){let data0 = data.version;if(typeof data0 === "string"){if(!pattern0.test(data0)){const err2 = {instancePath:instancePath+"/version",schemaPath:"#/properties/version/pattern",keyword:"pattern",params:{pattern: "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"},message:"must match pattern \""+"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"+"\""};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}}else {const err3 = {instancePath:instancePath+"/version",schemaPath:"#/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}}if(data.name !== undefined){let data1 = data.name;if(typeof data1 === "string"){if(func4(data1) > 214){const err4 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/maxLength",keyword:"maxLength",params:{limit: 214},message:"must NOT have more than 214 characters"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(func4(data1) < 1){const err5 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}if(!pattern1.test(data1)){const err6 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/pattern",keyword:"pattern",params:{pattern: "^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"},message:"must match pattern \""+"^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"+"\""};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}else {const err7 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.private !== undefined){if(typeof data.private !== "boolean"){const err8 = {instancePath:instancePath+"/private",schemaPath:"#/properties/private/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.main !== undefined){let data3 = data.main;if(typeof data3 === "string"){if(func4(data3) < 1){const err9 = {instancePath:instancePath+"/main",schemaPath:"#/properties/main/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}}else {const err10 = {instancePath:instancePath+"/main",schemaPath:"#/properties/main/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data.publishConfig !== undefined){let data4 = data.publishConfig;if(data4 && typeof data4 == "object" && !Array.isArray(data4)){if(data4.registry === undefined){const err11 = {instancePath:instancePath+"/publishConfig",schemaPath:"#/properties/publishConfig/required",keyword:"required",params:{missingProperty: "registry"},message:"must have required property '"+"registry"+"'"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}if(data4.access !== undefined){let data5 = data4.access;if(typeof data5 === "string"){if(func4(data5) < 1){const err12 = {instancePath:instancePath+"/publishConfig/access",schemaPath:"#/properties/publishConfig/properties/access/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}else {const err13 = {instancePath:instancePath+"/publishConfig/access",schemaPath:"#/properties/publishConfig/properties/access/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data4.registry !== undefined){let data6 = data4.registry;if(typeof data6 !== "string"){const err14 = {instancePath:instancePath+"/publishConfig/registry",schemaPath:"#/properties/publishConfig/properties/registry/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(!((data6 === "https://registry.npmjs.org") || (data6 === "https://registry.npmjs.org/"))){const err15 = {instancePath:instancePath+"/publishConfig/registry",schemaPath:"#/properties/publishConfig/properties/registry/enum",keyword:"enum",params:{allowedValues: schema22.properties.publishConfig.properties.registry.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}}else {const err16 = {instancePath:instancePath+"/publishConfig",schemaPath:"#/properties/publishConfig/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}if(data.repository !== undefined){let data7 = data.repository;if(data7 && typeof data7 == "object" && !Array.isArray(data7)){if(data7.type === undefined){const err17 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(data7.url === undefined){const err18 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}for(const key0 in data7){if(!((key0 === "type") || (key0 === "url"))){const err19 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data7.type !== undefined){let data8 = data7.type;if(typeof data8 === "string"){if(func4(data8) < 1){const err20 = {instancePath:instancePath+"/repository/type",schemaPath:"#/properties/repository/properties/type/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/repository/type",schemaPath:"#/properties/repository/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data7.url !== undefined){let data9 = data7.url;if(typeof data9 === "string"){if(func4(data9) < 1){const err22 = {instancePath:instancePath+"/repository/url",schemaPath:"#/properties/repository/properties/url/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/repository/url",schemaPath:"#/properties/repository/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}}else {const err25 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}validate20.errors = vErrors;return vErrors;} \ No newline at end of file diff --git a/packages/utils/src/json-schemas/validateSnapManifest.d.ts b/packages/utils/src/json-schemas/validateSnapManifest.d.ts deleted file mode 100644 index 2f95b8949d..0000000000 --- a/packages/utils/src/json-schemas/validateSnapManifest.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const validate: (manifest: unknown) => { message: string }[]; -export = validate; diff --git a/packages/utils/src/json-schemas/validateSnapManifest.js b/packages/utils/src/json-schemas/validateSnapManifest.js deleted file mode 100644 index f795b2bdfd..0000000000 --- a/packages/utils/src/json-schemas/validateSnapManifest.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";module.exports = validate20;module.exports.default = validate20;const schema22 = {"title":"Snap Manifest","description":"The Snap manifest file MUST be named `snap.manifest.json` and located in the package root directory.","type":"object","additionalProperties":false,"required":["version","description","proposedName","source","initialPermissions","manifestVersion"],"properties":{"version":{"type":"string","title":"Version","description":"MUST be a valid SemVer version string and equal to the corresponding `package.json` field.","pattern":"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"},"description":{"type":"string","title":"Description","description":"MUST be a non-empty string less than or equal to 280 characters. A short description of the Snap.","minLength":1,"maxLength":280},"proposedName":{"type":"string","title":"Proposed Name","description":"MUST be a string less than or equal to 214 characters. The Snap author's proposed name for the Snap. The Snap host application may display this name unmodified in its user interface. The proposed name SHOULD be human-readable.","minLength":1,"maxLength":214,"pattern":"^(?:[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*)|(?:(?:@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*/)?[A-Za-z0-9-~][A-Za-z0-9-._~]*)$"},"repository":{"title":"Repository","description":"MAY be omitted. If present, MUST be equal to the corresponding package.json field.","oneOf":[{"type":"null"},{"type":"object","additionalProperties":false,"required":["type","url"],"properties":{"type":{"type":"string","minLength":1},"url":{"type":"string","minLength":1}}}]},"source":{"type":"object","title":"Source","description":"Specifies some Snap metadata and where to fetch the Snap during installation.","additionalProperties":false,"required":["shasum","location"],"properties":{"shasum":{"type":"string","description":"MUST be the Base64-encoded string representation of the SHA-256 hash of the Snap source file.","minLength":44,"maxLength":44,"pattern":"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"},"location":{"title":"Source Location","type":"object","additionalProperties":false,"required":["npm"],"properties":{"npm":{"title":"npm","type":"object","additionalProperties":false,"required":["filePath","packageName","registry"],"properties":{"filePath":{"type":"string","title":"File Path","description":"The path to the Snap bundle file from the project root directory.","minLength":1},"iconPath":{"type":"string","title":"Icon Path","description":"The path to an .svg file from the project root directory.","pattern":"\\w+\\.svg$"},"packageName":{"type":"string","title":"Package Name","description":"The Snap's npm package name.","minLength":1},"registry":{"type":"string","title":"npm Registry","description":"The npm registry URL.","enum":["https://registry.npmjs.org","https://registry.npmjs.org/"]}}}}}}},"initialPermissions":{"type":"object","title":"Initial Permissions","description":"MUST be a valid EIP-2255 wallet_requestPermissions parameter object, specifying the initial permissions that will be requested when the Snap is added to the host application."},"manifestVersion":{"type":"string","title":"Manifest Version","description":"The Snap manifest specification version targeted by the manifest.","enum":["0.1"]}}};const pattern0 = new RegExp("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "u");const pattern1 = new RegExp("^(?:[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*)|(?:(?:@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*/)?[A-Za-z0-9-~][A-Za-z0-9-._~]*)$", "u");const pattern2 = new RegExp("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$", "u");const pattern3 = new RegExp("\\w+\\.svg$", "u");const func4 = require("ajv/dist/runtime/ucs2length").default;function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(data && typeof data == "object" && !Array.isArray(data)){if(data.version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.description === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "description"},message:"must have required property '"+"description"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.proposedName === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "proposedName"},message:"must have required property '"+"proposedName"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.source === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "source"},message:"must have required property '"+"source"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.initialPermissions === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "initialPermissions"},message:"must have required property '"+"initialPermissions"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.manifestVersion === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "manifestVersion"},message:"must have required property '"+"manifestVersion"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(((((((key0 === "version") || (key0 === "description")) || (key0 === "proposedName")) || (key0 === "repository")) || (key0 === "source")) || (key0 === "initialPermissions")) || (key0 === "manifestVersion"))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.version !== undefined){let data0 = data.version;if(typeof data0 === "string"){if(!pattern0.test(data0)){const err7 = {instancePath:instancePath+"/version",schemaPath:"#/properties/version/pattern",keyword:"pattern",params:{pattern: "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"},message:"must match pattern \""+"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"+"\""};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}else {const err8 = {instancePath:instancePath+"/version",schemaPath:"#/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.description !== undefined){let data1 = data.description;if(typeof data1 === "string"){if(func4(data1) > 280){const err9 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/maxLength",keyword:"maxLength",params:{limit: 280},message:"must NOT have more than 280 characters"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}if(func4(data1) < 1){const err10 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}else {const err11 = {instancePath:instancePath+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}if(data.proposedName !== undefined){let data2 = data.proposedName;if(typeof data2 === "string"){if(func4(data2) > 214){const err12 = {instancePath:instancePath+"/proposedName",schemaPath:"#/properties/proposedName/maxLength",keyword:"maxLength",params:{limit: 214},message:"must NOT have more than 214 characters"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}if(func4(data2) < 1){const err13 = {instancePath:instancePath+"/proposedName",schemaPath:"#/properties/proposedName/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}if(!pattern1.test(data2)){const err14 = {instancePath:instancePath+"/proposedName",schemaPath:"#/properties/proposedName/pattern",keyword:"pattern",params:{pattern: "^(?:[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*)|(?:(?:@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*/)?[A-Za-z0-9-~][A-Za-z0-9-._~]*)$"},message:"must match pattern \""+"^(?:[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*)|(?:(?:@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*/)?[A-Za-z0-9-~][A-Za-z0-9-._~]*)$"+"\""};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}else {const err15 = {instancePath:instancePath+"/proposedName",schemaPath:"#/properties/proposedName/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data.repository !== undefined){let data3 = data.repository;const _errs9 = errors;let valid1 = false;let passing0 = null;const _errs10 = errors;if(data3 !== null){const err16 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/oneOf/0/type",keyword:"type",params:{type: "null"},message:"must be null"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}var _valid0 = _errs10 === errors;if(_valid0){valid1 = true;passing0 = 0;}const _errs12 = errors;if(data3 && typeof data3 == "object" && !Array.isArray(data3)){if(data3.type === undefined){const err17 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/oneOf/1/required",keyword:"required",params:{missingProperty: "type"},message:"must have required property '"+"type"+"'"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}if(data3.url === undefined){const err18 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/oneOf/1/required",keyword:"required",params:{missingProperty: "url"},message:"must have required property '"+"url"+"'"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}for(const key1 in data3){if(!((key1 === "type") || (key1 === "url"))){const err19 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(data3.type !== undefined){let data4 = data3.type;if(typeof data4 === "string"){if(func4(data4) < 1){const err20 = {instancePath:instancePath+"/repository/type",schemaPath:"#/properties/repository/oneOf/1/properties/type/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/repository/type",schemaPath:"#/properties/repository/oneOf/1/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}if(data3.url !== undefined){let data5 = data3.url;if(typeof data5 === "string"){if(func4(data5) < 1){const err22 = {instancePath:instancePath+"/repository/url",schemaPath:"#/properties/repository/oneOf/1/properties/url/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/repository/url",schemaPath:"#/properties/repository/oneOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}var _valid0 = _errs12 === errors;if(_valid0 && valid1){valid1 = false;passing0 = [passing0, 1];}else {if(_valid0){valid1 = true;passing0 = 1;}}if(!valid1){const err25 = {instancePath:instancePath+"/repository",schemaPath:"#/properties/repository/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}else {errors = _errs9;if(vErrors !== null){if(_errs9){vErrors.length = _errs9;}else {vErrors = null;}}}}if(data.source !== undefined){let data6 = data.source;if(data6 && typeof data6 == "object" && !Array.isArray(data6)){if(data6.shasum === undefined){const err26 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/required",keyword:"required",params:{missingProperty: "shasum"},message:"must have required property '"+"shasum"+"'"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data6.location === undefined){const err27 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/required",keyword:"required",params:{missingProperty: "location"},message:"must have required property '"+"location"+"'"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}for(const key2 in data6){if(!((key2 === "shasum") || (key2 === "location"))){const err28 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(data6.shasum !== undefined){let data7 = data6.shasum;if(typeof data7 === "string"){if(func4(data7) > 44){const err29 = {instancePath:instancePath+"/source/shasum",schemaPath:"#/properties/source/properties/shasum/maxLength",keyword:"maxLength",params:{limit: 44},message:"must NOT have more than 44 characters"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}if(func4(data7) < 44){const err30 = {instancePath:instancePath+"/source/shasum",schemaPath:"#/properties/source/properties/shasum/minLength",keyword:"minLength",params:{limit: 44},message:"must NOT have fewer than 44 characters"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}if(!pattern2.test(data7)){const err31 = {instancePath:instancePath+"/source/shasum",schemaPath:"#/properties/source/properties/shasum/pattern",keyword:"pattern",params:{pattern: "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"},message:"must match pattern \""+"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"+"\""};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}else {const err32 = {instancePath:instancePath+"/source/shasum",schemaPath:"#/properties/source/properties/shasum/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}}if(data6.location !== undefined){let data8 = data6.location;if(data8 && typeof data8 == "object" && !Array.isArray(data8)){if(data8.npm === undefined){const err33 = {instancePath:instancePath+"/source/location",schemaPath:"#/properties/source/properties/location/required",keyword:"required",params:{missingProperty: "npm"},message:"must have required property '"+"npm"+"'"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}for(const key3 in data8){if(!(key3 === "npm")){const err34 = {instancePath:instancePath+"/source/location",schemaPath:"#/properties/source/properties/location/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}if(data8.npm !== undefined){let data9 = data8.npm;if(data9 && typeof data9 == "object" && !Array.isArray(data9)){if(data9.filePath === undefined){const err35 = {instancePath:instancePath+"/source/location/npm",schemaPath:"#/properties/source/properties/location/properties/npm/required",keyword:"required",params:{missingProperty: "filePath"},message:"must have required property '"+"filePath"+"'"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}if(data9.packageName === undefined){const err36 = {instancePath:instancePath+"/source/location/npm",schemaPath:"#/properties/source/properties/location/properties/npm/required",keyword:"required",params:{missingProperty: "packageName"},message:"must have required property '"+"packageName"+"'"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}if(data9.registry === undefined){const err37 = {instancePath:instancePath+"/source/location/npm",schemaPath:"#/properties/source/properties/location/properties/npm/required",keyword:"required",params:{missingProperty: "registry"},message:"must have required property '"+"registry"+"'"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}for(const key4 in data9){if(!((((key4 === "filePath") || (key4 === "iconPath")) || (key4 === "packageName")) || (key4 === "registry"))){const err38 = {instancePath:instancePath+"/source/location/npm",schemaPath:"#/properties/source/properties/location/properties/npm/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data9.filePath !== undefined){let data10 = data9.filePath;if(typeof data10 === "string"){if(func4(data10) < 1){const err39 = {instancePath:instancePath+"/source/location/npm/filePath",schemaPath:"#/properties/source/properties/location/properties/npm/properties/filePath/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/source/location/npm/filePath",schemaPath:"#/properties/source/properties/location/properties/npm/properties/filePath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data9.iconPath !== undefined){let data11 = data9.iconPath;if(typeof data11 === "string"){if(!pattern3.test(data11)){const err41 = {instancePath:instancePath+"/source/location/npm/iconPath",schemaPath:"#/properties/source/properties/location/properties/npm/properties/iconPath/pattern",keyword:"pattern",params:{pattern: "\\w+\\.svg$"},message:"must match pattern \""+"\\w+\\.svg$"+"\""};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/source/location/npm/iconPath",schemaPath:"#/properties/source/properties/location/properties/npm/properties/iconPath/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}if(data9.packageName !== undefined){let data12 = data9.packageName;if(typeof data12 === "string"){if(func4(data12) < 1){const err43 = {instancePath:instancePath+"/source/location/npm/packageName",schemaPath:"#/properties/source/properties/location/properties/npm/properties/packageName/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}else {const err44 = {instancePath:instancePath+"/source/location/npm/packageName",schemaPath:"#/properties/source/properties/location/properties/npm/properties/packageName/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}}if(data9.registry !== undefined){let data13 = data9.registry;if(typeof data13 !== "string"){const err45 = {instancePath:instancePath+"/source/location/npm/registry",schemaPath:"#/properties/source/properties/location/properties/npm/properties/registry/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err45];}else {vErrors.push(err45);}errors++;}if(!((data13 === "https://registry.npmjs.org") || (data13 === "https://registry.npmjs.org/"))){const err46 = {instancePath:instancePath+"/source/location/npm/registry",schemaPath:"#/properties/source/properties/location/properties/npm/properties/registry/enum",keyword:"enum",params:{allowedValues: schema22.properties.source.properties.location.properties.npm.properties.registry.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}}}else {const err47 = {instancePath:instancePath+"/source/location/npm",schemaPath:"#/properties/source/properties/location/properties/npm/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}}else {const err48 = {instancePath:instancePath+"/source/location",schemaPath:"#/properties/source/properties/location/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}else {const err49 = {instancePath:instancePath+"/source",schemaPath:"#/properties/source/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}}if(data.initialPermissions !== undefined){let data14 = data.initialPermissions;if(!(data14 && typeof data14 == "object" && !Array.isArray(data14))){const err50 = {instancePath:instancePath+"/initialPermissions",schemaPath:"#/properties/initialPermissions/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}if(data.manifestVersion !== undefined){let data15 = data.manifestVersion;if(typeof data15 !== "string"){const err51 = {instancePath:instancePath+"/manifestVersion",schemaPath:"#/properties/manifestVersion/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}if(!(data15 === "0.1")){const err52 = {instancePath:instancePath+"/manifestVersion",schemaPath:"#/properties/manifestVersion/enum",keyword:"enum",params:{allowedValues: schema22.properties.manifestVersion.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}}else {const err53 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}validate20.errors = vErrors;return vErrors;} \ No newline at end of file diff --git a/packages/utils/src/manifest.test.ts b/packages/utils/src/manifest.test.ts index 286177cb13..3735b2980f 100644 --- a/packages/utils/src/manifest.test.ts +++ b/packages/utils/src/manifest.test.ts @@ -9,6 +9,7 @@ import { import { NpmSnapFileNames, SnapFiles, + SnapManifest, SnapValidationFailureReason, } from './types'; import { readJsonFile } from './fs'; @@ -19,7 +20,6 @@ import { getPackageJson, getSnapManifest, } from './test-utils'; -import { SnapManifest } from './json-schemas'; import { ProgrammaticallyFixableSnapError } from './snaps'; import * as npm from './npm'; @@ -99,10 +99,10 @@ describe('checkManifest', () => { }); it('returns a warning if package.json is missing recommended fields', async () => { - await fs.writeFile( - PACKAGE_JSON_PATH, - JSON.stringify(getPackageJson({ repository: null })), - ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { repository, ...packageJson } = getPackageJson(); + + await fs.writeFile(PACKAGE_JSON_PATH, JSON.stringify(packageJson)); const { updated, warnings } = await checkManifest(BASE_PATH); expect(updated).toBe(true); diff --git a/packages/utils/src/manifest.ts b/packages/utils/src/manifest.ts index 272b9fada7..13837730ce 100644 --- a/packages/utils/src/manifest.ts +++ b/packages/utils/src/manifest.ts @@ -5,10 +5,10 @@ import { validateNpmSnap, validateNpmSnapManifest } from './npm'; import { NpmSnapFileNames, SnapFiles, + SnapManifest, SnapValidationFailureReason, UnvalidatedSnapFiles, } from './types'; -import { SnapManifest } from './json-schemas'; import { readSnapJsonFile } from './fs'; import { getSnapSourceShasum, ProgrammaticallyFixableSnapError } from './snaps'; import { deepClone } from './deep-clone'; @@ -208,7 +208,7 @@ export function fixManifest( case SnapValidationFailureReason.RepositoryMismatch: manifestCopy.repository = packageJson.repository ? deepClone(packageJson.repository) - : null; + : undefined; break; case SnapValidationFailureReason.ShasumMismatch: diff --git a/packages/utils/src/npm.ts b/packages/utils/src/npm.ts index 26782788c9..2f5df183dd 100644 --- a/packages/utils/src/npm.ts +++ b/packages/utils/src/npm.ts @@ -4,12 +4,11 @@ import { SnapFiles, SnapValidationFailureReason, UnvalidatedSnapFiles, -} from './types'; -import { + assertIsNpmSnapPackageJson, + assertIsSnapManifest, NpmSnapPackageJson, SnapManifest, - validateSnapJsonFile, -} from './json-schemas'; +} from './types'; import { ProgrammaticallyFixableSnapError, validateSnapShasum } from './snaps'; export const SVG_MAX_BYTE_SIZE = 100_000; @@ -54,13 +53,9 @@ export function validateNpmSnap( // Typecast: We are assured that the required files exist if we get here. const { manifest, packageJson, sourceCode, svgIcon } = snapFiles as SnapFiles; try { - validateSnapJsonFile(NpmSnapFileNames.Manifest, manifest); + assertIsSnapManifest(manifest); } catch (error) { - throw new Error( - `${errorPrefix ?? ''}"${NpmSnapFileNames.Manifest}" is invalid:\n${ - error.message - }`, - ); + throw new Error(`${errorPrefix ?? ''}${error.message}`); } const validatedManifest = manifest as SnapManifest; @@ -70,13 +65,9 @@ export function validateNpmSnap( } try { - validateSnapJsonFile(NpmSnapFileNames.PackageJson, packageJson); + assertIsNpmSnapPackageJson(packageJson); } catch (error) { - throw new Error( - `${errorPrefix ?? ''}"${NpmSnapFileNames.PackageJson}" is invalid:\n${ - error.message - }`, - ); + throw new Error(`${errorPrefix ?? ''}${error.message}`); } const validatedPackageJson = packageJson as NpmSnapPackageJson; diff --git a/packages/utils/src/snaps.ts b/packages/utils/src/snaps.ts index d991b70c4d..f99752cfb4 100644 --- a/packages/utils/src/snaps.ts +++ b/packages/utils/src/snaps.ts @@ -2,8 +2,12 @@ import { Json } from '@metamask/utils'; import { sha256 } from '@noble/hashes/sha256'; import { base64 } from '@scure/base'; import { SerializedEthereumRpcError } from 'eth-rpc-errors/dist/classes'; -import { SnapManifest } from './json-schemas'; -import { SnapId, SnapIdPrefixes, SnapValidationFailureReason } from './types'; +import { + SnapId, + SnapIdPrefixes, + SnapValidationFailureReason, + SnapManifest, +} from './types'; export const LOCALHOST_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']); export const SNAP_PREFIX = 'wallet_snap_'; diff --git a/packages/utils/src/test-utils/manifest.ts b/packages/utils/src/test-utils/manifest.ts index 89bde7dadd..e7c2a93d78 100644 --- a/packages/utils/src/test-utils/manifest.ts +++ b/packages/utils/src/test-utils/manifest.ts @@ -1,4 +1,4 @@ -import { NpmSnapPackageJson, SnapManifest } from '../json-schemas'; +import { NpmSnapPackageJson, SnapManifest } from '../types'; import { Chain, Namespace, @@ -6,7 +6,6 @@ import { SessionNamespace, } from '../namespace'; import { DEFAULT_SNAP_SHASUM } from './snap'; -import { PartialOrNull } from './types'; type GetSnapManifestOptions = Partial> & { shasum?: string; @@ -97,19 +96,14 @@ export const getPackageJson = ({ description = 'The test example snap!', main = 'src/index.js', repository = getDefaultRepository(), -}: PartialOrNull = {}): NpmSnapPackageJson => { - return Object.entries({ +}: Partial = {}): NpmSnapPackageJson => { + return { name, version, description, main, repository, - }).reduce((packageJson, [key, value]) => { - if (value) { - packageJson[key] = value; - } - return packageJson; - }, {} as NpmSnapPackageJson); + }; }; export const getChain = ({ diff --git a/packages/utils/src/test-utils/types.ts b/packages/utils/src/test-utils/types.ts deleted file mode 100644 index 8214fd11e6..0000000000 --- a/packages/utils/src/test-utils/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type PartialOrNull = { [P in keyof T]?: T[P] | undefined | null }; diff --git a/packages/utils/src/types.test.ts b/packages/utils/src/types.test.ts new file mode 100644 index 0000000000..54686a746a --- /dev/null +++ b/packages/utils/src/types.test.ts @@ -0,0 +1,107 @@ +import { + assertIsNpmSnapPackageJson, + assertIsSnapManifest, + isNpmSnapPackageJson, + isSnapManifest, +} from './types'; +import { getPackageJson, getSnapManifest } from './test-utils'; + +describe('isNpmSnapPackageJson', () => { + it('returns true for a valid package.json', () => { + expect(isNpmSnapPackageJson(getPackageJson())).toBe(true); + }); + + it.each([ + true, + false, + null, + undefined, + 0, + 1, + '', + 'foo', + [], + {}, + { name: 'foo' }, + { version: '1.0.0' }, + getPackageJson({ name: 'foo bar' }), + ])('returns false for an invalid package.json', (value) => { + expect(isNpmSnapPackageJson(value)).toBe(false); + }); +}); + +describe('assertIsNpmSnapPackageJson', () => { + it('does not throw for a valid package.json', () => { + expect(() => assertIsNpmSnapPackageJson(getPackageJson())).not.toThrow(); + }); + + it.each([ + true, + false, + null, + undefined, + 0, + 1, + '', + 'foo', + [], + {}, + { name: 'foo' }, + { version: '1.0.0' }, + getPackageJson({ name: 'foo bar' }), + ])('throws for an invalid package.json', (value) => { + expect(() => assertIsNpmSnapPackageJson(value)).toThrow( + '"package.json" is invalid:', + ); + }); +}); + +describe('isSnapManifest', () => { + it('returns true for a valid snap manifest', () => { + expect(isSnapManifest(getSnapManifest())).toBe(true); + }); + + it.each([ + true, + false, + null, + undefined, + 0, + 1, + '', + 'foo', + [], + {}, + { name: 'foo' }, + { version: '1.0.0' }, + getSnapManifest({ version: 'foo bar' }), + ])('returns false for an invalid snap manifest', (value) => { + expect(isSnapManifest(value)).toBe(false); + }); +}); + +describe('assertIsSnapManifest', () => { + it('does not throw for a valid snap manifest', () => { + expect(() => assertIsSnapManifest(getSnapManifest())).not.toThrow(); + }); + + it.each([ + true, + false, + null, + undefined, + 0, + 1, + '', + 'foo', + [], + {}, + { name: 'foo' }, + { version: '1.0.0' }, + getSnapManifest({ version: 'foo bar' }), + ])('throws for an invalid snap manifest', (value) => { + expect(() => assertIsSnapManifest(value)).toThrow( + '"snap.manifest.json" is invalid:', + ); + }); +}); diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index e4bc473734..941ad6568c 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -3,7 +3,159 @@ import { SnapKeyring as Keyring, } from '@metamask/snap-types'; import { Json } from '@metamask/utils'; -import { NpmSnapPackageJson, SnapManifest } from './json-schemas'; +import { + any, + Infer, + is, + literal, + object, + optional, + pattern, + record, + refine, + size, + string, + type, + union, +} from 'superstruct'; +import { valid as validSemver } from 'semver'; +import { assertStruct } from './assert'; + +export enum NpmSnapFileNames { + PackageJson = 'package.json', + Manifest = 'snap.manifest.json', +} + +/** + * A struct for validating a version string. + */ +export const VersionStruct = refine(string(), 'Version', (value) => { + return validSemver(value) !== null; +}); + +export const NameStruct = size( + pattern( + string(), + /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u, + ), + 1, + 214, +); + +// Note we use `type` instead of `object` here, because the latter does not +// allow unknown keys. +export const NpmSnapPackageJsonStruct = type({ + version: VersionStruct, + name: NameStruct, + main: optional(size(string(), 1, Infinity)), + repository: optional( + object({ + type: size(string(), 1, Infinity), + url: size(string(), 1, Infinity), + }), + ), +}); + +export type NpmSnapPackageJson = Infer & + Record; + +/** + * Check if the given value is a valid {@link NpmSnapPackageJson} object. + * + * @param value - The value to check. + * @returns Whether the value is a valid {@link NpmSnapPackageJson} object. + */ +export function isNpmSnapPackageJson( + value: unknown, +): value is NpmSnapPackageJson { + return is(value, NpmSnapPackageJsonStruct); +} + +/** + * Asserts that the given value is a valid {@link NpmSnapPackageJson} object. + * + * @param value - The value to check. + * @throws If the value is not a valid {@link NpmSnapPackageJson} object. + */ +export function assertIsNpmSnapPackageJson( + value: unknown, +): asserts value is NpmSnapPackageJson { + assertStruct( + value, + NpmSnapPackageJsonStruct, + `"${NpmSnapFileNames.PackageJson}" is invalid`, + ); +} + +export const SnapManifestStruct = object({ + version: VersionStruct, + description: size(string(), 1, 280), + proposedName: size( + pattern( + string(), + /^(?:[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*)|(?:(?:@[A-Za-z0-9-*~][A-Za-z0-9-*._~]*\/)?[A-Za-z0-9-~][A-Za-z0-9-._~]*)$/u, + ), + 1, + 214, + ), + repository: optional( + object({ + type: size(string(), 1, Infinity), + url: size(string(), 1, Infinity), + }), + ), + source: object({ + shasum: size( + pattern( + string(), + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/u, + ), + 44, + 44, + ), + location: object({ + npm: object({ + filePath: size(string(), 1, Infinity), + iconPath: optional(size(string(), 1, Infinity)), + packageName: NameStruct, + registry: union([ + literal('https://registry.npmjs.org'), + literal('https://registry.npmjs.org/'), + ]), + }), + }), + }), + initialPermissions: record(string(), any()), + manifestVersion: literal('0.1'), +}); + +export type SnapManifest = Infer; + +/** + * Check if the given value is a valid {@link SnapManifest} object. + * + * @param value - The value to check. + * @returns Whether the value is a valid {@link SnapManifest} object. + */ +export function isSnapManifest(value: unknown): value is SnapManifest { + return is(value, SnapManifestStruct); +} + +/** + * Assert that the given value is a valid {@link SnapManifest} object. + * + * @param value - The value to check. + * @throws If the value is not a valid {@link SnapManifest} object. + */ +export function assertIsSnapManifest( + value: unknown, +): asserts value is SnapManifest { + assertStruct( + value, + SnapManifestStruct, + `"${NpmSnapFileNames.Manifest}" is invalid`, + ); +} /** * An object for storing parsed but unvalidated Snap file contents. @@ -36,11 +188,6 @@ export enum SnapIdPrefixes { export type SnapId = string; -export enum NpmSnapFileNames { - PackageJson = 'package.json', - Manifest = 'snap.manifest.json', -} - /** * Snap validation failure reason codes that are programmatically fixable * if validation occurs during development. diff --git a/yarn.lock b/yarn.lock index 1e5a1ad234..3ebc1c5cb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3160,7 +3160,6 @@ __metadata: dependencies: "@babel/core": ^7.18.6 "@babel/types": ^7.18.7 - "@json-schema-tools/transpiler": ^1.10.2 "@lavamoat/allow-scripts": ^2.0.3 "@metamask/auto-changelog": ^2.6.0 "@metamask/eslint-config": ^9.0.0 @@ -3173,8 +3172,6 @@ __metadata: "@scure/base": ^1.1.1 "@types/jest": ^27.5.1 "@types/semver": ^7.3.10 - ajv: ^8.11.0 - ajv-cli: ^5.0.0 eslint: ^7.30.0 eslint-config-prettier: ^8.3.0 eslint-plugin-import: ^2.23.4 @@ -4833,28 +4830,6 @@ __metadata: languageName: node linkType: hard -"ajv-cli@npm:^5.0.0": - version: 5.0.0 - resolution: "ajv-cli@npm:5.0.0" - dependencies: - ajv: ^8.0.0 - fast-json-patch: ^2.0.0 - glob: ^7.1.0 - js-yaml: ^3.14.0 - json-schema-migrate: ^2.0.0 - json5: ^2.1.3 - minimist: ^1.2.0 - peerDependencies: - ts-node: ">=9.0.0" - peerDependenciesMeta: - ts-node: - optional: true - bin: - ajv: dist/index.js - checksum: e8ba29f618c653abba3a08d8784f8c882b5ac560dab587540af4d53d8230acb93af07c37165a0461294adb4015ba8ceb09916017a66754c782f950f9679ae94a - languageName: node - linkType: hard - "ajv-formats@npm:^2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" @@ -4901,7 +4876,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.11.0, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.8.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" dependencies: @@ -9269,15 +9244,6 @@ __metadata: languageName: node linkType: hard -"fast-json-patch@npm:^2.0.0": - version: 2.2.1 - resolution: "fast-json-patch@npm:2.2.1" - dependencies: - fast-deep-equal: ^2.0.1 - checksum: 955aebb3f873d1fb0452a5d8c34865ce4c3c6cdafeb7d3ad98d43b467de9a5a0d304132f8595fd2b373f8f4d200605947e865286b180f3a55e8377a634893164 - languageName: node - linkType: hard - "fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" @@ -11756,7 +11722,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.13.1, js-yaml@npm:^3.14.0": +"js-yaml@npm:^3.13.1": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" dependencies: @@ -11945,15 +11911,6 @@ __metadata: languageName: node linkType: hard -"json-schema-migrate@npm:^2.0.0": - version: 2.0.0 - resolution: "json-schema-migrate@npm:2.0.0" - dependencies: - ajv: ^8.0.0 - checksum: 21537305f3a5102695cfeeab997d565516297b240ee560bf7fe5c01ea4d56b65e0fbc4e9a4f9a32819e2cb1874daf43b06eb580e623d0aade0cf50d03ae40418 - languageName: node - linkType: hard - "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -12009,7 +11966,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2, json5@npm:^2.1.3, json5@npm:^2.2.1": +"json5@npm:^2.1.2, json5@npm:^2.2.1": version: 2.2.1 resolution: "json5@npm:2.2.1" bin: @@ -15384,13 +15341,13 @@ __metadata: linkType: hard "semver@npm:7.x, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.3.7 - resolution: "semver@npm:7.3.7" + version: 7.3.8 + resolution: "semver@npm:7.3.8" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 languageName: node linkType: hard