Skip to content

Commit

Permalink
Coerce snap manifests to remove prefixes
Browse files Browse the repository at this point in the history
  • Loading branch information
FrederikBolding committed Dec 8, 2022
1 parent c74da9d commit 10d00d2
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 18 deletions.
2 changes: 1 addition & 1 deletion packages/snaps-controllers/jest.config.js
Expand Up @@ -5,7 +5,7 @@ const baseConfig = require('../../jest.config.base');
module.exports = deepmerge(baseConfig, {
coverageThreshold: {
global: {
branches: 90.81,
branches: 91.08,
functions: 97.45,
lines: 97.45,
statements: 97.45,
Expand Down
4 changes: 2 additions & 2 deletions packages/snaps-controllers/src/snaps/location/http.test.ts
Expand Up @@ -62,8 +62,8 @@ describe('HttpLocation', () => {
const manifest = await location.manifest();
const bundle = await location.fetch('./foo.js');

expect(manifest.path).toBe('./snap.manifest.json');
expect(bundle.path).toBe('./foo.js');
expect(manifest.path).toBe('snap.manifest.json');
expect(bundle.path).toBe('foo.js');
expect(manifest.data.canonicalPath).toBe(canonical.manifest);
expect(bundle.data.canonicalPath).toBe(canonical.bundle);
});
Expand Down
7 changes: 3 additions & 4 deletions packages/snaps-controllers/src/snaps/location/http.ts
@@ -1,9 +1,9 @@
import {
SnapManifest,
assertIsSnapManifest,
VirtualFile,
HttpSnapIdStruct,
NpmSnapFileNames,
createSnapManifest,
} from '@metamask/snaps-utils';
import { assert, assertStruct } from '@metamask/utils';

Expand Down Expand Up @@ -60,11 +60,10 @@ export class HttpLocation implements SnapLocation {
await this.fetchFn(canonicalPath, this.fetchOptions)
).text();
const manifest = JSON.parse(contents);
assertIsSnapManifest(manifest);
const vfile = new VirtualFile<SnapManifest>({
value: contents,
result: manifest,
path: `./${NpmSnapFileNames.Manifest}`,
result: createSnapManifest(manifest),
path: NpmSnapFileNames.Manifest,
data: { canonicalPath },
});
this.validatedManifest = vfile;
Expand Down
4 changes: 2 additions & 2 deletions packages/snaps-controllers/src/snaps/location/local.test.ts
Expand Up @@ -65,8 +65,8 @@ describe('LocalLocation', () => {
const manifest = await location.manifest();
const bundle = await location.fetch('./foo.js');

expect(manifest.path).toBe('./snap.manifest.json');
expect(bundle.path).toBe('./foo.js');
expect(manifest.path).toBe('snap.manifest.json');
expect(bundle.path).toBe('foo.js');
expect(manifest.data.canonicalPath).toBe(canonical.manifest);
expect(bundle.data.canonicalPath).toBe(canonical.bundle);
});
Expand Down
7 changes: 3 additions & 4 deletions packages/snaps-controllers/src/snaps/location/npm.ts
@@ -1,6 +1,6 @@
import {
assertIsSemVerVersion,
assertIsSnapManifest,
createSnapManifest,
DEFAULT_REQUESTED_SNAP_VERSION,
getTargetVersion,
isValidUrl,
Expand Down Expand Up @@ -112,10 +112,9 @@ export class NpmLocation implements SnapLocation {
return this.validatedManifest.clone();
}

const vfile = await this.fetch('./snap.manifest.json');
const vfile = await this.fetch('snap.manifest.json');
const result = JSON.parse(vfile.toString());
assertIsSnapManifest(result);
vfile.result = result;
vfile.result = createSnapManifest(result);
this.validatedManifest = vfile as VirtualFile<SnapManifest>;

return this.manifest();
Expand Down
21 changes: 16 additions & 5 deletions packages/snaps-controllers/src/test-utils/location.ts
@@ -1,4 +1,8 @@
import { VirtualFile, SnapManifest } from '@metamask/snaps-utils';
import {
VirtualFile,
SnapManifest,
createSnapManifest,
} from '@metamask/snaps-utils';
import {
DEFAULT_SNAP_BUNDLE,
DEFAULT_SNAP_ICON,
Expand All @@ -7,7 +11,7 @@ import {
import { assert } from '@metamask/utils';
import { SnapLocation } from 'src/snaps/location';

const MANIFEST_PATH = './snap.manifest.json';
const MANIFEST_PATH = 'snap.manifest.json';

type LoopbackOptions = {
/**
Expand All @@ -24,6 +28,12 @@ type LoopbackOptions = {
shouldAlwaysReload?: boolean;
};

// Mirror NPM and HTTP implementation
const coerceManifest = (manifest: VirtualFile<SnapManifest>) => {
manifest.result = createSnapManifest(manifest.result);
return manifest;
};

export class LoopbackLocation implements SnapLocation {
#manifest: VirtualFile<SnapManifest>;

Expand All @@ -33,14 +43,15 @@ export class LoopbackLocation implements SnapLocation {

constructor(opts: LoopbackOptions = {}) {
const shouldAlwaysReload = opts.shouldAlwaysReload ?? false;
const manifest =
const manifest = coerceManifest(
opts.manifest instanceof VirtualFile
? opts.manifest
: new VirtualFile({
value: '',
result: opts.manifest ?? getSnapManifest(),
path: './snap.manifest.json',
});
path: 'snap.manifest.json',
}),
);
let files;
if (opts.files === undefined) {
files = [
Expand Down
36 changes: 36 additions & 0 deletions packages/snaps-utils/src/manifest/validation.test.ts
Expand Up @@ -7,6 +7,7 @@ import {
Base64Opts,
Bip32EntropyStruct,
Bip32PathStruct,
createSnapManifest,
isSnapManifest,
} from './validation';

Expand Down Expand Up @@ -228,3 +229,38 @@ describe('assertIsSnapManifest', () => {
);
});
});

describe('createSnapManifest', () => {
it('does not throw for a valid snap manifest', () => {
expect(() => createSnapManifest(getSnapManifest())).not.toThrow();
});

it('coerces source paths', () => {
expect(
createSnapManifest(
getSnapManifest({ filePath: './bundle.js', iconPath: './icon.svg' }),
),
).toStrictEqual(
getSnapManifest({ filePath: 'bundle.js', iconPath: 'icon.svg' }),
);
});

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) => {
// eslint-disable-next-line jest/require-to-throw-message
expect(() => createSnapManifest(value)).toThrow();
});
});
14 changes: 14 additions & 0 deletions packages/snaps-utils/src/manifest/validation.ts
Expand Up @@ -3,6 +3,7 @@ import {
array,
boolean,
coerce,
create,
enums,
Infer,
integer,
Expand Down Expand Up @@ -248,3 +249,16 @@ export function assertIsSnapManifest(
`"${NpmSnapFileNames.Manifest}" is invalid`,
);
}

/**
* Creates a {@link SnapManifest} object from JSON.
*
*
* @param value - The value to check.
* @throws If the value cannot be coerced to a {@link SnapManifest} object.
* @returns The created {@link SnapManifest} object.
*/
export function createSnapManifest(value: unknown): SnapManifest {
// TODO: Add a utility to prefix these errors similar to assertStruct
return create(value, SnapManifestStruct);
}

0 comments on commit 10d00d2

Please sign in to comment.