Skip to content

Commit 232d1f1

Browse files
committed
Refactor manifest validation to provide detected exports
1 parent eda3c21 commit 232d1f1

File tree

16 files changed

+297
-58
lines changed

16 files changed

+297
-58
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ linkStyle default opacity:0.5
4646
snaps_webpack_plugin(["@metamask/snaps-webpack-plugin"]);
4747
create_snap --> snaps_utils;
4848
snaps_browserify_plugin --> snaps_utils;
49+
snaps_cli --> snaps_rpc_methods;
4950
snaps_cli --> snaps_sdk;
5051
snaps_cli --> snaps_utils;
5152
snaps_cli --> snaps_webpack_plugin;

eslint.config.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ const config = createConfig([
88
{
99
ignores: [
1010
'**/assembly',
11-
'**/build',
1211
'**/coverage',
1312
'**/dist',
1413
'**/docs',

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

Lines changed: 2 additions & 5 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": "lAOhuBxoCTCoBqYjdh8u1Mww15lpVSplCQ7C/oogSss=",
10+
"shasum": "BMMk7RupGFky19WknPFIC/In2AGkXv7dvJ3PrGHXYXA=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",
@@ -17,10 +17,7 @@
1717
}
1818
},
1919
"initialPermissions": {
20-
"endowment:rpc": {
21-
"dapps": true,
22-
"snaps": true
23-
},
20+
"endowment:page-home": {},
2421
"snap_dialog": {},
2522
"snap_getBip32Entropy": [
2623
{

packages/snaps-cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"test:watch": "jest --watch"
6565
},
6666
"dependencies": {
67+
"@metamask/snaps-rpc-methods": "workspace:^",
6768
"@metamask/snaps-sdk": "workspace:^",
6869
"@metamask/snaps-utils": "workspace:^",
6970
"@metamask/snaps-webpack-plugin": "workspace:^",

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

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
import { isFile } from '@metamask/snaps-utils/node';
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';
24
import { assert } from '@metamask/utils';
3-
import { resolve as pathResolve } from 'path';
5+
import { red, reset, yellow } from 'chalk';
6+
import { readFile } from 'fs/promises';
7+
import { dirname, resolve as pathResolve } from 'path';
48

59
import { build } from './implementation';
610
import { getBundleAnalyzerPort } from './utils';
711
import type { ProcessedConfig } from '../../config';
812
import { CommandError } from '../../errors';
913
import type { Steps } from '../../utils';
10-
import { success, executeSteps, info } from '../../utils';
14+
import { error, success, executeSteps, info, warn } from '../../utils';
15+
import { formatError } from '../../webpack/utils';
1116
import { evaluate } from '../eval';
1217

1318
type BuildContext = {
1419
analyze: boolean;
1520
config: ProcessedConfig;
1621
port?: number;
22+
exports?: string[];
1723
};
1824

1925
const steps: Steps<BuildContext> = [
@@ -24,13 +30,13 @@ const steps: Steps<BuildContext> = [
2430

2531
if (!(await isFile(input))) {
2632
throw new CommandError(
27-
`Input file not found: "${input}". Make sure that the "input" field in your snap config is correct.`,
33+
`Input file not found: "${input}". Make sure that the "input" field in your Snap config is correct.`,
2834
);
2935
}
3036
},
3137
},
3238
{
33-
name: 'Building the snap bundle.',
39+
name: 'Building the Snap bundle.',
3440
task: async ({ analyze, config, spinner }) => {
3541
// We don't evaluate the bundle here, because it's done in a separate
3642
// step.
@@ -53,18 +59,85 @@ const steps: Steps<BuildContext> = [
5359
},
5460
},
5561
{
56-
name: 'Evaluating the snap bundle.',
62+
name: 'Evaluating the Snap bundle.',
5763
condition: ({ config }) => config.evaluate,
58-
task: async ({ config, spinner }) => {
64+
task: async (context) => {
65+
const { config, spinner } = context;
5966
const path = pathResolve(
6067
process.cwd(),
6168
config.output.path,
6269
config.output.filename,
6370
);
6471

65-
await evaluate(path);
72+
const { exports } = await evaluate(path);
6673

6774
info(`Snap bundle evaluated successfully.`, spinner);
75+
76+
return {
77+
...context,
78+
exports,
79+
};
80+
},
81+
},
82+
83+
// TODO: Share this between the `build` and `manifest` commands.
84+
{
85+
name: 'Validating the Snap manifest.',
86+
condition: ({ config }) => config.evaluate,
87+
task: async ({ config, exports, spinner }) => {
88+
const bundlePath = pathResolve(
89+
process.cwd(),
90+
config.output.path,
91+
config.output.filename,
92+
);
93+
94+
const { reports } = await checkManifest(dirname(config.manifest.path), {
95+
updateAndWriteManifest: config.manifest.update,
96+
sourceCode: await readFile(bundlePath, 'utf-8'),
97+
exports,
98+
handlerEndowments,
99+
writeFileFn: async (path, data) => {
100+
return writeManifest(path, data);
101+
},
102+
});
103+
104+
// TODO: Use `Object.groupBy` when available.
105+
const errors = reports
106+
.filter((report) => report.severity === 'error' && !report.wasFixed)
107+
.map((report) => report.message);
108+
const warnings = reports
109+
.filter((report) => report.severity === 'warning' && !report.wasFixed)
110+
.map((report) => report.message);
111+
const fixed = reports
112+
.filter((report) => report.wasFixed)
113+
.map((report) => report.message);
114+
115+
if (errors.length > 0) {
116+
error(
117+
`The following errors were found in the manifest:\n\n${errors
118+
.map((value) => formatError(value, '', red))
119+
.join('\n\n')}\n`,
120+
spinner,
121+
);
122+
}
123+
124+
if (warnings.length > 0) {
125+
warn(
126+
`The following warnings were found in the manifest:\n\n${warnings
127+
.map((value) => formatError(value, '', yellow))
128+
.join('\n\n')}\n`,
129+
spinner,
130+
);
131+
}
132+
133+
if (fixed.length > 0) {
134+
info(
135+
`The following issues were fixed in the manifest:\n\n${reset(
136+
fixed.map((value) => formatError(value, '', reset)).join('\n\n'),
137+
)}\n`,
138+
spinner,
139+
);
140+
}
68141
},
69142
},
70143
{

packages/snaps-cli/src/webpack/config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,7 @@ export async function getDefaultConfiguration(
305305
* required, it's highly recommended to use this plugin.
306306
*/
307307
new SnapsWebpackPlugin({
308-
manifestPath: config.manifest.path,
309-
writeManifest: config.manifest.update,
308+
manifestPath: undefined,
310309
eval: !options.watch && options.evaluate,
311310
}),
312311

packages/snaps-cli/src/webpack/plugins.ts

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { assert, hasProperty, isObject } from '@metamask/utils';
2-
import { bold, dim, red, yellow } from 'chalk';
2+
import { bold, red, yellow } from 'chalk';
33
import { isBuiltin } from 'module';
44
import type { Ora } from 'ora';
55
import type {
66
Compiler,
77
ProvidePlugin,
88
Resolver,
9-
StatsError,
109
WebpackPluginInstance,
1110
} from 'webpack';
1211
import { WebpackError } from 'webpack';
1312

14-
import { formatText, pluralize } from './utils';
13+
import { formatError, pluralize } from './utils';
1514
import { evaluate } from '../commands/eval';
16-
import { error, getErrorMessage, info, warn } from '../utils';
15+
import { error, info, warn } from '../utils';
1716

1817
export type SnapsStatsPluginOptions = {
1918
/**
@@ -65,7 +64,9 @@ export class SnapsStatsPlugin implements WebpackPluginInstance {
6564

6665
if (errors?.length) {
6766
const formattedErrors = errors
68-
.map((statsError) => this.#getStatsErrorMessage(statsError))
67+
.map((statsError) =>
68+
formatError(statsError.message, statsError.details, red),
69+
)
6970
.join('\n\n');
7071

7172
error(
@@ -88,7 +89,7 @@ export class SnapsStatsPlugin implements WebpackPluginInstance {
8889
if (warnings?.length) {
8990
const formattedWarnings = warnings
9091
.map((statsWarning) =>
91-
this.#getStatsErrorMessage(statsWarning, yellow),
92+
formatError(statsWarning.message, statsWarning.details, yellow),
9293
)
9394
.join('\n\n');
9495

@@ -119,29 +120,6 @@ export class SnapsStatsPlugin implements WebpackPluginInstance {
119120
}
120121
});
121122
}
122-
123-
/**
124-
* Get the error message for the given stats error.
125-
*
126-
* @param statsError - The stats error.
127-
* @param color - The color to use for the error message.
128-
* @returns The error message.
129-
*/
130-
#getStatsErrorMessage(statsError: StatsError, color = red) {
131-
const baseMessage = this.options.verbose
132-
? getErrorMessage(statsError)
133-
: statsError.message;
134-
135-
const [first, ...rest] = baseMessage.split('\n');
136-
137-
return [
138-
color(formatText(`• ${first}`, 4, 2)),
139-
...rest.map((message) => formatText(color(message), 4)),
140-
statsError.details && `\n${formatText(dim(statsError.details), 6)}`,
141-
]
142-
.filter(Boolean)
143-
.join('\n');
144-
}
145123
}
146124

147125
/**

packages/snaps-cli/src/webpack/utils.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { bytesToBase64 } from '@metamask/utils';
2-
import { dim } from 'chalk';
2+
import { dim, red } from 'chalk';
33
import { promises as fs } from 'fs';
44
import { builtinModules } from 'module';
55
import type { Ora } from 'ora';
@@ -390,3 +390,27 @@ export function getImageSVG(mimeType: string, bytes: Uint8Array) {
390390
const dataUrl = `data:${mimeType};base64,${bytesToBase64(bytes)}`;
391391
return `<svg xmlns="http://www.w3.org/2000/svg"><image href="${dataUrl}" /></svg>`;
392392
}
393+
394+
/**
395+
* Format an error message with the given details and colour.
396+
*
397+
* @param message - The error message.
398+
* @param details - The error details.
399+
* @param color - The colour to use for the error message.
400+
* @returns The formatted error message.
401+
*/
402+
export function formatError(
403+
message: string,
404+
details?: string | undefined,
405+
color = red,
406+
) {
407+
const [first, ...rest] = message.split('\n');
408+
409+
return [
410+
color(formatText(`• ${first}`, 4, 2)),
411+
...rest.map((restMessage) => formatText(color(restMessage), 4)),
412+
details && `\n${formatText(dim(details), 6)}`,
413+
]
414+
.filter(Boolean)
415+
.join('\n');
416+
}

packages/snaps-cli/tsconfig.build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"./src/**/__snapshots__"
1515
],
1616
"references": [
17+
{ "path": "../snaps-rpc-methods/tsconfig.build.json" },
1718
{ "path": "../snaps-utils/tsconfig.build.json" },
1819
{ "path": "../snaps-webpack-plugin/tsconfig.build.json" }
1920
]

packages/snaps-cli/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
},
77
"include": ["./src", "./src/**/*.json", "jest.setup.ts", "package.json"],
88
"references": [
9+
{ "path": "../snaps-rpc-methods" },
910
{ "path": "../snaps-utils" },
1011
{ "path": "../snaps-webpack-plugin" }
1112
]

0 commit comments

Comments
 (0)