From 2ae50e2ed83e5f16a6739da708f2fddef393eae2 Mon Sep 17 00:00:00 2001 From: Ryan Goulding Date: Mon, 15 Jan 2024 12:35:43 -0800 Subject: [PATCH] feat: Options CLI Builder Signed-off-by: Ryan Goulding --- packages/build-lz-options/.prettierignore | 2 + packages/build-lz-options/README.md | 31 +++ packages/build-lz-options/cli.js | 3 + packages/build-lz-options/jest.config.js | 9 + packages/build-lz-options/package.json | 61 ++++++ .../src/components/config.tsx | 47 +++++ .../src/components/outputOptions.tsx | 66 ++++++ packages/build-lz-options/src/config.ts | 59 ++++++ packages/build-lz-options/src/index.tsx | 84 ++++++++ packages/build-lz-options/src/types.ts | 21 ++ .../build-lz-options/src/utilities/prompts.ts | 197 ++++++++++++++++++ .../build-lz-options/src/utilities/tasks.ts | 68 ++++++ .../src/utilities/terminal.ts | 33 +++ packages/build-lz-options/tsconfig.json | 15 ++ packages/build-lz-options/tsconfig.test.json | 7 + packages/build-lz-options/tsup.config.ts | 17 ++ pnpm-lock.yaml | 64 +++++- 17 files changed, 782 insertions(+), 2 deletions(-) create mode 100644 packages/build-lz-options/.prettierignore create mode 100644 packages/build-lz-options/README.md create mode 100755 packages/build-lz-options/cli.js create mode 100644 packages/build-lz-options/jest.config.js create mode 100644 packages/build-lz-options/package.json create mode 100644 packages/build-lz-options/src/components/config.tsx create mode 100644 packages/build-lz-options/src/components/outputOptions.tsx create mode 100644 packages/build-lz-options/src/config.ts create mode 100644 packages/build-lz-options/src/index.tsx create mode 100644 packages/build-lz-options/src/types.ts create mode 100644 packages/build-lz-options/src/utilities/prompts.ts create mode 100644 packages/build-lz-options/src/utilities/tasks.ts create mode 100644 packages/build-lz-options/src/utilities/terminal.ts create mode 100644 packages/build-lz-options/tsconfig.json create mode 100644 packages/build-lz-options/tsconfig.test.json create mode 100644 packages/build-lz-options/tsup.config.ts diff --git a/packages/build-lz-options/.prettierignore b/packages/build-lz-options/.prettierignore new file mode 100644 index 000000000..763301fc0 --- /dev/null +++ b/packages/build-lz-options/.prettierignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/packages/build-lz-options/README.md b/packages/build-lz-options/README.md new file mode 100644 index 000000000..15c0ef999 --- /dev/null +++ b/packages/build-lz-options/README.md @@ -0,0 +1,31 @@ +

+ + LayerZero + +

+ +

build-lz-oapp

+ + +

+ + NPM Version + + Downloads + + NPM License +

