mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-23 07:18:57 -06:00
feat: Revamp @formbricks/js package (#2299)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
a9e45c0086
commit
0f95f1c98c
@@ -47,7 +47,6 @@ export default function AppPage({}) {
|
||||
userId,
|
||||
attributes,
|
||||
});
|
||||
window.formbricks = formbricks;
|
||||
}
|
||||
|
||||
// Connect next.js router to Formbricks
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import jsPackageJson from "@/../../packages/js/package.json";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import { IoLogoHtml5, IoLogoNpm } from "react-icons/io5";
|
||||
@@ -68,7 +67,7 @@ if (typeof window !== "undefined") {
|
||||
var t = document.createElement("script");
|
||||
t.type = "text/javascript";
|
||||
t.async = true;
|
||||
t.src = "https://unpkg.com/@formbricks/js@^${jsPackageJson.version}/dist/index.umd.js";
|
||||
t.src = "https://unpkg.com/@formbricks/js@^1.6.5/dist/index.umd.js";
|
||||
var e = document.getElementsByTagName("script")[0];
|
||||
e.parentNode.insertBefore(t, e);
|
||||
setTimeout(function(){
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import jsPackageJson from "@/../../packages/js/package.json";
|
||||
import packageJson from "@/package.json";
|
||||
import Link from "next/link";
|
||||
import "prismjs/themes/prism.css";
|
||||
@@ -109,7 +108,7 @@ if (typeof window !== "undefined") {
|
||||
</p>
|
||||
<CodeBlock language="js">{`<!-- START Formbricks Surveys -->
|
||||
<script type="text/javascript">
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^${jsPackageJson.version}/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.6.5/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->`}</CodeBlock>
|
||||
<p className="text-lg font-semibold text-slate-800">You're done 🎉</p>
|
||||
|
||||
@@ -23,12 +23,11 @@ interface SetupInstructionsOnboardingProps {
|
||||
export default function SetupInstructionsOnboarding({
|
||||
environmentId,
|
||||
webAppUrl,
|
||||
jsPackageVersion,
|
||||
}: SetupInstructionsOnboardingProps) {
|
||||
const [activeTab, setActiveId] = useState(tabs[0].id);
|
||||
const htmlSnippet = `<!-- START Formbricks Surveys -->
|
||||
<script type="text/javascript">
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^${jsPackageVersion}/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
|
||||
!function(){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://unpkg.com/@formbricks/js@^1.6.5/dist/index.umd.js";var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e),setTimeout(function(){window.formbricks.init({environmentId: "${environmentId}", apiHost: "${window.location.protocol}//${window.location.host}"})},500)}();
|
||||
</script>
|
||||
<!-- END Formbricks Surveys -->`;
|
||||
|
||||
|
||||
@@ -1,78 +1,22 @@
|
||||
// DEPRECATED - This file is deprecated and will be removed in the future
|
||||
// Deprecated since 22-03-2024
|
||||
// This endpoint has been deprecated. Please use the new endpoint /api/packages/js instead.
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import fs from "fs/promises";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { WEBAPP_URL } from "@formbricks/lib/constants";
|
||||
import { getEnvironment } from "@formbricks/lib/environment/service";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
let path: string;
|
||||
let append = "";
|
||||
switch (req.nextUrl.searchParams.get("module")) {
|
||||
case "surveys":
|
||||
path = `../../packages/surveys/dist/index.umd.js`;
|
||||
break;
|
||||
case "question-date":
|
||||
path = `../../packages/surveys/dist/question-date.umd.js`;
|
||||
break;
|
||||
case "js":
|
||||
case null:
|
||||
const format = ["umd", "iife"].includes(req.nextUrl.searchParams.get("format")!)
|
||||
? req.nextUrl.searchParams.get("format")!
|
||||
: "umd";
|
||||
path = `../../packages/js/dist/index.${format}.js`;
|
||||
try {
|
||||
append = await handleInit(req);
|
||||
} catch (error) {
|
||||
return responses.badRequestResponse(error.message);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return responses.badRequestResponse(
|
||||
"unknown module requested. module must be of type 'js' (default), 'surveys' or 'question-date'"
|
||||
);
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const jsCode = await loadAndAppendCode(path, append);
|
||||
|
||||
return new NextResponse(jsCode, {
|
||||
headers: {
|
||||
"Content-Type": "application/javascript",
|
||||
"Cache-Control": "public, s-maxage=600, max-age=1800, stale-while-revalidate=600, stale-if-error=600",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
return responses.goneResponse(
|
||||
"This endpoint has been deprecated. Please use the new endpoint /api/packages/<package-name>",
|
||||
{
|
||||
"x-deprecated": "true",
|
||||
"x-deprecated-date": "22-03-2024",
|
||||
"x-deprecated-redirect": `${WEBAPP_URL}/api/packages/js`,
|
||||
},
|
||||
});
|
||||
true
|
||||
);
|
||||
} catch (error) {
|
||||
return responses.internalServerErrorResponse("file not found");
|
||||
return responses.internalServerErrorResponse("this endpoint is not available");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleInit(req: NextRequest) {
|
||||
const environmentId = req.nextUrl.searchParams.get("environmentId");
|
||||
|
||||
if (environmentId) {
|
||||
let environment: TEnvironment | null;
|
||||
|
||||
try {
|
||||
environment = await getEnvironment(environmentId);
|
||||
} catch (error) {
|
||||
throw new Error(`error fetching environment: ${error.message}`);
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error("environment not found");
|
||||
}
|
||||
|
||||
if (environment) {
|
||||
return `formbricks.init({environmentId: "${environmentId}", apiHost: "${WEBAPP_URL}"});`;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function loadAndAppendCode(path: string, append: string): Promise<string> {
|
||||
let jsCode = await fs.readFile(path, "utf-8");
|
||||
return jsCode + append;
|
||||
}
|
||||
|
||||
42
apps/web/app/api/packages/[package]/route.ts
Normal file
42
apps/web/app/api/packages/[package]/route.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { responses } from "@/app/lib/api/response";
|
||||
import fs from "fs/promises";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(_: NextRequest, { params }: { params: { slug: string } }) {
|
||||
let path: string;
|
||||
const packageRequested = params["package"];
|
||||
|
||||
switch (packageRequested) {
|
||||
case "js-core":
|
||||
path = `../../packages/js-core/dist/index.umd.cjs`;
|
||||
break;
|
||||
case "surveys":
|
||||
path = `../../packages/surveys/dist/index.umd.cjs`;
|
||||
break;
|
||||
default:
|
||||
return responses.notFoundResponse(
|
||||
"package",
|
||||
packageRequested,
|
||||
true,
|
||||
"public, s-maxage=600, max-age=1800, stale-while-revalidate=600, stale-if-error=600"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const packageSrcCode = await fs.readFile(path, "utf-8");
|
||||
return new Response(packageSrcCode, {
|
||||
headers: {
|
||||
"Content-Type": "application/javascript",
|
||||
"Cache-Control": "public, s-maxage=600, max-age=1800, stale-while-revalidate=600, stale-if-error=600",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error reading file:", error);
|
||||
return responses.internalServerErrorResponse(
|
||||
"file not found:",
|
||||
true,
|
||||
"public, s-maxage=600, max-age=1800, stale-while-revalidate=600, stale-if-error=600"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export interface ApiSuccessResponse<T = { [key: string]: any }> {
|
||||
export interface ApiErrorResponse {
|
||||
code:
|
||||
| "not_found"
|
||||
| "gone"
|
||||
| "bad_request"
|
||||
| "internal_server_error"
|
||||
| "unauthorized"
|
||||
@@ -28,6 +29,19 @@ const corsHeaders = {
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
};
|
||||
|
||||
const goneResponse = (message: string, details?: { [key: string]: string }, cors: boolean = false) =>
|
||||
Response.json(
|
||||
{
|
||||
code: "gone",
|
||||
message,
|
||||
details: details || {},
|
||||
} as ApiErrorResponse,
|
||||
{
|
||||
status: 410,
|
||||
...(cors && { headers: corsHeaders }),
|
||||
}
|
||||
);
|
||||
|
||||
const badRequestResponse = (message: string, details?: { [key: string]: string }, cors: boolean = false) =>
|
||||
Response.json(
|
||||
{
|
||||
@@ -69,8 +83,18 @@ const methodNotAllowedResponse = (
|
||||
}
|
||||
);
|
||||
|
||||
const notFoundResponse = (resourceType: string, resourceId: string, cors: boolean = false) =>
|
||||
Response.json(
|
||||
const notFoundResponse = (
|
||||
resourceType: string,
|
||||
resourceId: string,
|
||||
cors: boolean = false,
|
||||
cache: string = "private, no-store"
|
||||
) => {
|
||||
const headers = {
|
||||
...(cors && corsHeaders),
|
||||
"Cache-Control": cache,
|
||||
};
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
code: "not_found",
|
||||
message: `${resourceType} not found`,
|
||||
@@ -81,9 +105,10 @@ const notFoundResponse = (resourceType: string, resourceId: string, cors: boolea
|
||||
} as ApiErrorResponse,
|
||||
{
|
||||
status: 404,
|
||||
...(cors && { headers: corsHeaders }),
|
||||
headers,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const notAuthenticatedResponse = (cors: boolean = false) =>
|
||||
Response.json(
|
||||
@@ -177,6 +202,7 @@ const tooManyRequestsResponse = (
|
||||
};
|
||||
|
||||
export const responses = {
|
||||
goneResponse,
|
||||
badRequestResponse,
|
||||
internalServerErrorResponse,
|
||||
missingFieldResponse,
|
||||
|
||||
@@ -3,6 +3,7 @@ export const loginRoute = (url: string) => url === "/api/auth/callback/credentia
|
||||
export const signupRoute = (url: string) => url === "/api/v1/users";
|
||||
|
||||
export const clientSideApiRoute = (url: string): boolean => {
|
||||
if (url.includes("/api/packages/")) return true;
|
||||
if (url.includes("/api/v1/js/actions")) return true;
|
||||
if (url.includes("/api/v1/client/storage")) return true;
|
||||
const regex = /^\/api\/v\d+\/client\//;
|
||||
|
||||
@@ -80,5 +80,6 @@ export const config = {
|
||||
"/environments/:path*",
|
||||
"/api/auth/signout",
|
||||
"/auth/login",
|
||||
"/api/packages/:path*",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -21,7 +21,11 @@ const nextConfig = {
|
||||
serverComponentsExternalPackages: ["@aws-sdk"],
|
||||
instrumentationHook: true,
|
||||
outputFileTracingIncludes: {
|
||||
"app/api/js": ["../../packages/**/*"],
|
||||
"app/api/packages/": [
|
||||
"../../packages/js/dist/*",
|
||||
"../../packages/js-core/dist/*",
|
||||
"../../packages/surveys/dist/*",
|
||||
],
|
||||
},
|
||||
},
|
||||
transpilePackages: ["@formbricks/database", "@formbricks/ee", "@formbricks/ui", "@formbricks/lib"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
|
||||
@@ -13,14 +13,16 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"source": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"type": "module",
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/index.umd.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.umd.js"
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.umd.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -11,7 +11,7 @@ export default defineConfig({
|
||||
// Could also be a dictionary or array of multiple entry points
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
name: "formbricks-api",
|
||||
formats: ["cjs", "es", "umd"],
|
||||
formats: ["es", "umd"],
|
||||
// the proper extensions will be added
|
||||
fileName: "index",
|
||||
},
|
||||
|
||||
3
packages/js-core/.eslintrc.cjs
Normal file
3
packages/js-core/.eslintrc.cjs
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ["turbo", "prettier"],
|
||||
};
|
||||
4
packages/js-core/.gitignore
vendored
Normal file
4
packages/js-core/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
.vscode
|
||||
build
|
||||
dist
|
||||
9
packages/js-core/LICENSE
Normal file
9
packages/js-core/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Formbricks GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
36
packages/js-core/README.md
Normal file
36
packages/js-core/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Formbricks Browser JS Library
|
||||
|
||||
[](https://www.npmjs.com/package/@formbricks/js)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
Please see [Formbricks Docs](https://formbricks.com/docs).
|
||||
Specifically, [Quickstart/Implementation details](https://formbricks.com/docs/getting-started/quickstart-in-app-survey).
|
||||
|
||||
## What is Formbricks
|
||||
|
||||
Formbricks is your go-to solution for in-product micro-surveys that will supercharge your product experience! 🚀 For more information please check out [formbricks.com](https://formbricks.com).
|
||||
|
||||
## How to use this library
|
||||
|
||||
1. Install the Formbricks package inside your project using npm:
|
||||
|
||||
```bash
|
||||
npm install -s @formbricks/js
|
||||
```
|
||||
|
||||
2. Import Formbricks and initialize the widget in your main component (e.g., App.tsx or App.js):
|
||||
|
||||
```javascript
|
||||
import formbricks from "@formbricks/js";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
formbricks.init({
|
||||
environmentId: "your-environment-id",
|
||||
apiHost: "https://app.formbricks.com",
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Replace your-environment-id with your actual environment ID. You can find your environment ID in the **Setup Checklist** in the Formbricks settings.
|
||||
|
||||
For more detailed guides for different frameworks, check out our [Framework Guides](https://formbricks.com/docs/getting-started/framework-guides).
|
||||
@@ -2,12 +2,12 @@
|
||||
<script type="text/javascript">
|
||||
!(function () {
|
||||
var t = document.createElement("script");
|
||||
(t.type = "text/javascript"), (t.async = !0), (t.src = "./dist/index.umd.js");
|
||||
(t.type = "text/javascript"), (t.async = !0), (t.src = "http://localhost:3000/api/packages/js-core");
|
||||
var e = document.getElementsByTagName("script")[0];
|
||||
e.parentNode.insertBefore(t, e),
|
||||
setTimeout(function () {
|
||||
window.formbricks.init({
|
||||
environmentId: "cltflogdl000aiw7m8esl9kjc",
|
||||
formbricks.init({
|
||||
environmentId: "clu9xfvdn0009vhyg9b6858gc",
|
||||
apiHost: "http://localhost:3000",
|
||||
});
|
||||
}, 500);
|
||||
57
packages/js-core/package.json
Normal file
57
packages/js-core/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@formbricks/js-core",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.0.0",
|
||||
"description": "Js core for Formbricks that contains the logic for executing the @formbricks/js library and is loaded asynchronously over the Formbricks API.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/formbricks/formbricks"
|
||||
},
|
||||
"keywords": [
|
||||
"Formbricks",
|
||||
"surveys",
|
||||
"experience management"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/index.umd.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.umd.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode dev",
|
||||
"build": "tsc && vite build",
|
||||
"build:dev": "tsc && vite build --mode dev",
|
||||
"go": "vite build --watch --mode dev",
|
||||
"lint": "eslint ./src --fix",
|
||||
"clean": "rimraf .turbo node_modules dist coverage"
|
||||
},
|
||||
"author": "Formbricks <hola@formbricks.com>",
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.24.0",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@formbricks/api": "workspace:*",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "1.10.12",
|
||||
"terser": "^5.29.1",
|
||||
"vite": "^5.1.6",
|
||||
"vite-plugin-dts": "^3.7.3"
|
||||
}
|
||||
}
|
||||
82
packages/js-core/src/index.ts
Normal file
82
packages/js-core/src/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { SurveyInlineProps, SurveyModalProps } from "@formbricks/types/formbricksSurveys";
|
||||
import { TJsConfigInput } from "@formbricks/types/js";
|
||||
|
||||
import { trackAction } from "./lib/actions";
|
||||
import { getApi } from "./lib/api";
|
||||
import { CommandQueue } from "./lib/commandQueue";
|
||||
import { ErrorHandler } from "./lib/errors";
|
||||
import { initialize } from "./lib/initialize";
|
||||
import { Logger } from "./lib/logger";
|
||||
import { checkPageUrl } from "./lib/noCodeActions";
|
||||
import { logoutPerson, resetPerson, setPersonAttribute, setPersonUserId } from "./lib/person";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
formbricksSurveys: {
|
||||
renderSurveyInline: (props: SurveyInlineProps) => void;
|
||||
renderSurveyModal: (props: SurveyModalProps) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const logger = Logger.getInstance();
|
||||
|
||||
logger.debug("Create command queue");
|
||||
const queue = new CommandQueue();
|
||||
|
||||
const init = async (initConfig: TJsConfigInput) => {
|
||||
console.log("init in formbricks-js-core");
|
||||
ErrorHandler.init(initConfig.errorHandler);
|
||||
queue.add(false, initialize, initConfig);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const setUserId = async (): Promise<void> => {
|
||||
queue.add(true, setPersonUserId);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const setEmail = async (email: string): Promise<void> => {
|
||||
setAttribute("email", email);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const setAttribute = async (key: string, value: any): Promise<void> => {
|
||||
queue.add(true, setPersonAttribute, key, value);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
queue.add(true, logoutPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const reset = async (): Promise<void> => {
|
||||
queue.add(true, resetPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const track = async (name: string, properties: any = {}): Promise<void> => {
|
||||
queue.add<any>(true, trackAction, name, properties);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const registerRouteChange = async (): Promise<void> => {
|
||||
queue.add(true, checkPageUrl);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const formbricks = {
|
||||
init,
|
||||
setUserId,
|
||||
setEmail,
|
||||
setAttribute,
|
||||
track,
|
||||
logout,
|
||||
reset,
|
||||
registerRouteChange,
|
||||
getApi,
|
||||
};
|
||||
|
||||
export type FormbricksType = typeof formbricks;
|
||||
export default formbricks as FormbricksType;
|
||||
@@ -38,8 +38,8 @@ export const triggerSurvey = async (survey: TSurvey): Promise<void> => {
|
||||
logger.debug("Survey display skipped based on displayPercentage.");
|
||||
return; // skip displaying the survey
|
||||
}
|
||||
await renderWidget(survey);
|
||||
}
|
||||
await renderWidget(survey);
|
||||
};
|
||||
|
||||
const renderWidget = async (survey: TSurvey) => {
|
||||
@@ -266,14 +266,12 @@ export const removeWidgetContainer = (): void => {
|
||||
};
|
||||
|
||||
const loadFormbricksSurveysExternally = (): Promise<typeof window.formbricksSurveys> => {
|
||||
const formbricksSurveysScriptSrc = import.meta.env.FORMBRICKS_SURVEYS_SCRIPT_SRC;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.formbricksSurveys) {
|
||||
resolve(window.formbricksSurveys);
|
||||
} else {
|
||||
const script = document.createElement("script");
|
||||
script.src = formbricksSurveysScriptSrc;
|
||||
script.src = `${config.get().apiHost}/api/packages/surveys`;
|
||||
script.async = true;
|
||||
script.onload = () => resolve(window.formbricksSurveys);
|
||||
script.onerror = (error) => {
|
||||
1
packages/js-core/src/vite-env.d.ts
vendored
Normal file
1
packages/js-core/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
10
packages/js-core/tsconfig.json
Normal file
10
packages/js-core/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@formbricks/tsconfig/js-library.json",
|
||||
"include": ["src", "package.json"],
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
29
packages/js-core/vite.config.ts
Normal file
29
packages/js-core/vite.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
|
||||
import webPackageJson from "../../apps/web/package.json";
|
||||
|
||||
const config = () => {
|
||||
return defineConfig({
|
||||
define: {
|
||||
"import.meta.env.VERSION": JSON.stringify(webPackageJson.version),
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: false, // keep the dist folder to avoid errors with pnpm go when folder is empty during build
|
||||
minify: "terser",
|
||||
sourcemap: true,
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
name: "formbricks",
|
||||
formats: ["es", "umd"],
|
||||
// the proper extensions will be added
|
||||
fileName: "index",
|
||||
},
|
||||
},
|
||||
plugins: [dts({ rollupTypes: true })],
|
||||
});
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/js",
|
||||
"license": "MIT",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.3",
|
||||
"description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"repository": {
|
||||
@@ -17,15 +17,15 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"type": "module",
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"main": "dist/index.umd.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"unpkg": "./dist/index.umd.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.umd.js",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.umd.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
@@ -39,20 +39,7 @@
|
||||
},
|
||||
"author": "Formbricks <hola@formbricks.com>",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/preset-env": "^7.24.0",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@formbricks/api": "workspace:*",
|
||||
"@formbricks/lib": "workspace:*",
|
||||
"@formbricks/surveys": "workspace:*",
|
||||
"@formbricks/tsconfig": "workspace:*",
|
||||
"@formbricks/types": "workspace:*",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "1.10.12",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"terser": "^5.29.1",
|
||||
"vite": "^5.1.6",
|
||||
"vite-plugin-dts": "^3.7.3"
|
||||
|
||||
@@ -1,81 +1,77 @@
|
||||
import { SurveyInlineProps, SurveyModalProps } from "@formbricks/types/formbricksSurveys";
|
||||
import { TJsConfigInput } from "@formbricks/types/js";
|
||||
|
||||
import { trackAction } from "./lib/actions";
|
||||
import { getApi } from "./lib/api";
|
||||
import { CommandQueue } from "./lib/commandQueue";
|
||||
import { ErrorHandler } from "./lib/errors";
|
||||
import { initialize } from "./lib/initialize";
|
||||
import { Logger } from "./lib/logger";
|
||||
import { checkPageUrl } from "./lib/noCodeActions";
|
||||
import { logoutPerson, resetPerson, setPersonAttribute, setPersonUserId } from "./lib/person";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
formbricksSurveys: {
|
||||
renderSurveyInline: (props: SurveyInlineProps) => void;
|
||||
renderSurveyModal: (props: SurveyModalProps) => void;
|
||||
};
|
||||
formbricks: any;
|
||||
}
|
||||
}
|
||||
|
||||
const logger = Logger.getInstance();
|
||||
let sdkLoadingPromise: Promise<void> | null = null;
|
||||
let isErrorLoadingSdk = false;
|
||||
|
||||
logger.debug("Create command queue");
|
||||
const queue = new CommandQueue();
|
||||
async function loadSDK(apiHost: string) {
|
||||
if (!window.formbricks) {
|
||||
const res = await fetch(`${apiHost}/api/packages/js-core`);
|
||||
if (!res.ok) throw new Error("Failed to load Formbricks SDK");
|
||||
const sdkScript = await res.text();
|
||||
const scriptTag = document.createElement("script");
|
||||
scriptTag.innerHTML = sdkScript;
|
||||
document.head.appendChild(scriptTag);
|
||||
|
||||
const init = async (initConfig: TJsConfigInput) => {
|
||||
ErrorHandler.init(initConfig.errorHandler);
|
||||
queue.add(false, initialize, initConfig);
|
||||
await queue.wait();
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (window.formbricks) {
|
||||
clearInterval(checkInterval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval);
|
||||
reject(new Error("Formbricks SDK loading timed out"));
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const formbricksProxyHandler: ProxyHandler<any> = {
|
||||
get(_target, prop, _receiver) {
|
||||
return async (...args: any[]) => {
|
||||
if (!window.formbricks && !sdkLoadingPromise && !isErrorLoadingSdk) {
|
||||
const { apiHost } = args[0];
|
||||
sdkLoadingPromise = loadSDK(apiHost).catch((error) => {
|
||||
console.error(`🧱 Formbricks - Error loading SDK: ${error}`);
|
||||
sdkLoadingPromise = null;
|
||||
isErrorLoadingSdk = true;
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
if (isErrorLoadingSdk) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sdkLoadingPromise) {
|
||||
await sdkLoadingPromise;
|
||||
}
|
||||
|
||||
if (!window.formbricks) {
|
||||
throw new Error("Formbricks SDK is not available");
|
||||
}
|
||||
|
||||
if (typeof window.formbricks[prop] !== "function") {
|
||||
console.error(`🧱 Formbricks - SDK does not support method ${String(prop)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
return window.formbricks[prop](...args);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const setUserId = async (): Promise<void> => {
|
||||
queue.add(true, setPersonUserId);
|
||||
await queue.wait();
|
||||
};
|
||||
const formbricks = new Proxy({}, formbricksProxyHandler);
|
||||
|
||||
const setEmail = async (email: string): Promise<void> => {
|
||||
setAttribute("email", email);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const setAttribute = async (key: string, value: any): Promise<void> => {
|
||||
queue.add(true, setPersonAttribute, key, value);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
queue.add(true, logoutPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const reset = async (): Promise<void> => {
|
||||
queue.add(true, resetPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const track = async (name: string, properties: any = {}): Promise<void> => {
|
||||
queue.add<any>(true, trackAction, name, properties);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const registerRouteChange = async (): Promise<void> => {
|
||||
queue.add(true, checkPageUrl);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const formbricks = {
|
||||
init,
|
||||
setUserId,
|
||||
setEmail,
|
||||
setAttribute,
|
||||
track,
|
||||
logout,
|
||||
reset,
|
||||
registerRouteChange,
|
||||
getApi,
|
||||
};
|
||||
|
||||
export type FormbricksType = typeof formbricks;
|
||||
export default formbricks as FormbricksType;
|
||||
export default formbricks;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
"extends": "@formbricks/tsconfig/js-library.json",
|
||||
"include": ["src", "package.json"],
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"declaration": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
@@ -2,20 +2,8 @@ import { resolve } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
|
||||
import surveysPackageJson from "../surveys/package.json";
|
||||
import packageJson from "./package.json";
|
||||
|
||||
const config = ({ mode }) => {
|
||||
const isDevelopment = mode === "dev";
|
||||
const formbricksSurveysScriptSrc = isDevelopment
|
||||
? "http://localhost:3003/index.umd.js"
|
||||
: `https://unpkg.com/@formbricks/surveys@~${surveysPackageJson.version}/dist/index.umd.js`;
|
||||
|
||||
const config = () => {
|
||||
return defineConfig({
|
||||
define: {
|
||||
"import.meta.env.FORMBRICKS_SURVEYS_SCRIPT_SRC": JSON.stringify(formbricksSurveysScriptSrc),
|
||||
"import.meta.env.VERSION": JSON.stringify(packageJson.version),
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: false, // keep the dist folder to avoid errors with pnpm go when folder is empty during build
|
||||
minify: "terser",
|
||||
@@ -23,8 +11,8 @@ const config = ({ mode }) => {
|
||||
lib: {
|
||||
// Could also be a dictionary or array of multiple entry points
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
name: "formbricks",
|
||||
formats: ["cjs", "es", "umd", "iife"],
|
||||
name: "formbricksJsWrapper",
|
||||
formats: ["es", "umd"],
|
||||
// the proper extensions will be added
|
||||
fileName: "index",
|
||||
},
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
{
|
||||
"name": "@formbricks/surveys",
|
||||
"license": "MIT",
|
||||
"version": "1.7.2",
|
||||
"description": "Formbricks-surveys is a helper library to embed surveys into your application",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Formbricks-surveys is a helper library to embed surveys into your application.",
|
||||
"homepage": "https://formbricks.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/formbricks/formbricks"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"source": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/index.umd.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.umd.js"
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.umd.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode dev",
|
||||
"serve": "serve dist -p 3003",
|
||||
"build": "pnpm run build:surveys && pnpm run build:question-date",
|
||||
"build:dev": "pnpm run build:surveys:dev && pnpm run build:question-date:dev",
|
||||
"build:surveys": "tsc && SURVEYS_PACKAGE_BUILD=surveys vite build",
|
||||
"build:surveys:dev": "tsc && SURVEYS_PACKAGE_BUILD=surveys vite build --mode dev",
|
||||
"build:question-date": "tsc && SURVEYS_PACKAGE_BUILD=question-date vite build",
|
||||
"build:question-date:dev": "tsc && SURVEYS_PACKAGE_BUILD=question-date vite build --mode dev",
|
||||
"go": "concurrently \"pnpm dev\" \"pnpm serve\"",
|
||||
"build": "tsc && vite build",
|
||||
"build:dev": "tsc && vite build --mode dev",
|
||||
"go": "vite build --watch --mode dev",
|
||||
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
|
||||
"preview": "vite preview",
|
||||
"clean": "rimraf .turbo node_modules dist"
|
||||
|
||||
@@ -11,6 +11,8 @@ import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
|
||||
import type { TSurveyDateQuestion } from "@formbricks/types/surveys";
|
||||
|
||||
import { initDatePicker } from "../../sideload/question-date/index";
|
||||
|
||||
interface DateQuestionProps {
|
||||
question: TSurveyDateQuestion;
|
||||
value: string | number | string[];
|
||||
@@ -45,43 +47,11 @@ export default function DateQuestion({
|
||||
useTtc(question.id, ttc, setTtc, startTime, setStartTime);
|
||||
|
||||
const defaultDate = value ? new Date(value as string) : undefined;
|
||||
const datePickerScriptSrc = import.meta.env.DATE_PICKER_SCRIPT_SRC;
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the DatePicker has already been loaded
|
||||
|
||||
if (!window.initDatePicker) {
|
||||
const script = document.createElement("script");
|
||||
|
||||
script.src = datePickerScriptSrc;
|
||||
|
||||
script.async = true;
|
||||
|
||||
document.body.appendChild(script);
|
||||
|
||||
script.onload = () => {
|
||||
// Initialize the DatePicker once the script is loaded
|
||||
window.initDatePicker(document.getElementById("date-picker-root")!, defaultDate, question.format);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return () => {
|
||||
document.body.removeChild(script);
|
||||
};
|
||||
} else {
|
||||
// If already loaded, remove the date picker and re-initialize it
|
||||
setLoading(false);
|
||||
|
||||
const datePickerContainer = document.getElementById("datePickerContainer");
|
||||
if (datePickerContainer) {
|
||||
datePickerContainer.remove();
|
||||
}
|
||||
|
||||
window.initDatePicker(document.getElementById("date-picker-root")!, defaultDate, question.format);
|
||||
}
|
||||
initDatePicker(document.getElementById("date-picker-root")!, defaultDate, question.format);
|
||||
setLoading(false);
|
||||
|
||||
return () => {};
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [question.format, question.id]);
|
||||
|
||||
|
||||
@@ -21,12 +21,10 @@ const addStylesToDom = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const init = (element: HTMLElement, selectedDate?: Date, format?: string) => {
|
||||
export const initDatePicker = (element: HTMLElement, selectedDate?: Date, format?: string) => {
|
||||
addStylesToDom();
|
||||
const container = document.createElement("div");
|
||||
container.id = "datePickerContainer";
|
||||
element.appendChild(container);
|
||||
render(<Question defaultDate={selectedDate} format={format} />, container);
|
||||
};
|
||||
|
||||
window.initDatePicker = init;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
/* 2 */
|
||||
border-color: theme("borderColor.DEFAULT", currentColor);
|
||||
/* 2 */
|
||||
|
||||
font-family: inherit, Arial, Helvetica, sans-serif;
|
||||
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
#fbjs ::before,
|
||||
|
||||
@@ -4,36 +4,27 @@ import { defineConfig, loadEnv } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
import packageJson from "./package.json";
|
||||
|
||||
const buildPackage = process.env.SURVEYS_PACKAGE_BUILD || "surveys";
|
||||
|
||||
const entryPoint = buildPackage === "surveys" ? "src/index.ts" : "src/sideload/question-date/index.tsx";
|
||||
const name = buildPackage === "surveys" ? "formbricks-surveys" : "formbricks-question-date";
|
||||
const fileName = buildPackage === "surveys" ? "index" : "question-date";
|
||||
|
||||
const config = ({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
|
||||
const isDevelopment = mode === "dev";
|
||||
const datePickerScriptSrc = isDevelopment
|
||||
? "http://localhost:3003/question-date.umd.js"
|
||||
: `https://app.formbricks.com/api/js?module=question-date`; // HOTFIX: Need to be changed to a better solution that is versioned
|
||||
|
||||
return defineConfig({
|
||||
define: {
|
||||
"process.env": env,
|
||||
"import.meta.env.DATE_PICKER_SCRIPT_SRC": JSON.stringify(datePickerScriptSrc),
|
||||
},
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
minify: "terser",
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
inlineDynamicImports: true,
|
||||
},
|
||||
},
|
||||
lib: {
|
||||
entry: resolve(__dirname, entryPoint),
|
||||
name,
|
||||
formats: ["cjs", "es", "umd"],
|
||||
fileName,
|
||||
entry: resolve(__dirname, "src/index.ts"),
|
||||
name: "formbricksSurveys",
|
||||
formats: ["es", "umd"],
|
||||
fileName: "index",
|
||||
},
|
||||
},
|
||||
plugins: [preact(), dts({ rollupTypes: true }), tsconfigPaths()],
|
||||
|
||||
159
pnpm-lock.yaml
generated
159
pnpm-lock.yaml
generated
@@ -576,9 +576,21 @@ importers:
|
||||
|
||||
packages/js:
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
specifier: ^7.24.0
|
||||
version: 7.24.0
|
||||
'@formbricks/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../tsconfig
|
||||
terser:
|
||||
specifier: ^5.29.1
|
||||
version: 5.29.1
|
||||
vite:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6(terser@5.29.1)
|
||||
vite-plugin-dts:
|
||||
specifier: ^3.7.3
|
||||
version: 3.7.3(typescript@5.3.3)(vite@5.1.6)
|
||||
|
||||
packages/js-core:
|
||||
devDependencies:
|
||||
'@babel/preset-env':
|
||||
specifier: ^7.24.0
|
||||
version: 7.24.0(@babel/core@7.24.0)
|
||||
@@ -591,9 +603,6 @@ importers:
|
||||
'@formbricks/lib':
|
||||
specifier: workspace:*
|
||||
version: link:../lib
|
||||
'@formbricks/surveys':
|
||||
specifier: workspace:*
|
||||
version: link:../surveys
|
||||
'@formbricks/tsconfig':
|
||||
specifier: workspace:*
|
||||
version: link:../tsconfig
|
||||
@@ -606,18 +615,12 @@ importers:
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0(eslint@8.57.0)(typescript@5.3.3)
|
||||
cross-env:
|
||||
specifier: ^7.0.3
|
||||
version: 7.0.3
|
||||
eslint-config-prettier:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.0(eslint@8.57.0)
|
||||
eslint-config-turbo:
|
||||
specifier: 1.10.12
|
||||
version: 1.10.12(eslint@8.57.0)
|
||||
isomorphic-fetch:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
terser:
|
||||
specifier: ^5.29.1
|
||||
version: 5.29.1
|
||||
@@ -1876,7 +1879,7 @@ packages:
|
||||
resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.17.0
|
||||
'@babel/types': 7.24.0
|
||||
jsesc: 2.5.2
|
||||
source-map: 0.5.7
|
||||
dev: true
|
||||
@@ -1885,7 +1888,7 @@ packages:
|
||||
resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/types': 7.24.0
|
||||
'@jridgewell/gen-mapping': 0.3.3
|
||||
'@jridgewell/trace-mapping': 0.3.20
|
||||
jsesc: 2.5.2
|
||||
@@ -1895,14 +1898,14 @@ packages:
|
||||
resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-builder-binary-assignment-operator-visitor@7.22.15:
|
||||
resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-compilation-targets@7.23.6:
|
||||
@@ -1911,7 +1914,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.23.5
|
||||
'@babel/helper-validator-option': 7.23.5
|
||||
browserslist: 4.22.2
|
||||
browserslist: 4.23.0
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
dev: true
|
||||
@@ -1970,29 +1973,29 @@ packages:
|
||||
resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/template': 7.22.15
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/template': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-hoist-variables@7.22.5:
|
||||
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-member-expression-to-functions@7.23.0:
|
||||
resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-module-imports@7.22.15:
|
||||
resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-module-transforms@7.23.3(@babel/core@7.23.6):
|
||||
@@ -2027,7 +2030,7 @@ packages:
|
||||
resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-plugin-utils@7.22.5:
|
||||
@@ -2068,21 +2071,21 @@ packages:
|
||||
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-skip-transparent-expression-wrappers@7.22.5:
|
||||
resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-split-export-declaration@7.22.6:
|
||||
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helper-string-parser@7.23.4:
|
||||
@@ -2104,17 +2107,17 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/helper-function-name': 7.23.0
|
||||
'@babel/template': 7.23.9
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/template': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/helpers@7.23.6:
|
||||
resolution: {integrity: sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/template': 7.22.15
|
||||
'@babel/traverse': 7.23.6
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/template': 7.24.0
|
||||
'@babel/traverse': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -2143,15 +2146,7 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.23.9:
|
||||
resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/parser@7.24.0:
|
||||
@@ -2295,7 +2290,7 @@ packages:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.0):
|
||||
@@ -2379,7 +2374,7 @@ packages:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.0):
|
||||
@@ -2496,7 +2491,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
'@babel/template': 7.23.9
|
||||
'@babel/template': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.24.0):
|
||||
@@ -2647,7 +2642,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
'@babel/helper-simple-access': 7.22.5
|
||||
dev: true
|
||||
|
||||
@@ -2849,9 +2844,9 @@ packages:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-annotate-as-pure': 7.22.5
|
||||
'@babel/helper-module-imports': 7.22.15
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
'@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.24.0)
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.24.0):
|
||||
@@ -2935,7 +2930,7 @@ packages:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-annotate-as-pure': 7.22.5
|
||||
'@babel/helper-create-class-features-plugin': 7.23.6(@babel/core@7.24.0)
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
'@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.24.0)
|
||||
dev: true
|
||||
|
||||
@@ -3080,7 +3075,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/types': 7.24.0
|
||||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
@@ -3091,7 +3086,7 @@ packages:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-plugin-utils': 7.24.0
|
||||
'@babel/helper-validator-option': 7.23.5
|
||||
'@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.24.0)
|
||||
'@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.24.0)
|
||||
@@ -3120,17 +3115,8 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.23.5
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.6
|
||||
dev: true
|
||||
|
||||
/@babel/template@7.23.9:
|
||||
resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.23.5
|
||||
'@babel/parser': 7.23.9
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@babel/template@7.24.0:
|
||||
@@ -3152,8 +3138,8 @@ packages:
|
||||
'@babel/helper-function-name': 7.23.0
|
||||
'@babel/helper-hoist-variables': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
debug: 4.3.4
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
@@ -3170,8 +3156,8 @@ packages:
|
||||
'@babel/helper-function-name': 7.23.0
|
||||
'@babel/helper-hoist-variables': 7.22.5
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
debug: 4.3.4
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
@@ -3213,15 +3199,6 @@ packages:
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@babel/types@7.23.9:
|
||||
resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.23.4
|
||||
'@babel/helper-validator-identifier': 7.22.20
|
||||
to-fast-properties: 2.0.0
|
||||
dev: true
|
||||
|
||||
/@babel/types@7.24.0:
|
||||
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -9227,20 +9204,20 @@ packages:
|
||||
/@types/babel__generator@7.6.8:
|
||||
resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@types/babel__template@7.4.4:
|
||||
resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/types': 7.23.6
|
||||
'@babel/parser': 7.24.0
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@types/babel__traverse@7.20.4:
|
||||
resolution: {integrity: sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==}
|
||||
dependencies:
|
||||
'@babel/types': 7.23.9
|
||||
'@babel/types': 7.24.0
|
||||
dev: true
|
||||
|
||||
/@types/bcryptjs@2.4.6:
|
||||
@@ -10141,7 +10118,7 @@ packages:
|
||||
/@vue/compiler-core@3.3.13:
|
||||
resolution: {integrity: sha512-bwi9HShGu7uaZLOErZgsH2+ojsEdsjerbf2cMXPwmvcgZfVPZ2BVZzCVnwZBxTAYd6Mzbmf6izcUNDkWnBBQ6A==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.6
|
||||
'@babel/parser': 7.24.0
|
||||
'@vue/shared': 3.3.13
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.0.2
|
||||
@@ -11004,6 +10981,7 @@ packages:
|
||||
electron-to-chromium: 1.4.615
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.13(browserslist@4.22.2)
|
||||
dev: false
|
||||
|
||||
/browserslist@4.23.0:
|
||||
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
|
||||
@@ -11608,7 +11586,7 @@ packages:
|
||||
/core-js-compat@3.34.0:
|
||||
resolution: {integrity: sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==}
|
||||
dependencies:
|
||||
browserslist: 4.22.2
|
||||
browserslist: 4.23.0
|
||||
dev: true
|
||||
|
||||
/core-util-is@1.0.3:
|
||||
@@ -11639,14 +11617,6 @@ packages:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
dev: true
|
||||
|
||||
/cross-env@7.0.3:
|
||||
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
|
||||
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
dev: true
|
||||
|
||||
/cross-spawn@5.1.0:
|
||||
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
|
||||
dependencies:
|
||||
@@ -12116,6 +12086,7 @@ packages:
|
||||
|
||||
/electron-to-chromium@1.4.615:
|
||||
resolution: {integrity: sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.690:
|
||||
resolution: {integrity: sha512-+2OAGjUx68xElQhydpcbqH50hE8Vs2K6TkAeLhICYfndb67CVH0UsZaijmRUE3rHlIxU1u0jxwhgVe6fK3YANA==}
|
||||
@@ -14291,15 +14262,6 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/isomorphic-fetch@3.0.0:
|
||||
resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
|
||||
dependencies:
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
whatwg-fetch: 3.6.20
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/isomorphic-ws@4.0.1(ws@8.16.0):
|
||||
resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==}
|
||||
peerDependencies:
|
||||
@@ -19747,6 +19709,7 @@ packages:
|
||||
browserslist: 4.22.2
|
||||
escalade: 3.1.1
|
||||
picocolors: 1.0.0
|
||||
dev: false
|
||||
|
||||
/update-browserslist-db@1.0.13(browserslist@4.23.0):
|
||||
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
|
||||
@@ -20265,10 +20228,6 @@ packages:
|
||||
iconv-lite: 0.6.3
|
||||
dev: true
|
||||
|
||||
/whatwg-fetch@3.6.20:
|
||||
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
|
||||
dev: true
|
||||
|
||||
/whatwg-mimetype@4.0.0:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
Reference in New Issue
Block a user