diff --git a/packages/snaps-cli/src/commands/build/implementation.test.ts b/packages/snaps-cli/src/commands/build/implementation.test.ts index bc6c13c9ab..1992b2ead4 100644 --- a/packages/snaps-cli/src/commands/build/implementation.test.ts +++ b/packages/snaps-cli/src/commands/build/implementation.test.ts @@ -6,7 +6,6 @@ import { getPackageJson, getSnapManifest, } from '@metamask/snaps-utils/test-utils'; -import type { SemVerVersion } from '@metamask/utils'; import normalFs from 'fs'; import { dirname } from 'path'; import type { Configuration } from 'webpack'; @@ -58,7 +57,7 @@ describe('build', () => { beforeEach(async () => { const { manifest } = await getMockSnapFilesWithUpdatedChecksum({ manifest: getSnapManifest({ - platformVersion: getPlatformVersion() as SemVerVersion, + platformVersion: getPlatformVersion(), }), }); diff --git a/packages/snaps-utils/src/manifest/manifest.test.ts b/packages/snaps-utils/src/manifest/manifest.test.ts index 72c917c23e..0e2d0b3375 100644 --- a/packages/snaps-utils/src/manifest/manifest.test.ts +++ b/packages/snaps-utils/src/manifest/manifest.test.ts @@ -30,6 +30,13 @@ import { import { NpmSnapFileNames } from '../types'; jest.mock('fs'); +jest.mock('../fs', () => ({ + ...jest.requireActual('../fs'), + useFileSystemCache: + (_key: string, _ttl: number, fn: () => Promise) => + async () => + fn(), +})); const BASE_PATH = '/snap'; const MANIFEST_PATH = join(BASE_PATH, NpmSnapFileNames.Manifest); @@ -157,6 +164,65 @@ describe('checkManifest', () => { expect(version).toBe('1.0.0'); }); + it('includes new validation warnings', async () => { + fetchMock.mockResponseOnce(MOCK_GITHUB_RESPONSE).mockResponseOnce( + JSON.stringify({ + dependencies: { + '@metamask/snaps-sdk': '1.0.0', + }, + }), + ); + + const manifest = getSnapManifest({ + shasum: '29MYwcRiruhy9BEJpN/TBIhxoD3t0P4OdXztV9rW8tc=', + }); + delete manifest.platformVersion; + + await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest)); + + const { files, updated, reports } = await checkManifest(BASE_PATH); + const unfixed = reports.filter((report) => !report.wasFixed); + const fixed = reports.filter((report) => report.wasFixed); + + const defaultManifest = await getDefaultManifest(); + + expect(files?.manifest.result).toStrictEqual(defaultManifest); + expect(updated).toBe(true); + + expect(unfixed).toHaveLength(1); + expect(unfixed).toContainEqual({ + id: 'production-platform-version', + severity: 'warning', + message: expect.stringContaining( + 'The current maximum supported version is "1.0.0". To resolve this, downgrade `@metamask/snaps-sdk` to a compatible version.', + ), + }); + + expect(fixed).toHaveLength(2); + expect(fixed).toContainEqual({ + id: 'platform-version-missing', + severity: 'error', + message: expect.stringContaining( + 'The "platformVersion" field is missing from the manifest.', + ), + wasFixed: true, + }); + + expect(fixed).toContainEqual({ + id: 'checksum', + severity: 'error', + message: expect.stringContaining( + '"snap.manifest.json" "shasum" field does not match computed shasum.', + ), + wasFixed: true, + }); + + const file = await readJsonFile(MANIFEST_PATH); + const { source, version } = file.result; + expect(source.shasum).toBe(defaultManifest.source.shasum); + expect(version).toBe('1.0.0'); + }); + it('returns a warning if package.json is missing recommended fields', async () => { const { repository, ...packageJson } = getPackageJson(); @@ -348,7 +414,7 @@ describe('runFixes', () => { const rule: ValidatorMeta = { severity: 'error', semanticCheck(_, context) { - context.report('Always fail', (files) => files); + context.report('always-fail', 'Always fail', (files) => files); }, }; @@ -360,7 +426,9 @@ describe('runFixes', () => { expect(fixesResults).toStrictEqual({ files, updated: false, - reports: [{ severity: 'error', message: 'Always fail' }], + reports: [ + { id: 'always-fail', severity: 'error', message: 'Always fail' }, + ], }); }); }); diff --git a/packages/snaps-utils/src/manifest/manifest.ts b/packages/snaps-utils/src/manifest/manifest.ts index 7fb896103d..4866bf9d98 100644 --- a/packages/snaps-utils/src/manifest/manifest.ts +++ b/packages/snaps-utils/src/manifest/manifest.ts @@ -232,6 +232,8 @@ export async function runFixes( assert(fixResults.files); fixResults.files.manifest = fixResults.files.manifest.clone(); + const mergedReports: ValidatorReport[] = deepClone(fixResults.reports); + for ( let attempts = 1; shouldRunFixes && attempts <= MAX_ATTEMPTS; @@ -259,15 +261,21 @@ export async function runFixes( fixResults = await runValidators(fixResults.files, rules); shouldRunFixes = hasFixes(fixResults, errorsOnly); + + fixResults.reports + .filter( + (report) => + !mergedReports.some((mergedReport) => mergedReport.id === report.id), + ) + .forEach((report) => mergedReports.push(report)); } - const initialReports: (CheckManifestReport & ValidatorReport)[] = deepClone( - results.reports, - ); + const allReports: (CheckManifestReport & ValidatorReport)[] = + deepClone(mergedReports); // Was fixed if (!shouldRunFixes) { - for (const report of initialReports) { + for (const report of allReports) { if (report.fix) { report.wasFixed = true; delete report.fix; @@ -277,18 +285,18 @@ export async function runFixes( return { files: fixResults.files, updated: true, - reports: initialReports, + reports: allReports, }; } - for (const report of initialReports) { + for (const report of allReports) { delete report.fix; } return { files: results.files, updated: false, - reports: initialReports, + reports: allReports, }; } diff --git a/packages/snaps-utils/src/manifest/validator-types.ts b/packages/snaps-utils/src/manifest/validator-types.ts index ac2cb61eb8..e1080cae3e 100644 --- a/packages/snaps-utils/src/manifest/validator-types.ts +++ b/packages/snaps-utils/src/manifest/validator-types.ts @@ -27,11 +27,12 @@ export type ValidatorContextOptions = { export type ValidatorSeverity = 'error' | 'warning'; export type ValidatorContext = { - readonly report: (message: string, fix?: ValidatorFix) => void; + readonly report: (id: string, message: string, fix?: ValidatorFix) => void; readonly options?: ValidatorContextOptions; }; export type ValidatorReport = { + id: string; severity: ValidatorSeverity; message: string; fix?: ValidatorFix; @@ -41,7 +42,8 @@ export type ValidatorMeta = { severity: ValidatorSeverity; /** - * 1. Run the validator on unverified files to ensure that the files are structurally sound. + * 1. Run the validator on unverified files to ensure that the files are + * structurally sound. * * @param files - Files to be verified * @param context - Validator context to report errors diff --git a/packages/snaps-utils/src/manifest/validator.ts b/packages/snaps-utils/src/manifest/validator.ts index a053e740e2..4a23597bfd 100644 --- a/packages/snaps-utils/src/manifest/validator.ts +++ b/packages/snaps-utils/src/manifest/validator.ts @@ -33,12 +33,22 @@ class Context implements ValidatorContext { this.#options = options; } - report(message: string, fix?: ValidatorFix): void { + /** + * Report a validation error or warning. + * + * @param id - The unique identifier for the report. + * @param message - The message describing the validation issue. + * @param fix - An optional fix function that can be used to automatically + * resolve the issue. + */ + report(id: string, message: string, fix?: ValidatorFix): void { assert(this.#nextSeverity !== undefined); + this.reports.push({ - severity: this.#nextSeverity, + id, message, fix, + severity: this.#nextSeverity, }); } @@ -80,6 +90,7 @@ export async function runValidators( context.prepareForValidator({ severity: rule.severity, }); + await rule.structureCheck?.(files, context); } @@ -93,6 +104,7 @@ export async function runValidators( context.prepareForValidator({ severity: rule.severity, }); + await rule.semanticCheck?.(files as SnapFiles, context); } diff --git a/packages/snaps-utils/src/manifest/validators/checksum.test.ts b/packages/snaps-utils/src/manifest/validators/checksum.test.ts index 74f233ce58..7f0e102782 100644 --- a/packages/snaps-utils/src/manifest/validators/checksum.test.ts +++ b/packages/snaps-utils/src/manifest/validators/checksum.test.ts @@ -27,6 +27,7 @@ describe('checksum', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + 'checksum', expect.stringContaining( '"snap.manifest.json" "shasum" field does not match computed shasum.', ), @@ -38,7 +39,7 @@ describe('checksum', () => { const manifest = getSnapManifest({ shasum: 'foobar' }); let fix: ValidatorFix | undefined; - const report = (_: any, fixer?: ValidatorFix) => { + const report = (_id: string, _message: string, fixer?: ValidatorFix) => { assert(fixer !== undefined); fix = fixer; }; diff --git a/packages/snaps-utils/src/manifest/validators/checksum.ts b/packages/snaps-utils/src/manifest/validators/checksum.ts index 9d675600a0..ce0326996a 100644 --- a/packages/snaps-utils/src/manifest/validators/checksum.ts +++ b/packages/snaps-utils/src/manifest/validators/checksum.ts @@ -14,6 +14,7 @@ export const checksum: ValidatorMeta = { const expectedChecksum = await getSnapChecksum(fetchedFiles); if (gotChecksum !== expectedChecksum) { context.report( + 'checksum', `"${NpmSnapFileNames.Manifest}" "shasum" field does not match computed shasum. Got "${gotChecksum}", expected "${expectedChecksum}".`, async ({ manifest }) => { manifest.source.shasum = expectedChecksum; diff --git a/packages/snaps-utils/src/manifest/validators/expected-files.test.ts b/packages/snaps-utils/src/manifest/validators/expected-files.test.ts index f7bf8cd283..17c285a8dd 100644 --- a/packages/snaps-utils/src/manifest/validators/expected-files.test.ts +++ b/packages/snaps-utils/src/manifest/validators/expected-files.test.ts @@ -25,6 +25,7 @@ describe('expectedFiles', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + `expected-files-${missingFile}`, expect.stringContaining('Missing file'), ); }, diff --git a/packages/snaps-utils/src/manifest/validators/expected-files.ts b/packages/snaps-utils/src/manifest/validators/expected-files.ts index 6eeb84be2f..8a2dc92bc3 100644 --- a/packages/snaps-utils/src/manifest/validators/expected-files.ts +++ b/packages/snaps-utils/src/manifest/validators/expected-files.ts @@ -17,7 +17,10 @@ export const expectedFiles: ValidatorMeta = { structureCheck(files, context) { for (const expectedFile of EXPECTED_SNAP_FILES) { if (!files[expectedFile]) { - context.report(`Missing file "${SnapFileNameFromKey[expectedFile]}".`); + context.report( + `expected-files-${expectedFile}`, + `Missing file "${SnapFileNameFromKey[expectedFile]}".`, + ); } } }, diff --git a/packages/snaps-utils/src/manifest/validators/icon-declared.test.ts b/packages/snaps-utils/src/manifest/validators/icon-declared.test.ts index 9c5692ebd5..09049badb9 100644 --- a/packages/snaps-utils/src/manifest/validators/icon-declared.test.ts +++ b/packages/snaps-utils/src/manifest/validators/icon-declared.test.ts @@ -23,6 +23,7 @@ describe('iconDeclared', () => { }); expect(report).toHaveBeenCalledWith( + 'icon-declared', 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/icon-declared.ts b/packages/snaps-utils/src/manifest/validators/icon-declared.ts index f843c43360..3a26fda0a7 100644 --- a/packages/snaps-utils/src/manifest/validators/icon-declared.ts +++ b/packages/snaps-utils/src/manifest/validators/icon-declared.ts @@ -8,6 +8,7 @@ export const iconDeclared: ValidatorMeta = { semanticCheck(files, context) { if (!files.manifest.result.source.location.npm.iconPath) { context.report( + 'icon-declared', 'No icon found in the Snap manifest. It is recommended to include an icon for the Snap. See https://docs.metamask.io/snaps/how-to/design-a-snap/#guidelines-at-a-glance for more information.', ); } diff --git a/packages/snaps-utils/src/manifest/validators/icon-dimensions.test.ts b/packages/snaps-utils/src/manifest/validators/icon-dimensions.test.ts index a5b0deb91b..9343e9f662 100644 --- a/packages/snaps-utils/src/manifest/validators/icon-dimensions.test.ts +++ b/packages/snaps-utils/src/manifest/validators/icon-dimensions.test.ts @@ -28,6 +28,7 @@ describe('iconDimensions', () => { }); expect(report).toHaveBeenCalledWith( + 'icon-dimensions', 'The icon in the Snap manifest is not square. It is recommended to use a square icon for the Snap.', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/icon-dimensions.ts b/packages/snaps-utils/src/manifest/validators/icon-dimensions.ts index 97470ffb4f..c08b76e5a6 100644 --- a/packages/snaps-utils/src/manifest/validators/icon-dimensions.ts +++ b/packages/snaps-utils/src/manifest/validators/icon-dimensions.ts @@ -14,6 +14,7 @@ export const iconDimensions: ValidatorMeta = { const dimensions = getSvgDimensions(files.svgIcon.toString()); if (dimensions && dimensions?.height !== dimensions.width) { context.report( + 'icon-dimensions', 'The icon in the Snap manifest is not square. It is recommended to use a square icon for the Snap.', ); } diff --git a/packages/snaps-utils/src/manifest/validators/icon-missing.test.ts b/packages/snaps-utils/src/manifest/validators/icon-missing.test.ts index 40452d2ae8..7a4b59eacc 100644 --- a/packages/snaps-utils/src/manifest/validators/icon-missing.test.ts +++ b/packages/snaps-utils/src/manifest/validators/icon-missing.test.ts @@ -23,6 +23,7 @@ describe('iconMissing', () => { await iconMissing.semanticCheck(files, { report }); expect(report).toHaveBeenCalledWith( + 'icon-missing', 'Could not find icon "images/icon.svg".', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/icon-missing.ts b/packages/snaps-utils/src/manifest/validators/icon-missing.ts index 1c1116ea8a..a6a4cfeadd 100644 --- a/packages/snaps-utils/src/manifest/validators/icon-missing.ts +++ b/packages/snaps-utils/src/manifest/validators/icon-missing.ts @@ -8,7 +8,7 @@ export const iconMissing: ValidatorMeta = { semanticCheck(files, context) { const { iconPath } = files.manifest.result.source.location.npm; if (iconPath && !files.svgIcon) { - context.report(`Could not find icon "${iconPath}".`); + context.report('icon-missing', `Could not find icon "${iconPath}".`); } }, }; diff --git a/packages/snaps-utils/src/manifest/validators/is-localization-file.test.ts b/packages/snaps-utils/src/manifest/validators/is-localization-file.test.ts index fcd268db7a..d2cbd14156 100644 --- a/packages/snaps-utils/src/manifest/validators/is-localization-file.test.ts +++ b/packages/snaps-utils/src/manifest/validators/is-localization-file.test.ts @@ -52,6 +52,7 @@ describe('isLocalizationFile', () => { ); expect(report).toHaveBeenCalledWith( + `is-localization-file-${localizationFile.path}-record-messages`, 'Failed to validate localization file "/foo": At path: messages — Expected a value of type record, but received: "foo".', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/is-localization-file.ts b/packages/snaps-utils/src/manifest/validators/is-localization-file.ts index 4b21947dd3..766811c450 100644 --- a/packages/snaps-utils/src/manifest/validators/is-localization-file.ts +++ b/packages/snaps-utils/src/manifest/validators/is-localization-file.ts @@ -16,6 +16,7 @@ export const isLocalizationFile: ValidatorMeta = { if (error) { for (const failure of error.failures()) { context.report( + `is-localization-file-${file.path}-${failure.type}-${failure.path.join('-')}`, `Failed to validate localization file "${ file.path }": ${getStructFailureMessage( diff --git a/packages/snaps-utils/src/manifest/validators/is-package-json.test.ts b/packages/snaps-utils/src/manifest/validators/is-package-json.test.ts index cd6f459d7b..d10ed98ded 100644 --- a/packages/snaps-utils/src/manifest/validators/is-package-json.test.ts +++ b/packages/snaps-utils/src/manifest/validators/is-package-json.test.ts @@ -64,6 +64,7 @@ describe('isPackageJson', () => { ); expect(report).toHaveBeenCalledWith( + 'is-package-json-string-version', '"package.json" is invalid: At path: version — Expected SemVer version, got "foo".', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/is-package-json.ts b/packages/snaps-utils/src/manifest/validators/is-package-json.ts index 3d3ea7dd78..a5daf69809 100644 --- a/packages/snaps-utils/src/manifest/validators/is-package-json.ts +++ b/packages/snaps-utils/src/manifest/validators/is-package-json.ts @@ -20,6 +20,7 @@ export const isPackageJson: ValidatorMeta = { if (error) { for (const failure of error.failures()) { context.report( + `is-package-json-${failure.type}-${failure.path.join('-')}`, `"${ NpmSnapFileNames.PackageJson }" is invalid: ${getStructFailureMessage( diff --git a/packages/snaps-utils/src/manifest/validators/is-snap-icon.test.ts b/packages/snaps-utils/src/manifest/validators/is-snap-icon.test.ts index c2c9fc12c3..573cc3a4c0 100644 --- a/packages/snaps-utils/src/manifest/validators/is-snap-icon.test.ts +++ b/packages/snaps-utils/src/manifest/validators/is-snap-icon.test.ts @@ -32,6 +32,9 @@ describe('isSnapIcon', () => { { report }, ); - expect(report).toHaveBeenCalledWith('Snap icon must be a valid SVG.'); + expect(report).toHaveBeenCalledWith( + 'is-snap-icon', + 'Snap icon must be a valid SVG.', + ); }); }); diff --git a/packages/snaps-utils/src/manifest/validators/is-snap-icon.ts b/packages/snaps-utils/src/manifest/validators/is-snap-icon.ts index ce92b4d9fc..1ccdd9b881 100644 --- a/packages/snaps-utils/src/manifest/validators/is-snap-icon.ts +++ b/packages/snaps-utils/src/manifest/validators/is-snap-icon.ts @@ -17,7 +17,7 @@ export const isSnapIcon: ValidatorMeta = { assertIsSnapIcon(files.svgIcon); } catch (error) { assert(error instanceof Error); - context.report(error.message); + context.report('is-snap-icon', error.message); } }, }; diff --git a/packages/snaps-utils/src/manifest/validators/is-snap-manifest.test.ts b/packages/snaps-utils/src/manifest/validators/is-snap-manifest.test.ts index d5f1178e13..5c27aec525 100644 --- a/packages/snaps-utils/src/manifest/validators/is-snap-manifest.test.ts +++ b/packages/snaps-utils/src/manifest/validators/is-snap-manifest.test.ts @@ -42,6 +42,7 @@ describe('isSnapManifest', () => { ); expect(report).toHaveBeenCalledWith( + 'is-snap-manifest-object-', '"snap.manifest.json" is invalid: Expected a value of type object, but received: "foo".', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/is-snap-manifest.ts b/packages/snaps-utils/src/manifest/validators/is-snap-manifest.ts index 411344e55e..a954dbc49f 100644 --- a/packages/snaps-utils/src/manifest/validators/is-snap-manifest.ts +++ b/packages/snaps-utils/src/manifest/validators/is-snap-manifest.ts @@ -18,6 +18,7 @@ export const isSnapManifest: ValidatorMeta = { if (error) { for (const failure of error.failures()) { context.report( + `is-snap-manifest-${failure.type}-${failure.path.join('-')}`, `"${NpmSnapFileNames.Manifest}" is invalid: ${getStructFailureMessage( SnapManifestStruct, failure, diff --git a/packages/snaps-utils/src/manifest/validators/manifest-localization.test.ts b/packages/snaps-utils/src/manifest/validators/manifest-localization.test.ts index 418fcede35..f8a66c9f29 100644 --- a/packages/snaps-utils/src/manifest/validators/manifest-localization.test.ts +++ b/packages/snaps-utils/src/manifest/validators/manifest-localization.test.ts @@ -43,6 +43,7 @@ describe('manifestLocalization', () => { await manifestLocalization.semanticCheck(files, { report }); expect(report).toHaveBeenCalledWith( + 'manifest-localization', 'Failed to localize Snap manifest: Failed to translate "{{ name }}": No translation found for "name" in "en" file.', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/manifest-localization.ts b/packages/snaps-utils/src/manifest/validators/manifest-localization.ts index 8b30d10827..b52826de0e 100644 --- a/packages/snaps-utils/src/manifest/validators/manifest-localization.ts +++ b/packages/snaps-utils/src/manifest/validators/manifest-localization.ts @@ -26,6 +26,7 @@ export const manifestLocalization: ValidatorMeta = { getLocalizedSnapManifest(manifest, locale, localizations); } catch (error) { context.report( + 'manifest-localization', `Failed to localize Snap manifest: ${getErrorMessage(error)}`, ); } diff --git a/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.test.ts b/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.test.ts index 74db6022c2..8db9d8501e 100644 --- a/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.test.ts +++ b/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.test.ts @@ -26,6 +26,7 @@ describe('packageJsonRecommendedFields', () => { await packageJsonRecommendedFields.semanticCheck(files, { report }); expect(report).toHaveBeenCalledWith( + 'package-json-recommended-fields-repository', 'Missing recommended package.json property: "repository".', ); }); diff --git a/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.ts b/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.ts index cd55e178cc..b4e4ed3a30 100644 --- a/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.ts +++ b/packages/snaps-utils/src/manifest/validators/package-json-recommended-fields.ts @@ -11,6 +11,7 @@ export const packageJsonRecommendedFields: ValidatorMeta = { for (const recommendedField of RECOMMENDED_FIELDS) { if (!files.packageJson.result[recommendedField]) { context.report( + `package-json-recommended-fields-${recommendedField}`, `Missing recommended package.json property: "${recommendedField}".`, ); } diff --git a/packages/snaps-utils/src/manifest/validators/package-name-match.test.ts b/packages/snaps-utils/src/manifest/validators/package-name-match.test.ts index 07d7820045..b6629540e6 100644 --- a/packages/snaps-utils/src/manifest/validators/package-name-match.test.ts +++ b/packages/snaps-utils/src/manifest/validators/package-name-match.test.ts @@ -26,11 +26,12 @@ describe('packageNameMatch', () => { await packageNameMatch.semanticCheck(files, { report }); expect(report).toHaveBeenCalledWith( + 'package-name-match', '"snap.manifest.json" npm package name ("foobar") does not match the "package.json" "name" field ("@metamask/example-snap").', expect.any(Function), ); - const { manifest: newManifest } = await report.mock.calls[0][1]({ + const { manifest: newManifest } = await report.mock.calls[0][2]({ manifest: deepClone(manifest), }); expect(manifest.source.location.npm.packageName).toBe('foobar'); diff --git a/packages/snaps-utils/src/manifest/validators/package-name-match.ts b/packages/snaps-utils/src/manifest/validators/package-name-match.ts index b8120ec796..ea67a3e031 100644 --- a/packages/snaps-utils/src/manifest/validators/package-name-match.ts +++ b/packages/snaps-utils/src/manifest/validators/package-name-match.ts @@ -12,6 +12,7 @@ export const packageNameMatch: ValidatorMeta = { files.manifest.result.source.location.npm.packageName; if (packageJsonName !== manifestPackageName) { context.report( + 'package-name-match', `"${NpmSnapFileNames.Manifest}" npm package name ("${manifestPackageName}") does not match the "${NpmSnapFileNames.PackageJson}" "name" field ("${packageJsonName}").`, ({ manifest }) => { manifest.source.location.npm.packageName = packageJsonName; diff --git a/packages/snaps-utils/src/manifest/validators/platform-version.test.ts b/packages/snaps-utils/src/manifest/validators/platform-version.test.ts index a20add0320..370f09dd3e 100644 --- a/packages/snaps-utils/src/manifest/validators/platform-version.test.ts +++ b/packages/snaps-utils/src/manifest/validators/platform-version.test.ts @@ -42,13 +42,14 @@ describe('platformVersion', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + 'platform-version-missing', expect.stringContaining( `The "platformVersion" field is missing from the manifest.`, ), expect.any(Function), ); - const fix = report.mock.calls[0][1]; + const fix = report.mock.calls[0][2]; expect(fix).toBeInstanceOf(Function); assert(fix); @@ -71,13 +72,14 @@ describe('platformVersion', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + 'platform-version-mismatch', expect.stringContaining( `The "platformVersion" field in the manifest must match the version of the Snaps SDK. Got "1.2.3", expected "${sdkVersion}".`, ), expect.any(Function), ); - const fix = report.mock.calls[0][1]; + const fix = report.mock.calls[0][2]; expect(fix).toBeInstanceOf(Function); assert(fix); diff --git a/packages/snaps-utils/src/manifest/validators/platform-version.ts b/packages/snaps-utils/src/manifest/validators/platform-version.ts index fe1f78cfa8..7fdb498718 100644 --- a/packages/snaps-utils/src/manifest/validators/platform-version.ts +++ b/packages/snaps-utils/src/manifest/validators/platform-version.ts @@ -21,6 +21,7 @@ export const platformVersion: ValidatorMeta = { if (!manifestPlatformVersion) { context.report( + 'platform-version-missing', 'The "platformVersion" field is missing from the manifest.', ({ manifest }) => { manifest.platformVersion = actualVersion; @@ -33,6 +34,7 @@ export const platformVersion: ValidatorMeta = { if (manifestPlatformVersion !== actualVersion) { context.report( + 'platform-version-mismatch', `The "platformVersion" field in the manifest must match the version of the Snaps SDK. Got "${manifestPlatformVersion}", expected "${actualVersion}".`, async ({ manifest }) => { manifest.platformVersion = actualVersion; diff --git a/packages/snaps-utils/src/manifest/validators/production-platform-version.test.ts b/packages/snaps-utils/src/manifest/validators/production-platform-version.test.ts index 9fc790f6a7..168490a64f 100644 --- a/packages/snaps-utils/src/manifest/validators/production-platform-version.test.ts +++ b/packages/snaps-utils/src/manifest/validators/production-platform-version.test.ts @@ -54,6 +54,7 @@ describe('productionPlatformVersion', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + 'production-platform-version', expect.stringContaining( 'The specified platform version "6.5.0" is not supported in the production version of MetaMask. The current maximum supported version is "6.1.0". To resolve this, downgrade `@metamask/snaps-sdk` to a compatible version.', ), diff --git a/packages/snaps-utils/src/manifest/validators/production-platform-version.ts b/packages/snaps-utils/src/manifest/validators/production-platform-version.ts index 68a1712ad9..33d60691bc 100644 --- a/packages/snaps-utils/src/manifest/validators/production-platform-version.ts +++ b/packages/snaps-utils/src/manifest/validators/production-platform-version.ts @@ -60,6 +60,7 @@ export const productionPlatformVersion: ValidatorMeta = { if (gt(manifestPlatformVersion, maximumVersion)) { context.report( + 'production-platform-version', `The specified platform version "${manifestPlatformVersion}" is not supported in the production version of MetaMask. The current maximum supported version is "${maximumVersion}". To resolve this, downgrade \`@metamask/snaps-sdk\` to a compatible version.`, ); } diff --git a/packages/snaps-utils/src/manifest/validators/repository-match.test.ts b/packages/snaps-utils/src/manifest/validators/repository-match.test.ts index 7f7c1a5513..b4e6649187 100644 --- a/packages/snaps-utils/src/manifest/validators/repository-match.test.ts +++ b/packages/snaps-utils/src/manifest/validators/repository-match.test.ts @@ -27,11 +27,12 @@ describe('repositoryMatch', () => { await repositoryMatch.semanticCheck(files, { report }); expect(report).toHaveBeenLastCalledWith( + 'repository-match', '"snap.manifest.json" "repository" field does not match the "package.json" "repository" field.', expect.any(Function), ); - const { manifest: newManifest } = await report.mock.calls[0][1]({ + const { manifest: newManifest } = await report.mock.calls[0][2]({ manifest: deepClone(manifest), }); expect(manifest.repository).toBeUndefined(); diff --git a/packages/snaps-utils/src/manifest/validators/repository-match.ts b/packages/snaps-utils/src/manifest/validators/repository-match.ts index 692b07ab7d..6744d55ce2 100644 --- a/packages/snaps-utils/src/manifest/validators/repository-match.ts +++ b/packages/snaps-utils/src/manifest/validators/repository-match.ts @@ -17,6 +17,7 @@ export const repositoryMatch: ValidatorMeta = { !deepEqual(packageJsonRepository, manifestRepository) ) { context.report( + 'repository-match', `"${NpmSnapFileNames.Manifest}" "repository" field does not match the "${NpmSnapFileNames.PackageJson}" "repository" field.`, ({ manifest }) => { manifest.repository = packageJsonRepository diff --git a/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts b/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts index df8f6ef629..e7f8fb5ce4 100644 --- a/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts +++ b/packages/snaps-utils/src/manifest/validators/unused-exports.test.ts @@ -46,13 +46,14 @@ describe('unusedExports', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + 'unused-endowments', expect.stringContaining( `The Snap requests permission for the following handlers, but does not export them: onHomePage (endowment:page-home).`, ), expect.any(Function), ); - const fix = report.mock.calls[0][1]; + const fix = report.mock.calls[0][2]; assert(fix); const { manifest } = await fix({ manifest: files.manifest.result }); @@ -86,6 +87,7 @@ describe('unusedExports', () => { expect(report).toHaveBeenCalledTimes(1); expect(report).toHaveBeenCalledWith( + 'unused-exports', expect.stringContaining( 'The Snap exports the following handlers, but does not request permission for them: onRpcRequest (endowment:rpc).', ), diff --git a/packages/snaps-utils/src/manifest/validators/unused-exports.ts b/packages/snaps-utils/src/manifest/validators/unused-exports.ts index e1f79f93b3..68522c4e3f 100644 --- a/packages/snaps-utils/src/manifest/validators/unused-exports.ts +++ b/packages/snaps-utils/src/manifest/validators/unused-exports.ts @@ -68,6 +68,7 @@ export const unusedExports: ValidatorMeta = { // 2. Adding the permission to the manifest is not always possible, as it // may require additional configuration in the manifest. context.report( + `unused-exports`, `The Snap exports the following handlers, but does not request permission for them: ${unusedHandlers.join( ', ', )}.`, @@ -80,6 +81,7 @@ export const unusedExports: ValidatorMeta = { .join(', '); context.report( + `unused-endowments`, `The Snap requests permission for the following handlers, but does not export them: ${formattedEndowments}.`, ({ manifest }) => { unusedEndowments.forEach(([, endowment]) => { diff --git a/packages/snaps-utils/src/manifest/validators/version-match.test.ts b/packages/snaps-utils/src/manifest/validators/version-match.test.ts index dae60819bf..99e859e84a 100644 --- a/packages/snaps-utils/src/manifest/validators/version-match.test.ts +++ b/packages/snaps-utils/src/manifest/validators/version-match.test.ts @@ -28,11 +28,12 @@ describe('versionMatch', () => { await versionMatch.semanticCheck(files, { report }); expect(report).toHaveBeenCalledWith( + 'version-match', '"snap.manifest.json" npm package version ("foo") does not match the "package.json" "version" field ("1.0.0").', expect.any(Function), ); - const { manifest: newManifest } = await report.mock.calls[0][1]({ + const { manifest: newManifest } = await report.mock.calls[0][2]({ manifest: deepClone(manifest), }); expect(manifest.version).toBe('foo'); diff --git a/packages/snaps-utils/src/manifest/validators/version-match.ts b/packages/snaps-utils/src/manifest/validators/version-match.ts index 4ad4c6be77..0cc90a0cc7 100644 --- a/packages/snaps-utils/src/manifest/validators/version-match.ts +++ b/packages/snaps-utils/src/manifest/validators/version-match.ts @@ -11,6 +11,7 @@ export const versionMatch: ValidatorMeta = { const manifestPackageVersion = files.manifest.result.version; if (packageJsonVersion !== manifestPackageVersion) { context.report( + 'version-match', `"${NpmSnapFileNames.Manifest}" npm package version ("${manifestPackageVersion}") does not match the "${NpmSnapFileNames.PackageJson}" "version" field ("${packageJsonVersion}").`, ({ manifest }) => { manifest.version = packageJsonVersion; diff --git a/packages/snaps-utils/src/platform-version.ts b/packages/snaps-utils/src/platform-version.ts index 140c18bea3..560f990dae 100644 --- a/packages/snaps-utils/src/platform-version.ts +++ b/packages/snaps-utils/src/platform-version.ts @@ -1,4 +1,5 @@ import packageJson from '@metamask/snaps-sdk/package.json'; +import type { SemVerVersion } from '@metamask/utils'; /** * Get the current supported platform version. @@ -10,5 +11,5 @@ import packageJson from '@metamask/snaps-sdk/package.json'; * @returns The platform version. */ export function getPlatformVersion() { - return packageJson.version; + return packageJson.version as SemVerVersion; }