From 8f1d57aa008aab51ed216b1049e951ad5fd6d023 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 21 Aug 2024 16:10:00 +0100 Subject: [PATCH] Introduce dotenv package for finding environment files (#1334) * Introduce dotenv package for finding environment files * Package-lock.json * Update references to old loadEnv * Update package references * Add the config files * Update loadEnv ref * Update loadEnv refs in demos * trigger tests * Update dotenv reference * Specify no tests --- demos/client-example-server/package.json | 1 + demos/client-example-server/vite.config.ts | 28 +- demos/client-example/package.json | 2 +- demos/client-example/vite.config.ts | 158 ++++---- .../client-frictionless-example/package.json | 2 +- .../vite.config.ts | 136 +++---- demos/client-pow-example/package.json | 2 +- demos/client-pow-example/vite.config.ts | 136 +++---- demos/provider-mock/package.json | 2 +- dev/config/src/vite/vite.backend.config.ts | 241 +++++------ dev/config/src/vite/vite.frontend.config.ts | 368 ++++++++--------- dev/flux/package.json | 2 +- dev/flux/src/lib/auth.ts | 379 +++++++++--------- dev/flux/src/lib/sep256k1Sign.ts | 151 +++---- dev/flux/src/lib/terminal.ts | 100 ++--- dev/flux/tsconfig.cjs.json | 2 +- dev/flux/tsconfig.json | 2 +- dev/scripts/package.json | 1 + dev/scripts/src/cli/index.ts | 181 ++++----- dev/scripts/src/scripts/generateMnemonic.ts | 34 +- dev/scripts/src/scripts/setVersion.ts | 283 ++++++------- dev/scripts/src/setup/setup.ts | 267 ++++++------ dev/scripts/src/util/updateEnv.ts | 149 +++---- dev/scripts/tsconfig.json | 3 + package-lock.json | 100 ++++- packages/cli/package.json | 1 + packages/cli/src/cli.ts | 84 ++-- packages/cli/src/env.ts | 52 --- packages/cli/src/index.ts | 1 - packages/cli/src/reloader.ts | 98 ++--- packages/cli/src/start.ts | 121 +++--- packages/cli/tsconfig.cjs.json | 3 + packages/cli/tsconfig.json | 3 + packages/cli/vite.config.ts | 52 +-- packages/contract/src/accounts/getPair.ts | 139 +++---- packages/dotenv/package.json | 48 +++ packages/dotenv/src/env.ts | 66 +++ packages/dotenv/src/index.ts | 1 + packages/dotenv/tsconfig.cjs.json | 14 + packages/dotenv/tsconfig.json | 9 + packages/dotenv/vite.cjs.config.ts | 19 + packages/dotenv/vite.test.config.ts | 32 ++ packages/procaptcha-bundle/vite.config.ts | 50 +-- packages/util/package.json | 3 +- 44 files changed, 1884 insertions(+), 1642 deletions(-) delete mode 100644 packages/cli/src/env.ts create mode 100644 packages/dotenv/package.json create mode 100644 packages/dotenv/src/env.ts create mode 100644 packages/dotenv/src/index.ts create mode 100644 packages/dotenv/tsconfig.cjs.json create mode 100644 packages/dotenv/tsconfig.json create mode 100644 packages/dotenv/vite.cjs.config.ts create mode 100644 packages/dotenv/vite.test.config.ts diff --git a/demos/client-example-server/package.json b/demos/client-example-server/package.json index 3ec078be22..d7e88862aa 100644 --- a/demos/client-example-server/package.json +++ b/demos/client-example-server/package.json @@ -44,6 +44,7 @@ }, "devDependencies": { "@prosopo/config": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@types/jsonwebtoken": "^9.0.2", "tslib": "2.6.2", "typescript": "5.1.6", diff --git a/demos/client-example-server/vite.config.ts b/demos/client-example-server/vite.config.ts index c487d33ba5..55f30f0750 100644 --- a/demos/client-example-server/vite.config.ts +++ b/demos/client-example-server/vite.config.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. import * as path from "node:path"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { ViteBackendConfig } from "@prosopo/config"; import { defineConfig } from "vite"; import { version } from "./package.json"; @@ -31,17 +31,17 @@ process.env.TS_NODE_PROJECT = path.resolve("./tsconfig.json"); // Merge with generic backend config export default defineConfig(async ({ command, mode }) => { - const backendConfig = await ViteBackendConfig( - packageName, - packageVersion, - bundleName, - dir, - entry, - command, - mode, - ); - return defineConfig({ - ...backendConfig, - server: { port: process.env.PROSOPO_SERVER_PORT }, - }); + const backendConfig = await ViteBackendConfig( + packageName, + packageVersion, + bundleName, + dir, + entry, + command, + mode, + ); + return defineConfig({ + ...backendConfig, + server: { port: process.env.PROSOPO_SERVER_PORT }, + }); }); diff --git a/demos/client-example/package.json b/demos/client-example/package.json index c125bf7531..145e6cb7d2 100644 --- a/demos/client-example/package.json +++ b/demos/client-example/package.json @@ -38,7 +38,7 @@ } }, "devDependencies": { - "@prosopo/cli": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/config": "2.0.1", "@prosopo/vite-plugin-watch-workspace": "2.0.1", "@types/node": "^20.3.1", diff --git a/demos/client-example/vite.config.ts b/demos/client-example/vite.config.ts index c6e3c58e9f..3267dfcc28 100644 --- a/demos/client-example/vite.config.ts +++ b/demos/client-example/vite.config.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. import * as path from "node:path"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { getLogger } from "@prosopo/common"; import { VitePluginCloseAndCopy } from "@prosopo/config"; import { VitePluginWatchWorkspace } from "@prosopo/vite-plugin-watch-workspace"; @@ -23,83 +23,83 @@ const dir = path.resolve("."); loadEnv(dir); // https://vitejs.dev/config/ export default defineConfig(async ({ command, mode }) => { - logger.info(`Running at ${dir} in ${mode} mode`); - // NODE_ENV must be wrapped in quotes. We just set it to the mode and ignore what's in the env file, otherwise the - // mode and NODE_ENV can end up out of sync (one set to development and the other set to production, which causes - // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 - logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); + logger.info(`Running at ${dir} in ${mode} mode`); + // NODE_ENV must be wrapped in quotes. We just set it to the mode and ignore what's in the env file, otherwise the + // mode and NODE_ENV can end up out of sync (one set to development and the other set to production, which causes + // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 + logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); - // Set the env vars that we want to be available in the browser - const define = { - // used to stop websockets package from breaking - "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), - "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), - "process.env.NODE_ENV": JSON.stringify(mode), - "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( - process.env.PROSOPO_DEFAULT_ENVIRONMENT, - ), - // only needed if bundling with a site key - "process.env.PROSOPO_SITE_KEY": JSON.stringify( - process.env.PROSOPO_SITE_KEY, - ), - "process.env.PROSOPO_WEB2": JSON.stringify(process.env.PROSOPO_WEB2), - "process.env.PROSOPO_SERVER_URL": JSON.stringify( - process.env.PROSOPO_SERVER_URL, - ), - "process.env.PROSOPO_SERVER_PORT": JSON.stringify( - process.env.PROSOPO_SERVER_PORT, - ), - "process.env.PROSOPO_PORT": JSON.stringify(process.env.PROSOPO_PORT), - "process.env._DEV_ONLY_WATCH_EVENTS": JSON.stringify( - process.env._DEV_ONLY_WATCH_EVENTS, - ), - }; - logger.debug("define", JSON.stringify(define)); - return { - watch: false, - mode: "development", - bundle: true, - define, - optimizeDeps: { - include: ["prop-types"], - }, - esbuild: { - target: [ - "es2020", - "chrome60", - "edge18", - "firefox60", - "node12", - "safari11", - ], - }, - build: { - modulePreload: { polyfill: true }, - lib: { - entry: path.resolve(__dirname, "./index.html"), - name: "client_example", - }, - }, - plugins: [ - // @ts-ignore - react(), - // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve - // mode, in which case we don't want to close the bundler because it will close the server - command !== "serve" ? VitePluginCloseAndCopy() : undefined, - // Watches external files (workspace packages) and rebuilds them when they change - await VitePluginWatchWorkspace({ - workspaceRoot: path.resolve("../.."), - currentPackage: `${path.resolve(".")}/**/*`, - format: "esm", - ignorePaths: [ - `${path.resolve("../..")}/demos/*`, - `${path.resolve("../..")}/dev/*`, - "**/dist/**/*", - ], - }), - ], - server: { - port: process.env.PROSOPO_PORT ? Number(process.env.PROSOPO_PORT) : 9230, - }, - }; + // Set the env vars that we want to be available in the browser + const define = { + // used to stop websockets package from breaking + "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), + "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), + "process.env.NODE_ENV": JSON.stringify(mode), + "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( + process.env.PROSOPO_DEFAULT_ENVIRONMENT, + ), + // only needed if bundling with a site key + "process.env.PROSOPO_SITE_KEY": JSON.stringify( + process.env.PROSOPO_SITE_KEY, + ), + "process.env.PROSOPO_WEB2": JSON.stringify(process.env.PROSOPO_WEB2), + "process.env.PROSOPO_SERVER_URL": JSON.stringify( + process.env.PROSOPO_SERVER_URL, + ), + "process.env.PROSOPO_SERVER_PORT": JSON.stringify( + process.env.PROSOPO_SERVER_PORT, + ), + "process.env.PROSOPO_PORT": JSON.stringify(process.env.PROSOPO_PORT), + "process.env._DEV_ONLY_WATCH_EVENTS": JSON.stringify( + process.env._DEV_ONLY_WATCH_EVENTS, + ), + }; + logger.debug("define", JSON.stringify(define)); + return { + watch: false, + mode: "development", + bundle: true, + define, + optimizeDeps: { + include: ["prop-types"], + }, + esbuild: { + target: [ + "es2020", + "chrome60", + "edge18", + "firefox60", + "node12", + "safari11", + ], + }, + build: { + modulePreload: { polyfill: true }, + lib: { + entry: path.resolve(__dirname, "./index.html"), + name: "client_example", + }, + }, + plugins: [ + // @ts-ignore + react(), + // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve + // mode, in which case we don't want to close the bundler because it will close the server + command !== "serve" ? VitePluginCloseAndCopy() : undefined, + // Watches external files (workspace packages) and rebuilds them when they change + await VitePluginWatchWorkspace({ + workspaceRoot: path.resolve("../.."), + currentPackage: `${path.resolve(".")}/**/*`, + format: "esm", + ignorePaths: [ + `${path.resolve("../..")}/demos/*`, + `${path.resolve("../..")}/dev/*`, + "**/dist/**/*", + ], + }), + ], + server: { + port: process.env.PROSOPO_PORT ? Number(process.env.PROSOPO_PORT) : 9230, + }, + }; }); diff --git a/demos/client-frictionless-example/package.json b/demos/client-frictionless-example/package.json index 7b19627480..33db2af47a 100644 --- a/demos/client-frictionless-example/package.json +++ b/demos/client-frictionless-example/package.json @@ -23,7 +23,7 @@ "@prosopo/procaptcha-pow": "2.0.1" }, "devDependencies": { - "@prosopo/cli": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/config": "2.0.1", "@types/node": "^20.3.1", "css-loader": "^6.8.1", diff --git a/demos/client-frictionless-example/vite.config.ts b/demos/client-frictionless-example/vite.config.ts index d1d29acc0b..68f32b9c5c 100644 --- a/demos/client-frictionless-example/vite.config.ts +++ b/demos/client-frictionless-example/vite.config.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. import * as path from "node:path"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { getLogger } from "@prosopo/common"; import { VitePluginCloseAndCopy } from "@prosopo/config"; import react from "@vitejs/plugin-react"; @@ -22,73 +22,73 @@ const dir = path.resolve("."); loadEnv(dir); // https://vitejs.dev/config/ export default defineConfig(({ command, mode }) => { - logger.info(`Running at ${dir} in ${mode} mode`); - // NODE_ENV must be wrapped in quotes. We just set it to the mode and ignore what's in the env file, otherwise the - // mode and NODE_ENV can end up out of sync (one set to development and the other set to production, which causes - // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 - logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); + logger.info(`Running at ${dir} in ${mode} mode`); + // NODE_ENV must be wrapped in quotes. We just set it to the mode and ignore what's in the env file, otherwise the + // mode and NODE_ENV can end up out of sync (one set to development and the other set to production, which causes + // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 + logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); - // Set the env vars that we want to be available in the browser - const define = { - // used to stop websockets package from breaking - "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), - "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), - "process.env.NODE_ENV": JSON.stringify(mode), - "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( - process.env.PROSOPO_SUBSTRATE_ENDPOINT, - ), - "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( - process.env.PROSOPO_DEFAULT_ENVIRONMENT, - ), - // only needed if bundling with a site key - "process.env.PROSOPO_SITE_KEY": JSON.stringify( - process.env.PROSOPO_SITE_KEY, - ), - "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( - process.env.PROSOPO_CONTRACT_ADDRESS, - ), - "process.env.PROSOPO_WEB2": JSON.stringify(process.env.PROSOPO_WEB2), - "process.env.PROSOPO_SERVER_URL": JSON.stringify( - process.env.PROSOPO_SERVER_URL, - ), - "process.env.PROSOPO_PORT": JSON.stringify(process.env.PROSOPO_PORT), - }; - logger.debug("define", JSON.stringify(define)); + // Set the env vars that we want to be available in the browser + const define = { + // used to stop websockets package from breaking + "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), + "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), + "process.env.NODE_ENV": JSON.stringify(mode), + "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( + process.env.PROSOPO_SUBSTRATE_ENDPOINT, + ), + "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( + process.env.PROSOPO_DEFAULT_ENVIRONMENT, + ), + // only needed if bundling with a site key + "process.env.PROSOPO_SITE_KEY": JSON.stringify( + process.env.PROSOPO_SITE_KEY, + ), + "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( + process.env.PROSOPO_CONTRACT_ADDRESS, + ), + "process.env.PROSOPO_WEB2": JSON.stringify(process.env.PROSOPO_WEB2), + "process.env.PROSOPO_SERVER_URL": JSON.stringify( + process.env.PROSOPO_SERVER_URL, + ), + "process.env.PROSOPO_PORT": JSON.stringify(process.env.PROSOPO_PORT), + }; + logger.debug("define", JSON.stringify(define)); - return { - watch: false, - mode: "development", - bundle: true, - define, - optimizeDeps: { - include: ["prop-types"], - }, - esbuild: { - target: [ - "es2020", - "chrome60", - "edge18", - "firefox60", - "node12", - "safari11", - ], - }, - build: { - modulePreload: { polyfill: true }, - lib: { - entry: path.resolve(__dirname, "./index.html"), - name: "client_example", - }, - }, - plugins: [ - // @ts-ignore - react(), - // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve - // mode, in which case we don't want to close the bundler because it will close the server - command !== "serve" ? VitePluginCloseAndCopy() : undefined, - ], - server: { - port: process.env.PROSOPO_PORT ? Number(process.env.PROSOPO_PORT) : 9234, - }, - }; + return { + watch: false, + mode: "development", + bundle: true, + define, + optimizeDeps: { + include: ["prop-types"], + }, + esbuild: { + target: [ + "es2020", + "chrome60", + "edge18", + "firefox60", + "node12", + "safari11", + ], + }, + build: { + modulePreload: { polyfill: true }, + lib: { + entry: path.resolve(__dirname, "./index.html"), + name: "client_example", + }, + }, + plugins: [ + // @ts-ignore + react(), + // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve + // mode, in which case we don't want to close the bundler because it will close the server + command !== "serve" ? VitePluginCloseAndCopy() : undefined, + ], + server: { + port: process.env.PROSOPO_PORT ? Number(process.env.PROSOPO_PORT) : 9234, + }, + }; }); diff --git a/demos/client-pow-example/package.json b/demos/client-pow-example/package.json index b24ec23828..bc898cd93c 100644 --- a/demos/client-pow-example/package.json +++ b/demos/client-pow-example/package.json @@ -23,7 +23,7 @@ "web-vitals": "^2.1.4" }, "devDependencies": { - "@prosopo/cli": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/config": "2.0.1", "@types/node": "^20.3.1", "css-loader": "^6.8.1", diff --git a/demos/client-pow-example/vite.config.ts b/demos/client-pow-example/vite.config.ts index d1d29acc0b..68f32b9c5c 100644 --- a/demos/client-pow-example/vite.config.ts +++ b/demos/client-pow-example/vite.config.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. import * as path from "node:path"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { getLogger } from "@prosopo/common"; import { VitePluginCloseAndCopy } from "@prosopo/config"; import react from "@vitejs/plugin-react"; @@ -22,73 +22,73 @@ const dir = path.resolve("."); loadEnv(dir); // https://vitejs.dev/config/ export default defineConfig(({ command, mode }) => { - logger.info(`Running at ${dir} in ${mode} mode`); - // NODE_ENV must be wrapped in quotes. We just set it to the mode and ignore what's in the env file, otherwise the - // mode and NODE_ENV can end up out of sync (one set to development and the other set to production, which causes - // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 - logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); + logger.info(`Running at ${dir} in ${mode} mode`); + // NODE_ENV must be wrapped in quotes. We just set it to the mode and ignore what's in the env file, otherwise the + // mode and NODE_ENV can end up out of sync (one set to development and the other set to production, which causes + // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 + logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); - // Set the env vars that we want to be available in the browser - const define = { - // used to stop websockets package from breaking - "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), - "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), - "process.env.NODE_ENV": JSON.stringify(mode), - "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( - process.env.PROSOPO_SUBSTRATE_ENDPOINT, - ), - "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( - process.env.PROSOPO_DEFAULT_ENVIRONMENT, - ), - // only needed if bundling with a site key - "process.env.PROSOPO_SITE_KEY": JSON.stringify( - process.env.PROSOPO_SITE_KEY, - ), - "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( - process.env.PROSOPO_CONTRACT_ADDRESS, - ), - "process.env.PROSOPO_WEB2": JSON.stringify(process.env.PROSOPO_WEB2), - "process.env.PROSOPO_SERVER_URL": JSON.stringify( - process.env.PROSOPO_SERVER_URL, - ), - "process.env.PROSOPO_PORT": JSON.stringify(process.env.PROSOPO_PORT), - }; - logger.debug("define", JSON.stringify(define)); + // Set the env vars that we want to be available in the browser + const define = { + // used to stop websockets package from breaking + "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), + "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), + "process.env.NODE_ENV": JSON.stringify(mode), + "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( + process.env.PROSOPO_SUBSTRATE_ENDPOINT, + ), + "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( + process.env.PROSOPO_DEFAULT_ENVIRONMENT, + ), + // only needed if bundling with a site key + "process.env.PROSOPO_SITE_KEY": JSON.stringify( + process.env.PROSOPO_SITE_KEY, + ), + "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( + process.env.PROSOPO_CONTRACT_ADDRESS, + ), + "process.env.PROSOPO_WEB2": JSON.stringify(process.env.PROSOPO_WEB2), + "process.env.PROSOPO_SERVER_URL": JSON.stringify( + process.env.PROSOPO_SERVER_URL, + ), + "process.env.PROSOPO_PORT": JSON.stringify(process.env.PROSOPO_PORT), + }; + logger.debug("define", JSON.stringify(define)); - return { - watch: false, - mode: "development", - bundle: true, - define, - optimizeDeps: { - include: ["prop-types"], - }, - esbuild: { - target: [ - "es2020", - "chrome60", - "edge18", - "firefox60", - "node12", - "safari11", - ], - }, - build: { - modulePreload: { polyfill: true }, - lib: { - entry: path.resolve(__dirname, "./index.html"), - name: "client_example", - }, - }, - plugins: [ - // @ts-ignore - react(), - // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve - // mode, in which case we don't want to close the bundler because it will close the server - command !== "serve" ? VitePluginCloseAndCopy() : undefined, - ], - server: { - port: process.env.PROSOPO_PORT ? Number(process.env.PROSOPO_PORT) : 9234, - }, - }; + return { + watch: false, + mode: "development", + bundle: true, + define, + optimizeDeps: { + include: ["prop-types"], + }, + esbuild: { + target: [ + "es2020", + "chrome60", + "edge18", + "firefox60", + "node12", + "safari11", + ], + }, + build: { + modulePreload: { polyfill: true }, + lib: { + entry: path.resolve(__dirname, "./index.html"), + name: "client_example", + }, + }, + plugins: [ + // @ts-ignore + react(), + // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve + // mode, in which case we don't want to close the bundler because it will close the server + command !== "serve" ? VitePluginCloseAndCopy() : undefined, + ], + server: { + port: process.env.PROSOPO_PORT ? Number(process.env.PROSOPO_PORT) : 9234, + }, + }; }); diff --git a/demos/provider-mock/package.json b/demos/provider-mock/package.json index 641b7b1f15..4c110b3087 100644 --- a/demos/provider-mock/package.json +++ b/demos/provider-mock/package.json @@ -15,7 +15,7 @@ "build": "tsc --build --verbose" }, "dependencies": { - "@prosopo/cli": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/common": "2.0.1", "@prosopo/types": "2.0.1", "es-main": "^1.3.0", diff --git a/dev/config/src/vite/vite.backend.config.ts b/dev/config/src/vite/vite.backend.config.ts index cf9cc3f251..156850ce25 100644 --- a/dev/config/src/vite/vite.backend.config.ts +++ b/dev/config/src/vite/vite.backend.config.ts @@ -1,12 +1,3 @@ -import { builtinModules } from "node:module"; -import path from "node:path"; -import { getLogger } from "@prosopo/common"; -import { nodeResolve } from "@rollup/plugin-node-resolve"; -import { wasm } from "@rollup/plugin-wasm"; -import type { Drop } from "esbuild"; -import css from "rollup-plugin-import-css"; -import type { UserConfig } from "vite"; -import { filterDependencies, getDependencies } from "../dependencies.js"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,132 +11,142 @@ import { filterDependencies, getDependencies } from "../dependencies.js"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import { builtinModules } from "node:module"; +import path from "node:path"; +import { getLogger } from "@prosopo/common"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import { wasm } from "@rollup/plugin-wasm"; +import type { Drop } from "esbuild"; +import css from "rollup-plugin-import-css"; +import type { UserConfig } from "vite"; +import { filterDependencies, getDependencies } from "../dependencies.js"; import { default as ClosePlugin } from "./vite-plugin-close-and-copy.js"; import VitePluginFixAbsoluteImports from "./vite-plugin-fix-absolute-imports.js"; const logger = getLogger("Info", "vite.backend.config.js"); export default async function ( - packageName: string, - packageVersion: string, - bundleName: string, - packageDir: string, - entry: string, - command?: string, - mode?: string, - optionalBaseDir = "../..", + packageName: string, + packageVersion: string, + bundleName: string, + packageDir: string, + entry: string, + command?: string, + mode?: string, + optionalBaseDir = "../..", ): Promise { - const isProduction = mode === "production"; + const isProduction = mode === "production"; - // Get all dependencies of the current package - const { dependencies: deps, optionalPeerDependencies } = - await getDependencies(packageName, true); + // Get all dependencies of the current package + const { dependencies: deps, optionalPeerDependencies } = + await getDependencies(packageName, true); - // Output directory is relative to directory of the package - const outDir = path.resolve(packageDir, "dist/bundle"); + // Output directory is relative to directory of the package + const outDir = path.resolve(packageDir, "dist/bundle"); - // Get rid of any dependencies we don't want to bundle - const { external, internal } = filterDependencies(deps, [ - "aws", - "webpack", - "vite", - "biome", - ]); + // Get rid of any dependencies we don't want to bundle + const { external, internal } = filterDependencies(deps, [ + "aws", + "webpack", + "vite", + "biome", + ]); - // Add the node builtins (path, fs, os, etc.) to the external list - const allExternal = [ - ...builtinModules, - ...builtinModules.map((m) => `node:${m}`), - ...external, - ...optionalPeerDependencies, - ]; + // Add the node builtins (path, fs, os, etc.) to the external list + const allExternal = [ + ...builtinModules, + ...builtinModules.map((m) => `node:${m}`), + ...external, + ...optionalPeerDependencies, + ]; - logger.info( - `Bundling. ${JSON.stringify(internal.slice(0, 10), null, 2)}... ${internal.length} deps`, - ); + logger.info( + `Bundling. ${JSON.stringify(internal.slice(0, 10), null, 2)}... ${internal.length} deps`, + ); - const define = { - "process.env.WS_NO_BUFFER_UTIL": "true", - "process.env.WS_NO_UTF_8_VALIDATE": "true", - "process.env.PROSOPO_PACKAGE_VERSION": JSON.stringify(packageVersion), - "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || mode), - ...(process.env.PROSOPO_DEFAULT_ENVIRONMENT && { - "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( - process.env.PROSOPO_DEFAULT_ENVIRONMENT, - ), - }), - ...(process.env.PROSOPO_DEFAULT_NETWORK && { - "process.env.PROSOPO_DEFAULT_NETWORK": JSON.stringify( - process.env.PROSOPO_DEFAULT_NETWORK, - ), - }), - ...(process.env.PROSOPO_SUBSTRATE_ENDPOINT && { - "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( - process.env.PROSOPO_SUBSTRATE_ENDPOINT, - ), - }), - ...(process.env.PROSOPO_CONTRACT_ADDRESS && { - "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( - process.env.PROSOPO_CONTRACT_ADDRESS, - ), - }), - }; + const define = { + "process.env.WS_NO_BUFFER_UTIL": "true", + "process.env.WS_NO_UTF_8_VALIDATE": "true", + "process.env.PROSOPO_PACKAGE_VERSION": JSON.stringify(packageVersion), + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || mode), + ...(process.env.PROSOPO_DEFAULT_ENVIRONMENT && { + "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( + process.env.PROSOPO_DEFAULT_ENVIRONMENT, + ), + }), + ...(process.env.PROSOPO_DEFAULT_NETWORK && { + "process.env.PROSOPO_DEFAULT_NETWORK": JSON.stringify( + process.env.PROSOPO_DEFAULT_NETWORK, + ), + }), + ...(process.env.PROSOPO_SUBSTRATE_ENDPOINT && { + "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( + process.env.PROSOPO_SUBSTRATE_ENDPOINT, + ), + }), + ...(process.env.PROSOPO_CONTRACT_ADDRESS && { + "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( + process.env.PROSOPO_CONTRACT_ADDRESS, + ), + }), + }; - logger.info(`Defined vars ${JSON.stringify(define, null, 2)}`); + logger.info(`Defined vars ${JSON.stringify(define, null, 2)}`); - const entryAbsolute = path.resolve(packageDir, entry); + const entryAbsolute = path.resolve(packageDir, entry); - // drop console logs if in production mode - const drop: Drop[] | undefined = - mode === "production" ? ["console", "debugger"] : undefined; + // drop console logs if in production mode + const drop: Drop[] | undefined = + mode === "production" ? ["console", "debugger"] : undefined; - return { - ssr: { - noExternal: internal, - external: allExternal, - }, - optimizeDeps: { - include: ["linked-dep", "node_modules"], - esbuildOptions: { - loader: { - ".node": "file", - }, - }, - }, - esbuild: { - platform: "node", - target: "node18", - drop, - legalComments: "none", - }, - define, - build: { - outDir, - minify: isProduction, - ssr: true, - target: "node18", - lib: { - entry: entryAbsolute, - name: bundleName, - fileName: `${bundleName}.[name].bundle.js`, - formats: ["es"], - }, - modulePreload: { polyfill: false }, - rollupOptions: { - treeshake: "smallest", - external: allExternal, - watch: false, - output: { - entryFileNames: `${bundleName}.[name].bundle.js`, - }, - plugins: [css(), wasm(), nodeResolve()], - }, - }, - plugins: [ - // plugin to replace stuff like import blah from string_encoder/lib/string_encoder.js with import blah from string_encoder - VitePluginFixAbsoluteImports(), - // plugin to close the bundle after build if not in serve mode - command !== "serve" ? ClosePlugin() : undefined, - ], - }; + return { + ssr: { + noExternal: internal, + external: allExternal, + }, + optimizeDeps: { + include: ["linked-dep", "node_modules"], + esbuildOptions: { + loader: { + ".node": "file", + }, + }, + }, + esbuild: { + platform: "node", + target: "node18", + drop, + legalComments: "none", + }, + define, + build: { + outDir, + minify: isProduction, + ssr: true, + target: "node18", + lib: { + entry: entryAbsolute, + name: bundleName, + fileName: `${bundleName}.[name].bundle.js`, + formats: ["es"], + }, + modulePreload: { polyfill: false }, + rollupOptions: { + treeshake: "smallest", + external: allExternal, + watch: false, + output: { + entryFileNames: `${bundleName}.[name].bundle.js`, + }, + plugins: [css(), wasm(), nodeResolve()], + }, + }, + plugins: [ + // plugin to replace stuff like import blah from string_encoder/lib/string_encoder.js with import blah from string_encoder + VitePluginFixAbsoluteImports(), + // plugin to close the bundle after build if not in serve mode + command !== "serve" ? ClosePlugin() : undefined, + ], + }; } diff --git a/dev/config/src/vite/vite.frontend.config.ts b/dev/config/src/vite/vite.frontend.config.ts index 4de0e83728..d91dfc3919 100644 --- a/dev/config/src/vite/vite.frontend.config.ts +++ b/dev/config/src/vite/vite.frontend.config.ts @@ -29,201 +29,201 @@ import type { ClosePluginOptions } from "./vite-plugin-close-and-copy.js"; const logger = getLogger("Info", "vite.config.js"); export default async function ( - packageName: string, - bundleName: string, - dir: string, - entry: string, - command?: string, - mode?: string, - copyOptions?: ClosePluginOptions, - tsConfigPaths?: string[], - workspaceRoot?: string, + packageName: string, + bundleName: string, + dir: string, + entry: string, + command?: string, + mode?: string, + copyOptions?: ClosePluginOptions, + tsConfigPaths?: string[], + workspaceRoot?: string, ): Promise { - logger.info(`Running at ${dir} in ${mode} mode`); - const isProduction = mode === "production"; - // NODE_ENV must be wrapped in quotes. - // If NODE_ENV ends up out of sync (one set to development and the other set to production), it causes - // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 - process.env.NODE_ENV = `${process.env.NODE_ENV || mode}`; - logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); + logger.info(`Running at ${dir} in ${mode} mode`); + const isProduction = mode === "production"; + // NODE_ENV must be wrapped in quotes. + // If NODE_ENV ends up out of sync (one set to development and the other set to production), it causes + // issues like this: https://github.com/hashicorp/next-mdx-remote/pull/323 + process.env.NODE_ENV = `${process.env.NODE_ENV || mode}`; + logger.info(`NODE_ENV: ${process.env.NODE_ENV}`); - // Set the env vars that we want to be available in the browser - const define = { - // used to stop websockets package from breaking - "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), - "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), - "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), - "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( - process.env.PROSOPO_SUBSTRATE_ENDPOINT, - ), - "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( - process.env.PROSOPO_DEFAULT_ENVIRONMENT || process.env.NODE_ENV || mode, - ), - "process.env.PROSOPO_DEFAULT_NETWORK": JSON.stringify( - process.env.PROSOPO_DEFAULT_NETWORK, - ), - "process.env.PROSOPO_SERVER_URL": JSON.stringify( - process.env.PROSOPO_SERVER_URL, - ), - "process.env._DEV_ONLY_WATCH_EVENTS": JSON.stringify( - process.env._DEV_ONLY_WATCH_EVENTS, - ), - "process.env.PROSOPO_MONGO_EVENTS_URI": JSON.stringify( - process.env.PROSOPO_MONGO_EVENTS_URI, - ), - "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( - process.env.PROSOPO_CONTRACT_ADDRESS, - ), - "process.env.PROSOPO_PACKAGE_VERSION": JSON.stringify( - process.env.PROSOPO_PACKAGE_VERSION, - ), - // only needed if bundling with a site key - "process.env.PROSOPO_SITE_KEY": JSON.stringify( - process.env.PROSOPO_SITE_KEY, - ), - }; + // Set the env vars that we want to be available in the browser + const define = { + // used to stop websockets package from breaking + "process.env.WS_NO_BUFFER_UTIL": JSON.stringify("true"), + "process.env.WS_NO_UTF_8_VALIDATE": JSON.stringify("true"), + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), + "process.env.PROSOPO_SUBSTRATE_ENDPOINT": JSON.stringify( + process.env.PROSOPO_SUBSTRATE_ENDPOINT, + ), + "process.env.PROSOPO_DEFAULT_ENVIRONMENT": JSON.stringify( + process.env.PROSOPO_DEFAULT_ENVIRONMENT || process.env.NODE_ENV || mode, + ), + "process.env.PROSOPO_DEFAULT_NETWORK": JSON.stringify( + process.env.PROSOPO_DEFAULT_NETWORK, + ), + "process.env.PROSOPO_SERVER_URL": JSON.stringify( + process.env.PROSOPO_SERVER_URL, + ), + "process.env._DEV_ONLY_WATCH_EVENTS": JSON.stringify( + process.env._DEV_ONLY_WATCH_EVENTS, + ), + "process.env.PROSOPO_MONGO_EVENTS_URI": JSON.stringify( + process.env.PROSOPO_MONGO_EVENTS_URI, + ), + "process.env.PROSOPO_CONTRACT_ADDRESS": JSON.stringify( + process.env.PROSOPO_CONTRACT_ADDRESS, + ), + "process.env.PROSOPO_PACKAGE_VERSION": JSON.stringify( + process.env.PROSOPO_PACKAGE_VERSION, + ), + // only needed if bundling with a site key + "process.env.PROSOPO_SITE_KEY": JSON.stringify( + process.env.PROSOPO_SITE_KEY, + ), + }; - logger.info(`Env vars: ${JSON.stringify(define, null, 4)}`); + logger.info(`Env vars: ${JSON.stringify(define, null, 4)}`); - // Get all dependencies of the current package - const { dependencies: deps, optionalPeerDependencies } = - await getDependencies(packageName, isProduction); + // Get all dependencies of the current package + const { dependencies: deps, optionalPeerDependencies } = + await getDependencies(packageName, isProduction); - // Get rid of any dependencies we don't want to bundle - const { external, internal } = filterDependencies(deps, [ - "pm2", - "nodejs-polars", - "aws", - "webpack", - "vite", - ]); + // Get rid of any dependencies we don't want to bundle + const { external, internal } = filterDependencies(deps, [ + "pm2", + "nodejs-polars", + "aws", + "webpack", + "vite", + ]); - // Add the node builtins (path, fs, os, etc.) to the external list - const allExternal = [ - ...builtinModules, - ...builtinModules.map((m) => `node:${m}`), - ...external, - ...optionalPeerDependencies, - ]; - logger.debug( - `Bundling. ${JSON.stringify(internal.slice(0, 10), null, 2)}... ${internal.length} deps`, - ); + // Add the node builtins (path, fs, os, etc.) to the external list + const allExternal = [ + ...builtinModules, + ...builtinModules.map((m) => `node:${m}`), + ...external, + ...optionalPeerDependencies, + ]; + logger.debug( + `Bundling. ${JSON.stringify(internal.slice(0, 10), null, 2)}... ${internal.length} deps`, + ); - // Required to print RegExp in console (e.g. alias keys) - // biome-ignore lint/suspicious/noExplicitAny: has to be any to represent object prototype - const proto = RegExp.prototype as any; - proto.toJSON = RegExp.prototype.toString; + // Required to print RegExp in console (e.g. alias keys) + // biome-ignore lint/suspicious/noExplicitAny: has to be any to represent object prototype + const proto = RegExp.prototype as any; + proto.toJSON = RegExp.prototype.toString; - // drop console logs if in production mode - let drop: undefined | Drop[]; - let pure: string[] = []; - if (isProduction) { - drop = ["debugger"]; - pure = ["console.log", "console.warn", "console.info", "console.debug"]; - } + // drop console logs if in production mode + let drop: undefined | Drop[]; + let pure: string[] = []; + if (isProduction) { + drop = ["debugger"]; + pure = ["console.log", "console.warn", "console.info", "console.debug"]; + } - logger.info("Bundle name", bundleName); - return { - ssr: { - target: "webworker", - }, - server: { - host: "127.0.0.1", - }, - mode: mode || "development", - optimizeDeps: { - include: ["linked-dep", "esm-dep > cjs-dep", "node_modules"], - force: true, - }, - esbuild: { - platform: "browser", - target: [ - "es2020", - "chrome60", - "edge18", - "firefox60", - "node12", - "safari11", - ], - drop, - pure, - legalComments: "none", - }, - define, + logger.info("Bundle name", bundleName); + return { + ssr: { + target: "webworker", + }, + server: { + host: "127.0.0.1", + }, + mode: mode || "development", + optimizeDeps: { + include: ["node_modules"], + force: true, + }, + esbuild: { + platform: "browser", + target: [ + "es2020", + "chrome60", + "edge18", + "firefox60", + "node12", + "safari11", + ], + drop, + pure, + legalComments: "none", + }, + define, - build: { - outDir: path.resolve(dir, "dist/bundle"), - minify: isProduction, - ssr: false, - lib: { - entry: path.resolve(dir, entry), - name: bundleName, - fileName: `${bundleName}.bundle.js`, - formats: ["es"], - }, - modulePreload: { polyfill: true }, - commonjsOptions: { - exclude: ["mongodb/*"], - transformMixedEsModules: true, - strictRequires: "debug", - }, + build: { + outDir: path.resolve(dir, "dist/bundle"), + minify: isProduction, + ssr: false, + lib: { + entry: path.resolve(dir, entry), + name: bundleName, + fileName: `${bundleName}.bundle.js`, + formats: ["es"], + }, + modulePreload: { polyfill: true }, + commonjsOptions: { + exclude: ["mongodb/*"], + transformMixedEsModules: true, + strictRequires: "debug", + }, - rollupOptions: { - treeshake: { - annotations: false, - propertyReadSideEffects: false, - tryCatchDeoptimization: false, - moduleSideEffects: "no-external", //true, - preset: "smallest", - unknownGlobalSideEffects: false, - }, - experimentalLogSideEffects: false, - external: allExternal, - watch: false, + rollupOptions: { + treeshake: { + annotations: false, + propertyReadSideEffects: false, + tryCatchDeoptimization: false, + moduleSideEffects: "no-external", //true, + preset: "smallest", + unknownGlobalSideEffects: false, + }, + experimentalLogSideEffects: false, + external: allExternal, + watch: false, - output: { - dir: path.resolve(dir, "dist/bundle"), - entryFileNames: `${bundleName}.bundle.js`, - }, + output: { + dir: path.resolve(dir, "dist/bundle"), + entryFileNames: `${bundleName}.bundle.js`, + }, - plugins: [ - css(), - wasm(), - // @ts-ignore - nodeResolve({ - browser: true, - preferBuiltins: false, - rootDir: path.resolve(dir, "../../"), - dedupe: ["react", "react-dom"], - modulesOnly: true, - }), - visualizer({ - open: true, - template: "treemap", //'list', - gzipSize: true, - brotliSize: true, - }), - // I think we can use this plugin to build all packages instead of relying on the tsc step that's - // currently a precursor in package.json. However, it fails for the following reason: - // https://github.com/rollup/plugins/issues/243 - // @ts-ignore - typescript({ - tsconfig: path.resolve("./tsconfig.json"), - compilerOptions: { rootDir: path.resolve("./src") }, - outDir: path.resolve(dir, "dist/bundle"), - }), - ], - }, - }, - plugins: [ - // Not sure if we need this plugin or not, it works without it - // @ts-ignore - viteReact(), - // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve - // mode, in which case we don't want to close the bundler because it will close the server - command !== "serve" ? VitePluginCloseAndCopy(copyOptions) : undefined, - // Means we can specify index.tsx instead of index.jsx in the index.html file - viteTsconfigPaths({ projects: tsConfigPaths }), - ], - }; + plugins: [ + css(), + wasm(), + // @ts-ignore + nodeResolve({ + browser: true, + preferBuiltins: false, + rootDir: path.resolve(dir, "../../"), + dedupe: ["react", "react-dom"], + modulesOnly: true, + }), + visualizer({ + open: true, + template: "treemap", //'list', + gzipSize: true, + brotliSize: true, + }), + // I think we can use this plugin to build all packages instead of relying on the tsc step that's + // currently a precursor in package.json. However, it fails for the following reason: + // https://github.com/rollup/plugins/issues/243 + // @ts-ignore + typescript({ + tsconfig: path.resolve("./tsconfig.json"), + compilerOptions: { rootDir: path.resolve("./src") }, + outDir: path.resolve(dir, "dist/bundle"), + }), + ], + }, + }, + plugins: [ + // Not sure if we need this plugin or not, it works without it + // @ts-ignore + viteReact(), + // Closes the bundler and copies the bundle to the client-bundle-example project unless we're in serve + // mode, in which case we don't want to close the bundler because it will close the server + command !== "serve" ? VitePluginCloseAndCopy(copyOptions) : undefined, + // Means we can specify index.tsx instead of index.jsx in the index.html file + viteTsconfigPaths({ projects: tsConfigPaths }), + ], + }; } diff --git a/dev/flux/package.json b/dev/flux/package.json index 8db711b8a4..095a35aa2b 100644 --- a/dev/flux/package.json +++ b/dev/flux/package.json @@ -24,7 +24,7 @@ "@noble/curves": "^1.3.0", "@polkadot/util": "12.6.2", "@polkadot/util-crypto": "12.6.2", - "@prosopo/cli": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/common": "2.0.1", "@prosopo/util": "2.0.1", "consola": "^3.2.3", diff --git a/dev/flux/src/lib/auth.ts b/dev/flux/src/lib/auth.ts index 49b1ab091e..b76da46d55 100644 --- a/dev/flux/src/lib/auth.ts +++ b/dev/flux/src/lib/auth.ts @@ -1,5 +1,3 @@ -import { base64Encode } from "@polkadot/util-crypto"; -import { loadEnv } from "@prosopo/cli"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +11,9 @@ import { loadEnv } from "@prosopo/cli"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import { base64Encode } from "@polkadot/util-crypto"; +import { loadEnv } from "@prosopo/dotenv"; import { ProsopoError, getLogger } from "@prosopo/common"; import qs from "qs"; import { errorHandler } from "../errorHandler.js"; @@ -24,230 +25,230 @@ const log = getLogger("Info", "auth.js"); export const FLUX_URL = new URL("https://api.runonflux.io/"); interface ResponseLoginPhrase { - status: string; - data: string; + status: string; + data: string; } interface DappDataResponse { - _id: string; - name_id: string; - active: boolean; - api_version: number; - contacts: string[]; - description: string; - expires: number; - expires_block: number; - expires_date: string; - expires_in: string; - fee: string; - geolocation: [ - string, - { - type: string; - cont: string; - contText: string; - data: string; - btn: string; - }, - ]; - hash: string; - hash_abbr: string; - instances: number; - lifetime_fees: number; - live: number; - name: string; - owner: string; - registered: number; - registered_date: string; - sync_date: string; - txid: string; - txs: { [key: string]: Transaction }; - updated: number; - updated_date: string; - url: string; - owner_abbr: string; - nodes_assigned: Node[]; - nodes: { [key: string]: NodeInfo }; - components_new: { - "Component Name": string; - "Component Ref": string; - Repository: string; - "Env Vars": string; - "Run Cmd": string; - Domains: string; - Directory: string; - "Public Port(s)": string; - "Private Port(s)": string; - "CPU Cores": number; - "RAM Memory": number; - "SSD Storage": number; - }[]; - domains: string[]; + _id: string; + name_id: string; + active: boolean; + api_version: number; + contacts: string[]; + description: string; + expires: number; + expires_block: number; + expires_date: string; + expires_in: string; + fee: string; + geolocation: [ + string, + { + type: string; + cont: string; + contText: string; + data: string; + btn: string; + }, + ]; + hash: string; + hash_abbr: string; + instances: number; + lifetime_fees: number; + live: number; + name: string; + owner: string; + registered: number; + registered_date: string; + sync_date: string; + txid: string; + txs: { [key: string]: Transaction }; + updated: number; + updated_date: string; + url: string; + owner_abbr: string; + nodes_assigned: Node[]; + nodes: { [key: string]: NodeInfo }; + components_new: { + "Component Name": string; + "Component Ref": string; + Repository: string; + "Env Vars": string; + "Run Cmd": string; + Domains: string; + Directory: string; + "Public Port(s)": string; + "Private Port(s)": string; + "CPU Cores": number; + "RAM Memory": number; + "SSD Storage": number; + }[]; + domains: string[]; } interface Node { - ip: string; - name: string; - broadcastedAt: string; - expireAt: string; - hash: string; + ip: string; + name: string; + broadcastedAt: string; + expireAt: string; + hash: string; } interface NodeInfo { - url: string; - fluxos: string; - ip: string; - location: string; - hash: string; - hash_abbr: string; + url: string; + fluxos: string; + ip: string; + location: string; + hash: string; + hash_abbr: string; } interface Transaction { - fee: string; - owner: string; - tx: string; - date: string; - expire: number; + fee: string; + owner: string; + tx: string; + date: string; + expire: number; } export const verifyLogin = async ( - zelid: string, - signature: string, - loginPhrase: string, - url?: URL, + zelid: string, + signature: string, + loginPhrase: string, + url?: URL, ) => { - const apiUrl = new URL(`${url || FLUX_URL}id/verifylogin`).toString(); - const data = qs.stringify({ - zelid, - signature, - loginPhrase, - }); - log.info("Data:", data); - log.info("apiUrl:", apiUrl); - const response = await fetch(apiUrl, { - method: "POST", - body: data, - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - }); - return await errorHandler(response); + const apiUrl = new URL(`${url || FLUX_URL}id/verifylogin`).toString(); + const data = qs.stringify({ + zelid, + signature, + loginPhrase, + }); + log.info("Data:", data); + log.info("apiUrl:", apiUrl); + const response = await fetch(apiUrl, { + method: "POST", + body: data, + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }); + return await errorHandler(response); }; const getLoginPhrase = async (url: URL): Promise => { - const apiURL = new URL("id/loginphrase", url); - log.info("Calling:", apiURL.href); - const response = await fetch(apiURL.toString()); - return (await errorHandler(response)).data; + const apiURL = new URL("id/loginphrase", url); + log.info("Calling:", apiURL.href); + const response = await fetch(apiURL.toString()); + return (await errorHandler(response)).data; }; export const getIndividualFluxAppDetails = async ( - dappName: string, - zelId: string, - signature: string, - loginPhrase: string, + dappName: string, + zelId: string, + signature: string, + loginPhrase: string, ): Promise => { - const apiUrl = `https://jetpackbridge.runonflux.io/api/v1/dapps.php?dapp=${dappName}&zelid=${zelId}&signature=${signature}&loginPhrase=${loginPhrase}`; - const response = await fetch(apiUrl); - return await errorHandler(response); + const apiUrl = `https://jetpackbridge.runonflux.io/api/v1/dapps.php?dapp=${dappName}&zelid=${zelId}&signature=${signature}&loginPhrase=${loginPhrase}`; + const response = await fetch(apiUrl); + return await errorHandler(response); }; const getFluxOSURLs = async ( - dappName: string, - zelId: string, - signature: string, - loginPhrase: string, + dappName: string, + zelId: string, + signature: string, + loginPhrase: string, ) => { - const data = await getIndividualFluxAppDetails( - dappName, - zelId, - signature, - loginPhrase, - ); - // return the fluxOS urls - return Object.values(data.nodes).map((node) => node.fluxos); + const data = await getIndividualFluxAppDetails( + dappName, + zelId, + signature, + loginPhrase, + ); + // return the fluxOS urls + return Object.values(data.nodes).map((node) => node.fluxos); }; export const getAuth = async (secretKey: Uint8Array, url: URL) => { - // Get Flux login phrase - const loginPhrase = await getLoginPhrase(url); - log.info("Login Phrase:", loginPhrase); + // Get Flux login phrase + const loginPhrase = await getLoginPhrase(url); + log.info("Login Phrase:", loginPhrase); - const signature = base64Encode(await sign(loginPhrase, { secretKey })); - log.info("Signature:", signature); - return { signature, loginPhrase }; + const signature = base64Encode(await sign(loginPhrase, { secretKey })); + log.info("Signature:", signature); + return { signature, loginPhrase }; }; const getNode = async ( - appName: string, - zelId: string, - signature: string, - loginPhrase: string, + appName: string, + zelId: string, + signature: string, + loginPhrase: string, ) => { - // Get details of individual Flux app - const individualNodeIPs = await getFluxOSURLs( - appName, - zelId, - signature, - loginPhrase, - ); - log.info("Individual Node IPs:", individualNodeIPs); - - // Choose a node at random from individualNodeIPs - const node = - individualNodeIPs[Math.floor(Math.random() * individualNodeIPs.length)]; - if (!node) { - throw new ProsopoError("DEVELOPER.GENERAL", { - context: { - error: "Failed to randomly select node", - appName, - zelId, - individualNodeIPs, - }, - }); - } - log.info("Node:", node); - // http as node is an IP address - return prefixIPAddress(node); + // Get details of individual Flux app + const individualNodeIPs = await getFluxOSURLs( + appName, + zelId, + signature, + loginPhrase, + ); + log.info("Individual Node IPs:", individualNodeIPs); + + // Choose a node at random from individualNodeIPs + const node = + individualNodeIPs[Math.floor(Math.random() * individualNodeIPs.length)]; + if (!node) { + throw new ProsopoError("DEVELOPER.GENERAL", { + context: { + error: "Failed to randomly select node", + appName, + zelId, + individualNodeIPs, + }, + }); + } + log.info("Node:", node); + // http as node is an IP address + return prefixIPAddress(node); }; export async function main( - publicKey: string, - privateKey: Uint8Array, - appName?: string, - ip?: string, + publicKey: string, + privateKey: Uint8Array, + appName?: string, + ip?: string, ) { - let nodeUIURL = ip ? prefixIPAddress(ip) : FLUX_URL; - - if (!ip) { - //if a flux ip has not been supplied we will first authenticate with the main flux api - const { signature, loginPhrase } = await getAuth(privateKey, nodeUIURL); - - if (appName) { - // if an app name has been specified then we are expecting to authenticate with a specific flux node - // Get a Flux node if one has not been supplied - nodeUIURL = await getNode(appName, publicKey, signature, loginPhrase); - } else { - // assume we only want authentication with main Flux API - return { - nodeUIURL: FLUX_URL, - nodeAPIURL: new URL(FLUX_URL), - nodeLoginPhrase: loginPhrase, - nodeSignature: signature, - }; - } - } - - // Get the admin API URL as it is different from the UI URL. This function should only be called once. - const nodeAPIURL = getNodeAPIURL(nodeUIURL.href); - - // Get a login token from the node - const nodeLoginPhrase = await getLoginPhrase(nodeAPIURL); - log.info("Node Login Phrase:", nodeLoginPhrase); - - // Sign the login token with zelcore private key - const nodeSignature = base64Encode( - await sign(nodeLoginPhrase, { secretKey: privateKey }), - ); - log.info("Node Signature:", nodeSignature); - - return { nodeUIURL, nodeAPIURL, nodeLoginPhrase, nodeSignature }; + let nodeUIURL = ip ? prefixIPAddress(ip) : FLUX_URL; + + if (!ip) { + //if a flux ip has not been supplied we will first authenticate with the main flux api + const { signature, loginPhrase } = await getAuth(privateKey, nodeUIURL); + + if (appName) { + // if an app name has been specified then we are expecting to authenticate with a specific flux node + // Get a Flux node if one has not been supplied + nodeUIURL = await getNode(appName, publicKey, signature, loginPhrase); + } else { + // assume we only want authentication with main Flux API + return { + nodeUIURL: FLUX_URL, + nodeAPIURL: new URL(FLUX_URL), + nodeLoginPhrase: loginPhrase, + nodeSignature: signature, + }; + } + } + + // Get the admin API URL as it is different from the UI URL. This function should only be called once. + const nodeAPIURL = getNodeAPIURL(nodeUIURL.href); + + // Get a login token from the node + const nodeLoginPhrase = await getLoginPhrase(nodeAPIURL); + log.info("Node Login Phrase:", nodeLoginPhrase); + + // Sign the login token with zelcore private key + const nodeSignature = base64Encode( + await sign(nodeLoginPhrase, { secretKey: privateKey }), + ); + log.info("Node Signature:", nodeSignature); + + return { nodeUIURL, nodeAPIURL, nodeLoginPhrase, nodeSignature }; } diff --git a/dev/flux/src/lib/sep256k1Sign.ts b/dev/flux/src/lib/sep256k1Sign.ts index a6a8d98700..cb43b21200 100644 --- a/dev/flux/src/lib/sep256k1Sign.ts +++ b/dev/flux/src/lib/sep256k1Sign.ts @@ -1,11 +1,3 @@ -import { secp256k1 } from "@noble/curves/secp256k1"; -import { bnToU8a, hexToU8a, u8aConcat, u8aToHex } from "@polkadot/util"; -import { - base58Decode, - base64Encode, - cryptoWaitReady, - sha256AsU8a, -} from "@polkadot/util-crypto"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +11,18 @@ import { // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import { secp256k1 } from "@noble/curves/secp256k1"; +import { bnToU8a, hexToU8a, u8aConcat, u8aToHex } from "@polkadot/util"; +import { + base58Decode, + base64Encode, + cryptoWaitReady, + sha256AsU8a, +} from "@polkadot/util-crypto"; import { BN_BE_256_OPTS } from "@polkadot/util-crypto/bn"; import type { Keypair } from "@polkadot/util-crypto/types"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { LogLevel, ProsopoEnvError, getLogger } from "@prosopo/common"; import { at } from "@prosopo/util"; import { isMain } from "@prosopo/util"; @@ -32,37 +33,37 @@ const logger = getLogger(LogLevel.enum.info, "flux.lib.sep256k1Sign"); const MESSAGE_MAGIC = "\u0018Bitcoin Signed Message:\n"; function hash256(buffer: Buffer) { - return Buffer.from(sha256AsU8a(sha256AsU8a(buffer))); + return Buffer.from(sha256AsU8a(sha256AsU8a(buffer))); } function hasher(message: string, messagePrefixIn: string): Buffer { - const messagePrefix = messagePrefixIn || "\u0018Bitcoin Signed Message:\n"; - const messagePrefixBuffer = Buffer.from(messagePrefix, "utf8"); - const messageBuffer = Buffer.from(message, "utf8"); - const messageVISize = varuint.encodingLength(messageBuffer.length); - const buffer = Buffer.allocUnsafe( - messagePrefix.length + messageVISize + messageBuffer.length, - ); - messagePrefixBuffer.copy(buffer, 0); - varuint.encode(messageBuffer.length, buffer, messagePrefixBuffer.length); - messageBuffer.copy(buffer, messagePrefixBuffer.length + messageVISize); - return hash256(buffer); + const messagePrefix = messagePrefixIn || "\u0018Bitcoin Signed Message:\n"; + const messagePrefixBuffer = Buffer.from(messagePrefix, "utf8"); + const messageBuffer = Buffer.from(message, "utf8"); + const messageVISize = varuint.encodingLength(messageBuffer.length); + const buffer = Buffer.allocUnsafe( + messagePrefix.length + messageVISize + messageBuffer.length, + ); + messagePrefixBuffer.copy(buffer, 0); + varuint.encode(messageBuffer.length, buffer, messagePrefixBuffer.length); + messageBuffer.copy(buffer, messagePrefixBuffer.length + messageVISize); + return hash256(buffer); } export async function sign(message: string, { secretKey }: Partial) { - if (!secretKey) - throw new ProsopoEnvError("DEVELOPER.MISSING_SECRET_KEY", { - context: { error: "No secret key provided", failedFuncName: sign.name }, - }); - await cryptoWaitReady(); - const data: Buffer = hasher(message, MESSAGE_MAGIC); - const signature = secp256k1.sign(data, secretKey); - return u8aConcat( - // add 4 for compressed and then 27 for the 27th recovery byte - Buffer.alloc(1, signature.recovery + 4 + 27), - bnToU8a(signature.r, BN_BE_256_OPTS), - bnToU8a(signature.s, BN_BE_256_OPTS), - ); + if (!secretKey) + throw new ProsopoEnvError("DEVELOPER.MISSING_SECRET_KEY", { + context: { error: "No secret key provided", failedFuncName: sign.name }, + }); + await cryptoWaitReady(); + const data: Buffer = hasher(message, MESSAGE_MAGIC); + const signature = secp256k1.sign(data, secretKey); + return u8aConcat( + // add 4 for compressed and then 27 for the 27th recovery byte + Buffer.alloc(1, signature.recovery + 4 + 27), + bnToU8a(signature.r, BN_BE_256_OPTS), + bnToU8a(signature.s, BN_BE_256_OPTS), + ); } // https://bitcoin.stackexchange.com/a/61972 @@ -70,50 +71,50 @@ export async function sign(message: string, { secretKey }: Partial) { // 0x80..................................................................6430148d // .................................................................. export function wifToPrivateKey(wif: string): Uint8Array { - let substractLength: number; + let substractLength: number; - if (wif.length === 51) { - // compression not included - substractLength = 8; // remove 4 bytes from WIF so 8 in hex - } else if (wif.length === 52) { - // compression included - substractLength = 10; // remove 5 bytes from WIF so 10 in hex - } else { - throw new ProsopoEnvError("DEVELOPER.GENERAL", { - context: { error: "Invalid WIF", failedFuncName: wifToPrivateKey.name }, - }); - } - const secretKeyHexLong = u8aToHex(base58Decode(wif)); - // remove 0x and the version byte prefix 80 from the start. Remove the Compression Byte suffix and the Checksum from - // the end - const secretKeyHex = `0x${secretKeyHexLong.substring(4, secretKeyHexLong.length - substractLength)}`; - return hexToU8a(secretKeyHex); + if (wif.length === 51) { + // compression not included + substractLength = 8; // remove 4 bytes from WIF so 8 in hex + } else if (wif.length === 52) { + // compression included + substractLength = 10; // remove 5 bytes from WIF so 10 in hex + } else { + throw new ProsopoEnvError("DEVELOPER.GENERAL", { + context: { error: "Invalid WIF", failedFuncName: wifToPrivateKey.name }, + }); + } + const secretKeyHexLong = u8aToHex(base58Decode(wif)); + // remove 0x and the version byte prefix 80 from the start. Remove the Compression Byte suffix and the Checksum from + // the end + const secretKeyHex = `0x${secretKeyHexLong.substring(4, secretKeyHexLong.length - substractLength)}`; + return hexToU8a(secretKeyHex); } // if main process if (isMain(import.meta.url)) { - const secretKey = wifToPrivateKey( - process.env.PROSOPO_ZELCORE_PRIVATE_KEY || "", - ); - const publicKey: Uint8Array = base58Decode( - process.env.PROSOPO_ZELCORE_PUBLIC_KEY || "", - ); - const keypair: Keypair = { secretKey, publicKey }; - const message = at(process.argv.slice(2), 0).trim(); - if (message.length === 0) { - console.error("No message provided"); - process.exit(); - } - sign(message, keypair) - .then((sig) => { - const hexSig = u8aToHex(sig); - logger.info(`Hex Signature : ${hexSig}`); - logger.info(`Public Key: ${publicKey}`); - logger.info(`Base64 Signature: ${base64Encode(hexSig)}`); - process.exit(); - }) - .catch((error) => { - console.error(error); - process.exit(); - }); + const secretKey = wifToPrivateKey( + process.env.PROSOPO_ZELCORE_PRIVATE_KEY || "", + ); + const publicKey: Uint8Array = base58Decode( + process.env.PROSOPO_ZELCORE_PUBLIC_KEY || "", + ); + const keypair: Keypair = { secretKey, publicKey }; + const message = at(process.argv.slice(2), 0).trim(); + if (message.length === 0) { + console.error("No message provided"); + process.exit(); + } + sign(message, keypair) + .then((sig) => { + const hexSig = u8aToHex(sig); + logger.info(`Hex Signature : ${hexSig}`); + logger.info(`Public Key: ${publicKey}`); + logger.info(`Base64 Signature: ${base64Encode(hexSig)}`); + process.exit(); + }) + .catch((error) => { + console.error(error); + process.exit(); + }); } diff --git a/dev/flux/src/lib/terminal.ts b/dev/flux/src/lib/terminal.ts index 95ee49fbd6..bb09eff63b 100644 --- a/dev/flux/src/lib/terminal.ts +++ b/dev/flux/src/lib/terminal.ts @@ -1,4 +1,3 @@ -import { loadEnv } from "@prosopo/cli"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,62 +15,63 @@ import { LogLevel, getLogger } from "@prosopo/common"; import { io } from "socket.io-client"; import { main as authMain, verifyLogin } from "./auth.js"; import { getSocketURL } from "./url.js"; +import { loadEnv } from "@prosopo/dotenv"; loadEnv(); const logger = getLogger(LogLevel.enum.info, "flux.lib.terminal"); export async function main( - publicKey: string, - privateKey: Uint8Array, - appName: string, - ip?: string, + publicKey: string, + privateKey: Uint8Array, + appName: string, + ip?: string, ) { - let running = true; - try { - // Get auth details - const { nodeAPIURL, nodeLoginPhrase, nodeSignature } = await authMain( - publicKey, - privateKey, - appName, - ip, - ); + let running = true; + try { + // Get auth details + const { nodeAPIURL, nodeLoginPhrase, nodeSignature } = await authMain( + publicKey, + privateKey, + appName, + ip, + ); - // Login to the node - await verifyLogin(publicKey, nodeSignature, nodeLoginPhrase, nodeAPIURL); + // Login to the node + await verifyLogin(publicKey, nodeSignature, nodeLoginPhrase, nodeAPIURL); - const selectedIp = nodeAPIURL.toString(); - const url = selectedIp.split(":")[0]; - if (!url) throw new Error("No url"); - const socketUrl = getSocketURL(nodeAPIURL); + const selectedIp = nodeAPIURL.toString(); + const url = selectedIp.split(":")[0]; + if (!url) throw new Error("No url"); + const socketUrl = getSocketURL(nodeAPIURL); - const socket = io(socketUrl.href); - socket.on("connect", () => { - logger.info("connected"); - logger.info(socket.id); - }); - socket.on("message", (message) => { - socket.emit("message", { - message, - id: socket.id, - }); - }); - socket.on("connect_error", (err) => { - logger.info(`connect_error due to ${err.message}`); - console.error(err); - }); - socket.on("disconnect", () => { - logger.info("disconnected"); - running = false; - }); - socket.on("error", (e) => { - logger.info("error", e); - running = false; - }); - while (running) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } catch (e) { - console.error(e); - running = false; - } + const socket = io(socketUrl.href); + socket.on("connect", () => { + logger.info("connected"); + logger.info(socket.id); + }); + socket.on("message", (message) => { + socket.emit("message", { + message, + id: socket.id, + }); + }); + socket.on("connect_error", (err) => { + logger.info(`connect_error due to ${err.message}`); + console.error(err); + }); + socket.on("disconnect", () => { + logger.info("disconnected"); + running = false; + }); + socket.on("error", (e) => { + logger.info("error", e); + running = false; + }); + while (running) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } catch (e) { + console.error(e); + running = false; + } } diff --git a/dev/flux/tsconfig.cjs.json b/dev/flux/tsconfig.cjs.json index a812c27b9d..3c45247d00 100644 --- a/dev/flux/tsconfig.cjs.json +++ b/dev/flux/tsconfig.cjs.json @@ -7,7 +7,7 @@ "include": ["./src/**/*.ts", "./src/**/*.d.ts", "./src/**/*.tsx"], "references": [ { - "path": "../../packages/cli" + "path": "../../packages/dotenv" }, { "path": "../../packages/common" diff --git a/dev/flux/tsconfig.json b/dev/flux/tsconfig.json index 507536cee8..2b19dfd989 100644 --- a/dev/flux/tsconfig.json +++ b/dev/flux/tsconfig.json @@ -8,7 +8,7 @@ "include": ["src", "src/**/*.json"], "references": [ { - "path": "../../packages/cli" + "path": "../../packages/dotenv" }, { "path": "../../packages/common" diff --git a/dev/scripts/package.json b/dev/scripts/package.json index d467ede879..2b972f3ac1 100644 --- a/dev/scripts/package.json +++ b/dev/scripts/package.json @@ -35,6 +35,7 @@ "@prosopo/database": "2.0.1", "@prosopo/datasets": "2.0.1", "@prosopo/datasets-fs": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/env": "2.0.1", "@prosopo/file-server": "2.0.1", "@prosopo/procaptcha": "2.0.1", diff --git a/dev/scripts/src/cli/index.ts b/dev/scripts/src/cli/index.ts index 8b130a96f3..173cb3771b 100644 --- a/dev/scripts/src/cli/index.ts +++ b/dev/scripts/src/cli/index.ts @@ -1,6 +1,3 @@ -import path from "node:path"; -import { isHex } from "@polkadot/util"; -import { getEnv, loadEnv } from "@prosopo/cli"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +11,10 @@ import { getEnv, loadEnv } from "@prosopo/cli"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import path from "node:path"; +import { isHex } from "@polkadot/util"; +import { getEnv, loadEnv } from "@prosopo/dotenv"; import { LogLevel, getLogger } from "@prosopo/common"; import { getLogLevel } from "@prosopo/common"; import { getScriptsPkgDir } from "@prosopo/config"; @@ -29,96 +30,96 @@ const rootDir = path.resolve("."); loadEnv(rootDir); export async function processArgs(args: string[]) { - const parsed = await yargs(hideBin(args)).option("logLevel", { - describe: "set log level", - choices: Object.keys(LogLevel.enum), - }).argv; + const parsed = await yargs(hideBin(args)).option("logLevel", { + describe: "set log level", + choices: Object.keys(LogLevel.enum), + }).argv; - const log = getLogger(getLogLevel(parsed.logLevel), "CLI"); + const log = getLogger(getLogLevel(parsed.logLevel), "CLI"); - await yargs(hideBin(args)) - .usage("Usage: $0 [global options] [options]") - .command( - "create_env_files", - "Copies the env.xyz files to .env.xyz", - (yargs) => yargs, - async () => { - const env = getEnv(); - const scripts = getScriptsPkgDir(); - await exec(`cp -v ${scripts}/env.${env} ${scripts}/.env.${env}`); - }, - [], - ) - .command({ - command: "setup", + await yargs(hideBin(args)) + .usage("Usage: $0 [global options] [options]") + .command( + "create_env_files", + "Copies the env.xyz files to .env.xyz", + (yargs) => yargs, + async () => { + const env = getEnv(); + const scripts = getScriptsPkgDir(); + await exec(`cp -v ${scripts}/env.${env} ${scripts}/.env.${env}`); + }, + [], + ) + .command({ + command: "setup", - describe: - "Setup the development environment by registering a provider, staking, loading a data set and then registering a dapp and staking.", - builder: (yargs) => - yargs.option("force", { - type: "boolean", - demandOption: false, - desc: "Force provider re-registration and dataset setup", - }), + describe: + "Setup the development environment by registering a provider, staking, loading a data set and then registering a dapp and staking.", + builder: (yargs) => + yargs.option("force", { + type: "boolean", + demandOption: false, + desc: "Force provider re-registration and dataset setup", + }), - handler: async (argv) => { - log.info("Running setup scripts"); - await setup(!!argv.force); - }, - }) - .command({ - command: "import_contract", - describe: "Import a contract into the contract package.", - builder: (yargs) => - yargs - .option("in", { - type: "string", - demandOption: true, - desc: "The path to the contract's abi", - }) - .option("out", { - type: "string", - demandOption: true, - desc: "The path to the output directory", - }), - handler: async (argv) => { - await importContract(argv.in, argv.out); - }, - }) - .command({ - command: "version", - describe: "Set the version of packages", - builder: (yargs) => - yargs.option("v", { type: "string", demandOption: true }), - handler: async (argv) => { - await setVersion(String(argv.v)); - }, - }) - .command({ - command: "token ", - describe: "Encode or Decode a Procaptcha token to the JSON output format", - builder: (yargs) => - yargs.positional("token", { - describe: "a Procaptcha token to decode", - type: "string", - demandOption: true, - }), - handler: async (argv) => { - if (!isHex(argv.token)) { - log.debug("Encoding token to hex"); - log.info(encodeProcaptchaOutput(JSON.parse(argv.token))); - } else { - log.debug("Decoding token from hex"); - log.info(decodeProcaptchaOutput(argv.token)); - } - }, - }).argv; + handler: async (argv) => { + log.info("Running setup scripts"); + await setup(!!argv.force); + }, + }) + .command({ + command: "import_contract", + describe: "Import a contract into the contract package.", + builder: (yargs) => + yargs + .option("in", { + type: "string", + demandOption: true, + desc: "The path to the contract's abi", + }) + .option("out", { + type: "string", + demandOption: true, + desc: "The path to the output directory", + }), + handler: async (argv) => { + await importContract(argv.in, argv.out); + }, + }) + .command({ + command: "version", + describe: "Set the version of packages", + builder: (yargs) => + yargs.option("v", { type: "string", demandOption: true }), + handler: async (argv) => { + await setVersion(String(argv.v)); + }, + }) + .command({ + command: "token ", + describe: "Encode or Decode a Procaptcha token to the JSON output format", + builder: (yargs) => + yargs.positional("token", { + describe: "a Procaptcha token to decode", + type: "string", + demandOption: true, + }), + handler: async (argv) => { + if (!isHex(argv.token)) { + log.debug("Encoding token to hex"); + log.info(encodeProcaptchaOutput(JSON.parse(argv.token))); + } else { + log.debug("Decoding token from hex"); + log.info(decodeProcaptchaOutput(argv.token)); + } + }, + }).argv; } processArgs(process.argv) - .then(() => { - process.exit(0); - }) - .catch((error) => { - console.error(error); - process.exit(1); - }); + .then(() => { + process.exit(0); + }) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/dev/scripts/src/scripts/generateMnemonic.ts b/dev/scripts/src/scripts/generateMnemonic.ts index 0d701116e8..49a46e1203 100644 --- a/dev/scripts/src/scripts/generateMnemonic.ts +++ b/dev/scripts/src/scripts/generateMnemonic.ts @@ -12,32 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { LogLevel, getLogger } from "@prosopo/common"; import { generateMnemonic } from "@prosopo/contract"; import { updateEnvFile } from "../setup/index.js"; loadEnv(); const logger = getLogger( - process.env.PROSOPO_LOG_LEVEL || LogLevel.enum.info, - "generateMnemonic", + process.env.PROSOPO_LOG_LEVEL || LogLevel.enum.info, + "generateMnemonic", ); async function mnemonic(addToEnv: boolean) { - const [mnemonic, address] = await generateMnemonic(); - logger.info(`Address: ${address}`); - logger.info(`Mnemonic: ${mnemonic}`); - if (addToEnv) { - await updateEnvFile({ - PROSOPO_PROVIDER_MNEMONIC: `"${mnemonic}"`, - PROSOPO_PROVIDER_ADDRESS: address, - }); - } + const [mnemonic, address] = await generateMnemonic(); + logger.info(`Address: ${address}`); + logger.info(`Mnemonic: ${mnemonic}`); + if (addToEnv) { + await updateEnvFile({ + PROSOPO_PROVIDER_MNEMONIC: `"${mnemonic}"`, + PROSOPO_PROVIDER_ADDRESS: address, + }); + } } mnemonic(process.argv.includes("--env")) - .then(() => process.exit(0)) - .catch((e) => { - console.error(e); - process.exit(1); - }); + .then(() => process.exit(0)) + .catch((e) => { + console.error(e); + process.exit(1); + }); diff --git a/dev/scripts/src/scripts/setVersion.ts b/dev/scripts/src/scripts/setVersion.ts index 89855b5066..ffe685797b 100644 --- a/dev/scripts/src/scripts/setVersion.ts +++ b/dev/scripts/src/scripts/setVersion.ts @@ -1,7 +1,3 @@ -import fs from "node:fs"; -import path from "node:path"; -import { parse, stringify } from "@iarna/toml"; -import { loadEnv } from "@prosopo/cli"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +11,11 @@ import { loadEnv } from "@prosopo/cli"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import fs from "node:fs"; +import path from "node:path"; +import { parse, stringify } from "@iarna/toml"; +import { loadEnv } from "@prosopo/dotenv"; import { getLogLevel, getLogger } from "@prosopo/common"; import { getRootDir } from "@prosopo/config"; @@ -26,149 +27,149 @@ const log = getLogger(logLevel, "setVersion"); log.info("Log level:", logLevel); const parseVersion = (version: string) => { - try { - const parts = version.split("."); - if (parts.length !== 3) { - throw new Error(); - } - let [major, minor, patch] = parts; - major = Number.parseInt(major ?? "").toString(); - minor = Number.parseInt(minor ?? "").toString(); - patch = Number.parseInt(patch ?? "").toString(); - if (major === "NaN" || minor === "NaN" || patch === "NaN") { - throw new Error(); - } - return `${major}.${minor}.${patch}`; - } catch (e) { - throw new Error("Version must be in the format of x.y.z"); - } + try { + const parts = version.split("."); + if (parts.length !== 3) { + throw new Error(); + } + let [major, minor, patch] = parts; + major = Number.parseInt(major ?? "").toString(); + minor = Number.parseInt(minor ?? "").toString(); + patch = Number.parseInt(patch ?? "").toString(); + if (major === "NaN" || minor === "NaN" || patch === "NaN") { + throw new Error(); + } + return `${major}.${minor}.${patch}`; + } catch (e) { + throw new Error("Version must be in the format of x.y.z"); + } }; const find = (pth: string, filter: (pth: string) => boolean): string[] => { - const files = fs.readdirSync(pth); - const results: string[] = []; - for (const file of files) { - const fullPath = path.join(pth, file); - if (filter(fullPath)) { - results.push(fullPath); - } - try { - if (fs.statSync(fullPath).isDirectory()) { - results.push(...find(fullPath, filter)); - } - } catch (e) { - log.debug("Not a directory: {fullPath}"); - } - } - return results; + const files = fs.readdirSync(pth); + const results: string[] = []; + for (const file of files) { + const fullPath = path.join(pth, file); + if (filter(fullPath)) { + results.push(fullPath); + } + try { + if (fs.statSync(fullPath).isDirectory()) { + results.push(...find(fullPath, filter)); + } + } catch (e) { + log.debug("Not a directory: {fullPath}"); + } + } + return results; }; export default async function setVersion(versionIn: string, ignore?: string[]) { - log.info("Setting version to ", versionIn); - const version = parseVersion(versionIn); - const root = getRootDir(); - const ignorePaths = ["node_modules", "cargo-cache", ...(ignore ?? [])]; - log.debug("Ignoring paths: ", ignorePaths); - // walk through all files finding .json or .toml - const files = find(root, (pth) => { - // ignore node_modules and any user specified paths - if (ignorePaths.some((ignorePath) => pth.includes(ignorePath))) { - return false; - } - const basename = path.basename(pth); - return basename === "package.json" || basename === "Cargo.toml"; - }); - // split into json and toml - // biome-ignore lint/complexity/noForEach: TODO fix - files - .filter((pth) => path.extname(pth) === ".json") - .forEach((pth) => { - log.debug("setting version in", pth); - const content = fs.readFileSync(pth, "utf8"); - // replace version in all json files - const jsonContent = JSON.parse(content); - if (jsonContent.version) { - // only replace if version is set - jsonContent.version = version; - } - // go through dependencies - for (const obj of [ - jsonContent.dependencies ?? {}, - jsonContent.devDependencies ?? {}, - jsonContent.peerDependencies ?? {}, - ]) { - // detect any prosopo dependencies - for (const key of Object.keys(obj)) { - if (key.startsWith("@prosopo") && !key.includes("typechain")) { - // and replace version - log.debug(`setting ${key} to ${version} in ${pth}`); - obj[key] = version; - } - } - } - fs.writeFileSync(pth, `${JSON.stringify(jsonContent, null, 4)}\n`); - }); + log.info("Setting version to ", versionIn); + const version = parseVersion(versionIn); + const root = getRootDir(); + const ignorePaths = ["node_modules", "cargo-cache", ...(ignore ?? [])]; + log.debug("Ignoring paths: ", ignorePaths); + // walk through all files finding .json or .toml + const files = find(root, (pth) => { + // ignore node_modules and any user specified paths + if (ignorePaths.some((ignorePath) => pth.includes(ignorePath))) { + return false; + } + const basename = path.basename(pth); + return basename === "package.json" || basename === "Cargo.toml"; + }); + // split into json and toml + // biome-ignore lint/complexity/noForEach: TODO fix + files + .filter((pth) => path.extname(pth) === ".json") + .forEach((pth) => { + log.debug("setting version in", pth); + const content = fs.readFileSync(pth, "utf8"); + // replace version in all json files + const jsonContent = JSON.parse(content); + if (jsonContent.version) { + // only replace if version is set + jsonContent.version = version; + } + // go through dependencies + for (const obj of [ + jsonContent.dependencies ?? {}, + jsonContent.devDependencies ?? {}, + jsonContent.peerDependencies ?? {}, + ]) { + // detect any prosopo dependencies + for (const key of Object.keys(obj)) { + if (key.startsWith("@prosopo") && !key.includes("typechain")) { + // and replace version + log.debug(`setting ${key} to ${version} in ${pth}`); + obj[key] = version; + } + } + } + fs.writeFileSync(pth, `${JSON.stringify(jsonContent, null, 4)}\n`); + }); - // replace version in tomls - // biome-ignore lint/complexity/noForEach: TODO fix - files - .filter((pth) => path.extname(pth) === ".toml") - .filter((pth) => { - // ignore node_modules and any user specified paths - return !ignorePaths.some((ignorePath) => pth.includes(ignorePath)); - }) - .forEach((pth) => { - log.debug("setting version in", pth); - const content = fs.readFileSync(pth, "utf8"); - // replace version in all toml files - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - const tomlContent: any = parse(content); - if (tomlContent.workspace) { - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - if ((tomlContent.workspace as any).version) { - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - (tomlContent.workspace as any).version = version; - } - } else { - // replace dependency versions in all toml files - tomlContent.package.version = version; - } - fs.writeFileSync(pth, `${stringify(tomlContent)}\n`); - }); + // replace version in tomls + // biome-ignore lint/complexity/noForEach: TODO fix + files + .filter((pth) => path.extname(pth) === ".toml") + .filter((pth) => { + // ignore node_modules and any user specified paths + return !ignorePaths.some((ignorePath) => pth.includes(ignorePath)); + }) + .forEach((pth) => { + log.debug("setting version in", pth); + const content = fs.readFileSync(pth, "utf8"); + // replace version in all toml files + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + const tomlContent: any = parse(content); + if (tomlContent.workspace) { + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + if ((tomlContent.workspace as any).version) { + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + (tomlContent.workspace as any).version = version; + } + } else { + // replace dependency versions in all toml files + tomlContent.package.version = version; + } + fs.writeFileSync(pth, `${stringify(tomlContent)}\n`); + }); - // go through tomls again now versions have updated and update the version field for dependencies with paths set, as we can follow the path to get the version - // biome-ignore lint/complexity/noForEach: TODO fix - files - .filter((pth) => path.extname(pth) === ".toml") - .forEach((pth) => { - log.debug("setting dependency versions in", pth); - const content = fs.readFileSync(pth, "utf8"); - // replace version in all toml files - const tomlContent = parse(content); - if (tomlContent.workspace) { - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - if ((tomlContent.workspace as any).version) { - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - (tomlContent.workspace as any).version = version; - } - } else { - for (const obj of [ - tomlContent.dependencies ?? {}, - tomlContent["dev-dependencies"] ?? {}, - ]) { - // detect any prosopo dependencies - for (const [key, value] of Object.entries(obj)) { - if (value.path) { - // trace path to get version - path.join(value.path, "Cargo.toml"); - const depContent = fs.readFileSync(pth, "utf8"); - const depTomlContent = parse(depContent); - value.version = depTomlContent.version; - } - } - } - } - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - fs.writeFileSync(pth, `${stringify(tomlContent as any)}\n`); - }); + // go through tomls again now versions have updated and update the version field for dependencies with paths set, as we can follow the path to get the version + // biome-ignore lint/complexity/noForEach: TODO fix + files + .filter((pth) => path.extname(pth) === ".toml") + .forEach((pth) => { + log.debug("setting dependency versions in", pth); + const content = fs.readFileSync(pth, "utf8"); + // replace version in all toml files + const tomlContent = parse(content); + if (tomlContent.workspace) { + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + if ((tomlContent.workspace as any).version) { + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + (tomlContent.workspace as any).version = version; + } + } else { + for (const obj of [ + tomlContent.dependencies ?? {}, + tomlContent["dev-dependencies"] ?? {}, + ]) { + // detect any prosopo dependencies + for (const [key, value] of Object.entries(obj)) { + if (value.path) { + // trace path to get version + path.join(value.path, "Cargo.toml"); + const depContent = fs.readFileSync(pth, "utf8"); + const depTomlContent = parse(depContent); + value.version = depTomlContent.version; + } + } + } + } + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + fs.writeFileSync(pth, `${stringify(tomlContent as any)}\n`); + }); } diff --git a/dev/scripts/src/setup/setup.ts b/dev/scripts/src/setup/setup.ts index 01adba2644..d227f1c7d8 100644 --- a/dev/scripts/src/setup/setup.ts +++ b/dev/scripts/src/setup/setup.ts @@ -1,4 +1,3 @@ -import path from "node:path"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,17 +11,19 @@ import path from "node:path"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import path from "node:path"; import { BN } from "@polkadot/util"; import { isAddress } from "@polkadot/util-crypto"; import { defaultConfig, getSecret } from "@prosopo/cli"; -import { getEnvFile } from "@prosopo/cli"; +import { getEnvFile } from "@prosopo/dotenv"; import { LogLevel, ProsopoEnvError, getLogger } from "@prosopo/common"; import { generateMnemonic, getPairAsync } from "@prosopo/contract"; import { ProviderEnvironment } from "@prosopo/env"; import { - type IDappAccount, - type IProviderAccount, - Payee, + type IDappAccount, + type IProviderAccount, + Payee, } from "@prosopo/types"; import { get } from "@prosopo/util"; import fse from "fs-extra"; @@ -34,155 +35,155 @@ const __dirname = path.resolve(); // Take the root dir from the environment or assume it's the root of this package function getRootDir() { - const rootDir = - process.env.PROSOPO_ROOT_DIR || path.resolve(__dirname, "../.."); - logger.info("Root dir:", rootDir); - return rootDir; + const rootDir = + process.env.PROSOPO_ROOT_DIR || path.resolve(__dirname, "../.."); + logger.info("Root dir:", rootDir); + return rootDir; } function getDatasetFilePath() { - const datasetFile = - process.env.PROSOPO_PROVIDER_DATASET_FILE || - path.resolve("../data/captchas.json"); - logger.info("Dataset file:", datasetFile); - return datasetFile; + const datasetFile = + process.env.PROSOPO_PROVIDER_DATASET_FILE || + path.resolve("../data/captchas.json"); + logger.info("Dataset file:", datasetFile); + return datasetFile; } function getDefaultProvider(): IProviderAccount { - const host = process.env.PROSOPO_PROVIDER_HOST || "localhost"; - return { - url: process.env.PROSOPO_API_PORT - ? `http://${host}:${process.env.PROSOPO_API_PORT}` - : `http://${host}:9229`, - fee: 10, - payee: Payee.dapp, - stake: new BN(10 ** 13), - datasetFile: getDatasetFilePath(), - address: process.env.PROSOPO_PROVIDER_ADDRESS || "", - secret: getSecret(), - captchaDatasetId: "", - }; + const host = process.env.PROSOPO_PROVIDER_HOST || "localhost"; + return { + url: process.env.PROSOPO_API_PORT + ? `http://${host}:${process.env.PROSOPO_API_PORT}` + : `http://${host}:9229`, + fee: 10, + payee: Payee.dapp, + stake: new BN(10 ** 13), + datasetFile: getDatasetFilePath(), + address: process.env.PROSOPO_PROVIDER_ADDRESS || "", + secret: getSecret(), + captchaDatasetId: "", + }; } function getDefaultDapp(): IDappAccount { - return { - secret: "//Eve", - fundAmount: new BN(10 ** 12), - }; + return { + secret: "//Eve", + fundAmount: new BN(10 ** 12), + }; } async function copyEnvFile() { - try { - const rootDir = getRootDir(); - // TODO move all env files to a single template location - const tplLocation = path.resolve(rootDir, "./dev/scripts"); - const tplEnvFile = getEnvFile(tplLocation, "env"); - const envFile = getEnvFile(tplLocation, ".env"); - await fse.copy(tplEnvFile, envFile, { overwrite: false }); - } catch (err) { - logger.debug(err); - } + try { + const rootDir = getRootDir(); + // TODO move all env files to a single template location + const tplLocation = path.resolve(rootDir, "./dev/scripts"); + const tplEnvFile = getEnvFile(tplLocation, "env"); + const envFile = getEnvFile(tplLocation, ".env"); + await fse.copy(tplEnvFile, envFile, { overwrite: false }); + } catch (err) { + logger.debug(err); + } } function updateEnvFileVar(source: string, name: string, value: string) { - const envVar = new RegExp(`.*(${name}=)(.*)`, "g"); - if (envVar.test(source)) { - return source.replace(envVar, `$1${value}`); - } - return `${source}\n${name}=${value}`; + const envVar = new RegExp(`.*(${name}=)(.*)`, "g"); + if (envVar.test(source)) { + return source.replace(envVar, `$1${value}`); + } + return `${source}\n${name}=${value}`; } export async function updateEnvFile(vars: Record) { - const rootDir = getRootDir(); - const envFile = getEnvFile(rootDir, ".env"); + const rootDir = getRootDir(); + const envFile = getEnvFile(rootDir, ".env"); - let readEnvFile = await fse.readFile(envFile, "utf8"); + let readEnvFile = await fse.readFile(envFile, "utf8"); - for (const key in vars) { - readEnvFile = updateEnvFileVar(readEnvFile, key, get(vars, key)); - } - logger.info(`Updating ${envFile}`); - await fse.writeFile(envFile, readEnvFile); + for (const key in vars) { + readEnvFile = updateEnvFileVar(readEnvFile, key, get(vars, key)); + } + logger.info(`Updating ${envFile}`); + await fse.writeFile(envFile, readEnvFile); } export async function setup(force: boolean) { - const defaultProvider = getDefaultProvider(); - const defaultDapp = getDefaultDapp(); - - if (defaultProvider.secret) { - const hasProviderAccount = - defaultProvider.address && defaultProvider.secret; - logger.debug("ENVIRONMENT", process.env.NODE_ENV); - - const [mnemonic, address] = !hasProviderAccount - ? await generateMnemonic() - : [defaultProvider.secret, defaultProvider.address]; - - logger.debug(`Address: ${address}`); - logger.debug(`Mnemonic: ${mnemonic}`); - logger.debug("Writing .env file..."); - await copyEnvFile(); - - if (!process.env.PROSOPO_SITE_KEY) { - throw new ProsopoEnvError("DEVELOPER.PROSOPO_SITE_KEY_MISSING"); - } - - const config = defaultConfig(); - const providerSecret = config.account.secret; - const network = config.networks[config.defaultNetwork]; - const pair = await getPairAsync(network, providerSecret); - - const env = new ProviderEnvironment(defaultConfig(), pair); - await env.isReady(); - - defaultProvider.secret = mnemonic; - - env.logger.info(`Registering provider... ${defaultProvider.address}`); - - defaultProvider.pair = await getPairAsync(network, providerSecret); - - // If no PROSOPO_SITE_KEY is present, we will register a test account like //Eve. - // If a PROSOPO_SITE_KEY is present, we want to register it in the contract. - // If a DAPP_SECRET is present, we want the PROSOPO_SITE_KEY account to register itself. - // Otherwise, a test account like //Eve is used to register the PROSOPO_SITE_KEY account. - defaultDapp.pair = await getPairAsync(network, defaultDapp.secret); - let dappAddressToRegister = defaultDapp.pair.address; - if ( - process.env.PROSOPO_SITE_KEY && - isAddress(process.env.PROSOPO_SITE_KEY) - ) { - dappAddressToRegister = process.env.PROSOPO_SITE_KEY; - if (process.env.PROSOPO_SITE_PRIVATE_KEY) { - defaultDapp.secret = process.env.PROSOPO_SITE_PRIVATE_KEY; - defaultDapp.pair = await getPairAsync(network, defaultDapp.secret); - dappAddressToRegister = defaultDapp.pair.address; - } - } - - await setupProvider(env, defaultProvider); - - env.logger.info(`Registering dapp... ${defaultDapp.pair.address}`); - - if (!hasProviderAccount) { - await updateEnvFile({ - PROVIDER_MNEMONIC: `"${mnemonic}"`, - PROVIDER_ADDRESS: address, - }); - } - env.logger.debug("Updating env files with PROSOPO_SITE_KEY"); - await updateDemoHTMLFiles( - [/data-sitekey="(\w{48})"/, /siteKey:\s*'(\w{48})'/], - defaultDapp.pair.address, - env.logger, - ); - await updateEnvFiles( - ["NEXT_PUBLIC_PROSOPO_SITE_KEY", "PROSOPO_SITE_KEY"], - defaultDapp.pair.address, - env.logger, - ); - process.exit(); - } else { - console.error("no secret found in .env file"); - throw new ProsopoEnvError("GENERAL.NO_MNEMONIC_OR_SEED"); - } + const defaultProvider = getDefaultProvider(); + const defaultDapp = getDefaultDapp(); + + if (defaultProvider.secret) { + const hasProviderAccount = + defaultProvider.address && defaultProvider.secret; + logger.debug("ENVIRONMENT", process.env.NODE_ENV); + + const [mnemonic, address] = !hasProviderAccount + ? await generateMnemonic() + : [defaultProvider.secret, defaultProvider.address]; + + logger.debug(`Address: ${address}`); + logger.debug(`Mnemonic: ${mnemonic}`); + logger.debug("Writing .env file..."); + await copyEnvFile(); + + if (!process.env.PROSOPO_SITE_KEY) { + throw new ProsopoEnvError("DEVELOPER.PROSOPO_SITE_KEY_MISSING"); + } + + const config = defaultConfig(); + const providerSecret = config.account.secret; + const network = config.networks[config.defaultNetwork]; + const pair = await getPairAsync(network, providerSecret); + + const env = new ProviderEnvironment(defaultConfig(), pair); + await env.isReady(); + + defaultProvider.secret = mnemonic; + + env.logger.info(`Registering provider... ${defaultProvider.address}`); + + defaultProvider.pair = await getPairAsync(network, providerSecret); + + // If no PROSOPO_SITE_KEY is present, we will register a test account like //Eve. + // If a PROSOPO_SITE_KEY is present, we want to register it in the contract. + // If a DAPP_SECRET is present, we want the PROSOPO_SITE_KEY account to register itself. + // Otherwise, a test account like //Eve is used to register the PROSOPO_SITE_KEY account. + defaultDapp.pair = await getPairAsync(network, defaultDapp.secret); + let dappAddressToRegister = defaultDapp.pair.address; + if ( + process.env.PROSOPO_SITE_KEY && + isAddress(process.env.PROSOPO_SITE_KEY) + ) { + dappAddressToRegister = process.env.PROSOPO_SITE_KEY; + if (process.env.PROSOPO_SITE_PRIVATE_KEY) { + defaultDapp.secret = process.env.PROSOPO_SITE_PRIVATE_KEY; + defaultDapp.pair = await getPairAsync(network, defaultDapp.secret); + dappAddressToRegister = defaultDapp.pair.address; + } + } + + await setupProvider(env, defaultProvider); + + env.logger.info(`Registering dapp... ${defaultDapp.pair.address}`); + + if (!hasProviderAccount) { + await updateEnvFile({ + PROVIDER_MNEMONIC: `"${mnemonic}"`, + PROVIDER_ADDRESS: address, + }); + } + env.logger.debug("Updating env files with PROSOPO_SITE_KEY"); + await updateDemoHTMLFiles( + [/data-sitekey="(\w{48})"/, /siteKey:\s*'(\w{48})'/], + defaultDapp.pair.address, + env.logger, + ); + await updateEnvFiles( + ["NEXT_PUBLIC_PROSOPO_SITE_KEY", "PROSOPO_SITE_KEY"], + defaultDapp.pair.address, + env.logger, + ); + process.exit(); + } else { + console.error("no secret found in .env file"); + throw new ProsopoEnvError("GENERAL.NO_MNEMONIC_OR_SEED"); + } } diff --git a/dev/scripts/src/util/updateEnv.ts b/dev/scripts/src/util/updateEnv.ts index a0dced2a4c..32829e6966 100644 --- a/dev/scripts/src/util/updateEnv.ts +++ b/dev/scripts/src/util/updateEnv.ts @@ -1,6 +1,3 @@ -import fs from "node:fs"; -import path from "node:path"; -import { getEnv } from "@prosopo/cli"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,93 +11,97 @@ import { getEnv } from "@prosopo/cli"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import fs from "node:fs"; +import path from "node:path"; +import { getEnv } from "@prosopo/dotenv"; import type { Logger } from "@prosopo/common"; import { at } from "@prosopo/util"; import dotenv from "dotenv"; import fg from "fast-glob"; const ignore = [ - "**/node_modules/**", - "node_modules/**", - "../../**/node_modules/**", - "../node_modules/**", - "../../node_modules/**", + "**/node_modules/**", + "node_modules/**", + "../../**/node_modules/**", + "../node_modules/**", + "../../node_modules/**", ]; const __dirname = path.resolve(); export async function findEnvFiles(logger: Logger, cwd?: string) { - const env = getEnv(); - const fileName = `.env.${env}`; - // options is optional - logger.info("Searching for files"); - return await fg(`${cwd || "../.."}/**/${fileName}`, { - ignore, - }); + const env = getEnv(); + const fileName = `.env.${env}`; + // options is optional + logger.info("Searching for files"); + return await fg(`${cwd || "../.."}/**/${fileName}`, { + ignore, + }); } export async function updateDemoHTMLFiles( - varMatchers: RegExp[], - varValue: string, - logger: Logger, + varMatchers: RegExp[], + varValue: string, + logger: Logger, ) { - // replace the site key in the html files - const files = await fg("../../demos/**/*.html", { - ignore: ignore, - }); - logger.info("HTML files found", files); - // biome-ignore lint/complexity/noForEach: TODO fix - files.forEach((file) => { - // the following code loads a .env file, searches for the variable and replaces it - // then saves the file - const filePath = path.resolve(process.cwd(), file); - const contents = fs.readFileSync(filePath).toString(); - let newContents = contents; - for (const varMatcher of varMatchers) { - const matches = contents.match(varMatcher); - if (matches) { - // replace the site key - const matchedVar = at(matches, 1); - logger.info("matchedVar", matchedVar); - newContents = contents.replaceAll(matchedVar, varValue); - break; - } - } + // replace the site key in the html files + const files = await fg("../../demos/**/*.html", { + ignore: ignore, + }); + logger.info("HTML files found", files); + // biome-ignore lint/complexity/noForEach: TODO fix + files.forEach((file) => { + // the following code loads a .env file, searches for the variable and replaces it + // then saves the file + const filePath = path.resolve(process.cwd(), file); + const contents = fs.readFileSync(filePath).toString(); + let newContents = contents; + for (const varMatcher of varMatchers) { + const matches = contents.match(varMatcher); + if (matches) { + // replace the site key + const matchedVar = at(matches, 1); + logger.info("matchedVar", matchedVar); + newContents = contents.replaceAll(matchedVar, varValue); + break; + } + } - if (newContents !== contents) { - // write the file back - fs.writeFileSync(path.resolve(__dirname, filePath), newContents); - } - }); + if (newContents !== contents) { + // write the file back + fs.writeFileSync(path.resolve(__dirname, filePath), newContents); + } + }); } export async function updateEnvFiles( - varNames: string[], - varValue: string, - logger: Logger, - cwd?: string, + varNames: string[], + varValue: string, + logger: Logger, + cwd?: string, ) { - const files = await findEnvFiles(logger, cwd); - logger.info("Env files found", files); - // biome-ignore lint/complexity/noForEach: TODO fix - files.forEach((file) => { - let write = false; - // the following code loads a .env file, searches for the variable and replaces it - // then saves the file - const filePath = path.resolve(cwd || process.cwd(), file); - const envConfig = dotenv.parse(fs.readFileSync(filePath)); - for (const varName of varNames) { - if (varName in envConfig) { - envConfig[varName] = varValue; - write = true; - } - } - if (write) { - // write the file back - fs.writeFileSync( - path.resolve(__dirname, filePath), - Object.keys(envConfig) - .map((k) => `${k}=${envConfig[k]}`) - .join("\n"), - ); - } - }); + const files = await findEnvFiles(logger, cwd); + logger.info("Env files found", files); + // biome-ignore lint/complexity/noForEach: TODO fix + files.forEach((file) => { + let write = false; + // the following code loads a .env file, searches for the variable and replaces it + // then saves the file + const filePath = path.resolve(cwd || process.cwd(), file); + const envConfig = dotenv.parse(fs.readFileSync(filePath)); + for (const varName of varNames) { + if (varName in envConfig) { + envConfig[varName] = varValue; + write = true; + } + } + if (write) { + // write the file back + fs.writeFileSync( + path.resolve(__dirname, filePath), + Object.keys(envConfig) + .map((k) => `${k}=${envConfig[k]}`) + .join("\n"), + ); + } + }); } diff --git a/dev/scripts/tsconfig.json b/dev/scripts/tsconfig.json index b11a66f64f..4ba13d5e50 100644 --- a/dev/scripts/tsconfig.json +++ b/dev/scripts/tsconfig.json @@ -34,6 +34,9 @@ { "path": "../../packages/datasets-fs" }, + { + "path": "../../packages/dotenv" + }, { "path": "../../packages/env" }, diff --git a/package-lock.json b/package-lock.json index e517f9df40..cf08c0e9f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,8 +85,8 @@ "react-router-dom": "^6.22.3" }, "devDependencies": { - "@prosopo/cli": "2.0.1", "@prosopo/config": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/vite-plugin-watch-workspace": "2.0.1", "@types/node": "^20.3.1", "css-loader": "^6.8.1", @@ -119,6 +119,7 @@ }, "devDependencies": { "@prosopo/config": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@types/jsonwebtoken": "^9.0.2", "tslib": "2.6.2", "typescript": "5.1.6", @@ -148,8 +149,8 @@ "web-vitals": "^2.1.4" }, "devDependencies": { - "@prosopo/cli": "2.0.1", "@prosopo/config": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@types/node": "^20.3.1", "css-loader": "^6.8.1", "tslib": "2.6.2", @@ -179,8 +180,8 @@ "web-vitals": "^2.1.4" }, "devDependencies": { - "@prosopo/cli": "2.0.1", "@prosopo/config": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@types/node": "^20.3.1", "css-loader": "^6.8.1", "tslib": "2.6.2", @@ -220,8 +221,8 @@ "version": "2.0.1", "license": "Apache-2.0", "dependencies": { - "@prosopo/cli": "2.0.1", "@prosopo/common": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/types": "2.0.1", "es-main": "^1.3.0", "express": "^4.18.1" @@ -344,8 +345,8 @@ "@noble/curves": "^1.3.0", "@polkadot/util": "12.6.2", "@polkadot/util-crypto": "12.6.2", - "@prosopo/cli": "2.0.1", "@prosopo/common": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/util": "2.0.1", "consola": "^3.2.3", "dotenv": "^16.0.3", @@ -510,6 +511,7 @@ "@prosopo/database": "2.0.1", "@prosopo/datasets": "2.0.1", "@prosopo/datasets-fs": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/env": "2.0.1", "@prosopo/file-server": "2.0.1", "@prosopo/procaptcha": "2.0.1", @@ -4673,6 +4675,10 @@ "resolved": "packages/detector", "link": true }, + "node_modules/@prosopo/dotenv": { + "resolved": "packages/dotenv", + "link": true + }, "node_modules/@prosopo/env": { "resolved": "packages/env", "link": true @@ -18844,6 +18850,17 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unique-filename": { "version": "2.0.1", "dev": true, @@ -20403,6 +20420,7 @@ "@polkadot/util-crypto": "12.6.2", "@prosopo/common": "2.0.1", "@prosopo/contract": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/env": "2.0.1", "@prosopo/provider": "2.0.1", "@prosopo/types": "2.0.1", @@ -20595,6 +20613,75 @@ "node": ">=14.17" } }, + "packages/dotenv": { + "name": "@prosopo/dotenv", + "version": "2.0.1", + "license": "Apache-2.0", + "dependencies": { + "dotenv": "^16.0.1", + "find-up": "^7.0.0" + }, + "devDependencies": { + "tslib": "2.6.2", + "typescript": "5.1.6" + }, + "engines": { + "node": ">=20", + "npm": ">=9" + } + }, + "packages/dotenv/node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/dotenv/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/dotenv/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/dotenv/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "packages/env": { "name": "@prosopo/env", "version": "2.0.1", @@ -21005,14 +21092,13 @@ "dependencies": { "@noble/hashes": "^1.3.3", "@prosopo/config": "2.0.1", - "@prosopo/util": "2.0.1", + "dotenv": "^16.0.1", "lodash": "^4.17.21", "seedrandom": "^3.0.5" }, "devDependencies": { "@types/lodash": "^4.14.198", "@types/seedrandom": "^3.0.5", - "dotenv": "^16.0.1", "tslib": "2.6.2", "typescript": "5.1.6", "vitest": "^1.3.1" diff --git a/packages/cli/package.json b/packages/cli/package.json index 2d4d727541..eb952b1449 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,6 +29,7 @@ "@polkadot/util-crypto": "12.6.2", "@prosopo/common": "2.0.1", "@prosopo/contract": "2.0.1", + "@prosopo/dotenv": "2.0.1", "@prosopo/env": "2.0.1", "@prosopo/provider": "2.0.1", "@prosopo/types": "2.0.1", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 9b6a5064f4..913f9bd214 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -17,67 +17,65 @@ import { getPairAsync } from "@prosopo/contract"; import type { ProsopoConfigOutput } from "@prosopo/types"; import { isMain } from "@prosopo/util"; import { processArgs } from "./argv.js"; -import { loadEnv } from "./env.js"; +import { loadEnv } from "@prosopo/dotenv"; import getConfig from "./prosopo.config.js"; import ReloadingAPI from "./reloader.js"; const log = getLogger(LogLevel.enum.info, "CLI"); async function main() { - const envPath = loadEnv(); + const envPath = loadEnv(); - // quick fix to allow for new dataset structure that only has `{ solved: true }` captchas - const config: ProsopoConfigOutput = getConfig( - undefined, - undefined, - undefined, - { - solved: { count: 2 }, - unsolved: { count: 0 }, - }, - ); + // quick fix to allow for new dataset structure that only has `{ solved: true }` captchas + const config: ProsopoConfigOutput = getConfig( + undefined, + undefined, + undefined, + { + solved: { count: 2 }, + unsolved: { count: 0 }, + }, + ); - if (config.devOnlyWatchEvents) { - log.warn( - ` + if (config.devOnlyWatchEvents) { + log.warn( + ` ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! EVENT TRACKING ON. IF NOT DEVELOPMENT, PLEASE STOP, CHANGE THE ENVIRONMENT, AND RESTART ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! `, - ); - } + ); + } - const pair = await getPairAsync( - config.networks[config.defaultNetwork], - config.account.secret, - config.account.address, - ); + const pair = await getPairAsync( + config.networks[config.defaultNetwork], + config.account.secret, + config.account.address, + ); - log.info(`Pair address: ${pair.address}`); + log.info(`Pair address: ${pair.address}`); - log.info(`Contract address: ${process.env.PROSOPO_CONTRACT_ADDRESS}`); + const processedArgs = await processArgs(process.argv, pair, config); - const processedArgs = await processArgs(process.argv, pair, config); - - log.info(`Processsed args: ${JSON.stringify(processedArgs, null, 4)}`); - if (processedArgs.api) { - await new ReloadingAPI(envPath, config, pair, processedArgs) - .start() - .then(() => { - log.info("Reloading API started..."); - }); - } else { - process.exit(0); - } + log.info(`Processsed args: ${JSON.stringify(processedArgs, null, 4)}`); + if (processedArgs.api) { + await new ReloadingAPI(envPath, config, pair, processedArgs) + .start() + .then(() => { + log.info("Reloading API started..."); + }); + } else { + process.exit(0); + } } //if main process if (isMain(import.meta.url, "provider")) { - main() - .then(() => { - log.info("Running main process..."); - }) - .catch((error) => { - log.error(error); - }); + main() + .then(() => { + log.info("Running main process..."); + }) + .catch((error) => { + log.error(error); + }); } diff --git a/packages/cli/src/env.ts b/packages/cli/src/env.ts deleted file mode 100644 index 4627772a84..0000000000 --- a/packages/cli/src/env.ts +++ /dev/null @@ -1,52 +0,0 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; -// Copyright 2021-2024 Prosopo (UK) Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import { LogLevel, getLogger } from "@prosopo/common"; -import dotenv from "dotenv"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const logger = getLogger( - process.env.PROSOPO_LOG_LEVEL || LogLevel.enum.info, - "env", -); - -export function getEnv() { - if (process.env.NODE_ENV) { - return process.env.NODE_ENV.replace(/\W/g, ""); - } - return "development"; -} - -export function loadEnv( - rootDir?: string, - filename?: string, - filePath?: string, -): string { - const envPath = getEnvFile(path.resolve(rootDir || "."), filename, filePath); - const args = { path: envPath }; - logger.info(`Loading env from ${envPath}`); - dotenv.config(args); - return envPath; -} - -export function getEnvFile( - rootDir?: string, - filename = ".env", - filepath = path.join(__dirname, "../.."), -) { - const env = getEnv(); - return path.join(rootDir || filepath, `${filename}.${env}`); -} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index d623be271f..581e0fbbeb 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -17,4 +17,3 @@ export * from "./start.js"; export * from "./process.env.js"; export * from "./files.js"; export { default as defaultConfig } from "./prosopo.config.js"; -export * from "./env.js"; diff --git a/packages/cli/src/reloader.ts b/packages/cli/src/reloader.ts index 8879afc0bc..fc18eed0f5 100644 --- a/packages/cli/src/reloader.ts +++ b/packages/cli/src/reloader.ts @@ -18,62 +18,62 @@ import { LogLevel, getLogger } from "@prosopo/common"; import { ProviderEnvironment } from "@prosopo/env"; import type { ProsopoConfigOutput } from "@prosopo/types"; import type { AwaitedProcessedArgs } from "./argv.js"; -import { loadEnv } from "./env.js"; +import { loadEnv } from "@prosopo/dotenv"; import { start } from "./start.js"; const log = getLogger(LogLevel.enum.info, "CLI"); export default class ReloadingAPI { - // biome-ignore lint/suspicious/noExplicitAny: TODO fix - private _envWatcher: any; - private _envPath: string; - private _config: ProsopoConfigOutput; - private _pair: KeyringPair; - private _processedArgs: AwaitedProcessedArgs; - private api: Server | undefined; - private _restarting: boolean; + // biome-ignore lint/suspicious/noExplicitAny: TODO fix + private _envWatcher: any; + private _envPath: string; + private _config: ProsopoConfigOutput; + private _pair: KeyringPair; + private _processedArgs: AwaitedProcessedArgs; + private api: Server | undefined; + private _restarting: boolean; - constructor( - envPath: string, - config: ProsopoConfigOutput, - pair: KeyringPair, - processedArgs: AwaitedProcessedArgs, - ) { - this._envPath = envPath; - this._config = config; - this._pair = pair; - this._processedArgs = processedArgs; - this._restarting = false; - } + constructor( + envPath: string, + config: ProsopoConfigOutput, + pair: KeyringPair, + processedArgs: AwaitedProcessedArgs, + ) { + this._envPath = envPath; + this._config = config; + this._pair = pair; + this._processedArgs = processedArgs; + this._restarting = false; + } - public async start() { - log.info("Starting API"); - this._envWatcher = await this._watchEnv(); - loadEnv(); - const env = new ProviderEnvironment(this._config, this._pair); - await env.isReady(); - this.api = await start(env, !!this._processedArgs.adminApi); - } + public async start() { + log.info("Starting API"); + this._envWatcher = await this._watchEnv(); + loadEnv(); + const env = new ProviderEnvironment(this._config, this._pair); + await env.isReady(); + this.api = await start(env, !!this._processedArgs.adminApi); + } - public async stop() { - log.info("Stopping API"); - return new Promise((resolve) => { - if (this.api) { - this.api.close(resolve); - } - }); - } + public async stop() { + log.info("Stopping API"); + return new Promise((resolve) => { + if (this.api) { + this.api.close(resolve); + } + }); + } - private async _watchEnv() { - return fs.watchFile(this._envPath, async () => { - log.info(`env file change detected. Restarting: ${this._restarting}`); - if (!this._restarting) { - this._restarting = true; - await this.stop(); - loadEnv(); - await this.start(); - this._restarting = false; - } - }); - } + private async _watchEnv() { + return fs.watchFile(this._envPath, async () => { + log.info(`env file change detected. Restarting: ${this._restarting}`); + if (!this._restarting) { + this._restarting = true; + await this.stop(); + loadEnv(); + await this.start(); + this._restarting = false; + } + }); + } } diff --git a/packages/cli/src/start.ts b/packages/cli/src/start.ts index 9090fbc63f..9116ef94da 100644 --- a/packages/cli/src/start.ts +++ b/packages/cli/src/start.ts @@ -1,13 +1,3 @@ -import type { Server } from "node:net"; -import { i18nMiddleware } from "@prosopo/common"; -import { getPairAsync } from "@prosopo/contract"; -import { ProviderEnvironment } from "@prosopo/env"; -import { - prosopoAdminRouter, - prosopoRouter, - prosopoVerifyRouter, - storeCaptchasExternally, -} from "@prosopo/provider"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,74 +11,85 @@ import { // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import type { Server } from "node:net"; +import { i18nMiddleware } from "@prosopo/common"; +import { getPairAsync } from "@prosopo/contract"; +import { ProviderEnvironment } from "@prosopo/env"; +import { + prosopoAdminRouter, + prosopoRouter, + prosopoVerifyRouter, + storeCaptchasExternally, +} from "@prosopo/provider"; import type { CombinedApiPaths } from "@prosopo/types"; import cors from "cors"; import express from "express"; import rateLimit from "express-rate-limit"; -import { loadEnv } from "./env.js"; +import { loadEnv } from "@prosopo/dotenv"; import { getDB, getSecret } from "./process.env.js"; import getConfig from "./prosopo.config.js"; function startApi(env: ProviderEnvironment, admin = false): Server { - env.logger.info("Starting Prosopo API"); - const apiApp = express(); - const apiPort = env.config.server.port; - // https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues - apiApp.set( - "trust proxy", - env.config.proxyCount /* number of proxies between user and server */, - ); - apiApp.use(cors()); - apiApp.use(express.json({ limit: "50mb" })); - apiApp.use(i18nMiddleware({})); - apiApp.use(prosopoRouter(env)); - apiApp.use(prosopoVerifyRouter(env)); + env.logger.info("Starting Prosopo API"); + const apiApp = express(); + const apiPort = env.config.server.port; + // https://express-rate-limit.mintlify.app/guides/troubleshooting-proxy-issues + apiApp.set( + "trust proxy", + env.config.proxyCount /* number of proxies between user and server */, + ); + apiApp.use(cors()); + apiApp.use(express.json({ limit: "50mb" })); + apiApp.use(i18nMiddleware({})); + apiApp.use(prosopoRouter(env)); + apiApp.use(prosopoVerifyRouter(env)); - if (admin) { - apiApp.use(prosopoAdminRouter(env)); - } + if (admin) { + apiApp.use(prosopoAdminRouter(env)); + } - // Rate limiting - const rateLimits = env.config.rateLimits; - for (const [path, limit] of Object.entries(rateLimits)) { - const enumPath = path as CombinedApiPaths; - apiApp.use(enumPath, rateLimit(limit)); - } + // Rate limiting + const rateLimits = env.config.rateLimits; + for (const [path, limit] of Object.entries(rateLimits)) { + const enumPath = path as CombinedApiPaths; + apiApp.use(enumPath, rateLimit(limit)); + } - return apiApp.listen(apiPort, () => { - env.logger.info(`Prosopo app listening at http://localhost:${apiPort}`); - }); + return apiApp.listen(apiPort, () => { + env.logger.info(`Prosopo app listening at http://localhost:${apiPort}`); + }); } export async function start(env?: ProviderEnvironment, admin?: boolean) { - if (!env) { - loadEnv(); + if (!env) { + loadEnv(); - // Fail to start api if db is not defined - getDB(); + // Fail to start api if db is not defined + getDB(); - const secret = getSecret(); - const config = getConfig(undefined, undefined, undefined, { - solved: { count: 2 }, - unsolved: { count: 0 }, - }); + const secret = getSecret(); + const config = getConfig(undefined, undefined, undefined, { + solved: { count: 2 }, + unsolved: { count: 0 }, + }); - const pair = await getPairAsync( - config.networks[config.defaultNetwork], - secret, - "", - ); - env = new ProviderEnvironment(config, pair); - } + const pair = await getPairAsync( + config.networks[config.defaultNetwork], + secret, + "", + ); + env = new ProviderEnvironment(config, pair); + } - await env.isReady(); + await env.isReady(); - // Start the scheduled job - if (env.pair) { - storeCaptchasExternally(env.pair, env.config).catch((err) => { - console.error("Failed to start scheduler:", err); - }); - } + // Start the scheduled job + if (env.pair) { + storeCaptchasExternally(env.pair, env.config).catch((err) => { + console.error("Failed to start scheduler:", err); + }); + } - return startApi(env, admin); + return startApi(env, admin); } diff --git a/packages/cli/tsconfig.cjs.json b/packages/cli/tsconfig.cjs.json index 75e0fd769b..a01ae1390e 100644 --- a/packages/cli/tsconfig.cjs.json +++ b/packages/cli/tsconfig.cjs.json @@ -15,6 +15,9 @@ { "path": "../contract/tsconfig.cjs.json" }, + { + "path": "../dotenv/tsconfig.cjs.json" + }, { "path": "../env/tsconfig.cjs.json" }, diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 69e836f65b..c237870d7a 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -16,6 +16,9 @@ { "path": "../contract" }, + { + "path": "../dotenv" + }, { "path": "../env" }, diff --git a/packages/cli/vite.config.ts b/packages/cli/vite.config.ts index aa4d578a80..dfe071c5d3 100644 --- a/packages/cli/vite.config.ts +++ b/packages/cli/vite.config.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. import * as path from "node:path"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { ViteBackendConfig } from "@prosopo/config"; import { defineConfig } from "vite"; import { version } from "./package.json"; @@ -31,29 +31,29 @@ process.env.TS_NODE_PROJECT = path.resolve("./tsconfig.json"); // Merge with generic backend config export default defineConfig(async ({ command, mode }) => { - const backendConfig = await ViteBackendConfig( - packageName, - packageVersion, - bundleName, - dir, - entry, - command, - mode, - ); - return defineConfig({ - define: { - ...backendConfig.define, - ...(process.env.PROSOPO_MONGO_EVENTS_URI && { - "process.env.PROSOPO_MONGO_EVENTS_URI": JSON.stringify( - process.env.PROSOPO_MONGO_EVENTS_URI, - ), - }), - ...(process.env._DEV_ONLY_WATCH_EVENTS && { - "process.env._DEV_ONLY_WATCH_EVENTS": JSON.stringify( - process.env._DEV_ONLY_WATCH_EVENTS, - ), - }), - }, - ...backendConfig, - }); + const backendConfig = await ViteBackendConfig( + packageName, + packageVersion, + bundleName, + dir, + entry, + command, + mode, + ); + return defineConfig({ + define: { + ...backendConfig.define, + ...(process.env.PROSOPO_MONGO_EVENTS_URI && { + "process.env.PROSOPO_MONGO_EVENTS_URI": JSON.stringify( + process.env.PROSOPO_MONGO_EVENTS_URI, + ), + }), + ...(process.env._DEV_ONLY_WATCH_EVENTS && { + "process.env._DEV_ONLY_WATCH_EVENTS": JSON.stringify( + process.env._DEV_ONLY_WATCH_EVENTS, + ), + }), + }, + ...backendConfig, + }); }); diff --git a/packages/contract/src/accounts/getPair.ts b/packages/contract/src/accounts/getPair.ts index d303d1770f..2a2bf23ef4 100644 --- a/packages/contract/src/accounts/getPair.ts +++ b/packages/contract/src/accounts/getPair.ts @@ -1,6 +1,3 @@ -import type { ApiPromise } from "@polkadot/api/promise/Api"; -import { Keyring } from "@polkadot/keyring"; -import type { KeyringPair, KeyringPair$Json } from "@polkadot/keyring/types"; // Copyright 2021-2024 Prosopo (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +11,10 @@ import type { KeyringPair, KeyringPair$Json } from "@polkadot/keyring/types"; // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +import type { ApiPromise } from "@polkadot/api/promise/Api"; +import { Keyring } from "@polkadot/keyring"; +import type { KeyringPair, KeyringPair$Json } from "@polkadot/keyring/types"; import type { AccountId } from "@polkadot/types/interfaces"; import { cryptoWaitReady } from "@polkadot/util-crypto"; import { mnemonicValidate } from "@polkadot/util-crypto/mnemonic"; @@ -22,85 +23,85 @@ import { hexToU8a } from "@polkadot/util/hex"; import { isHex } from "@polkadot/util/is"; import { ProsopoEnvError } from "@prosopo/common"; import { - type NetworkConfig, - NetworkPairTypeSchema, - type PolkadotSecretJSON, + type NetworkConfig, + NetworkPairTypeSchema, + type PolkadotSecretJSON, } from "@prosopo/types"; export async function getPairAsync( - networkConfig?: NetworkConfig, - secret?: string | KeyringPair$Json | PolkadotSecretJSON, - account?: string | Uint8Array, - pairType?: KeypairType, - ss58Format?: number, + networkConfig?: NetworkConfig, + secret?: string | KeyringPair$Json | PolkadotSecretJSON, + account?: string | Uint8Array, + pairType?: KeypairType, + ss58Format?: number, ): Promise { - await cryptoWaitReady(); - return getPair(networkConfig, secret, account, pairType, ss58Format); + await cryptoWaitReady(); + return getPair(networkConfig, secret, account, pairType, ss58Format); } export function getPair( - networkConfig?: NetworkConfig, - secret?: string | KeyringPair$Json | PolkadotSecretJSON, - account?: string | Uint8Array, - pairType?: KeypairType, - ss58Format?: number, + networkConfig?: NetworkConfig, + secret?: string | KeyringPair$Json | PolkadotSecretJSON, + account?: string | Uint8Array, + pairType?: KeypairType, + ss58Format?: number, ): KeyringPair { - if (networkConfig) { - pairType = networkConfig.pairType; - ss58Format = networkConfig.ss58Format; - } else if (!pairType || !ss58Format) { - throw new ProsopoEnvError("GENERAL.NO_PAIR_TYPE_OR_SS58_FORMAT"); - } - const keyring = new Keyring({ type: pairType, ss58Format }); - if (!secret && account) { - return keyring.addFromAddress(account); - } - if (secret && typeof secret === "string") { - if (mnemonicValidate(secret)) { - return keyring.addFromMnemonic(secret); - } - if (isHex(secret)) { - return keyring.addFromSeed(hexToU8a(secret)); - } - if (secret.startsWith("//")) { - return keyring.addFromUri(secret); - } - try { - const json = JSON.parse(secret); - const { - encoding: { content }, - } = json; - const keyring = new Keyring({ type: content[1], ss58Format }); - return keyring.addFromJson(json as KeyringPair$Json); - } catch (e) { - throw new ProsopoEnvError("GENERAL.NO_MNEMONIC_OR_SEED", { - context: { error: e }, - }); - } - } else if (typeof secret === "object") { - return keyring.addFromJson(secret as KeyringPair$Json); - } else { - throw new ProsopoEnvError("GENERAL.NO_MNEMONIC_OR_SEED"); - } + if (networkConfig) { + pairType = networkConfig.pairType; + ss58Format = networkConfig.ss58Format; + } else if (!pairType || !ss58Format) { + throw new ProsopoEnvError("GENERAL.NO_PAIR_TYPE_OR_SS58_FORMAT"); + } + const keyring = new Keyring({ type: pairType, ss58Format }); + if (!secret && account) { + return keyring.addFromAddress(account); + } + if (secret && typeof secret === "string") { + if (mnemonicValidate(secret)) { + return keyring.addFromMnemonic(secret); + } + if (isHex(secret)) { + return keyring.addFromSeed(hexToU8a(secret)); + } + if (secret.startsWith("//")) { + return keyring.addFromUri(secret); + } + try { + const json = JSON.parse(secret); + const { + encoding: { content }, + } = json; + const keyring = new Keyring({ type: content[1], ss58Format }); + return keyring.addFromJson(json as KeyringPair$Json); + } catch (e) { + throw new ProsopoEnvError("GENERAL.NO_MNEMONIC_OR_SEED", { + context: { error: e }, + }); + } + } else if (typeof secret === "object") { + return keyring.addFromJson(secret as KeyringPair$Json); + } else { + throw new ProsopoEnvError("GENERAL.NO_MNEMONIC_OR_SEED"); + } } export function getReadOnlyPair( - api: ApiPromise, - userAccount?: string, + api: ApiPromise, + userAccount?: string, ): KeyringPair { - // 5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM is the all zero address - return getPair( - undefined, - undefined, - userAccount || getZeroAddress(api).toHex(), - NetworkPairTypeSchema.parse("sr25519"), - api.registry.chainSS58, - ); + // 5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM is the all zero address + return getPair( + undefined, + undefined, + userAccount || getZeroAddress(api).toHex(), + NetworkPairTypeSchema.parse("sr25519"), + api.registry.chainSS58, + ); } export function getZeroAddress(api: ApiPromise): AccountId { - return api.registry.createType( - "AccountId", - new Uint8Array(new Array(32).fill(0)), - ); + return api.registry.createType( + "AccountId", + new Uint8Array(new Array(32).fill(0)), + ); } diff --git a/packages/dotenv/package.json b/packages/dotenv/package.json new file mode 100644 index 0000000000..3db2a7b343 --- /dev/null +++ b/packages/dotenv/package.json @@ -0,0 +1,48 @@ +{ + "name": "@prosopo/dotenv", + "version": "2.0.1", + "author": "PROSOPO LIMITED ", + "license": "Apache-2.0", + "private": false, + "engines": { + "node": ">=20", + "npm": ">=9" + }, + "scripts": { + "clean": "tsc --build --clean", + "build": "tsc --build --verbose", + "build:cjs": "npx vite --config vite.cjs.config.ts build", + "cli": "node ./dist/js/cli.js", + "test": "echo \"No test specified\"" + }, + "main": "./dist/index.js", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/cjs/index.cjs" + } + }, + "types": "./dist/index.d.ts", + "dependencies": { + "dotenv": "^16.0.1", + "find-up": "^7.0.0" + }, + "devDependencies": { + "tslib": "2.6.2", + "typescript": "5.1.6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/prosopo/captcha.git" + }, + "bugs": { + "url": "https://github.com/prosopo/captcha/issues" + }, + "homepage": "https://github.com/prosopo/captcha#readme", + "publishConfig": { + "registry": "https://registry.npmjs.org" + }, + "description": "Package to handle environment discovery and loading", + "sideEffects": false +} diff --git a/packages/dotenv/src/env.ts b/packages/dotenv/src/env.ts new file mode 100644 index 0000000000..44f15a4af1 --- /dev/null +++ b/packages/dotenv/src/env.ts @@ -0,0 +1,66 @@ +// Copyright 2021-2024 Prosopo (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { LogLevel, getLogger } from "@prosopo/common"; +import dotenv from "dotenv"; +import { findUpSync } from "find-up"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const logger = getLogger( + process.env.PROSOPO_LOG_LEVEL || LogLevel.enum.info, + "env", +); + +export function getEnv() { + if (process.env.NODE_ENV) { + return process.env.NODE_ENV.replace(/\W/g, ""); + } + return "development"; +} + +export function loadEnv( + rootDir?: string, + filename?: string, + filePath?: string, +): string { + const envPath = getEnvFile(path.resolve(rootDir || "."), filename, filePath); + const args = { path: envPath }; + logger.info(`Loading env from ${envPath}`); + dotenv.config(args); + return envPath; +} + +/** + * Get the path to the .env file. Search up directories until `.env.${env}` is found. + * If not found, look in the root directory, if specified, or 2 directories up from this file. + * @param rootDir + * @param filename + * @param filepath + */ +export function getEnvFile( + rootDir?: string, + filename = ".env", + filepath = path.join(__dirname, "../.."), +) { + const env = getEnv(); + const fileNameFull = `${filename}.${env}`; + + return ( + findUpSync(fileNameFull, { type: "file" }) || + path.join(rootDir || filepath, fileNameFull) + ); +} diff --git a/packages/dotenv/src/index.ts b/packages/dotenv/src/index.ts new file mode 100644 index 0000000000..39f5fe22f7 --- /dev/null +++ b/packages/dotenv/src/index.ts @@ -0,0 +1 @@ +export * from "./env.js"; diff --git a/packages/dotenv/tsconfig.cjs.json b/packages/dotenv/tsconfig.cjs.json new file mode 100644 index 0000000000..3b068013b8 --- /dev/null +++ b/packages/dotenv/tsconfig.cjs.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.cjs.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist/cjs" + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.json", + "./src/**/*.d.ts", + "./src/**/*.tsx" + ], + "references": [] +} diff --git a/packages/dotenv/tsconfig.json b/packages/dotenv/tsconfig.json new file mode 100644 index 0000000000..a18f3294e8 --- /dev/null +++ b/packages/dotenv/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.esm.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src", "src/**/*.json"], + "references": [] +} diff --git a/packages/dotenv/vite.cjs.config.ts b/packages/dotenv/vite.cjs.config.ts new file mode 100644 index 0000000000..b631fd793f --- /dev/null +++ b/packages/dotenv/vite.cjs.config.ts @@ -0,0 +1,19 @@ +import path from "node:path"; +// Copyright 2021-2024 Prosopo (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { ViteCommonJSConfig } from "@prosopo/config"; + +export default function () { + return ViteCommonJSConfig("util", path.resolve("./tsconfig.cjs.json")); +} diff --git a/packages/dotenv/vite.test.config.ts b/packages/dotenv/vite.test.config.ts new file mode 100644 index 0000000000..8970726bb8 --- /dev/null +++ b/packages/dotenv/vite.test.config.ts @@ -0,0 +1,32 @@ +import fs from "node:fs"; +import path from "node:path"; +// Copyright 2021-2024 Prosopo (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { ViteTestConfig } from "@prosopo/config"; +import dotenv from "dotenv"; +process.env.NODE_ENV = "test"; +// if .env.test exists at this level, use it, otherwise use the one at the root +const envFile = `.env.${process.env.NODE_ENV || "development"}`; +let envPath = envFile; +if (fs.existsSync(envFile)) { + envPath = path.resolve(envFile); +} else if (fs.existsSync(`../../${envFile}`)) { + envPath = path.resolve(`../../${envFile}`); +} else { + throw new Error(`No ${envFile} file found`); +} + +dotenv.config({ path: envPath }); + +export default ViteTestConfig; diff --git a/packages/procaptcha-bundle/vite.config.ts b/packages/procaptcha-bundle/vite.config.ts index 6346ad9521..302def2298 100644 --- a/packages/procaptcha-bundle/vite.config.ts +++ b/packages/procaptcha-bundle/vite.config.ts @@ -11,11 +11,13 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + import * as fs from "node:fs"; import * as path from "node:path"; -import { loadEnv } from "@prosopo/cli"; +import { loadEnv } from "@prosopo/dotenv"; import { ViteFrontendConfig } from "@prosopo/config"; import { defineConfig } from "vite"; + // load env using our util because vite loadEnv is not working for .env.development loadEnv(); @@ -28,36 +30,36 @@ const bundleName = "procaptcha"; const packageName = "@prosopo/procaptcha-bundle"; const entry = "./src/index.tsx"; const copyOptions = copyTo - ? { - srcDir: "./dist/bundle", - destDir: copyTo, - } - : undefined; + ? { + srcDir: "./dist/bundle", + destDir: copyTo, + } + : undefined; const tsConfigPaths = [path.resolve("./tsconfig.json")]; const packagesDir = path.resolve(".."); const workspaceRoot = path.resolve("../../"); // Get all folders in packagesDir const packages = fs - .readdirSync(packagesDir) - .filter((f) => fs.statSync(path.join(packagesDir, f)).isDirectory()); + .readdirSync(packagesDir) + .filter((f) => fs.statSync(path.join(packagesDir, f)).isDirectory()); for (const packageName of packages) { - // Add the tsconfig for each package to tsConfigPaths - tsConfigPaths.push(path.resolve(`../${packageName}/tsconfig.json`)); + // Add the tsconfig for each package to tsConfigPaths + tsConfigPaths.push(path.resolve(`../${packageName}/tsconfig.json`)); } // Merge with generic frontend config export default defineConfig(async ({ command, mode }) => { - const frontendConfig = await ViteFrontendConfig( - packageName, - bundleName, - path.resolve(), - entry, - command, - mode, - copyOptions, - tsConfigPaths, - workspaceRoot, - ); - return { - ...frontendConfig, - }; + const frontendConfig = await ViteFrontendConfig( + packageName, + bundleName, + path.resolve(), + entry, + command, + mode, + copyOptions, + tsConfigPaths, + workspaceRoot, + ); + return { + ...frontendConfig, + }; }); diff --git a/packages/util/package.json b/packages/util/package.json index 86fc4a41d9..3cb58aefa6 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -33,14 +33,13 @@ "dependencies": { "@noble/hashes": "^1.3.3", "@prosopo/config": "2.0.1", - "@prosopo/util": "2.0.1", + "dotenv": "^16.0.1", "lodash": "^4.17.21", "seedrandom": "^3.0.5" }, "devDependencies": { "@types/lodash": "^4.14.198", "@types/seedrandom": "^3.0.5", - "dotenv": "^16.0.1", "tslib": "2.6.2", "typescript": "5.1.6", "vitest": "^1.3.1"