+ +## Create LayerZero OApp Options Static Badge + +The easiest way to get started with LayerZero smart contract development. This CLI tool enables you to quickly start building on top of LayerZero omnichain interoperability protocol. To get started, use the following command: + +```bash +npx build-lz-options@latest +# or +yarn build-lz-options +# or +pnpm build-lz-options +# or +bunx build-lz-options +``` diff --git a/packages/build-lz-options/cli.js b/packages/build-lz-options/cli.js new file mode 100755 index 000000000..16d249fda --- /dev/null +++ b/packages/build-lz-options/cli.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +import('./dist/index.js'); diff --git a/packages/build-lz-options/jest.config.js b/packages/build-lz-options/jest.config.js new file mode 100644 index 000000000..17ae5d1ac --- /dev/null +++ b/packages/build-lz-options/jest.config.js @@ -0,0 +1,9 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testTimeout: 15000, + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, +}; diff --git a/packages/build-lz-options/package.json b/packages/build-lz-options/package.json new file mode 100644 index 000000000..905ecd988 --- /dev/null +++ b/packages/build-lz-options/package.json @@ -0,0 +1,61 @@ +{ + "name": "build-lz-options", + "version": "0.0.1", + "description": "Build LayerZero options with one command", + "keywords": [ + "LayerZero", + "OApp", + "Options" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/LayerZero-Labs/devtools.git", + "directory": "packages/build-lz-options" + }, + "license": "MIT", + "bin": { + "build-lz-options": "./cli.js" + }, + "files": [ + "cli.js", + "dist" + ], + "scripts": { + "prebuild": "tsc -noEmit", + "build": "$npm_execpath tsup", + "clean": "rm -rf dist", + "dev": "$npm_execpath tsup --watch", + "lint": "$npm_execpath eslint '**/*.{js,ts,json}'", + "start": "./cli.js", + "test": "jest" + }, + "dependencies": { + "yoga-layout-prebuilt": "^1.10.0" + }, + "devDependencies": { + "@layerzerolabs/devtools-evm": "~0.0.2", + "@layerzerolabs/io-devtools": "~0.0.2", + "@layerzerolabs/lz-utility-v2": "~2.0.7", + "@types/jest": "^29.5.11", + "@types/prompts": "^2.4.9", + "@types/react": "^17.0.74", + "commander": "^11.1.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "ink": "^3.2.0", + "ink-gradient": "^2.0.0", + "jest": "^29.7.0", + "prompts": "^2.4.2", + "react": "^17.0.2", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.2", + "tsup": "~8.0.1", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/build-lz-options/src/components/config.tsx b/packages/build-lz-options/src/components/config.tsx new file mode 100644 index 000000000..a8f4a5112 --- /dev/null +++ b/packages/build-lz-options/src/components/config.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { Box, Text } from "ink"; +import { OptionType1, OptionConfig, OptionType2 } from "@/types"; + +interface Props { + value: OptionConfig; +} + +interface OptionType1Props { + props: OptionType1; +} + +interface OptionType2Props extends OptionType1Props { + props: OptionType2; +} + +export const ConfigSummary: React.FC = ({ value }) => { + return ( + + + Creating LayerZero options {value.type.label} + + + ); +}; + +export const Option1Summary: React.FC = ({ props }) => { + return ( + + + Gas Limit: {props.gasLimit} + + + ); +}; + +export const Option2Summary: React.FC = ({ props }) => { + return ( + + + Gas Limit: {props.gasLimit} + Native Drop Amount: {props.nativeDropAmount} + Native Drop Address: {props.nativeDropAddress} + + + ); +}; diff --git a/packages/build-lz-options/src/components/outputOptions.tsx b/packages/build-lz-options/src/components/outputOptions.tsx new file mode 100644 index 000000000..ca841629e --- /dev/null +++ b/packages/build-lz-options/src/components/outputOptions.tsx @@ -0,0 +1,66 @@ +import { makeBytes32 } from "@layerzerolabs/devtools-evm"; +import React, { useEffect } from "react"; +import type { OptionType1, OptionType2, OptionType3 } from "@/types"; +import { Box } from "ink"; +import { useTask } from "@/utilities/tasks"; +import { optionsType1, optionsType2 } from "@layerzerolabs/lz-utility-v2"; + +interface Props { + props: OptionType1; +} + +export const outputOptionsType1 = async (gasLimit: OptionType1) => { + console.log(optionsType1(gasLimit.gasLimit)); +}; + +export const OutputOptionsType1: React.FC = ({ props }) => { + const output = useTask(() => outputOptionsType1(props)); + + useEffect(() => { + output.run().catch(() => {}); + }, [output.run]); + + return ; +}; + +interface OptionsType2Props { + props: OptionType2; +} + +const outputOptionsType2 = async (options: OptionType2) => { + console.log( + optionsType2( + options.gasLimit, + options.nativeDropAmount, + makeBytes32(options.nativeDropAddress), + ), + ); +}; + +export const OutputOptionsType2: React.FC = ({ props }) => { + const output = useTask(() => outputOptionsType2(props)); + + useEffect(() => { + output.run().catch(() => {}); + }, [output.run]); + + return ; +}; + +interface OptionsType3Props { + props: OptionType3; +} + +const outputOptionsType3 = async (options: OptionType3) => { + console.log(options.output); +}; + +export const OutputOptionsType3: React.FC = ({ props }) => { + const output = useTask(() => outputOptionsType3(props)); + + useEffect(() => { + output.run().catch(() => {}); + }, [output.run]); + + return ; +}; diff --git a/packages/build-lz-options/src/config.ts b/packages/build-lz-options/src/config.ts new file mode 100644 index 000000000..35fd8cc49 --- /dev/null +++ b/packages/build-lz-options/src/config.ts @@ -0,0 +1,59 @@ +import type { OptionType } from '@/types' +import { ExecutorOptionType, WorkerId } from '@layerzerolabs/lz-utility-v2' + +/** + * Supported Option Types. + */ +export const OPTION_TYPES: OptionType[] = [ + { + // TODO: use OptionType.TYPE_1 once exported from lz-utility-v2 + id: '1', + label: '1: gas for remote execution', + }, + { + // TODO: use OptionType.TYPE_2 once exported from lz-utility-v2 + id: '2', + label: '2: gas for remote execution and native drop', + }, + { + // TODO: use OptionType.TYPE_3 once exported from lz-utility-v2 + id: '3', + label: '3: options builder (EndpointV2 only)', + }, +] + +/** + * Supported Executor Option Types. + */ +export const EXECUTOR_OPTION_TYPE = [ + { + id: ExecutorOptionType.LZ_RECEIVE, + label: '1: lzReceive', + }, + { + id: ExecutorOptionType.NATIVE_DROP, + label: '2: nativeDrop', + }, + { + id: ExecutorOptionType.COMPOSE, + label: '3: compose', + }, + { + id: ExecutorOptionType.ORDERED, + label: '4: ordered', + }, +] + +/** + * Supported Worker Types. + */ +export const WORKER_TYPE = [ + { + id: WorkerId.EXECUTOR, + label: '1 Executor', + }, + { + id: WorkerId.VERIFIER, + label: '2 Verifier', + }, +] diff --git a/packages/build-lz-options/src/index.tsx b/packages/build-lz-options/src/index.tsx new file mode 100644 index 000000000..7eb6b4877 --- /dev/null +++ b/packages/build-lz-options/src/index.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { render } from "ink"; +import { Command } from "commander"; +import { + promptForOptionType, + promptForOptionType1, + promptForOptionType2, + promptForOptionType3, +} from "@/utilities/prompts"; +import { + ConfigSummary, + Option1Summary, + Option2Summary, +} from "@/components/config"; +import { + OutputOptionsType1, + OutputOptionsType2, + OutputOptionsType3, +} from "@/components/outputOptions"; +import { printLogo } from "@layerzerolabs/io-devtools/swag"; + +new Command("build-lz-options") + .description("Create LayerZero OApp options with one command") + .action(async () => { + printLogo(); + + // First we get the config from the user + const config = await promptForOptionType(); + render().unmount(); + + switch (config.type.id) { + case "1": { + const options = await promptForOptionType1(); + render( + , + ).unmount(); + render( + , + ); + break; + } + case "2": { + const options = await promptForOptionType2(); + render( + , + ).unmount(); + render( + , + ); + break; + } + case "3": { + const options = await promptForOptionType3(); + render( + , + ); + } + } + }) + .parseAsync(); diff --git a/packages/build-lz-options/src/types.ts b/packages/build-lz-options/src/types.ts new file mode 100644 index 000000000..8beff0b21 --- /dev/null +++ b/packages/build-lz-options/src/types.ts @@ -0,0 +1,21 @@ +export interface OptionConfig { + type: OptionType +} + +export interface OptionType { + id: string + label: string +} + +export interface OptionType1 { + gasLimit: string +} + +export interface OptionType2 extends OptionType1 { + nativeDropAmount: string + nativeDropAddress: string +} + +export interface OptionType3 { + output: string +} diff --git a/packages/build-lz-options/src/utilities/prompts.ts b/packages/build-lz-options/src/utilities/prompts.ts new file mode 100644 index 000000000..cf6b3c9ff --- /dev/null +++ b/packages/build-lz-options/src/utilities/prompts.ts @@ -0,0 +1,197 @@ +import { EXECUTOR_OPTION_TYPE, OPTION_TYPES, WORKER_TYPE } from '@/config' +import { OptionType1, OptionType2 } from '@/types' +import { makeBytes32 } from '@layerzerolabs/devtools-evm' +import { ExecutorOptionType, Options, WorkerId } from '@layerzerolabs/lz-utility-v2' +import prompts, { PromptObject } from 'prompts' +import { handlePromptState, promptToContinue } from '@layerzerolabs/io-devtools' + +const MAX_UINT_128 = BigInt(2) ** BigInt(128) - BigInt(1) +const MAX_UINT_8 = 0xffff +const DEFAULT_INITIAL_TEXT_NUMBER = BigInt('200000') + +/** + * Helper function to validate a string as a bigint. + * @param {string} str input string + * @param {bigint} max + * @param {bigint} min defaults to BigInt(0) + */ +const isValidBigInt = (str: string, max: bigint, min: bigint = BigInt(0)): boolean => { + try { + const value = BigInt(str) + return value >= min && value <= max + } catch (e) { + return false + } +} + +/** + * Helper function to create a prompt for a bigint. + * @param {string} name output variable name + * @param {string} message prompt message + * @param {bigint} initial defaults to DEFAULT_INITIAL_TEXT_NUMBER + * @param {bigint} max defaults to MAX_UINT_128 + * @param {bigint} min defaults to BigInt(0) + */ +const promptForBigInt = ( + name: string, + message: string, + initial: bigint = DEFAULT_INITIAL_TEXT_NUMBER, + max: bigint = MAX_UINT_128, + min: bigint = BigInt(0) +): PromptObject => { + // wrapper around prompts to handle bigint using string serialization + return { + onState: handlePromptState, + type: 'text', + name, + message, + initial: initial.toString(), + validate: (str: string) => isValidBigInt(str, max, min), + } +} + +/** + * Helper function to prompt for supported option type (1, 2, or 3). + */ +export const promptForOptionType = () => + prompts([ + { + onState: handlePromptState, + type: 'select', + name: 'type', + message: 'Which option type?', + choices: OPTION_TYPES.map((type) => ({ title: type.label, value: type })), + }, + ]) + +const promptForGasLimit: PromptObject = promptForBigInt('gasLimit', 'What gas limit do you want to set?') + +const promptForNativeDropAmount: PromptObject = promptForBigInt( + 'nativeDropAmount', + 'What native gas drop do you want to set?' +) + +/** + * Prompt for verifier / executor index. + */ +const promptForIndex: PromptObject = { + onState: handlePromptState, + type: 'number', + name: 'index', + message: 'What is the index?', + initial: 0, + min: 0, + max: MAX_UINT_8, +} + +const promptForNativeDropAddress: PromptObject = { + onState: handlePromptState, + type: 'text', + name: 'nativeDropAddress', + message: 'What native gas drop do you want to set?', + initial: makeBytes32(), +} + +const promptForWorkerType = () => + prompts([ + { + onState: handlePromptState, + type: 'select', + name: 'type', + message: 'Which worker type?', + choices: WORKER_TYPE.map((type) => ({ title: type.label, value: type })), + }, + ]) + +const promptForExecutorOptionType = () => + prompts([ + { + onState: handlePromptState, + type: 'select', + name: 'type', + message: 'Which option3 type?', + choices: EXECUTOR_OPTION_TYPE.map((type) => ({ title: type.label, value: type })), + }, + ]) + +const promptExecutorLzReceiveOption = async (options: Options): Promise => { + const { gasLimit, nativeDropAmount } = await prompts([promptForGasLimit, promptForNativeDropAmount]) + return options.addExecutorLzReceiveOption(gasLimit, nativeDropAmount) +} + +const promptExecutorNativeDropOption = async (options: Options): Promise => { + const { nativeDropAmount, nativeDropAddress } = await prompts([ + promptForNativeDropAmount, + promptForNativeDropAddress, + ]) + return options.addExecutorNativeDropOption(nativeDropAmount, nativeDropAddress) +} + +const promptExecutorComposeOption = async (options: Options): Promise => { + const { index, gasLimit, nativeDropAmount } = await prompts([ + promptForIndex, + promptForGasLimit, + promptForNativeDropAmount, + ]) + return options.addExecutorComposeOption(index, gasLimit, nativeDropAmount) +} + +const promptForExecutorOption = async (options: Options): Promise => { + const executorOptionType = await promptForExecutorOptionType() + switch (executorOptionType.type?.id) { + case ExecutorOptionType.LZ_RECEIVE: { + options = await promptExecutorLzReceiveOption(options) + break + } + case ExecutorOptionType.NATIVE_DROP: { + options = await promptExecutorNativeDropOption(options) + break + } + case ExecutorOptionType.COMPOSE: { + options = await promptExecutorComposeOption(options) + break + } + case ExecutorOptionType.ORDERED: { + options = options.addExecutorOrderedExecutionOption() + break + } + } + return options +} + +const promptVerifierPrecrimeOption = async (options: Options): Promise => { + const { index } = await prompts([promptForIndex]) + return options.addVerifierPrecrimeOption(index) +} + +/** + * Helper function to prompt for OptionType.TYPE_1. + */ +export const promptForOptionType1 = () => prompts([promptForGasLimit]) as never as Promise + +/** + * Helper function to prompt for OptionType.TYPE_2. + */ +export const promptForOptionType2 = (): Promise => + prompts([promptForGasLimit, promptForNativeDropAmount, promptForNativeDropAddress]) as never as Promise + +/** + * Helper function to prompt for OptionType.TYPE_3. + */ +export const promptForOptionType3 = async (): Promise => { + let options = Options.newOptions() + do { + const workerType = await promptForWorkerType() + switch (workerType.type.id) { + case WorkerId.EXECUTOR: { + options = await promptForExecutorOption(options) + break + } + case WorkerId.VERIFIER: { + options = await promptVerifierPrecrimeOption(options) + break + } + } + } while (await promptToContinue()) + return options +} diff --git a/packages/build-lz-options/src/utilities/tasks.ts b/packages/build-lz-options/src/utilities/tasks.ts new file mode 100644 index 000000000..f23ec9a51 --- /dev/null +++ b/packages/build-lz-options/src/utilities/tasks.ts @@ -0,0 +1,68 @@ +import { useCallback, useRef, useState } from 'react' + +/** + * TaskState holds three different states of a task: + * + * - Pending state + * - Success state + * - Failure state + */ +export type TaskState = + | { + loading: true + success?: never + failure?: never + data?: never + error?: never + } + | { + loading?: false + success: true + failure?: false + data: T + error?: never + } + | { + loading?: false + success?: false + failure: true + data?: never + error: unknown + } + +/** + * Poor person's version of react-query + * + * Handles state management of a promise run just like useMutation() from react-query, + * just with one less package and a bit less sophisticated reference checking mechanism + * (especially the part where it expect the component that uses this not to unmount or change the task + * at runtime) + * + * @param {() => Promise} task + * @returns + */ +export const useTask = (task: () => Promise) => { + const [state, setState] = useState>() + + // We'll keep the task in a reference so that we don't force the users to pass in memoized objects + const taskRef = useRef(task) + taskRef.current = task + + const run = useCallback(() => { + // Set state to loading + setState({ loading: true }) + + return taskRef.current().then( + // Set state to success + (data) => { + return setState({ success: true, data }), Promise.resolve(data) + }, + // Set state to failure + (error) => { + return setState({ failure: true, error }), Promise.reject(error) + } + ) + }, [taskRef]) + + return { run, state } +} diff --git a/packages/build-lz-options/src/utilities/terminal.ts b/packages/build-lz-options/src/utilities/terminal.ts new file mode 100644 index 000000000..6aa347d36 --- /dev/null +++ b/packages/build-lz-options/src/utilities/terminal.ts @@ -0,0 +1,33 @@ +const ENTER_ALT_SCREEN_ANSI = '\x1b[?1049h' +const EXIT_ALT_SCREEN_ANSI = '\x1b[?1049l' + +/** + * Helper function that wraps socket writes with a promise + * + * @param socket `WriteStream` + * @returns `(content: string) => Promise` + */ +const createWrite = (socket: NodeJS.WriteStream) => (content: string) => { + return new Promise((resolve, reject) => { + socket.write(content, (error) => { + if (error != null) reject(error) + else resolve() + }) + }) +} + +/** + * Starts an alt screen and returns a callback that exits back to the default screen. + * This makes the app "full screen" + * + * See https://github.com/vadimdemedes/ink/issues/263 for more info + * + * @returns `Promise<() => void>` + */ +export const altScreen = async () => { + const write = createWrite(process.stdout) + + await write(ENTER_ALT_SCREEN_ANSI) + + return () => write(EXIT_ALT_SCREEN_ANSI) +} diff --git a/packages/build-lz-options/tsconfig.json b/packages/build-lz-options/tsconfig.json new file mode 100644 index 000000000..f871c86ab --- /dev/null +++ b/packages/build-lz-options/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "dist", + "declaration": false, + "jsx": "react", + "lib": ["dom", "dom.Iterable", "es2022"], + "resolveJsonModule": false, + "types": ["jest", "node"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src", "test", "types"] +} diff --git a/packages/build-lz-options/tsconfig.test.json b/packages/build-lz-options/tsconfig.test.json new file mode 100644 index 000000000..c17742ae6 --- /dev/null +++ b/packages/build-lz-options/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Node10" + } +} diff --git a/packages/build-lz-options/tsup.config.ts b/packages/build-lz-options/tsup.config.ts new file mode 100644 index 000000000..13a441a52 --- /dev/null +++ b/packages/build-lz-options/tsup.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/index.tsx'], + outDir: './dist', + clean: true, + dts: false, + minify: true, + sourcemap: false, + splitting: false, + treeshake: true, + format: ['cjs'], + env: { + NODE_ENV: 'production', + }, + external: ['yoga-layout-prebuilt'], +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c037e293..42d9c9ae0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -261,6 +261,67 @@ importers: specifier: ^5.3.3 version: 5.3.3 + packages/build-lz-options: + dependencies: + yoga-layout-prebuilt: + specifier: ^1.10.0 + version: 1.10.0 + devDependencies: + '@layerzerolabs/devtools-evm': + specifier: ~0.0.2 + version: link:../devtools-evm + '@layerzerolabs/io-devtools': + specifier: ~0.0.2 + version: link:../io-devtools + '@layerzerolabs/lz-utility-v2': + specifier: ~2.0.7 + version: 2.0.7 + '@types/jest': + specifier: ^29.5.11 + version: 29.5.11 + '@types/prompts': + specifier: ^2.4.9 + version: 2.4.9 + '@types/react': + specifier: ^17.0.74 + version: 17.0.74 + commander: + specifier: ^11.1.0 + version: 11.1.0 + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.33.2(eslint@8.56.0) + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@8.56.0) + ink: + specifier: ^3.2.0 + version: 3.2.0(@types/react@17.0.74)(react@17.0.2) + ink-gradient: + specifier: ^2.0.0 + version: 2.0.0(ink@3.2.0)(react@17.0.2) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + prompts: + specifier: ^2.4.2 + version: 2.4.2 + react: + specifier: ^17.0.2 + version: 17.0.2 + ts-jest: + specifier: ^29.1.1 + version: 29.1.1(@babel/core@7.23.7)(esbuild@0.19.11)(jest@29.7.0)(typescript@5.3.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.14)(typescript@5.3.3) + tsup: + specifier: ~8.0.1 + version: 8.0.1(ts-node@10.9.2)(typescript@5.3.3) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + packages/create-lz-oapp: dependencies: yoga-layout-prebuilt: @@ -6759,7 +6820,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -9536,7 +9596,7 @@ packages: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true dependencies: - glob: 7.2.0 + glob: 7.2.3 /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}