Skip to content

Commit 5df253f

Browse files
committed
Add tests and refactor manifest validation logic
1 parent 20cb6a6 commit 5df253f

File tree

24 files changed

+732
-248
lines changed

24 files changed

+732
-248
lines changed

packages/examples/packages/bip32/snap.manifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "S8JJzZaDPhHp16nyJVCPY++KVgOpYSCUrEVp/dmbsRE=",
10+
"shasum": "I0xKCYPmg9IuOUQO/JbXr5YFHLhIKvNfr6JDfe+CsTE=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",
@@ -17,7 +17,6 @@
1717
}
1818
},
1919
"initialPermissions": {
20-
"endowment:page-home": {},
2120
"snap_dialog": {},
2221
"snap_getBip32Entropy": [
2322
{

packages/examples/packages/wasm/snap.manifest.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "0iz4fr9QPZehD0EQCZ5rkpOKiKCKVhUwKEZ7ZhjUBLk=",
10+
"shasum": "CUAG/rLnbWYRzl9u72XRYmlYDg5Lh6vKG7s0Io3iLOU=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",
@@ -17,9 +17,6 @@
1717
}
1818
},
1919
"initialPermissions": {
20-
"endowment:rpc": {
21-
"dapps": true
22-
},
2320
"endowment:webassembly": {}
2421
},
2522
"platformVersion": "6.22.1",

packages/snaps-cli/src/__mocks__/ora.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ class MockSpinner {
99

1010
fail = jest.fn();
1111

12-
stop = jest.fn();
12+
stop = jest.fn().mockImplementation(() => {
13+
this.isSpinning = false;
14+
});
1315

1416
clear = jest.fn();
1517

packages/snaps-cli/src/commands/build/build.e2e.test.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import { promises as fs } from 'fs';
2-
import { join } from 'path';
3-
41
import type { TestRunner } from '../../test-utils';
52
import { getCommandRunner } from '../../test-utils';
63

@@ -21,20 +18,19 @@ describe('mm-snap build', () => {
2118
expect.stringMatching(/Checking the input file\./u),
2219
);
2320
expect(runner.stdout).toContainEqual(
24-
expect.stringMatching(/Building the snap bundle\./u),
21+
expect.stringMatching(/Building the Snap bundle\./u),
2522
);
26-
expect(runner.stderr).toContainEqual(
27-
expect.stringMatching(
28-
/Compiled \d+ files? in \d+ms with \d+ warnings?\./u,
29-
),
23+
24+
expect(runner.stdout).toContainEqual(
25+
expect.stringMatching(/Compiled \d+ files? in \d+ms\./u),
3026
);
31-
expect(runner.stderr).toContainEqual(
27+
expect(runner.stdout).toContainEqual(
3228
expect.stringContaining(
3329
'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.',
3430
),
3531
);
3632
expect(runner.stdout).toContainEqual(
37-
expect.stringMatching(/Evaluating the snap bundle\./u),
33+
expect.stringMatching(/Evaluating the Snap bundle\./u),
3834
);
3935
expect(runner.exitCode).toBe(0);
4036
},

packages/snaps-cli/src/commands/build/build.test.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,34 @@ import { buildHandler } from './build';
77
import { build } from './implementation';
88
import { getMockConfig } from '../../test-utils';
99
import { evaluate } from '../eval';
10+
import { manifest } from '../manifest';
1011

1112
jest.mock('fs');
1213
jest.mock('../eval');
14+
jest.mock('../manifest');
1315
jest.mock('./implementation');
1416

1517
jest.mock('webpack-bundle-analyzer', () => ({
1618
BundleAnalyzerPlugin: jest.fn(),
1719
}));
1820

1921
describe('buildHandler', () => {
20-
it('builds a snap', async () => {
22+
beforeEach(() => {
23+
jest.mocked(evaluate).mockResolvedValue({
24+
exports: [],
25+
stdout: '',
26+
stderr: '',
27+
});
28+
29+
jest.mocked(manifest).mockResolvedValue({
30+
valid: true,
31+
errors: 0,
32+
warnings: 0,
33+
fixed: 0,
34+
});
35+
});
36+
37+
it('builds a Snap', async () => {
2138
await fs.promises.writeFile('/input.js', DEFAULT_SNAP_BUNDLE);
2239

2340
jest.spyOn(console, 'log').mockImplementation();
@@ -43,7 +60,7 @@ describe('buildHandler', () => {
4360
);
4461
});
4562

46-
it('analyzes a snap bundle', async () => {
63+
it('analyzes a Snap bundle', async () => {
4764
await fs.promises.writeFile('/input.js', DEFAULT_SNAP_BUNDLE);
4865

4966
jest.spyOn(console, 'log').mockImplementation();
@@ -121,7 +138,7 @@ describe('buildHandler', () => {
121138
expect(process.exitCode).toBe(1);
122139
expect(log).toHaveBeenCalledWith(
123140
expect.stringMatching(
124-
/Input file not found: ".+"\. Make sure that the "input" field in your snap config is correct\./u,
141+
/Input file not found: ".+"\. Make sure that the "input" field in your Snap config is correct\./u,
125142
),
126143
);
127144
});

packages/snaps-cli/src/commands/build/build.ts

Lines changed: 18 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
import { handlerEndowments } from '@metamask/snaps-rpc-methods';
2-
import { checkManifest, isFile } from '@metamask/snaps-utils/node';
3-
import { writeManifest } from '@metamask/snaps-webpack-plugin';
1+
import { isFile } from '@metamask/snaps-utils/node';
42
import { assert } from '@metamask/utils';
5-
import { red, reset, yellow } from 'chalk';
6-
import { readFile } from 'fs/promises';
7-
import { dirname, resolve as pathResolve } from 'path';
3+
import { resolve } from 'path';
84

95
import { build } from './implementation';
106
import { getBundleAnalyzerPort } from './utils';
117
import type { ProcessedConfig } from '../../config';
128
import { CommandError } from '../../errors';
139
import type { Steps } from '../../utils';
14-
import { error, success, executeSteps, info, warn } from '../../utils';
15-
import { formatError } from '../../webpack/utils';
10+
import { info, success, executeSteps } from '../../utils';
1611
import { evaluate } from '../eval';
12+
import { manifest } from '../manifest';
13+
import { showManifestMessage } from '../manifest/manifest';
1714

1815
export type BuildContext = {
1916
analyze: boolean;
2017
build: boolean;
2118
config: ProcessedConfig;
22-
port?: number;
2319
exports?: string[];
20+
port?: number;
2421
};
2522

2623
export const steps: Steps<BuildContext> = [
@@ -40,7 +37,9 @@ export const steps: Steps<BuildContext> = [
4037
{
4138
name: 'Building the Snap bundle.',
4239
condition: ({ build: enableBuild }) => enableBuild,
43-
task: async ({ analyze, build: enableBuild, config, spinner }) => {
40+
task: async (context) => {
41+
const { analyze, config, spinner } = context;
42+
4443
// We don't evaluate the bundle here, because it's done in a separate
4544
// step.
4645
const compiler = await build(config, {
@@ -51,10 +50,7 @@ export const steps: Steps<BuildContext> = [
5150

5251
if (analyze) {
5352
return {
54-
analyze,
55-
build: enableBuild,
56-
config,
57-
spinner,
53+
...context,
5854
port: await getBundleAnalyzerPort(compiler),
5955
};
6056
}
@@ -68,7 +64,7 @@ export const steps: Steps<BuildContext> = [
6864
enableBuild && config.evaluate,
6965
task: async (context) => {
7066
const { config, spinner } = context;
71-
const path = pathResolve(
67+
const path = resolve(
7268
process.cwd(),
7369
config.output.path,
7470
config.output.filename,
@@ -84,65 +80,18 @@ export const steps: Steps<BuildContext> = [
8480
};
8581
},
8682
},
87-
88-
// TODO: Share this between the `build` and `manifest` commands.
8983
{
9084
name: 'Validating the Snap manifest.',
91-
condition: ({ config }) => config.evaluate,
9285
task: async ({ config, exports, spinner }) => {
93-
const bundlePath = pathResolve(
94-
process.cwd(),
95-
config.output.path,
96-
config.output.filename,
97-
);
98-
99-
const { reports } = await checkManifest(dirname(config.manifest.path), {
100-
updateAndWriteManifest: config.manifest.update,
101-
sourceCode: await readFile(bundlePath, 'utf-8'),
86+
const stats = await manifest(
87+
config,
88+
config.manifest.path,
89+
config.manifest.update,
10290
exports,
103-
handlerEndowments,
104-
writeFileFn: async (path, data) => {
105-
return writeManifest(path, data);
106-
},
107-
});
108-
109-
// TODO: Use `Object.groupBy` when available.
110-
const errors = reports
111-
.filter((report) => report.severity === 'error' && !report.wasFixed)
112-
.map((report) => report.message);
113-
const warnings = reports
114-
.filter((report) => report.severity === 'warning' && !report.wasFixed)
115-
.map((report) => report.message);
116-
const fixed = reports
117-
.filter((report) => report.wasFixed)
118-
.map((report) => report.message);
119-
120-
if (errors.length > 0) {
121-
error(
122-
`The following errors were found in the manifest:\n\n${errors
123-
.map((value) => formatError(value, '', red))
124-
.join('\n\n')}\n`,
125-
spinner,
126-
);
127-
}
128-
129-
if (warnings.length > 0) {
130-
warn(
131-
`The following warnings were found in the manifest:\n\n${warnings
132-
.map((value) => formatError(value, '', yellow))
133-
.join('\n\n')}\n`,
134-
spinner,
135-
);
136-
}
91+
spinner,
92+
);
13793

138-
if (fixed.length > 0) {
139-
info(
140-
`The following issues were fixed in the manifest:\n\n${reset(
141-
fixed.map((value) => formatError(value, '', reset)).join('\n\n'),
142-
)}\n`,
143-
spinner,
144-
);
145-
}
94+
showManifestMessage(stats, config.manifest.update, spinner);
14695
},
14796
},
14897
{

packages/snaps-cli/src/commands/build/implementation.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '@metamask/snaps-utils/test-utils';
99
import type { SemVerVersion } from '@metamask/utils';
1010
import normalFs from 'fs';
11-
import { dirname, resolve } from 'path';
11+
import { dirname } from 'path';
1212
import type { Configuration } from 'webpack';
1313

1414
import { build } from './implementation';
@@ -86,7 +86,7 @@ describe('build', () => {
8686

8787
it('builds the snap bundle using Webpack', async () => {
8888
jest.spyOn(process, 'cwd').mockReturnValue('/snap');
89-
const warn = jest.spyOn(console, 'warn').mockImplementation();
89+
const log = jest.spyOn(console, 'log').mockImplementation();
9090

9191
const config = getMockConfig({
9292
input: '/snap/input.js',
@@ -107,8 +107,8 @@ describe('build', () => {
107107
await build(config);
108108

109109
// Manifest checksum mismatch is the warning
110-
expect(warn).toHaveBeenCalledWith(
111-
expect.stringMatching(/Compiled 1 file in \d+ms with 1 warning\./u),
110+
expect(log).toHaveBeenCalledWith(
111+
expect.stringMatching(/Compiled 1 file in \d+ms\./u),
112112
);
113113

114114
const output = await fs.readFile('/snap/output.js', 'utf8');
@@ -119,7 +119,7 @@ describe('build', () => {
119119

120120
it('builds an unminimized snap bundle using Webpack', async () => {
121121
jest.spyOn(process, 'cwd').mockReturnValue('/snap');
122-
const warn = jest.spyOn(console, 'warn').mockImplementation();
122+
const log = jest.spyOn(console, 'log').mockImplementation();
123123

124124
const config = getMockConfig({
125125
input: '/snap/input.js',
@@ -141,8 +141,8 @@ describe('build', () => {
141141
await build(config);
142142

143143
// Manifest checksum mismatch is the warning
144-
expect(warn).toHaveBeenCalledWith(
145-
expect.stringMatching(/Compiled 1 file in \d+ms with 1 warning\./u),
144+
expect(log).toHaveBeenCalledWith(
145+
expect.stringMatching(/Compiled 1 file in \d+ms\./u),
146146
);
147147

148148
const output = await fs.readFile('/snap/output.js', 'utf8');

packages/snaps-cli/src/commands/build/implementation.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import { getCompiler } from '../../webpack';
1111
* @param options - The Webpack options.
1212
* @returns A promise that resolves when the bundle is built.
1313
*/
14-
export async function build(
15-
config: ProcessedConfig,
16-
options?: WebpackOptions,
17-
) {
14+
export async function build(config: ProcessedConfig, options?: WebpackOptions) {
1815
const compiler = await getCompiler(config, options);
1916
return await new Promise<Compiler>((resolve, reject) => {
2017
compiler.run((runError) => {

packages/snaps-cli/src/commands/build/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ const command = {
88
command: ['build', 'b'],
99
desc: 'Build snap from source',
1010
builder: (yarg: yargs.Argv) => {
11-
yarg
12-
.option('analyze', builders.analyze)
11+
yarg.option('analyze', builders.analyze);
1312
},
1413
handler: async (argv: YargsArgs) =>
1514
buildHandler(argv.context.config, argv.analyze),

0 commit comments

Comments
 (0)