Skip to content

Commit 1ae60eb

Browse files
committed
Merge manifest validation reports to include latest validation errors and warnings
1 parent 29b393e commit 1ae60eb

39 files changed

+130
-25
lines changed

packages/snaps-utils/src/manifest/manifest.test.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ import {
3030
import { NpmSnapFileNames } from '../types';
3131

3232
jest.mock('fs');
33+
jest.mock('../fs', () => ({
34+
...jest.requireActual('../fs'),
35+
useFileSystemCache:
36+
<Type>(_key: string, _ttl: number, fn: () => Promise<Type>) =>
37+
async () =>
38+
fn(),
39+
}));
3340

3441
const BASE_PATH = '/snap';
3542
const MANIFEST_PATH = join(BASE_PATH, NpmSnapFileNames.Manifest);
@@ -157,6 +164,39 @@ describe('checkManifest', () => {
157164
expect(version).toBe('1.0.0');
158165
});
159166

167+
it('includes new validation warnings', async () => {
168+
fetchMock.mockResponseOnce(MOCK_GITHUB_RESPONSE).mockResponseOnce(
169+
JSON.stringify({
170+
dependencies: {
171+
'@metamask/snaps-sdk': '1.0.0',
172+
},
173+
}),
174+
);
175+
176+
const manifest = getSnapManifest({
177+
shasum: '29MYwcRiruhy9BEJpN/TBIhxoD3t0P4OdXztV9rW8tc=',
178+
});
179+
delete manifest.platformVersion;
180+
181+
await fs.writeFile(MANIFEST_PATH, JSON.stringify(manifest));
182+
183+
const { files, updated, reports } = await checkManifest(BASE_PATH);
184+
const unfixed = reports.filter((report) => !report.wasFixed);
185+
const fixed = reports.filter((report) => report.wasFixed);
186+
187+
const defaultManifest = await getDefaultManifest();
188+
189+
expect(files?.manifest.result).toStrictEqual(defaultManifest);
190+
expect(updated).toBe(true);
191+
expect(unfixed).toHaveLength(1);
192+
expect(fixed).toHaveLength(2);
193+
194+
const file = await readJsonFile<SnapManifest>(MANIFEST_PATH);
195+
const { source, version } = file.result;
196+
expect(source.shasum).toBe(defaultManifest.source.shasum);
197+
expect(version).toBe('1.0.0');
198+
});
199+
160200
it('returns a warning if package.json is missing recommended fields', async () => {
161201
const { repository, ...packageJson } = getPackageJson();
162202

@@ -348,7 +388,7 @@ describe('runFixes', () => {
348388
const rule: ValidatorMeta = {
349389
severity: 'error',
350390
semanticCheck(_, context) {
351-
context.report('Always fail', (files) => files);
391+
context.report('always-fail', 'Always fail', (files) => files);
352392
},
353393
};
354394

@@ -360,7 +400,9 @@ describe('runFixes', () => {
360400
expect(fixesResults).toStrictEqual({
361401
files,
362402
updated: false,
363-
reports: [{ severity: 'error', message: 'Always fail' }],
403+
reports: [
404+
{ id: 'always-fail', severity: 'error', message: 'Always fail' },
405+
],
364406
});
365407
});
366408
});

packages/snaps-utils/src/manifest/manifest.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ export async function runFixes(
232232
assert(fixResults.files);
233233
fixResults.files.manifest = fixResults.files.manifest.clone();
234234

235+
const mergedReports: ValidatorReport[] = deepClone(fixResults.reports);
236+
235237
for (
236238
let attempts = 1;
237239
shouldRunFixes && attempts <= MAX_ATTEMPTS;
@@ -259,15 +261,21 @@ export async function runFixes(
259261

260262
fixResults = await runValidators(fixResults.files, rules);
261263
shouldRunFixes = hasFixes(fixResults, errorsOnly);
264+
265+
mergedReports.push(
266+
...fixResults.reports.filter(
267+
(report) =>
268+
!mergedReports.find((mergedReport) => mergedReport.id === report.id),
269+
),
270+
);
262271
}
263272

264-
const initialReports: (CheckManifestReport & ValidatorReport)[] = deepClone(
265-
results.reports,
266-
);
273+
const allReports: (CheckManifestReport & ValidatorReport)[] =
274+
deepClone(mergedReports);
267275

268276
// Was fixed
269277
if (!shouldRunFixes) {
270-
for (const report of initialReports) {
278+
for (const report of allReports) {
271279
if (report.fix) {
272280
report.wasFixed = true;
273281
delete report.fix;
@@ -277,18 +285,18 @@ export async function runFixes(
277285
return {
278286
files: fixResults.files,
279287
updated: true,
280-
reports: initialReports,
288+
reports: allReports,
281289
};
282290
}
283291

284-
for (const report of initialReports) {
292+
for (const report of allReports) {
285293
delete report.fix;
286294
}
287295

288296
return {
289297
files: results.files,
290298
updated: false,
291-
reports: initialReports,
299+
reports: allReports,
292300
};
293301
}
294302

packages/snaps-utils/src/manifest/validator-types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ export type ValidatorContextOptions = {
2727
export type ValidatorSeverity = 'error' | 'warning';
2828

2929
export type ValidatorContext = {
30-
readonly report: (message: string, fix?: ValidatorFix) => void;
30+
readonly report: (id: string, message: string, fix?: ValidatorFix) => void;
3131
readonly options?: ValidatorContextOptions;
3232
};
3333

3434
export type ValidatorReport = {
35+
id: string;
3536
severity: ValidatorSeverity;
3637
message: string;
3738
fix?: ValidatorFix;
@@ -41,7 +42,8 @@ export type ValidatorMeta = {
4142
severity: ValidatorSeverity;
4243

4344
/**
44-
* 1. Run the validator on unverified files to ensure that the files are structurally sound.
45+
* 1. Run the validator on unverified files to ensure that the files are
46+
* structurally sound.
4547
*
4648
* @param files - Files to be verified
4749
* @param context - Validator context to report errors

packages/snaps-utils/src/manifest/validator.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,22 @@ class Context implements ValidatorContext {
3333
this.#options = options;
3434
}
3535

36-
report(message: string, fix?: ValidatorFix): void {
36+
/**
37+
* Report a validation error or warning.
38+
*
39+
* @param id - The unique identifier for the report.
40+
* @param message - The message describing the validation issue.
41+
* @param fix - An optional fix function that can be used to automatically
42+
* resolve the issue.
43+
*/
44+
report(id: string, message: string, fix?: ValidatorFix): void {
3745
assert(this.#nextSeverity !== undefined);
46+
3847
this.reports.push({
39-
severity: this.#nextSeverity,
48+
id,
4049
message,
4150
fix,
51+
severity: this.#nextSeverity,
4252
});
4353
}
4454

@@ -80,6 +90,7 @@ export async function runValidators(
8090
context.prepareForValidator({
8191
severity: rule.severity,
8292
});
93+
8394
await rule.structureCheck?.(files, context);
8495
}
8596

@@ -93,6 +104,7 @@ export async function runValidators(
93104
context.prepareForValidator({
94105
severity: rule.severity,
95106
});
107+
96108
await rule.semanticCheck?.(files as SnapFiles, context);
97109
}
98110

packages/snaps-utils/src/manifest/validators/checksum.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('checksum', () => {
2727

2828
expect(report).toHaveBeenCalledTimes(1);
2929
expect(report).toHaveBeenCalledWith(
30+
'checksum',
3031
expect.stringContaining(
3132
'"snap.manifest.json" "shasum" field does not match computed shasum.',
3233
),
@@ -38,7 +39,7 @@ describe('checksum', () => {
3839
const manifest = getSnapManifest({ shasum: 'foobar' });
3940

4041
let fix: ValidatorFix | undefined;
41-
const report = (_: any, fixer?: ValidatorFix) => {
42+
const report = (_id: string, _message: string, fixer?: ValidatorFix) => {
4243
assert(fixer !== undefined);
4344
fix = fixer;
4445
};

packages/snaps-utils/src/manifest/validators/checksum.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const checksum: ValidatorMeta = {
1414
const expectedChecksum = await getSnapChecksum(fetchedFiles);
1515
if (gotChecksum !== expectedChecksum) {
1616
context.report(
17+
'checksum',
1718
`"${NpmSnapFileNames.Manifest}" "shasum" field does not match computed shasum. Got "${gotChecksum}", expected "${expectedChecksum}".`,
1819
async ({ manifest }) => {
1920
manifest.source.shasum = expectedChecksum;

packages/snaps-utils/src/manifest/validators/expected-files.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe('expectedFiles', () => {
2525

2626
expect(report).toHaveBeenCalledTimes(1);
2727
expect(report).toHaveBeenCalledWith(
28+
'expected-files',
2829
expect.stringContaining('Missing file'),
2930
);
3031
},

packages/snaps-utils/src/manifest/validators/expected-files.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export const expectedFiles: ValidatorMeta = {
1717
structureCheck(files, context) {
1818
for (const expectedFile of EXPECTED_SNAP_FILES) {
1919
if (!files[expectedFile]) {
20-
context.report(`Missing file "${SnapFileNameFromKey[expectedFile]}".`);
20+
context.report(
21+
'expected-files',
22+
`Missing file "${SnapFileNameFromKey[expectedFile]}".`,
23+
);
2124
}
2225
}
2326
},

packages/snaps-utils/src/manifest/validators/icon-declared.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('iconDeclared', () => {
2323
});
2424

2525
expect(report).toHaveBeenCalledWith(
26+
'icon-declared',
2627
'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.',
2728
);
2829
});

packages/snaps-utils/src/manifest/validators/icon-declared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const iconDeclared: ValidatorMeta = {
88
semanticCheck(files, context) {
99
if (!files.manifest.result.source.location.npm.iconPath) {
1010
context.report(
11+
'icon-declared',
1112
'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.',
1213
);
1314
}

0 commit comments

Comments
 (0)