From 89d79f792fa4eb8c6e75e50cc605702e35fd7491 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Wed, 16 Nov 2022 14:30:38 +0100 Subject: [PATCH 01/13] Add @metamask/snaps-ui package --- packages/snaps-ui/.eslintrc.js | 3 ++ packages/snaps-ui/.gitignore | 10 +++++ packages/snaps-ui/CHANGELOG.md | 9 +++++ packages/snaps-ui/LICENSE | 15 +++++++ packages/snaps-ui/README.md | 12 ++++++ packages/snaps-ui/jest.config.js | 13 ++++++ packages/snaps-ui/package.json | 58 +++++++++++++++++++++++++++ packages/snaps-ui/tsconfig.build.json | 20 +++++++++ packages/snaps-ui/tsconfig.json | 8 ++++ yarn.lock | 30 ++++++++++++++ 10 files changed, 178 insertions(+) create mode 100644 packages/snaps-ui/.eslintrc.js create mode 100644 packages/snaps-ui/.gitignore create mode 100644 packages/snaps-ui/CHANGELOG.md create mode 100644 packages/snaps-ui/LICENSE create mode 100644 packages/snaps-ui/README.md create mode 100644 packages/snaps-ui/jest.config.js create mode 100644 packages/snaps-ui/package.json create mode 100644 packages/snaps-ui/tsconfig.build.json create mode 100644 packages/snaps-ui/tsconfig.json diff --git a/packages/snaps-ui/.eslintrc.js b/packages/snaps-ui/.eslintrc.js new file mode 100644 index 0000000000..5a2cc7f1ec --- /dev/null +++ b/packages/snaps-ui/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['../../.eslintrc.js'], +}; diff --git a/packages/snaps-ui/.gitignore b/packages/snaps-ui/.gitignore new file mode 100644 index 0000000000..a4ddfffb10 --- /dev/null +++ b/packages/snaps-ui/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +node_modules +dist +dist-temp +temp +*.log +*.tgz + +public/ +src/__GENERATED__/ diff --git a/packages/snaps-ui/CHANGELOG.md b/packages/snaps-ui/CHANGELOG.md new file mode 100644 index 0000000000..65889e6fbf --- /dev/null +++ b/packages/snaps-ui/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/MetaMask/snaps-monorepo/ diff --git a/packages/snaps-ui/LICENSE b/packages/snaps-ui/LICENSE new file mode 100644 index 0000000000..52357a65da --- /dev/null +++ b/packages/snaps-ui/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2022 MetaMask + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/snaps-ui/README.md b/packages/snaps-ui/README.md new file mode 100644 index 0000000000..412f08d377 --- /dev/null +++ b/packages/snaps-ui/README.md @@ -0,0 +1,12 @@ +# @metamask/snaps-ui + +A MetaMask Snaps UI library. + +## Installation + +Use Node.js `16.0.0` or later. We recommend using [nvm](https://github.com/nvm-sh/nvm) for managing Node.js versions. + +Install a dependency in your snap project using `yarn` or `npm`: + +- `npm install @metamask/snaps-ui` +- `yarn add @metamask/snaps-ui` diff --git a/packages/snaps-ui/jest.config.js b/packages/snaps-ui/jest.config.js new file mode 100644 index 0000000000..58387b4ef9 --- /dev/null +++ b/packages/snaps-ui/jest.config.js @@ -0,0 +1,13 @@ +const deepmerge = require('deepmerge'); +const baseConfig = require('../../jest.config.base'); + +module.exports = deepmerge(baseConfig, { + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100, + }, + }, +}); diff --git a/packages/snaps-ui/package.json b/packages/snaps-ui/package.json new file mode 100644 index 0000000000..7922802bf1 --- /dev/null +++ b/packages/snaps-ui/package.json @@ -0,0 +1,58 @@ +{ + "name": "@metamask/snaps-ui", + "version": "0.23.0", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/snaps-monorepo.git" + }, + "main": "dist/index.js", + "files": [ + "dist/" + ], + "scripts": { + "test": "jest && yarn posttest", + "posttest": "jest-it-up --margin 0.25", + "test:ci": "yarn test", + "lint:eslint": "eslint . --cache --ext js,ts", + "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' --ignore-path ../../.gitignore", + "lint": "yarn lint:eslint && yarn lint:misc --check", + "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write", + "lint:changelog": "yarn auto-changelog validate", + "build": "tsc --project tsconfig.build.json", + "build:clean": "yarn clean && yarn build", + "clean": "rimraf '*.tsbuildinfo' 'dist/*'", + "publish:package": "../../scripts/publish-package.sh" + }, + "devDependencies": { + "@lavamoat/allow-scripts": "^2.0.3", + "@metamask/auto-changelog": "^2.6.0", + "@metamask/eslint-config": "^9.0.0", + "@metamask/eslint-config-jest": "^9.0.0", + "@metamask/eslint-config-nodejs": "^9.0.0", + "@metamask/eslint-config-typescript": "^9.0.1", + "@types/jest": "^27.5.1", + "@types/semver": "^7.3.10", + "deepmerge": "^4.2.2", + "eslint": "^7.30.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jest": "^24.4.0", + "eslint-plugin-jsdoc": "^36.1.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.0", + "jest": "^29.0.2", + "jest-it-up": "^2.0.0", + "prettier": "^2.3.2", + "prettier-plugin-packagejson": "^2.2.11", + "rimraf": "^3.0.2", + "ts-jest": "^29.0.0", + "typescript": "^4.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/snaps-ui/tsconfig.build.json b/packages/snaps-ui/tsconfig.build.json new file mode 100644 index 0000000000..4dc952c93e --- /dev/null +++ b/packages/snaps-ui/tsconfig.build.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["./src"], + "exclude": [ + "**/*.test.ts", + "./src/**/test-utils", + "./src/**/__mocks__", + "./src/**/__snapshots__" + ], + "references": [ + { + "path": "../snaps-utils/tsconfig.build.json" + } + ] +} diff --git a/packages/snaps-ui/tsconfig.json b/packages/snaps-ui/tsconfig.json new file mode 100644 index 0000000000..a4f2b5a12e --- /dev/null +++ b/packages/snaps-ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "include": ["./src"], + "references": [{ "path": "../snaps-utils" }] +} diff --git a/yarn.lock b/yarn.lock index c8c2a9c45a..83af1189ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3190,6 +3190,36 @@ __metadata: languageName: unknown linkType: soft +"@metamask/snaps-ui@workspace:packages/snaps-ui": + version: 0.0.0-use.local + resolution: "@metamask/snaps-ui@workspace:packages/snaps-ui" + dependencies: + "@lavamoat/allow-scripts": ^2.0.3 + "@metamask/auto-changelog": ^2.6.0 + "@metamask/eslint-config": ^9.0.0 + "@metamask/eslint-config-jest": ^9.0.0 + "@metamask/eslint-config-nodejs": ^9.0.0 + "@metamask/eslint-config-typescript": ^9.0.1 + "@types/jest": ^27.5.1 + "@types/semver": ^7.3.10 + deepmerge: ^4.2.2 + eslint: ^7.30.0 + eslint-config-prettier: ^8.3.0 + eslint-plugin-import: ^2.23.4 + eslint-plugin-jest: ^24.4.0 + eslint-plugin-jsdoc: ^36.1.0 + eslint-plugin-node: ^11.1.0 + eslint-plugin-prettier: ^3.4.0 + jest: ^29.0.2 + jest-it-up: ^2.0.0 + prettier: ^2.3.2 + prettier-plugin-packagejson: ^2.2.11 + rimraf: ^3.0.2 + ts-jest: ^29.0.0 + typescript: ^4.4.0 + languageName: unknown + linkType: soft + "@metamask/snaps-utils@^0.24.1, @metamask/snaps-utils@workspace:packages/snaps-utils": version: 0.0.0-use.local resolution: "@metamask/snaps-utils@workspace:packages/snaps-utils" From 1384cd2256fe0e896baf723abf94afbaa0c7bd33 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Thu, 17 Nov 2022 09:14:30 +0100 Subject: [PATCH 02/13] Add basic components --- packages/snaps-ui/package.json | 3 ++ packages/snaps-ui/src/nodes.ts | 57 ++++++++++++++++++++++++++++++++++ yarn.lock | 1 + 3 files changed, 61 insertions(+) create mode 100644 packages/snaps-ui/src/nodes.ts diff --git a/packages/snaps-ui/package.json b/packages/snaps-ui/package.json index 7922802bf1..790f712674 100644 --- a/packages/snaps-ui/package.json +++ b/packages/snaps-ui/package.json @@ -23,6 +23,9 @@ "clean": "rimraf '*.tsbuildinfo' 'dist/*'", "publish:package": "../../scripts/publish-package.sh" }, + "dependencies": { + "superstruct": "^0.16.7" + }, "devDependencies": { "@lavamoat/allow-scripts": "^2.0.3", "@metamask/auto-changelog": "^2.6.0", diff --git a/packages/snaps-ui/src/nodes.ts b/packages/snaps-ui/src/nodes.ts new file mode 100644 index 0000000000..949d554b57 --- /dev/null +++ b/packages/snaps-ui/src/nodes.ts @@ -0,0 +1,57 @@ +import { + array, + assign, + Infer, + lazy, + literal, + object, + string, + Struct, + union, +} from 'superstruct'; + +export const NodeStruct = object({ + type: string(), +}); + +export type Node = { + type: string; +}; + +export enum NodeType { + Panel = 'panel', + Text = 'text', +} + +export const PanelStruct: Struct = assign( + NodeStruct, + object({ + type: literal(NodeType.Panel), + + // This node references itself indirectly, so we need to use `lazy()`. + // eslint-disable-next-line @typescript-eslint/no-use-before-define + children: lazy(() => array(AnyNodeStruct)), + }), +) as unknown as Struct; + +export type Panel = { type: NodeType.Panel; children: Component }; + +export const TextStruct = assign( + NodeStruct, + object({ + type: literal(NodeType.Text), + text: string(), + }), +); + +/** + * A text node, that renders the text as one or more paragraphs. + * + * @property type - The type of the node, must be the string 'text'. + * @property text - The text content of the node, either as plain text, or as a + * markdown string. + */ +export type Text = Infer; + +export type Component = Panel | Text; +export const AnyNodeStruct = union([PanelStruct, TextStruct]); diff --git a/yarn.lock b/yarn.lock index 83af1189ab..4c21815521 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3215,6 +3215,7 @@ __metadata: prettier: ^2.3.2 prettier-plugin-packagejson: ^2.2.11 rimraf: ^3.0.2 + superstruct: ^0.16.7 ts-jest: ^29.0.0 typescript: ^4.4.0 languageName: unknown From face65c74d182a124066329c10266c025ae53cc9 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Thu, 17 Nov 2022 14:21:19 +0100 Subject: [PATCH 03/13] Add more nodes and type validation --- packages/snaps-ui/src/index.ts | 2 + packages/snaps-ui/src/nodes.ts | 74 +++++++++++++++- packages/snaps-ui/src/validation.test.ts | 105 +++++++++++++++++++++++ packages/snaps-ui/src/validation.ts | 14 +++ 4 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 packages/snaps-ui/src/index.ts create mode 100644 packages/snaps-ui/src/validation.test.ts create mode 100644 packages/snaps-ui/src/validation.ts diff --git a/packages/snaps-ui/src/index.ts b/packages/snaps-ui/src/index.ts new file mode 100644 index 0000000000..a3f87b79c6 --- /dev/null +++ b/packages/snaps-ui/src/index.ts @@ -0,0 +1,2 @@ +export * from './nodes'; +export * from './validation'; diff --git a/packages/snaps-ui/src/nodes.ts b/packages/snaps-ui/src/nodes.ts index 949d554b57..980e19ffa2 100644 --- a/packages/snaps-ui/src/nodes.ts +++ b/packages/snaps-ui/src/nodes.ts @@ -19,10 +19,38 @@ export type Node = { }; export enum NodeType { + Divider = 'divider', + Heading = 'heading', Panel = 'panel', + Spacer = 'spacer', + Spinner = 'spinner', Text = 'text', } +export const DividerStruct = object({ + type: literal(NodeType.Divider), +}); + +/** + * A divider node, that renders a line between other nodes. + */ +export type Divider = Infer; + +export const HeadingStruct = object({ + type: literal(NodeType.Heading), + text: string(), +}); + +/** + * A heading node, that renders the text as a heading. The level of the heading + * is determined by the depth of the heading in the document. + * + * @property type - The type of the node, must be the string 'text'. + * @property text - The text content of the node, either as plain text, or as a + * markdown string. + */ +export type Heading = Infer; + export const PanelStruct: Struct = assign( NodeStruct, object({ @@ -30,11 +58,38 @@ export const PanelStruct: Struct = assign( // This node references itself indirectly, so we need to use `lazy()`. // eslint-disable-next-line @typescript-eslint/no-use-before-define - children: lazy(() => array(AnyNodeStruct)), + children: lazy(() => array(ComponentStruct)), }), ) as unknown as Struct; -export type Panel = { type: NodeType.Panel; children: Component }; +/** + * A panel node, which renders its children. + * + * @property type - The type of the node, must be the string 'text'. + * @property text - The text content of the node, either as plain text, or as a + * markdown string. + */ +// This node references itself indirectly, so it cannot be inferred. +export type Panel = { type: NodeType.Panel; children: Component[] }; + +export const SpacerStruct = object({ + type: literal(NodeType.Spacer), +}); + +/** + * A spacer node, that renders a blank space between other nodes. + */ +export type Spacer = Infer; + +export const SpinnerStruct = object({ + type: literal(NodeType.Spinner), +}); + +/** + * A spinner node, that renders a spinner, either as a full-screen overlay, or + * inline when nested inside a {@link Panel}. + */ +export type Spinner = Infer; export const TextStruct = assign( NodeStruct, @@ -53,5 +108,16 @@ export const TextStruct = assign( */ export type Text = Infer; -export type Component = Panel | Text; -export const AnyNodeStruct = union([PanelStruct, TextStruct]); +export const ComponentStruct = union([ + DividerStruct, + HeadingStruct, + PanelStruct, + SpacerStruct, + SpinnerStruct, + TextStruct, +]); + +/** + * All supported component types. + */ +export type Component = Infer; diff --git a/packages/snaps-ui/src/validation.test.ts b/packages/snaps-ui/src/validation.test.ts new file mode 100644 index 0000000000..27b955b8f3 --- /dev/null +++ b/packages/snaps-ui/src/validation.test.ts @@ -0,0 +1,105 @@ +import { + Divider, + Heading, + NodeType, + Panel, + Spacer, + Spinner, + Text, +} from './nodes'; +import { isComponent } from './validation'; + +describe('isComponent', () => { + it('returns true for a divider component', () => { + const divider: Divider = { + type: NodeType.Divider, + }; + + expect(isComponent(divider)).toBe(true); + }); + + it('returns true for a heading component', () => { + const heading: Heading = { + type: NodeType.Heading, + text: 'Hello, world!', + }; + + expect(isComponent(heading)).toBe(true); + }); + + it('returns true for a panel component', () => { + const panel: Panel = { + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }; + + expect(isComponent(panel)).toBe(true); + }); + + it('returns true for nested panels', () => { + const panel: Panel = { + type: NodeType.Panel, + children: [ + { + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }, + ], + }; + + expect(isComponent(panel)).toBe(true); + }); + + it('returns true for a spacer component', () => { + const spacer: Spacer = { + type: NodeType.Spacer, + }; + + expect(isComponent(spacer)).toBe(true); + }); + + it('returns true for a spinner component', () => { + const spinner: Spinner = { + type: NodeType.Spinner, + }; + + expect(isComponent(spinner)).toBe(true); + }); + + it('returns true for a text component', () => { + const text: Text = { + type: NodeType.Text, + text: 'Hello, world!', + }; + + expect(isComponent(text)).toBe(true); + }); + + it.each([ + true, + false, + null, + undefined, + 0, + 1, + '', + 'Hello, world!', + {}, + { type: NodeType.Heading }, + { type: NodeType.Heading, foo: 'bar' }, + { type: NodeType.Heading, text: 0 }, + { type: 'foo' }, + ])(`returns false for %p`, (value) => { + expect(isComponent(value)).toBe(false); + }); +}); diff --git a/packages/snaps-ui/src/validation.ts b/packages/snaps-ui/src/validation.ts new file mode 100644 index 0000000000..08b0dc347c --- /dev/null +++ b/packages/snaps-ui/src/validation.ts @@ -0,0 +1,14 @@ +import { is } from 'superstruct'; + +import { Component, ComponentStruct } from './nodes'; + +/** + * Check if the given value is a {@link Component}. This performs recursive + * validation of the component's children (if any). + * + * @param value - The value to check. + * @returns `true` if the value is a {@link Component}, `false` otherwise. + */ +export function isComponent(value: unknown): value is Component { + return is(value, ComponentStruct); +} From 2ae9f68d1d8a3f4eea519d6382e76ebfc18af77c Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Mon, 21 Nov 2022 17:33:18 +0100 Subject: [PATCH 04/13] Add builder and more validation --- packages/snaps-ui/package.json | 1 + packages/snaps-ui/src/builder.test.ts | 146 +++++++++++++++++++++++ packages/snaps-ui/src/builder.ts | 121 +++++++++++++++++++ packages/snaps-ui/src/index.ts | 1 + packages/snaps-ui/src/validation.test.ts | 97 ++++++++++++++- packages/snaps-ui/src/validation.ts | 12 ++ yarn.lock | 1 + 7 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 packages/snaps-ui/src/builder.test.ts create mode 100644 packages/snaps-ui/src/builder.ts diff --git a/packages/snaps-ui/package.json b/packages/snaps-ui/package.json index 790f712674..cfb583f362 100644 --- a/packages/snaps-ui/package.json +++ b/packages/snaps-ui/package.json @@ -24,6 +24,7 @@ "publish:package": "../../scripts/publish-package.sh" }, "dependencies": { + "@metamask/utils": "^3.3.1", "superstruct": "^0.16.7" }, "devDependencies": { diff --git a/packages/snaps-ui/src/builder.test.ts b/packages/snaps-ui/src/builder.test.ts new file mode 100644 index 0000000000..77e2533b5f --- /dev/null +++ b/packages/snaps-ui/src/builder.test.ts @@ -0,0 +1,146 @@ +import { divider, heading, panel, spacer, spinner, text } from './builder'; +import { NodeType } from './nodes'; + +describe('divider', () => { + it('creates a divider component', () => { + expect(divider()).toStrictEqual({ + type: NodeType.Divider, + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => divider({ type: NodeType.Heading })).toThrow( + 'Invalid divider component: At path: type -- Expected the literal `"divider"`, but received: "heading".', + ); + }); +}); + +describe('heading', () => { + it('creates a heading component', () => { + expect(heading({ text: 'Hello, world!' })).toStrictEqual({ + type: NodeType.Heading, + text: 'Hello, world!', + }); + + expect(heading({ text: 'foo bar' })).toStrictEqual({ + type: NodeType.Heading, + text: 'foo bar', + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => heading({ type: NodeType.Divider })).toThrow( + 'Invalid heading component: At path: type -- Expected the literal `"heading"`, but received: "divider".', + ); + + // @ts-expect-error - Invalid args. + expect(() => heading({})).toThrow( + 'Invalid heading component: At path: text -- Expected a string, but received: undefined.', + ); + }); +}); + +describe('panel', () => { + it('creates a panel component', () => { + expect( + panel({ children: [heading({ text: 'Hello, world!' })] }), + ).toStrictEqual({ + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }); + + expect( + panel({ + children: [panel({ children: [heading({ text: 'Hello, world!' })] })], + }), + ).toStrictEqual({ + type: NodeType.Panel, + children: [ + { + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }, + ], + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => panel({ type: NodeType.Divider })).toThrow( + 'Invalid panel component: At path: type -- Expected the literal `"panel"`, but received: "divider".', + ); + + // @ts-expect-error - Invalid args. + expect(() => panel({})).toThrow( + 'Invalid panel component: At path: children -- Expected an array value, but received: undefined.', + ); + }); +}); + +describe('spacer', () => { + it('creates a spacer component', () => { + expect(spacer()).toStrictEqual({ + type: NodeType.Spacer, + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => spacer({ type: NodeType.Divider })).toThrow( + 'Invalid spacer component: At path: type -- Expected the literal `"spacer"`, but received: "divider".', + ); + }); +}); + +describe('spinner', () => { + it('creates a spinner component', () => { + expect(spinner()).toStrictEqual({ + type: NodeType.Spinner, + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => spinner({ type: NodeType.Divider })).toThrow( + 'Invalid spinner component: At path: type -- Expected the literal `"spinner"`, but received: "divider".', + ); + }); +}); + +describe('text', () => { + it('creates a text component', () => { + expect(text({ text: 'Hello, world!' })).toStrictEqual({ + type: NodeType.Text, + text: 'Hello, world!', + }); + + expect(text({ text: 'foo bar' })).toStrictEqual({ + type: NodeType.Text, + text: 'foo bar', + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => text({ type: NodeType.Divider })).toThrow( + 'Invalid text component: At path: type -- Expected the literal `"text"`, but received: "divider".', + ); + + // @ts-expect-error - Invalid args. + expect(() => text({})).toThrow( + 'Invalid text component: At path: text -- Expected a string, but received: undefined.', + ); + }); +}); diff --git a/packages/snaps-ui/src/builder.ts b/packages/snaps-ui/src/builder.ts new file mode 100644 index 0000000000..637441277d --- /dev/null +++ b/packages/snaps-ui/src/builder.ts @@ -0,0 +1,121 @@ +import { Struct } from 'superstruct'; +import { assertStruct } from '@metamask/utils'; +import { + Component, + DividerStruct, + HeadingStruct, + NodeType, + PanelStruct, + SpacerStruct, + SpinnerStruct, + TextStruct, +} from './nodes'; + +/** + * A function that builds a {@link Component}. This infers the proper args type + * from the given node. + */ +type NodeBuilder = Omit extends Record + ? (args?: undefined) => Node + : (args: Omit) => Node; + +/** + * A function that returns a function to "build" a {@link Component}. It infers + * the type of the component from the given struct, and performs validation on + * the created component. + * + * @param type - The type of the component to build. + * @param struct - The struct to use to validate the component. + * @returns A function that builds a component of the given type. + */ +function createBuilder( + type: NodeType, + struct: Struct, +): NodeBuilder { + return (args: Omit | undefined) => { + const node = { type, ...args }; + + // The user could be passing invalid values to the builder, so we need to + // validate them as per the component's struct. + assertStruct(node, struct, `Invalid ${type} component`); + return node; + }; +} + +/** + * Create a {@link Divider} node. + * + * @returns The divider node as object. + * @example + * ```typescript + * const node = divider(); + * ``` + */ +export const divider = createBuilder(NodeType.Divider, DividerStruct); + +/** + * Create a {@link Heading} node. + * + * @param args - The node arguments. + * @param args.text - The heading text. + * @returns The heading node as object. + * @example + * ```typescript + * const node = heading({ text: 'Hello world!' }); + * ``` + */ +export const heading = createBuilder(NodeType.Heading, HeadingStruct); + +/** + * Create a {@link Panel} node. + * + * @param args - The node arguments. + * @param args.children - The child nodes of the panel. This can be any valid + * {@link Component}. + * @returns The panel node as object. + * @example + * ```typescript + * const node = panel({ + * children: [ + * heading({ text: 'Hello, world!' }), + * text({ text: 'This is a panel.' }), + * ], + * }); + * ``` + */ +export const panel = createBuilder(NodeType.Panel, PanelStruct); + +/** + * Create a {@link Spacer} node. + * + * @returns The spacer node as object. + * @example + * ```typescript + * const node = spacer(); + * ``` + */ +export const spacer = createBuilder(NodeType.Spacer, SpacerStruct); + +/** + * Create a {@link Spinner} node. + * + * @returns The spinner node as object. + * @example + * ```typescript + * const node = spinner(); + * ``` + */ +export const spinner = createBuilder(NodeType.Spinner, SpinnerStruct); + +/** + * Create a {@link Text} node. + * + * @param args - The node arguments. + * @param args.text - The text content of the node. + * @returns The text node as object. + * @example + * ```typescript + * const node = text({ text: 'Hello, world!' }); + * ``` + */ +export const text = createBuilder(NodeType.Text, TextStruct); diff --git a/packages/snaps-ui/src/index.ts b/packages/snaps-ui/src/index.ts index a3f87b79c6..1fab305a3c 100644 --- a/packages/snaps-ui/src/index.ts +++ b/packages/snaps-ui/src/index.ts @@ -1,2 +1,3 @@ +export * from './builder'; export * from './nodes'; export * from './validation'; diff --git a/packages/snaps-ui/src/validation.test.ts b/packages/snaps-ui/src/validation.test.ts index 27b955b8f3..0c96154a9b 100644 --- a/packages/snaps-ui/src/validation.test.ts +++ b/packages/snaps-ui/src/validation.test.ts @@ -7,7 +7,7 @@ import { Spinner, Text, } from './nodes'; -import { isComponent } from './validation'; +import { assertIsComponent, isComponent } from './validation'; describe('isComponent', () => { it('returns true for a divider component', () => { @@ -103,3 +103,98 @@ describe('isComponent', () => { expect(isComponent(value)).toBe(false); }); }); + +describe('assertIsComponent', () => { + it('does not throw for a divider component', () => { + const divider: Divider = { + type: NodeType.Divider, + }; + + expect(() => assertIsComponent(divider)).not.toThrow(); + }); + + it('does not throw for a heading component', () => { + const heading: Heading = { + type: NodeType.Heading, + text: 'Hello, world!', + }; + + expect(() => assertIsComponent(heading)).not.toThrow(); + }); + + it('does not throw for a panel component', () => { + const panel: Panel = { + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }; + + expect(() => assertIsComponent(panel)).not.toThrow(); + }); + + it('does not throw for nested panels', () => { + const panel: Panel = { + type: NodeType.Panel, + children: [ + { + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }, + ], + }; + + expect(() => assertIsComponent(panel)).not.toThrow(); + }); + + it('does not throw for a spacer component', () => { + const spacer: Spacer = { + type: NodeType.Spacer, + }; + + expect(() => assertIsComponent(spacer)).not.toThrow(); + }); + + it('does not throw for a spinner component', () => { + const spinner: Spinner = { + type: NodeType.Spinner, + }; + + expect(() => assertIsComponent(spinner)).not.toThrow(); + }); + + it('does not throw for a text component', () => { + const text: Text = { + type: NodeType.Text, + text: 'Hello, world!', + }; + + expect(() => assertIsComponent(text)).not.toThrow(); + }); + + it.each([ + true, + false, + null, + undefined, + 0, + 1, + '', + 'Hello, world!', + {}, + { type: NodeType.Heading }, + { type: NodeType.Heading, foo: 'bar' }, + { type: NodeType.Heading, text: 0 }, + { type: 'foo' }, + ])(`throws for %p`, (value) => { + expect(() => assertIsComponent(value)).toThrow('Invalid component:'); + }); +}); diff --git a/packages/snaps-ui/src/validation.ts b/packages/snaps-ui/src/validation.ts index 08b0dc347c..9206b60bf8 100644 --- a/packages/snaps-ui/src/validation.ts +++ b/packages/snaps-ui/src/validation.ts @@ -1,5 +1,6 @@ import { is } from 'superstruct'; +import { assertStruct } from '@metamask/utils'; import { Component, ComponentStruct } from './nodes'; /** @@ -12,3 +13,14 @@ import { Component, ComponentStruct } from './nodes'; export function isComponent(value: unknown): value is Component { return is(value, ComponentStruct); } + +/** + * Assert that the given value is a {@link Component}. This performs recursive + * validation of the component's children (if any). + * + * @param value - The value to check. + * @throws If the value is not a {@link Component}. + */ +export function assertIsComponent(value: unknown): asserts value is Component { + assertStruct(value, ComponentStruct, 'Invalid component'); +} diff --git a/yarn.lock b/yarn.lock index 4c21815521..fae0578ba8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3200,6 +3200,7 @@ __metadata: "@metamask/eslint-config-jest": ^9.0.0 "@metamask/eslint-config-nodejs": ^9.0.0 "@metamask/eslint-config-typescript": ^9.0.1 + "@metamask/utils": ^3.3.1 "@types/jest": ^27.5.1 "@types/semver": ^7.3.10 deepmerge: ^4.2.2 From fdebd4c5ae3ca777a1ae9ef45b3b97cf817cf937 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Mon, 21 Nov 2022 18:13:32 +0100 Subject: [PATCH 05/13] Update yarn.lock --- yarn.lock | 369 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 343 insertions(+), 26 deletions(-) diff --git a/yarn.lock b/yarn.lock index fae0578ba8..57c6ffb279 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,15 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:7.12.11": + version: 7.12.11 + resolution: "@babel/code-frame@npm:7.12.11" + dependencies: + "@babel/highlight": ^7.10.4 + checksum: 3963eff3ebfb0e091c7e6f99596ef4b258683e4ba8a134e4e95f77afe85be5c931e184fff6435fb4885d12eba04a5e25532f7fbc292ca13b48e7da943474e2f3 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -332,7 +341,7 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.18.6": +"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" dependencies: @@ -1361,6 +1370,17 @@ __metadata: languageName: node linkType: hard +"@es-joy/jsdoccomment@npm:0.10.8": + version: 0.10.8 + resolution: "@es-joy/jsdoccomment@npm:0.10.8" + dependencies: + comment-parser: 1.2.4 + esquery: ^1.4.0 + jsdoc-type-pratt-parser: 1.1.1 + checksum: 3e144ef393459a541b64f6c9c8e62fb6d9b47e1a2c626410487ede12c472064f6ce6e0911df60b42ccf126d5a66102707eef59ca14767cb7aeb5e608b227558d + languageName: node + linkType: hard + "@es-joy/jsdoccomment@npm:~0.36.0": version: 0.36.0 resolution: "@es-joy/jsdoccomment@npm:0.36.0" @@ -1372,6 +1392,23 @@ __metadata: languageName: node linkType: hard +"@eslint/eslintrc@npm:^0.4.3": + version: 0.4.3 + resolution: "@eslint/eslintrc@npm:0.4.3" + dependencies: + ajv: ^6.12.4 + debug: ^4.1.1 + espree: ^7.3.0 + globals: ^13.9.0 + ignore: ^4.0.6 + import-fresh: ^3.2.1 + js-yaml: ^3.13.1 + minimatch: ^3.0.4 + strip-json-comments: ^3.1.1 + checksum: 03a7704150b868c318aab6a94d87a33d30dc2ec579d27374575014f06237ba1370ae11178db772f985ef680d469dc237e7b16a1c5d8edaaeb8c3733e7a95a6d3 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^1.3.3": version: 1.3.3 resolution: "@eslint/eslintrc@npm:1.3.3" @@ -2137,6 +2174,17 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/config-array@npm:^0.5.0": + version: 0.5.0 + resolution: "@humanwhocodes/config-array@npm:0.5.0" + dependencies: + "@humanwhocodes/object-schema": ^1.2.0 + debug: ^4.1.1 + minimatch: ^3.0.4 + checksum: 44ee6a9f05d93dd9d5935a006b17572328ba9caff8002442f601736cbda79c580cc0f5a49ce9eb88fbacc5c3a6b62098357c2e95326cd17bb9f1a6c61d6e95e7 + languageName: node + linkType: hard + "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -2144,7 +2192,7 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^1.2.1": +"@humanwhocodes/object-schema@npm:^1.2.0, @humanwhocodes/object-schema@npm:^1.2.1": version: 1.2.1 resolution: "@humanwhocodes/object-schema@npm:1.2.1" checksum: a824a1ec31591231e4bad5787641f59e9633827d0a2eaae131a288d33c9ef0290bd16fda8da6f7c0fcb014147865d12118df10db57f27f41e20da92369fcb3f1 @@ -3904,7 +3952,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d @@ -4176,6 +4224,22 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/experimental-utils@npm:^4.0.1": + version: 4.33.0 + resolution: "@typescript-eslint/experimental-utils@npm:4.33.0" + dependencies: + "@types/json-schema": ^7.0.7 + "@typescript-eslint/scope-manager": 4.33.0 + "@typescript-eslint/types": 4.33.0 + "@typescript-eslint/typescript-estree": 4.33.0 + eslint-scope: ^5.1.1 + eslint-utils: ^3.0.0 + peerDependencies: + eslint: "*" + checksum: f859800ada0884f92db6856f24efcb1d073ac9883ddc2b1aa9339f392215487895bed8447ebce3741e8141bb32e545244abef62b73193ba9a8a0527c523aabae + languageName: node + linkType: hard + "@typescript-eslint/parser@npm:^5.42.1": version: 5.43.0 resolution: "@typescript-eslint/parser@npm:5.43.0" @@ -4193,6 +4257,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:4.33.0": + version: 4.33.0 + resolution: "@typescript-eslint/scope-manager@npm:4.33.0" + dependencies: + "@typescript-eslint/types": 4.33.0 + "@typescript-eslint/visitor-keys": 4.33.0 + checksum: 9a25fb7ba7c725ea7227a24d315b0f6aacbad002e2549a049edf723c1d3615c22f5c301f0d7d615b377f2cdf2f3519d97e79af0c459de6ef8d2aaf0906dff13e + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/scope-manager@npm:5.43.0" @@ -4220,6 +4294,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:4.33.0": + version: 4.33.0 + resolution: "@typescript-eslint/types@npm:4.33.0" + checksum: 3baae1ca35872421b4eb60f5d3f3f32dc1d513f2ae0a67dee28c7d159fd7a43ed0d11a8a5a0f0c2d38507ffa036fc7c511cb0f18a5e8ac524b3ebde77390ec53 + languageName: node + linkType: hard + "@typescript-eslint/types@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/types@npm:5.43.0" @@ -4227,6 +4308,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:4.33.0": + version: 4.33.0 + resolution: "@typescript-eslint/typescript-estree@npm:4.33.0" + dependencies: + "@typescript-eslint/types": 4.33.0 + "@typescript-eslint/visitor-keys": 4.33.0 + debug: ^4.3.1 + globby: ^11.0.3 + is-glob: ^4.0.1 + semver: ^7.3.5 + tsutils: ^3.21.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 2566984390c76bd95f43240057215c068c69769e406e27aba41e9f21fd300074d6772e4983fa58fe61e80eb5550af1548d2e31e80550d92ba1d051bb00fe6f5c + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/typescript-estree@npm:5.43.0" @@ -4263,6 +4362,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:4.33.0": + version: 4.33.0 + resolution: "@typescript-eslint/visitor-keys@npm:4.33.0" + dependencies: + "@typescript-eslint/types": 4.33.0 + eslint-visitor-keys: ^2.0.0 + checksum: 59953e474ad4610c1aa23b2b1a964445e2c6201521da6367752f37939d854352bbfced5c04ea539274065e012b1337ba3ffa49c2647a240a4e87155378ba9873 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/visitor-keys@npm:5.43.0" @@ -4550,7 +4659,7 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.3.2": +"acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -4584,7 +4693,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^7.0.0, acorn@npm:^7.1.1": +"acorn@npm:^7.0.0, acorn@npm:^7.1.1, acorn@npm:^7.4.0": version: 7.4.1 resolution: "acorn@npm:7.4.1" bin: @@ -4692,15 +4801,15 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.8.0": - version: 8.11.0 - resolution: "ajv@npm:8.11.0" +"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.8.0": + version: 8.11.2 + resolution: "ajv@npm:8.11.2" dependencies: fast-deep-equal: ^3.1.1 json-schema-traverse: ^1.0.0 require-from-string: ^2.0.2 uri-js: ^4.2.2 - checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + checksum: 53435bf79ee7d1eabba8085962dba4c08d08593334b304db7772887f0b7beebc1b3d957432f7437ed4b60e53b5d966a57b439869890209c50fed610459999e3e languageName: node linkType: hard @@ -4713,10 +4822,10 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:^4.1.0": - version: 4.1.1 - resolution: "ansi-colors@npm:4.1.1" - checksum: 138d04a51076cb085da0a7e2d000c5c0bb09f6e772ed5c65c53cb118d37f6c5f1637506d7155fb5f330f0abcf6f12fa2e489ac3f8cdab9da393bf1bb4f9a32b0 +"ansi-colors@npm:^4.1.0, ansi-colors@npm:^4.1.1": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: a9c2ec842038a1fabc7db9ece7d3177e2fe1c5dc6f0c51ecfbf5f39911427b89c00b5dc6b8bd95f82a26e9b16aaae2e83d45f060e98070ce4d1333038edceb0e languageName: node linkType: hard @@ -6580,6 +6689,13 @@ __metadata: languageName: node linkType: hard +"comment-parser@npm:1.2.4": + version: 1.2.4 + resolution: "comment-parser@npm:1.2.4" + checksum: 36ac280bce4c472fac22b3ec4d8aebb4d3d7c22c6808c70174f4deabee3b82144db66f8bd61eca9c514a6d0f12f6087ddab99e7d531e660d0da793b4730fd445 + languageName: node + linkType: hard + "comment-parser@npm:1.3.1": version: 1.3.1 resolution: "comment-parser@npm:1.3.1" @@ -7014,7 +7130,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -7535,6 +7651,15 @@ __metadata: languageName: node linkType: hard +"enquirer@npm:^2.3.5": + version: 2.3.6 + resolution: "enquirer@npm:2.3.6" + dependencies: + ansi-colors: ^4.1.1 + checksum: 1c0911e14a6f8d26721c91e01db06092a5f7675159f0261d69c403396a385afd13dd76825e7678f66daffa930cfaa8d45f506fb35f818a2788463d022af1b884 + languageName: node + linkType: hard + "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -7805,7 +7930,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.5.0": +"eslint-config-prettier@npm:^8.3.0, eslint-config-prettier@npm:^8.5.0": version: 8.5.0 resolution: "eslint-config-prettier@npm:8.5.0" peerDependencies: @@ -7850,7 +7975,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.26.0": +"eslint-plugin-import@npm:^2.23.4, eslint-plugin-import@npm:^2.26.0": version: 2.26.0 resolution: "eslint-plugin-import@npm:2.26.0" dependencies: @@ -7873,6 +7998,21 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-jest@npm:^24.4.0": + version: 24.7.0 + resolution: "eslint-plugin-jest@npm:24.7.0" + dependencies: + "@typescript-eslint/experimental-utils": ^4.0.1 + peerDependencies: + "@typescript-eslint/eslint-plugin": ">= 4" + eslint: ">=5" + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + checksum: a4056582825ab3359d2e0e3aae50518f6f867d1cfb3240496605247d3ff9c84b4164f1a7e1f7087d5a2eae1343d738ada1ba74c422b13ad20b737601dc47ae08 + languageName: node + linkType: hard + "eslint-plugin-jest@npm:^27.1.5": version: 27.1.5 resolution: "eslint-plugin-jest@npm:27.1.5" @@ -7890,6 +8030,25 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-jsdoc@npm:^36.1.0": + version: 36.1.1 + resolution: "eslint-plugin-jsdoc@npm:36.1.1" + dependencies: + "@es-joy/jsdoccomment": 0.10.8 + comment-parser: 1.2.4 + debug: ^4.3.2 + esquery: ^1.4.0 + jsdoc-type-pratt-parser: ^1.1.1 + lodash: ^4.17.21 + regextras: ^0.8.0 + semver: ^7.3.5 + spdx-expression-parse: ^3.0.1 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 + checksum: ceaca9f5c39dbbab4cbb2f302e787d1581919273b4a25adca9d1b6d83fbca66a0e58da024bc1f3f21c8710f473745a297cb2a1fe5ed56c9d1f16967995293620 + languageName: node + linkType: hard + "eslint-plugin-jsdoc@npm:^39.6.2": version: 39.6.2 resolution: "eslint-plugin-jsdoc@npm:39.6.2" @@ -7923,6 +8082,21 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-prettier@npm:^3.4.0": + version: 3.4.1 + resolution: "eslint-plugin-prettier@npm:3.4.1" + dependencies: + prettier-linter-helpers: ^1.0.0 + peerDependencies: + eslint: ">=5.0.0" + prettier: ">=1.13.0" + peerDependenciesMeta: + eslint-config-prettier: + optional: true + checksum: fa6a89f0d7cba1cc87064352f5a4a68dc3739448dd279bec2bced1bfa3b704467e603d13b69dcec853f8fa30b286b8b715912898e9da776e1b016cf0ee48bd99 + languageName: node + linkType: hard + "eslint-plugin-prettier@npm:^4.2.1": version: 4.2.1 resolution: "eslint-plugin-prettier@npm:4.2.1" @@ -7958,7 +8132,7 @@ __metadata: languageName: node linkType: hard -"eslint-utils@npm:^2.0.0": +"eslint-utils@npm:^2.0.0, eslint-utils@npm:^2.1.0": version: 2.1.0 resolution: "eslint-utils@npm:2.1.0" dependencies: @@ -7978,7 +8152,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^1.1.0": +"eslint-visitor-keys@npm:^1.1.0, eslint-visitor-keys@npm:^1.3.0": version: 1.3.0 resolution: "eslint-visitor-keys@npm:1.3.0" checksum: 37a19b712f42f4c9027e8ba98c2b06031c17e0c0a4c696cd429bd9ee04eb43889c446f2cd545e1ff51bef9593fcec94ecd2c2ef89129fcbbf3adadbef520376a @@ -7999,6 +8173,56 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^7.30.0": + version: 7.32.0 + resolution: "eslint@npm:7.32.0" + dependencies: + "@babel/code-frame": 7.12.11 + "@eslint/eslintrc": ^0.4.3 + "@humanwhocodes/config-array": ^0.5.0 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.0.1 + doctrine: ^3.0.0 + enquirer: ^2.3.5 + escape-string-regexp: ^4.0.0 + eslint-scope: ^5.1.1 + eslint-utils: ^2.1.0 + eslint-visitor-keys: ^2.0.0 + espree: ^7.3.1 + esquery: ^1.4.0 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + functional-red-black-tree: ^1.0.1 + glob-parent: ^5.1.2 + globals: ^13.6.0 + ignore: ^4.0.6 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + js-yaml: ^3.13.1 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.0.4 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + progress: ^2.0.0 + regexpp: ^3.1.0 + semver: ^7.2.1 + strip-ansi: ^6.0.0 + strip-json-comments: ^3.1.0 + table: ^6.0.9 + text-table: ^0.2.0 + v8-compile-cache: ^2.0.3 + bin: + eslint: bin/eslint.js + checksum: cc85af9985a3a11085c011f3d27abe8111006d34cc274291b3c4d7bea51a4e2ff6135780249becd919ba7f6d6d1ecc38a6b73dacb6a7be08d38453b344dc8d37 + languageName: node + linkType: hard + "eslint@npm:^8.27.0": version: 8.27.0 resolution: "eslint@npm:8.27.0" @@ -8048,6 +8272,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^7.3.0, espree@npm:^7.3.1": + version: 7.3.1 + resolution: "espree@npm:7.3.1" + dependencies: + acorn: ^7.4.0 + acorn-jsx: ^5.3.1 + eslint-visitor-keys: ^1.3.0 + checksum: aa9b50dcce883449af2e23bc2b8d9abb77118f96f4cb313935d6b220f77137eaef7724a83c3f6243b96bc0e4ab14766198e60818caad99f9519ae5a336a39b45 + languageName: node + linkType: hard + "espree@npm:^9.4.0": version: 9.4.1 resolution: "espree@npm:9.4.1" @@ -9668,12 +9903,12 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.17.0 - resolution: "globals@npm:13.17.0" +"globals@npm:^13.15.0, globals@npm:^13.6.0, globals@npm:^13.9.0": + version: 13.18.0 + resolution: "globals@npm:13.18.0" dependencies: type-fest: ^0.20.2 - checksum: fbaf4112e59b92c9f5575e85ce65e9e17c0b82711196ec5f58beb08599bbd92fd72703d6dfc9b080381fd35b644e1b11dcf25b38cc2341ec21df942594cbc8ce + checksum: 9fdaa74cfd5d4ac91319662f512c29b11d1d2deb9c8a20d3998097671deba83d195f20730b2345887de3ddab958a6fa68952feed9ae836ee4594a82ace62fdb4 languageName: node linkType: hard @@ -9693,7 +9928,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.1.0": +"globby@npm:^11.0.3, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -10151,6 +10386,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^4.0.6": + version: 4.0.6 + resolution: "ignore@npm:4.0.6" + checksum: 248f82e50a430906f9ee7f35e1158e3ec4c3971451dd9f99c9bc1548261b4db2b99709f60ac6c6cac9333494384176cc4cc9b07acbe42d52ac6a09cad734d800 + languageName: node + linkType: hard + "ignore@npm:^5.1.1, ignore@npm:^5.1.9, ignore@npm:^5.2.0": version: 5.2.0 resolution: "ignore@npm:5.2.0" @@ -11548,6 +11790,20 @@ __metadata: languageName: node linkType: hard +"jsdoc-type-pratt-parser@npm:1.1.1": + version: 1.1.1 + resolution: "jsdoc-type-pratt-parser@npm:1.1.1" + checksum: 90522d1da193e1280c3e041561de20cb2f580dd823ad60f5c08e8f429dacc2e944259ed682c98c62d32f3fc8148a79becb47a47455a8093cebb5377b1c2ecbf2 + languageName: node + linkType: hard + +"jsdoc-type-pratt-parser@npm:^1.1.1": + version: 1.2.0 + resolution: "jsdoc-type-pratt-parser@npm:1.2.0" + checksum: 8be7a0e1373ad7662edd2e0f804dda462e9c56c9297aa722466ebe4bfd8c96ecdc5ce42ca91e0ca97ff6cc5c7ca730a9f5a0bc6939d7695559f0a8b95e3a2ca2 + languageName: node + linkType: hard + "jsdoc-type-pratt-parser@npm:~3.1.0": version: 3.1.0 resolution: "jsdoc-type-pratt-parser@npm:3.1.0" @@ -12139,6 +12395,13 @@ __metadata: languageName: node linkType: hard +"lodash.truncate@npm:^4.4.2": + version: 4.4.2 + resolution: "lodash.truncate@npm:4.4.2" + checksum: b463d8a382cfb5f0e71c504dcb6f807a7bd379ff1ea216669aa42c52fc28c54e404bfbd96791aa09e6df0de2c1d7b8f1b7f4b1a61f324d38fe98bc535aeee4f5 + languageName: node + linkType: hard + "lodash@npm:^4.17.14, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -13759,7 +14022,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.7.1": +"prettier@npm:^2.3.2, prettier@npm:^2.7.1": version: 2.7.1 resolution: "prettier@npm:2.7.1" bin: @@ -13844,6 +14107,13 @@ __metadata: languageName: node linkType: hard +"progress@npm:^2.0.0": + version: 2.0.3 + resolution: "progress@npm:2.0.3" + checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7 + languageName: node + linkType: hard + "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -14288,7 +14558,7 @@ __metadata: languageName: node linkType: hard -"regexpp@npm:^3.0.0, regexpp@npm:^3.2.0": +"regexpp@npm:^3.0.0, regexpp@npm:^3.1.0, regexpp@npm:^3.2.0": version: 3.2.0 resolution: "regexpp@npm:3.2.0" checksum: a78dc5c7158ad9ddcfe01aa9144f46e192ddbfa7b263895a70a5c6c73edd9ce85faf7c0430e59ac38839e1734e275b9c3de5c57ee3ab6edc0e0b1bdebefccef8 @@ -14309,6 +14579,13 @@ __metadata: languageName: node linkType: hard +"regextras@npm:^0.8.0": + version: 0.8.0 + resolution: "regextras@npm:0.8.0" + checksum: b7ec5b32a2b98b4b27048d44f8ab90009873c1307f2cf89321aa8c4cbb8147f1bee07863f4dadf585546ca0b91a234ad9804954dea5fc029421f6c25a4523798 + languageName: node + linkType: hard + "regjsgen@npm:^0.6.0": version: 0.6.0 resolution: "regjsgen@npm:0.6.0" @@ -14908,7 +15185,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": +"semver@npm:7.x, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": version: 7.3.8 resolution: "semver@npm:7.3.8" dependencies: @@ -15845,6 +16122,19 @@ __metadata: languageName: node linkType: hard +"table@npm:^6.0.9": + version: 6.8.1 + resolution: "table@npm:6.8.1" + dependencies: + ajv: ^8.0.1 + lodash.truncate: ^4.4.2 + slice-ansi: ^4.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + checksum: 08249c7046125d9d0a944a6e96cfe9ec66908d6b8a9db125531be6eb05fa0de047fd5542e9d43b4f987057f00a093b276b8d3e19af162a9c40db2681058fd306 + languageName: node + linkType: hard + "tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0": version: 2.2.1 resolution: "tapable@npm:2.2.1" @@ -16418,6 +16708,16 @@ __metadata: languageName: unknown linkType: soft +"typescript@npm:^4.4.0": + version: 4.9.3 + resolution: "typescript@npm:4.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 17b8f816050b412403e38d48eef0e893deb6be522d6dc7caf105e54a72e34daf6835c447735fd2b28b66784e72bfbf87f627abb4818a8e43d1fa8106396128dc + languageName: node + linkType: hard + "typescript@npm:~4.8.4": version: 4.8.4 resolution: "typescript@npm:4.8.4" @@ -16428,6 +16728,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@^4.4.0#~builtin": + version: 4.9.3 + resolution: "typescript@patch:typescript@npm%3A4.9.3#~builtin::version=4.9.3&hash=7ad353" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: ef65c22622d864497d0a0c5db693523329b3284c15fe632e93ad9aa059e8dc38ef3bd767d6f26b1e5ecf9446f49bd0f6c4e5714a2eeaf352805dc002479843d1 + languageName: node + linkType: hard + "typescript@patch:typescript@~4.8.4#~builtin": version: 4.8.4 resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=7ad353" @@ -16724,6 +17034,13 @@ __metadata: languageName: node linkType: hard +"v8-compile-cache@npm:^2.0.3": + version: 2.3.0 + resolution: "v8-compile-cache@npm:2.3.0" + checksum: adb0a271eaa2297f2f4c536acbfee872d0dd26ec2d76f66921aa7fc437319132773483344207bdbeee169225f4739016d8d2dbf0553913a52bb34da6d0334f8e + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.1": version: 9.0.1 resolution: "v8-to-istanbul@npm:9.0.1" From d6e3d116324b344d297ad0d918445bca9a055420 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 13:23:15 +0100 Subject: [PATCH 06/13] Fix dependencies --- packages/snaps-ui/.eslintrc.js | 4 + packages/snaps-ui/package.json | 26 ++- packages/snaps-ui/src/builder.ts | 3 +- yarn.lock | 377 +++---------------------------- 4 files changed, 51 insertions(+), 359 deletions(-) diff --git a/packages/snaps-ui/.eslintrc.js b/packages/snaps-ui/.eslintrc.js index 5a2cc7f1ec..a47fd0b65d 100644 --- a/packages/snaps-ui/.eslintrc.js +++ b/packages/snaps-ui/.eslintrc.js @@ -1,3 +1,7 @@ module.exports = { extends: ['../../.eslintrc.js'], + + parserOptions: { + tsconfigRootDir: __dirname, + }, }; diff --git a/packages/snaps-ui/package.json b/packages/snaps-ui/package.json index cfb583f362..6f572f3e17 100644 --- a/packages/snaps-ui/package.json +++ b/packages/snaps-ui/package.json @@ -30,27 +30,29 @@ "devDependencies": { "@lavamoat/allow-scripts": "^2.0.3", "@metamask/auto-changelog": "^2.6.0", - "@metamask/eslint-config": "^9.0.0", - "@metamask/eslint-config-jest": "^9.0.0", - "@metamask/eslint-config-nodejs": "^9.0.0", - "@metamask/eslint-config-typescript": "^9.0.1", + "@metamask/eslint-config": "^11.0.0", + "@metamask/eslint-config-jest": "^11.0.0", + "@metamask/eslint-config-nodejs": "^11.0.1", + "@metamask/eslint-config-typescript": "^11.0.0", "@types/jest": "^27.5.1", "@types/semver": "^7.3.10", + "@typescript-eslint/eslint-plugin": "^5.42.1", + "@typescript-eslint/parser": "^5.42.1", "deepmerge": "^4.2.2", - "eslint": "^7.30.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.23.4", - "eslint-plugin-jest": "^24.4.0", - "eslint-plugin-jsdoc": "^36.1.0", + "eslint": "^8.27.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^27.1.5", + "eslint-plugin-jsdoc": "^39.6.2", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-prettier": "^4.2.1", "jest": "^29.0.2", "jest-it-up": "^2.0.0", - "prettier": "^2.3.2", + "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.2.11", "rimraf": "^3.0.2", "ts-jest": "^29.0.0", - "typescript": "^4.4.0" + "typescript": "~4.8.4" }, "engines": { "node": ">=16.0.0" diff --git a/packages/snaps-ui/src/builder.ts b/packages/snaps-ui/src/builder.ts index 637441277d..dd96fcdeab 100644 --- a/packages/snaps-ui/src/builder.ts +++ b/packages/snaps-ui/src/builder.ts @@ -1,5 +1,6 @@ -import { Struct } from 'superstruct'; import { assertStruct } from '@metamask/utils'; +import { Struct } from 'superstruct'; + import { Component, DividerStruct, diff --git a/yarn.lock b/yarn.lock index 57c6ffb279..d7e6d32abf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,15 +22,6 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:7.12.11": - version: 7.12.11 - resolution: "@babel/code-frame@npm:7.12.11" - dependencies: - "@babel/highlight": ^7.10.4 - checksum: 3963eff3ebfb0e091c7e6f99596ef4b258683e4ba8a134e4e95f77afe85be5c931e184fff6435fb4885d12eba04a5e25532f7fbc292ca13b48e7da943474e2f3 - languageName: node - linkType: hard - "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -341,7 +332,7 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.18.6": +"@babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" dependencies: @@ -1370,17 +1361,6 @@ __metadata: languageName: node linkType: hard -"@es-joy/jsdoccomment@npm:0.10.8": - version: 0.10.8 - resolution: "@es-joy/jsdoccomment@npm:0.10.8" - dependencies: - comment-parser: 1.2.4 - esquery: ^1.4.0 - jsdoc-type-pratt-parser: 1.1.1 - checksum: 3e144ef393459a541b64f6c9c8e62fb6d9b47e1a2c626410487ede12c472064f6ce6e0911df60b42ccf126d5a66102707eef59ca14767cb7aeb5e608b227558d - languageName: node - linkType: hard - "@es-joy/jsdoccomment@npm:~0.36.0": version: 0.36.0 resolution: "@es-joy/jsdoccomment@npm:0.36.0" @@ -1392,23 +1372,6 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^0.4.3": - version: 0.4.3 - resolution: "@eslint/eslintrc@npm:0.4.3" - dependencies: - ajv: ^6.12.4 - debug: ^4.1.1 - espree: ^7.3.0 - globals: ^13.9.0 - ignore: ^4.0.6 - import-fresh: ^3.2.1 - js-yaml: ^3.13.1 - minimatch: ^3.0.4 - strip-json-comments: ^3.1.1 - checksum: 03a7704150b868c318aab6a94d87a33d30dc2ec579d27374575014f06237ba1370ae11178db772f985ef680d469dc237e7b16a1c5d8edaaeb8c3733e7a95a6d3 - languageName: node - linkType: hard - "@eslint/eslintrc@npm:^1.3.3": version: 1.3.3 resolution: "@eslint/eslintrc@npm:1.3.3" @@ -2174,17 +2137,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.5.0": - version: 0.5.0 - resolution: "@humanwhocodes/config-array@npm:0.5.0" - dependencies: - "@humanwhocodes/object-schema": ^1.2.0 - debug: ^4.1.1 - minimatch: ^3.0.4 - checksum: 44ee6a9f05d93dd9d5935a006b17572328ba9caff8002442f601736cbda79c580cc0f5a49ce9eb88fbacc5c3a6b62098357c2e95326cd17bb9f1a6c61d6e95e7 - languageName: node - linkType: hard - "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -2192,7 +2144,7 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^1.2.0, @humanwhocodes/object-schema@npm:^1.2.1": +"@humanwhocodes/object-schema@npm:^1.2.1": version: 1.2.1 resolution: "@humanwhocodes/object-schema@npm:1.2.1" checksum: a824a1ec31591231e4bad5787641f59e9633827d0a2eaae131a288d33c9ef0290bd16fda8da6f7c0fcb014147865d12118df10db57f27f41e20da92369fcb3f1 @@ -3244,29 +3196,31 @@ __metadata: dependencies: "@lavamoat/allow-scripts": ^2.0.3 "@metamask/auto-changelog": ^2.6.0 - "@metamask/eslint-config": ^9.0.0 - "@metamask/eslint-config-jest": ^9.0.0 - "@metamask/eslint-config-nodejs": ^9.0.0 - "@metamask/eslint-config-typescript": ^9.0.1 + "@metamask/eslint-config": ^11.0.0 + "@metamask/eslint-config-jest": ^11.0.0 + "@metamask/eslint-config-nodejs": ^11.0.1 + "@metamask/eslint-config-typescript": ^11.0.0 "@metamask/utils": ^3.3.1 "@types/jest": ^27.5.1 "@types/semver": ^7.3.10 + "@typescript-eslint/eslint-plugin": ^5.42.1 + "@typescript-eslint/parser": ^5.42.1 deepmerge: ^4.2.2 - eslint: ^7.30.0 - eslint-config-prettier: ^8.3.0 - eslint-plugin-import: ^2.23.4 - eslint-plugin-jest: ^24.4.0 - eslint-plugin-jsdoc: ^36.1.0 + eslint: ^8.27.0 + eslint-config-prettier: ^8.5.0 + eslint-plugin-import: ^2.26.0 + eslint-plugin-jest: ^27.1.5 + eslint-plugin-jsdoc: ^39.6.2 eslint-plugin-node: ^11.1.0 - eslint-plugin-prettier: ^3.4.0 + eslint-plugin-prettier: ^4.2.1 jest: ^29.0.2 jest-it-up: ^2.0.0 - prettier: ^2.3.2 + prettier: ^2.7.1 prettier-plugin-packagejson: ^2.2.11 rimraf: ^3.0.2 superstruct: ^0.16.7 ts-jest: ^29.0.0 - typescript: ^4.4.0 + typescript: ~4.8.4 languageName: unknown linkType: soft @@ -3952,7 +3906,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.5, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d @@ -4224,22 +4178,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/experimental-utils@npm:^4.0.1": - version: 4.33.0 - resolution: "@typescript-eslint/experimental-utils@npm:4.33.0" - dependencies: - "@types/json-schema": ^7.0.7 - "@typescript-eslint/scope-manager": 4.33.0 - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/typescript-estree": 4.33.0 - eslint-scope: ^5.1.1 - eslint-utils: ^3.0.0 - peerDependencies: - eslint: "*" - checksum: f859800ada0884f92db6856f24efcb1d073ac9883ddc2b1aa9339f392215487895bed8447ebce3741e8141bb32e545244abef62b73193ba9a8a0527c523aabae - languageName: node - linkType: hard - "@typescript-eslint/parser@npm:^5.42.1": version: 5.43.0 resolution: "@typescript-eslint/parser@npm:5.43.0" @@ -4257,16 +4195,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/scope-manager@npm:4.33.0" - dependencies: - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/visitor-keys": 4.33.0 - checksum: 9a25fb7ba7c725ea7227a24d315b0f6aacbad002e2549a049edf723c1d3615c22f5c301f0d7d615b377f2cdf2f3519d97e79af0c459de6ef8d2aaf0906dff13e - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/scope-manager@npm:5.43.0" @@ -4294,13 +4222,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/types@npm:4.33.0" - checksum: 3baae1ca35872421b4eb60f5d3f3f32dc1d513f2ae0a67dee28c7d159fd7a43ed0d11a8a5a0f0c2d38507ffa036fc7c511cb0f18a5e8ac524b3ebde77390ec53 - languageName: node - linkType: hard - "@typescript-eslint/types@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/types@npm:5.43.0" @@ -4308,24 +4229,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/typescript-estree@npm:4.33.0" - dependencies: - "@typescript-eslint/types": 4.33.0 - "@typescript-eslint/visitor-keys": 4.33.0 - debug: ^4.3.1 - globby: ^11.0.3 - is-glob: ^4.0.1 - semver: ^7.3.5 - tsutils: ^3.21.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 2566984390c76bd95f43240057215c068c69769e406e27aba41e9f21fd300074d6772e4983fa58fe61e80eb5550af1548d2e31e80550d92ba1d051bb00fe6f5c - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/typescript-estree@npm:5.43.0" @@ -4362,16 +4265,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:4.33.0": - version: 4.33.0 - resolution: "@typescript-eslint/visitor-keys@npm:4.33.0" - dependencies: - "@typescript-eslint/types": 4.33.0 - eslint-visitor-keys: ^2.0.0 - checksum: 59953e474ad4610c1aa23b2b1a964445e2c6201521da6367752f37939d854352bbfced5c04ea539274065e012b1337ba3ffa49c2647a240a4e87155378ba9873 - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:5.43.0": version: 5.43.0 resolution: "@typescript-eslint/visitor-keys@npm:5.43.0" @@ -4659,7 +4552,7 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.3.1, acorn-jsx@npm:^5.3.2": +"acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -4693,7 +4586,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^7.0.0, acorn@npm:^7.1.1, acorn@npm:^7.4.0": +"acorn@npm:^7.0.0, acorn@npm:^7.1.1": version: 7.4.1 resolution: "acorn@npm:7.4.1" bin: @@ -4801,7 +4694,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.8.0": version: 8.11.2 resolution: "ajv@npm:8.11.2" dependencies: @@ -4822,7 +4715,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:^4.1.0, ansi-colors@npm:^4.1.1": +"ansi-colors@npm:^4.1.0": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: a9c2ec842038a1fabc7db9ece7d3177e2fe1c5dc6f0c51ecfbf5f39911427b89c00b5dc6b8bd95f82a26e9b16aaae2e83d45f060e98070ce4d1333038edceb0e @@ -6689,13 +6582,6 @@ __metadata: languageName: node linkType: hard -"comment-parser@npm:1.2.4": - version: 1.2.4 - resolution: "comment-parser@npm:1.2.4" - checksum: 36ac280bce4c472fac22b3ec4d8aebb4d3d7c22c6808c70174f4deabee3b82144db66f8bd61eca9c514a6d0f12f6087ddab99e7d531e660d0da793b4730fd445 - languageName: node - linkType: hard - "comment-parser@npm:1.3.1": version: 1.3.1 resolution: "comment-parser@npm:1.3.1" @@ -7130,7 +7016,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -7651,15 +7537,6 @@ __metadata: languageName: node linkType: hard -"enquirer@npm:^2.3.5": - version: 2.3.6 - resolution: "enquirer@npm:2.3.6" - dependencies: - ansi-colors: ^4.1.1 - checksum: 1c0911e14a6f8d26721c91e01db06092a5f7675159f0261d69c403396a385afd13dd76825e7678f66daffa930cfaa8d45f506fb35f818a2788463d022af1b884 - languageName: node - linkType: hard - "entities@npm:^2.0.0": version: 2.2.0 resolution: "entities@npm:2.2.0" @@ -7930,7 +7807,7 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.3.0, eslint-config-prettier@npm:^8.5.0": +"eslint-config-prettier@npm:^8.5.0": version: 8.5.0 resolution: "eslint-config-prettier@npm:8.5.0" peerDependencies: @@ -7975,7 +7852,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.23.4, eslint-plugin-import@npm:^2.26.0": +"eslint-plugin-import@npm:^2.26.0": version: 2.26.0 resolution: "eslint-plugin-import@npm:2.26.0" dependencies: @@ -7998,21 +7875,6 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jest@npm:^24.4.0": - version: 24.7.0 - resolution: "eslint-plugin-jest@npm:24.7.0" - dependencies: - "@typescript-eslint/experimental-utils": ^4.0.1 - peerDependencies: - "@typescript-eslint/eslint-plugin": ">= 4" - eslint: ">=5" - peerDependenciesMeta: - "@typescript-eslint/eslint-plugin": - optional: true - checksum: a4056582825ab3359d2e0e3aae50518f6f867d1cfb3240496605247d3ff9c84b4164f1a7e1f7087d5a2eae1343d738ada1ba74c422b13ad20b737601dc47ae08 - languageName: node - linkType: hard - "eslint-plugin-jest@npm:^27.1.5": version: 27.1.5 resolution: "eslint-plugin-jest@npm:27.1.5" @@ -8030,25 +7892,6 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^36.1.0": - version: 36.1.1 - resolution: "eslint-plugin-jsdoc@npm:36.1.1" - dependencies: - "@es-joy/jsdoccomment": 0.10.8 - comment-parser: 1.2.4 - debug: ^4.3.2 - esquery: ^1.4.0 - jsdoc-type-pratt-parser: ^1.1.1 - lodash: ^4.17.21 - regextras: ^0.8.0 - semver: ^7.3.5 - spdx-expression-parse: ^3.0.1 - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 - checksum: ceaca9f5c39dbbab4cbb2f302e787d1581919273b4a25adca9d1b6d83fbca66a0e58da024bc1f3f21c8710f473745a297cb2a1fe5ed56c9d1f16967995293620 - languageName: node - linkType: hard - "eslint-plugin-jsdoc@npm:^39.6.2": version: 39.6.2 resolution: "eslint-plugin-jsdoc@npm:39.6.2" @@ -8082,21 +7925,6 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-prettier@npm:^3.4.0": - version: 3.4.1 - resolution: "eslint-plugin-prettier@npm:3.4.1" - dependencies: - prettier-linter-helpers: ^1.0.0 - peerDependencies: - eslint: ">=5.0.0" - prettier: ">=1.13.0" - peerDependenciesMeta: - eslint-config-prettier: - optional: true - checksum: fa6a89f0d7cba1cc87064352f5a4a68dc3739448dd279bec2bced1bfa3b704467e603d13b69dcec853f8fa30b286b8b715912898e9da776e1b016cf0ee48bd99 - languageName: node - linkType: hard - "eslint-plugin-prettier@npm:^4.2.1": version: 4.2.1 resolution: "eslint-plugin-prettier@npm:4.2.1" @@ -8132,7 +7960,7 @@ __metadata: languageName: node linkType: hard -"eslint-utils@npm:^2.0.0, eslint-utils@npm:^2.1.0": +"eslint-utils@npm:^2.0.0": version: 2.1.0 resolution: "eslint-utils@npm:2.1.0" dependencies: @@ -8152,7 +7980,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^1.1.0, eslint-visitor-keys@npm:^1.3.0": +"eslint-visitor-keys@npm:^1.1.0": version: 1.3.0 resolution: "eslint-visitor-keys@npm:1.3.0" checksum: 37a19b712f42f4c9027e8ba98c2b06031c17e0c0a4c696cd429bd9ee04eb43889c446f2cd545e1ff51bef9593fcec94ecd2c2ef89129fcbbf3adadbef520376a @@ -8173,56 +8001,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^7.30.0": - version: 7.32.0 - resolution: "eslint@npm:7.32.0" - dependencies: - "@babel/code-frame": 7.12.11 - "@eslint/eslintrc": ^0.4.3 - "@humanwhocodes/config-array": ^0.5.0 - ajv: ^6.10.0 - chalk: ^4.0.0 - cross-spawn: ^7.0.2 - debug: ^4.0.1 - doctrine: ^3.0.0 - enquirer: ^2.3.5 - escape-string-regexp: ^4.0.0 - eslint-scope: ^5.1.1 - eslint-utils: ^2.1.0 - eslint-visitor-keys: ^2.0.0 - espree: ^7.3.1 - esquery: ^1.4.0 - esutils: ^2.0.2 - fast-deep-equal: ^3.1.3 - file-entry-cache: ^6.0.1 - functional-red-black-tree: ^1.0.1 - glob-parent: ^5.1.2 - globals: ^13.6.0 - ignore: ^4.0.6 - import-fresh: ^3.0.0 - imurmurhash: ^0.1.4 - is-glob: ^4.0.0 - js-yaml: ^3.13.1 - json-stable-stringify-without-jsonify: ^1.0.1 - levn: ^0.4.1 - lodash.merge: ^4.6.2 - minimatch: ^3.0.4 - natural-compare: ^1.4.0 - optionator: ^0.9.1 - progress: ^2.0.0 - regexpp: ^3.1.0 - semver: ^7.2.1 - strip-ansi: ^6.0.0 - strip-json-comments: ^3.1.0 - table: ^6.0.9 - text-table: ^0.2.0 - v8-compile-cache: ^2.0.3 - bin: - eslint: bin/eslint.js - checksum: cc85af9985a3a11085c011f3d27abe8111006d34cc274291b3c4d7bea51a4e2ff6135780249becd919ba7f6d6d1ecc38a6b73dacb6a7be08d38453b344dc8d37 - languageName: node - linkType: hard - "eslint@npm:^8.27.0": version: 8.27.0 resolution: "eslint@npm:8.27.0" @@ -8272,17 +8050,6 @@ __metadata: languageName: node linkType: hard -"espree@npm:^7.3.0, espree@npm:^7.3.1": - version: 7.3.1 - resolution: "espree@npm:7.3.1" - dependencies: - acorn: ^7.4.0 - acorn-jsx: ^5.3.1 - eslint-visitor-keys: ^1.3.0 - checksum: aa9b50dcce883449af2e23bc2b8d9abb77118f96f4cb313935d6b220f77137eaef7724a83c3f6243b96bc0e4ab14766198e60818caad99f9519ae5a336a39b45 - languageName: node - linkType: hard - "espree@npm:^9.4.0": version: 9.4.1 resolution: "espree@npm:9.4.1" @@ -9903,7 +9670,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0, globals@npm:^13.6.0, globals@npm:^13.9.0": +"globals@npm:^13.15.0": version: 13.18.0 resolution: "globals@npm:13.18.0" dependencies: @@ -9928,7 +9695,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.3, globby@npm:^11.1.0": +"globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -10386,13 +10153,6 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^4.0.6": - version: 4.0.6 - resolution: "ignore@npm:4.0.6" - checksum: 248f82e50a430906f9ee7f35e1158e3ec4c3971451dd9f99c9bc1548261b4db2b99709f60ac6c6cac9333494384176cc4cc9b07acbe42d52ac6a09cad734d800 - languageName: node - linkType: hard - "ignore@npm:^5.1.1, ignore@npm:^5.1.9, ignore@npm:^5.2.0": version: 5.2.0 resolution: "ignore@npm:5.2.0" @@ -11790,20 +11550,6 @@ __metadata: languageName: node linkType: hard -"jsdoc-type-pratt-parser@npm:1.1.1": - version: 1.1.1 - resolution: "jsdoc-type-pratt-parser@npm:1.1.1" - checksum: 90522d1da193e1280c3e041561de20cb2f580dd823ad60f5c08e8f429dacc2e944259ed682c98c62d32f3fc8148a79becb47a47455a8093cebb5377b1c2ecbf2 - languageName: node - linkType: hard - -"jsdoc-type-pratt-parser@npm:^1.1.1": - version: 1.2.0 - resolution: "jsdoc-type-pratt-parser@npm:1.2.0" - checksum: 8be7a0e1373ad7662edd2e0f804dda462e9c56c9297aa722466ebe4bfd8c96ecdc5ce42ca91e0ca97ff6cc5c7ca730a9f5a0bc6939d7695559f0a8b95e3a2ca2 - languageName: node - linkType: hard - "jsdoc-type-pratt-parser@npm:~3.1.0": version: 3.1.0 resolution: "jsdoc-type-pratt-parser@npm:3.1.0" @@ -12395,13 +12141,6 @@ __metadata: languageName: node linkType: hard -"lodash.truncate@npm:^4.4.2": - version: 4.4.2 - resolution: "lodash.truncate@npm:4.4.2" - checksum: b463d8a382cfb5f0e71c504dcb6f807a7bd379ff1ea216669aa42c52fc28c54e404bfbd96791aa09e6df0de2c1d7b8f1b7f4b1a61f324d38fe98bc535aeee4f5 - languageName: node - linkType: hard - "lodash@npm:^4.17.14, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -14022,7 +13761,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.3.2, prettier@npm:^2.7.1": +"prettier@npm:^2.7.1": version: 2.7.1 resolution: "prettier@npm:2.7.1" bin: @@ -14107,13 +13846,6 @@ __metadata: languageName: node linkType: hard -"progress@npm:^2.0.0": - version: 2.0.3 - resolution: "progress@npm:2.0.3" - checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7 - languageName: node - linkType: hard - "promise-inflight@npm:^1.0.1": version: 1.0.1 resolution: "promise-inflight@npm:1.0.1" @@ -14558,7 +14290,7 @@ __metadata: languageName: node linkType: hard -"regexpp@npm:^3.0.0, regexpp@npm:^3.1.0, regexpp@npm:^3.2.0": +"regexpp@npm:^3.0.0, regexpp@npm:^3.2.0": version: 3.2.0 resolution: "regexpp@npm:3.2.0" checksum: a78dc5c7158ad9ddcfe01aa9144f46e192ddbfa7b263895a70a5c6c73edd9ce85faf7c0430e59ac38839e1734e275b9c3de5c57ee3ab6edc0e0b1bdebefccef8 @@ -14579,13 +14311,6 @@ __metadata: languageName: node linkType: hard -"regextras@npm:^0.8.0": - version: 0.8.0 - resolution: "regextras@npm:0.8.0" - checksum: b7ec5b32a2b98b4b27048d44f8ab90009873c1307f2cf89321aa8c4cbb8147f1bee07863f4dadf585546ca0b91a234ad9804954dea5fc029421f6c25a4523798 - languageName: node - linkType: hard - "regjsgen@npm:^0.6.0": version: 0.6.0 resolution: "regjsgen@npm:0.6.0" @@ -15185,7 +14910,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": +"semver@npm:7.x, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": version: 7.3.8 resolution: "semver@npm:7.3.8" dependencies: @@ -16122,19 +15847,6 @@ __metadata: languageName: node linkType: hard -"table@npm:^6.0.9": - version: 6.8.1 - resolution: "table@npm:6.8.1" - dependencies: - ajv: ^8.0.1 - lodash.truncate: ^4.4.2 - slice-ansi: ^4.0.0 - string-width: ^4.2.3 - strip-ansi: ^6.0.1 - checksum: 08249c7046125d9d0a944a6e96cfe9ec66908d6b8a9db125531be6eb05fa0de047fd5542e9d43b4f987057f00a093b276b8d3e19af162a9c40db2681058fd306 - languageName: node - linkType: hard - "tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0": version: 2.2.1 resolution: "tapable@npm:2.2.1" @@ -16708,16 +16420,6 @@ __metadata: languageName: unknown linkType: soft -"typescript@npm:^4.4.0": - version: 4.9.3 - resolution: "typescript@npm:4.9.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 17b8f816050b412403e38d48eef0e893deb6be522d6dc7caf105e54a72e34daf6835c447735fd2b28b66784e72bfbf87f627abb4818a8e43d1fa8106396128dc - languageName: node - linkType: hard - "typescript@npm:~4.8.4": version: 4.8.4 resolution: "typescript@npm:4.8.4" @@ -16728,16 +16430,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@^4.4.0#~builtin": - version: 4.9.3 - resolution: "typescript@patch:typescript@npm%3A4.9.3#~builtin::version=4.9.3&hash=7ad353" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: ef65c22622d864497d0a0c5db693523329b3284c15fe632e93ad9aa059e8dc38ef3bd767d6f26b1e5ecf9446f49bd0f6c4e5714a2eeaf352805dc002479843d1 - languageName: node - linkType: hard - "typescript@patch:typescript@~4.8.4#~builtin": version: 4.8.4 resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=7ad353" @@ -17034,13 +16726,6 @@ __metadata: languageName: node linkType: hard -"v8-compile-cache@npm:^2.0.3": - version: 2.3.0 - resolution: "v8-compile-cache@npm:2.3.0" - checksum: adb0a271eaa2297f2f4c536acbfee872d0dd26ec2d76f66921aa7fc437319132773483344207bdbeee169225f4739016d8d2dbf0553913a52bb34da6d0334f8e - languageName: node - linkType: hard - "v8-to-istanbul@npm:^9.0.1": version: 9.0.1 resolution: "v8-to-istanbul@npm:9.0.1" From f3799f2e611e133136a8dad94a18fc0f56b7e6ce Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 14:20:38 +0100 Subject: [PATCH 07/13] Add shorthand builder style --- packages/snaps-ui/src/builder.test.ts | 51 ++++++++++++++++ packages/snaps-ui/src/builder.ts | 88 ++++++++++++++++++++++----- 2 files changed, 124 insertions(+), 15 deletions(-) diff --git a/packages/snaps-ui/src/builder.test.ts b/packages/snaps-ui/src/builder.test.ts index 77e2533b5f..957ced335e 100644 --- a/packages/snaps-ui/src/builder.test.ts +++ b/packages/snaps-ui/src/builder.test.ts @@ -29,6 +29,18 @@ describe('heading', () => { }); }); + it('creates a heading component using the shorthand form', () => { + expect(heading('Hello, world!')).toStrictEqual({ + type: NodeType.Heading, + text: 'Hello, world!', + }); + + expect(heading('foo bar')).toStrictEqual({ + type: NodeType.Heading, + text: 'foo bar', + }); + }); + it('validates the args', () => { // @ts-expect-error - Invalid args. expect(() => heading({ type: NodeType.Divider })).toThrow( @@ -76,6 +88,33 @@ describe('panel', () => { }); }); + it('creates a panel component using the shorthand form', () => { + expect(panel([heading('Hello, world!')])).toStrictEqual({ + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }); + + expect(panel([panel([heading('Hello, world!')])])).toStrictEqual({ + type: NodeType.Panel, + children: [ + { + type: NodeType.Panel, + children: [ + { + type: NodeType.Heading, + text: 'Hello, world!', + }, + ], + }, + ], + }); + }); + it('validates the args', () => { // @ts-expect-error - Invalid args. expect(() => panel({ type: NodeType.Divider })).toThrow( @@ -132,6 +171,18 @@ describe('text', () => { }); }); + it('creates a text component using the shorthand form', () => { + expect(text('Hello, world!')).toStrictEqual({ + type: NodeType.Text, + text: 'Hello, world!', + }); + + expect(text('foo bar')).toStrictEqual({ + type: NodeType.Text, + text: 'foo bar', + }); + }); + it('validates the args', () => { // @ts-expect-error - Invalid args. expect(() => text({ type: NodeType.Divider })).toThrow( diff --git a/packages/snaps-ui/src/builder.ts b/packages/snaps-ui/src/builder.ts index dd96fcdeab..6a417d4597 100644 --- a/packages/snaps-ui/src/builder.ts +++ b/packages/snaps-ui/src/builder.ts @@ -1,4 +1,4 @@ -import { assertStruct } from '@metamask/utils'; +import { assertStruct, isPlainObject } from '@metamask/utils'; import { Struct } from 'superstruct'; import { @@ -16,25 +16,73 @@ import { * A function that builds a {@link Component}. This infers the proper args type * from the given node. */ -type NodeBuilder = Omit extends Record - ? (args?: undefined) => Node - : (args: Omit) => Node; +type NodeBuilder = Omit< + Node, + 'type' +> extends Record + ? (...args: []) => Node + : (...args: [Omit] | NodeArrayType) => Node; + +/** + * Map from an array of node keys to the corresponding array type. + * + * @example + * ```typescript + * type Node = { type: 'node'; a: string; b: number; c: boolean }; + * type Keys = ['a', 'b', 'c']; + * + * type NodeArray = NodeArrayType; // [string, number, boolean] + * ``` + */ +type NodeArrayType = { + [Key in keyof Keys]: Node[Keys[Key]]; +}; /** * A function that returns a function to "build" a {@link Component}. It infers * the type of the component from the given struct, and performs validation on * the created component. * + * The returned function can handle the node arguments in two ways: + * 1. As a single object, with the keys corresponding to the node's properties, + * excluding the `type` property. + * 2. As an array of arguments, with the order corresponding to the given keys. + * * @param type - The type of the component to build. * @param struct - The struct to use to validate the component. + * @param keys - The keys of the component to use as arguments to the builder. + * The order of the keys determines the order of the arguments. * @returns A function that builds a component of the given type. */ -function createBuilder( +function createBuilder< + Node extends Component, + Keys extends (keyof Node)[] = [], +>( type: NodeType, struct: Struct, -): NodeBuilder { - return (args: Omit | undefined) => { - const node = { type, ...args }; + keys: Keys = [] as unknown as Keys, +): NodeBuilder { + return (...args: [Omit] | NodeArrayType | []) => { + // Node passed as a single object. + if (args.length === 1 && isPlainObject(args[0])) { + const node = { type, ...args[0] }; + + // The user could be passing invalid values to the builder, so we need to + // validate them as per the component's struct. + assertStruct(node, struct, `Invalid ${type} component`); + return node; + } + + // Node passed as an array of arguments. + const node = keys.reduce>( + (partialNode, key, index) => { + return { + ...partialNode, + [key]: args[index], + }; + }, + { type }, + ); // The user could be passing invalid values to the builder, so we need to // validate them as per the component's struct. @@ -57,20 +105,23 @@ export const divider = createBuilder(NodeType.Divider, DividerStruct); /** * Create a {@link Heading} node. * - * @param args - The node arguments. + * @param args - The node arguments. This can either be a string, or an object + * with the `text` property. * @param args.text - The heading text. * @returns The heading node as object. * @example * ```typescript - * const node = heading({ text: 'Hello world!' }); + * const node = heading({ text: 'Hello, world!' }); + * const node = heading('Hello, world!'); * ``` */ -export const heading = createBuilder(NodeType.Heading, HeadingStruct); +export const heading = createBuilder(NodeType.Heading, HeadingStruct, ['text']); /** * Create a {@link Panel} node. * - * @param args - The node arguments. + * @param args - The node arguments. This can be either an array of children, or + * an object with a `children` property. * @param args.children - The child nodes of the panel. This can be any valid * {@link Component}. * @returns The panel node as object. @@ -82,9 +133,14 @@ export const heading = createBuilder(NodeType.Heading, HeadingStruct); * text({ text: 'This is a panel.' }), * ], * }); + * + * const node = panel([ + * heading('Hello, world!'), + * text('This is a panel.'), + * ]); * ``` */ -export const panel = createBuilder(NodeType.Panel, PanelStruct); +export const panel = createBuilder(NodeType.Panel, PanelStruct, ['children']); /** * Create a {@link Spacer} node. @@ -111,12 +167,14 @@ export const spinner = createBuilder(NodeType.Spinner, SpinnerStruct); /** * Create a {@link Text} node. * - * @param args - The node arguments. + * @param args - The node arguments. This can be either a string, or an object + * with a `text` property. * @param args.text - The text content of the node. * @returns The text node as object. * @example * ```typescript * const node = text({ text: 'Hello, world!' }); + * const node = text('Hello, world!'); * ``` */ -export const text = createBuilder(NodeType.Text, TextStruct); +export const text = createBuilder(NodeType.Text, TextStruct, ['text']); From 66f41423011d5ebf64326afe94701b28f6f7112c Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 14:27:46 +0100 Subject: [PATCH 08/13] Add reference to snaps-ui in tsconfig --- tsconfig.build.json | 1 + tsconfig.json | 1 + 2 files changed, 2 insertions(+) diff --git a/tsconfig.build.json b/tsconfig.build.json index 59375b7503..e714788657 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -8,6 +8,7 @@ { "path": "./packages/snaps-controllers/tsconfig.build.json" }, { "path": "./packages/snaps-execution-environments/tsconfig.build.json" }, { "path": "./packages/snaps-rollup-plugin/tsconfig.build.json" }, + { "path": "./packages/snaps-ui/tsconfig.build.json" }, { "path": "./packages/snaps-utils/tsconfig.build.json" }, { "path": "./packages/snaps-webpack-plugin/tsconfig.build.json" } ] diff --git a/tsconfig.json b/tsconfig.json index 338debf019..a8eec200ae 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ { "path": "./packages/snaps-controllers" }, { "path": "./packages/snaps-execution-environments" }, { "path": "./packages/snaps-rollup-plugin" }, + { "path": "./packages/snaps-ui" }, { "path": "./packages/snaps-utils" }, { "path": "./packages/snaps-webpack-plugin" } ] From ae0983b37f63f4c89e2eccd21d9f1eab693ec262 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 14:31:48 +0100 Subject: [PATCH 09/13] Fix some lint issues --- packages/snaps-ui/jest.config.js | 1 + packages/snaps-ui/src/validation.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/snaps-ui/jest.config.js b/packages/snaps-ui/jest.config.js index 58387b4ef9..dca85e9a3b 100644 --- a/packages/snaps-ui/jest.config.js +++ b/packages/snaps-ui/jest.config.js @@ -1,4 +1,5 @@ const deepmerge = require('deepmerge'); + const baseConfig = require('../../jest.config.base'); module.exports = deepmerge(baseConfig, { diff --git a/packages/snaps-ui/src/validation.ts b/packages/snaps-ui/src/validation.ts index 9206b60bf8..819ef66c3a 100644 --- a/packages/snaps-ui/src/validation.ts +++ b/packages/snaps-ui/src/validation.ts @@ -1,6 +1,6 @@ +import { assertStruct } from '@metamask/utils'; import { is } from 'superstruct'; -import { assertStruct } from '@metamask/utils'; import { Component, ComponentStruct } from './nodes'; /** From 0c4a4df4f7f21a4415b7c506115391dc12355bd2 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 18:53:00 +0100 Subject: [PATCH 10/13] Add copyable component --- packages/snaps-ui/src/builder.test.ts | 48 ++++++++++++++++++++++++++- packages/snaps-ui/src/builder.ts | 13 ++++++++ packages/snaps-ui/src/nodes.ts | 12 +++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/snaps-ui/src/builder.test.ts b/packages/snaps-ui/src/builder.test.ts index 957ced335e..dc3b43426d 100644 --- a/packages/snaps-ui/src/builder.test.ts +++ b/packages/snaps-ui/src/builder.test.ts @@ -1,6 +1,52 @@ -import { divider, heading, panel, spacer, spinner, text } from './builder'; +import { + copyable, + divider, + heading, + panel, + spacer, + spinner, + text, +} from './builder'; import { NodeType } from './nodes'; +describe('copyable', () => { + it('creates a copyable component', () => { + expect(copyable({ text: 'Hello, world!' })).toStrictEqual({ + type: NodeType.Copyable, + text: 'Hello, world!', + }); + + expect(copyable({ text: 'foo bar' })).toStrictEqual({ + type: NodeType.Copyable, + text: 'foo bar', + }); + }); + + it('creates a copyable component using the shorthand form', () => { + expect(copyable('Hello, world!')).toStrictEqual({ + type: NodeType.Copyable, + text: 'Hello, world!', + }); + + expect(copyable('foo bar')).toStrictEqual({ + type: NodeType.Copyable, + text: 'foo bar', + }); + }); + + it('validates the args', () => { + // @ts-expect-error - Invalid args. + expect(() => copyable({ type: NodeType.Divider })).toThrow( + 'Invalid copyable component: At path: type -- Expected the literal `"copyable"`, but received: "divider".', + ); + + // @ts-expect-error - Invalid args. + expect(() => copyable({})).toThrow( + 'Invalid copyable component: At path: text -- Expected a string, but received: undefined.', + ); + }); +}); + describe('divider', () => { it('creates a divider component', () => { expect(divider()).toStrictEqual({ diff --git a/packages/snaps-ui/src/builder.ts b/packages/snaps-ui/src/builder.ts index 6a417d4597..c512627ed7 100644 --- a/packages/snaps-ui/src/builder.ts +++ b/packages/snaps-ui/src/builder.ts @@ -3,6 +3,7 @@ import { Struct } from 'superstruct'; import { Component, + CopyableStruct, DividerStruct, HeadingStruct, NodeType, @@ -91,6 +92,18 @@ function createBuilder< }; } +/** + * Create a {@link Copyable} component. + * + * @param args - The node arguments. This can either be a string, or an object + * with the `text` property. + * @param args.text - The text to copy. + * @returns A {@link Copyable} component. + */ +export const copyable = createBuilder(NodeType.Copyable, CopyableStruct, [ + 'text', +]); + /** * Create a {@link Divider} node. * diff --git a/packages/snaps-ui/src/nodes.ts b/packages/snaps-ui/src/nodes.ts index 980e19ffa2..c47bbf131d 100644 --- a/packages/snaps-ui/src/nodes.ts +++ b/packages/snaps-ui/src/nodes.ts @@ -19,6 +19,7 @@ export type Node = { }; export enum NodeType { + Copyable = 'copyable', Divider = 'divider', Heading = 'heading', Panel = 'panel', @@ -27,6 +28,16 @@ export enum NodeType { Text = 'text', } +export const CopyableStruct = object({ + type: literal(NodeType.Copyable), + text: string(), +}); + +/** + * Text that can be copied to the clipboard. + */ +export type Copyable = Infer; + export const DividerStruct = object({ type: literal(NodeType.Divider), }); @@ -109,6 +120,7 @@ export const TextStruct = assign( export type Text = Infer; export const ComponentStruct = union([ + CopyableStruct, DividerStruct, HeadingStruct, PanelStruct, From dea1c87d9ed11c1ce5d44e7729793a1960ef80f4 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 19:03:17 +0100 Subject: [PATCH 11/13] Ignore specified type if any --- packages/snaps-ui/src/builder.test.ts | 28 +++++++++++++-------------- packages/snaps-ui/src/builder.ts | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/snaps-ui/src/builder.test.ts b/packages/snaps-ui/src/builder.test.ts index dc3b43426d..c0aaf39118 100644 --- a/packages/snaps-ui/src/builder.test.ts +++ b/packages/snaps-ui/src/builder.test.ts @@ -36,8 +36,8 @@ describe('copyable', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => copyable({ type: NodeType.Divider })).toThrow( - 'Invalid copyable component: At path: type -- Expected the literal `"copyable"`, but received: "divider".', + expect(() => copyable({ text: 'foo', bar: 'baz' })).toThrow( + 'Invalid copyable component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); // @ts-expect-error - Invalid args. @@ -56,8 +56,8 @@ describe('divider', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => divider({ type: NodeType.Heading })).toThrow( - 'Invalid divider component: At path: type -- Expected the literal `"divider"`, but received: "heading".', + expect(() => divider({ bar: 'baz' })).toThrow( + 'Invalid divider component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); }); }); @@ -89,8 +89,8 @@ describe('heading', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => heading({ type: NodeType.Divider })).toThrow( - 'Invalid heading component: At path: type -- Expected the literal `"heading"`, but received: "divider".', + expect(() => heading({ text: 'foo', bar: 'baz' })).toThrow( + 'Invalid heading component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); // @ts-expect-error - Invalid args. @@ -163,8 +163,8 @@ describe('panel', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => panel({ type: NodeType.Divider })).toThrow( - 'Invalid panel component: At path: type -- Expected the literal `"panel"`, but received: "divider".', + expect(() => panel({ children: [], bar: 'baz' })).toThrow( + 'Invalid panel component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); // @ts-expect-error - Invalid args. @@ -183,8 +183,8 @@ describe('spacer', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => spacer({ type: NodeType.Divider })).toThrow( - 'Invalid spacer component: At path: type -- Expected the literal `"spacer"`, but received: "divider".', + expect(() => spacer({ bar: 'baz' })).toThrow( + 'Invalid spacer component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); }); }); @@ -198,8 +198,8 @@ describe('spinner', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => spinner({ type: NodeType.Divider })).toThrow( - 'Invalid spinner component: At path: type -- Expected the literal `"spinner"`, but received: "divider".', + expect(() => spinner({ bar: 'baz' })).toThrow( + 'Invalid spinner component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); }); }); @@ -231,8 +231,8 @@ describe('text', () => { it('validates the args', () => { // @ts-expect-error - Invalid args. - expect(() => text({ type: NodeType.Divider })).toThrow( - 'Invalid text component: At path: type -- Expected the literal `"text"`, but received: "divider".', + expect(() => text({ text: 'foo', bar: 'baz' })).toThrow( + 'Invalid text component: At path: bar -- Expected a value of type `never`, but received: `"baz"`.', ); // @ts-expect-error - Invalid args. diff --git a/packages/snaps-ui/src/builder.ts b/packages/snaps-ui/src/builder.ts index c512627ed7..93dab4fc3e 100644 --- a/packages/snaps-ui/src/builder.ts +++ b/packages/snaps-ui/src/builder.ts @@ -66,7 +66,7 @@ function createBuilder< return (...args: [Omit] | NodeArrayType | []) => { // Node passed as a single object. if (args.length === 1 && isPlainObject(args[0])) { - const node = { type, ...args[0] }; + const node = { ...args[0], type }; // The user could be passing invalid values to the builder, so we need to // validate them as per the component's struct. From 46116c119487a474cfa1bf0273a283956bcb97a0 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 19:05:28 +0100 Subject: [PATCH 12/13] Remove unnecessary .gitignore --- packages/snaps-ui/.gitignore | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/snaps-ui/.gitignore diff --git a/packages/snaps-ui/.gitignore b/packages/snaps-ui/.gitignore deleted file mode 100644 index a4ddfffb10..0000000000 --- a/packages/snaps-ui/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -node_modules -dist -dist-temp -temp -*.log -*.tgz - -public/ -src/__GENERATED__/ From 40b1aec7ac683832db1ff34929b6e3d402ae2441 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Tue, 22 Nov 2022 19:06:52 +0100 Subject: [PATCH 13/13] Fix package version --- packages/snaps-ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snaps-ui/package.json b/packages/snaps-ui/package.json index 6f572f3e17..06c1c19d16 100644 --- a/packages/snaps-ui/package.json +++ b/packages/snaps-ui/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/snaps-ui", - "version": "0.23.0", + "version": "0.24.1", "repository": { "type": "git", "url": "https://github.com/MetaMask/snaps-monorepo.git"