mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 08:20:14 +01:00
Feat/startup config api (#518)
* feat: add api for config * feat: add data service to client * feat: update client pages with values from config endpoint * test: update tests * Update configurations and documentation to remove VITE_SHOW_GOOGLE_LOGIN_OPTION and change VITE_APP_TITLE to APP_TITLE * include APP_TITLE with startup config * Add test for new route * update backend-review pipeline * comment out test until we can figure out testing routes in CI * update: .env.example --------- Co-authored-by: fuegovic <32828263+fuegovic@users.noreply.github.com>
This commit is contained in:
parent
2da81db440
commit
3634d8691a
25 changed files with 419 additions and 75 deletions
21
.env.example
21
.env.example
|
|
@ -2,6 +2,8 @@
|
|||
# Server configuration:
|
||||
##########################
|
||||
|
||||
APP_TITLE=LibreChat
|
||||
|
||||
# The server will listen to localhost:3080 by default. You can change the target IP as you want.
|
||||
# If you want to make this server available externally, for example to share the server with others
|
||||
# or expose this from a Docker container, set host to 0.0.0.0 or your external IP interface.
|
||||
|
|
@ -171,6 +173,9 @@ MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFCt
|
|||
# User System:
|
||||
##########################
|
||||
|
||||
# Allow Public Registration
|
||||
ALLOW_REGISTRATION=true
|
||||
|
||||
# JWT Secrets
|
||||
JWT_SECRET=secret
|
||||
JWT_REFRESH_SECRET=secret
|
||||
|
|
@ -197,19 +202,3 @@ SESSION_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
|||
|
||||
DOMAIN_CLIENT=http://localhost:3080
|
||||
DOMAIN_SERVER=http://localhost:3080
|
||||
|
||||
###########################
|
||||
# Frontend Configuration (Vite):
|
||||
###########################
|
||||
|
||||
# Custom app name, this text will be displayed in the landing page and the footer.
|
||||
VITE_APP_TITLE="LibreChat"
|
||||
|
||||
# Enable Social Login
|
||||
# This enables/disables the Login with Google button on the login page.
|
||||
# Set to true if you have registered the app with google cloud services
|
||||
# and have set the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET above
|
||||
VITE_SHOW_GOOGLE_LOGIN_OPTION=false
|
||||
|
||||
# Allow Public Registration
|
||||
ALLOW_REGISTRATION=true
|
||||
|
|
|
|||
5
.github/workflows/backend-review.yml
vendored
5
.github/workflows/backend-review.yml
vendored
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
name: Backend Unit Tests
|
||||
on:
|
||||
push:
|
||||
|
|
@ -34,7 +33,7 @@ jobs:
|
|||
run: npm ci
|
||||
|
||||
# - name: Install Linux X64 Sharp
|
||||
# run: npm install --platform=linux --arch=x64 --verbose sharp
|
||||
# run: npm install --platform=linux --arch=x64 --verbose sharp
|
||||
|
||||
- name: Run unit tests
|
||||
run: cd api && npm run test:ci
|
||||
run: cd api && npm run test:ci
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
"devDependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"path": "^0.12.7"
|
||||
"path": "^0.12.7",
|
||||
"supertest": "^6.3.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ config.validate(); // Validate the config
|
|||
app.use('/api/tokenizer', routes.tokenizer);
|
||||
app.use('/api/endpoints', routes.endpoints);
|
||||
app.use('/api/plugins', routes.plugins);
|
||||
app.use('/api/config', routes.config);
|
||||
|
||||
// static files
|
||||
app.get('/*', function (req, res) {
|
||||
|
|
|
|||
37
api/server/routes/__tests__/config.spec.js
Normal file
37
api/server/routes/__tests__/config.spec.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
const request = require('supertest');
|
||||
const express = require('express');
|
||||
const routes = require('../');
|
||||
|
||||
const app = express();
|
||||
app.use('/api/config', routes.config);
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.APP_TITLE;
|
||||
delete process.env.GOOGLE_CLIENT_ID;
|
||||
delete process.env.GOOGLE_CLIENT_SECRET;
|
||||
delete process.env.DOMAIN_SERVER;
|
||||
delete process.env.ALLOW_REGISTRATION;
|
||||
});
|
||||
|
||||
//TODO: This works/passes locally but http request tests fail with 404 in CI. Need to figure out why.
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
describe.skip('GET /', () => {
|
||||
it('should return 200 and the correct body', async () => {
|
||||
process.env.APP_TITLE = 'Test Title';
|
||||
process.env.GOOGLE_CLIENT_ID = 'Test Google Client Id';
|
||||
process.env.GOOGLE_CLIENT_SECRET = 'Test Google Client Secret';
|
||||
process.env.DOMAIN_SERVER = 'http://test-server.com';
|
||||
process.env.ALLOW_REGISTRATION = 'true';
|
||||
|
||||
const response = await request(app).get('/');
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.body).toEqual({
|
||||
appTitle: 'Test Title',
|
||||
googleLoginEnabled: true,
|
||||
serverDomain: 'http://test-server.com',
|
||||
registrationEnabled: 'true',
|
||||
});
|
||||
});
|
||||
});
|
||||
18
api/server/routes/config.js
Normal file
18
api/server/routes/config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async function (req, res) {
|
||||
try {
|
||||
const appTitle = process.env.APP_TITLE || 'LibreChat';
|
||||
const googleLoginEnabled = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET;
|
||||
const serverDomain = process.env.DOMAIN_SERVER || 'http://localhost:3080';
|
||||
const registrationEnabled = process.env.ALLOW_REGISTRATION || true;
|
||||
|
||||
return res.status(200).send({appTitle, googleLoginEnabled, serverDomain, registrationEnabled});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send({error: err.message});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -10,6 +10,7 @@ const oauth = require('./oauth');
|
|||
const { router: endpoints } = require('./endpoints');
|
||||
const plugins = require('./plugins');
|
||||
const user = require('./user');
|
||||
const config = require('./config');
|
||||
|
||||
module.exports = {
|
||||
search,
|
||||
|
|
@ -23,5 +24,6 @@ module.exports = {
|
|||
user,
|
||||
tokenizer,
|
||||
endpoints,
|
||||
plugins
|
||||
plugins,
|
||||
config
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import { useEffect } from 'react';
|
|||
import LoginForm from './LoginForm';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SHOW_GOOGLE_LOGIN_OPTION, ALLOW_REGISTRATION, DOMAIN_SERVER } from "~/utils/envConstants";
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
|
||||
function Login() {
|
||||
const { login, error, isAuthenticated } = useAuthContext();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ function Login() {
|
|||
</div>
|
||||
)}
|
||||
<LoginForm onSubmit={login} />
|
||||
{ALLOW_REGISTRATION && (
|
||||
{startupConfig?.registrationEnabled && (
|
||||
<p className="my-4 text-center text-sm font-light text-gray-700">
|
||||
{' '}
|
||||
Don't have an account?{' '}
|
||||
|
|
@ -38,7 +39,7 @@ function Login() {
|
|||
</a>
|
||||
</p>
|
||||
)}
|
||||
{SHOW_GOOGLE_LOGIN_OPTION && (
|
||||
{startupConfig?.googleLoginEnabled && (
|
||||
<>
|
||||
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
|
||||
<div className="absolute bg-white px-3 text-xs">Or</div>
|
||||
|
|
@ -47,7 +48,7 @@ function Login() {
|
|||
<a
|
||||
aria-label="Login with Google"
|
||||
className="justify-left flex w-full items-center space-x-3 rounded-md border border-gray-300 px-5 py-3 hover:bg-gray-50 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1"
|
||||
href={`${DOMAIN_SERVER}/oauth/google`}
|
||||
href={`${startupConfig.serverDomain}/oauth/google`}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRegisterUserMutation, TRegisterUser } from '~/data-provider';
|
||||
import { SHOW_GOOGLE_LOGIN_OPTION, DOMAIN_SERVER } from '~/utils/envConstants';
|
||||
import { useRegisterUserMutation, TRegisterUser, useGetStartupConfig } from '~/data-provider';
|
||||
|
||||
function Registration() {
|
||||
const navigate = useNavigate();
|
||||
const { data: startupConfig } = useGetStartupConfig();
|
||||
|
||||
const {
|
||||
register,
|
||||
|
|
@ -34,6 +34,12 @@ function Registration() {
|
|||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (startupConfig?.registrationEnabled === false) {
|
||||
navigate('/login');
|
||||
}
|
||||
}, [startupConfig, navigate]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 sm:pt-0">
|
||||
<div className="mt-6 w-96 overflow-hidden bg-white px-6 py-4 sm:max-w-md sm:rounded-lg">
|
||||
|
|
@ -266,7 +272,7 @@ function Registration() {
|
|||
Login
|
||||
</a>
|
||||
</p>
|
||||
{SHOW_GOOGLE_LOGIN_OPTION && (
|
||||
{startupConfig?.googleLoginEnabled && (
|
||||
<>
|
||||
<div className="relative mt-6 flex w-full items-center justify-center border border-t uppercase">
|
||||
<div className="absolute bg-white px-3 text-xs">Or</div>
|
||||
|
|
@ -275,7 +281,7 @@ function Registration() {
|
|||
<div className="mt-4 flex gap-x-2">
|
||||
<a
|
||||
aria-label="Login with Google"
|
||||
href={`${DOMAIN_SERVER}/oauth/google`}
|
||||
href={`${startupConfig.serverDomain}/oauth/google`}
|
||||
className="justify-left flex w-full items-center space-x-3 rounded-md border border-gray-300 px-5 py-3 hover:bg-gray-50 focus:ring-2 focus:ring-violet-600 focus:ring-offset-1"
|
||||
>
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -3,12 +3,6 @@ import userEvent from '@testing-library/user-event';
|
|||
import Login from '../Login';
|
||||
import * as mockDataProvider from '~/data-provider';
|
||||
|
||||
jest.mock('~/utils/envConstants', () => ({
|
||||
DOMAIN_SERVER: 'mock-server',
|
||||
SHOW_GOOGLE_LOGIN_OPTION: true,
|
||||
ALLOW_REGISTRATION: true
|
||||
}));
|
||||
|
||||
jest.mock('~/data-provider');
|
||||
|
||||
const setup = ({
|
||||
|
|
@ -23,6 +17,15 @@ const setup = ({
|
|||
mutate: jest.fn(),
|
||||
data: {},
|
||||
isSuccess: false
|
||||
},
|
||||
useGetStartupCongfigReturnValue = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
data: {
|
||||
googleLoginEnabled: true,
|
||||
registrationEnabled: true,
|
||||
serverDomain: 'mock-server'
|
||||
}
|
||||
}
|
||||
} = {}) => {
|
||||
const mockUseLoginUser = jest
|
||||
|
|
@ -33,12 +36,16 @@ const setup = ({
|
|||
.spyOn(mockDataProvider, 'useGetUserQuery')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useGetUserQueryReturnValue);
|
||||
const mockUseGetStartupConfig = jest
|
||||
.spyOn(mockDataProvider, 'useGetStartupConfig')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useGetStartupCongfigReturnValue);
|
||||
const renderResult = render(<Login />);
|
||||
|
||||
return {
|
||||
...renderResult,
|
||||
mockUseLoginUser,
|
||||
mockUseGetUserQuery
|
||||
mockUseGetUserQuery,
|
||||
mockUseGetStartupConfig
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,6 @@ import userEvent from '@testing-library/user-event';
|
|||
import Registration from '../Registration';
|
||||
import * as mockDataProvider from '~/data-provider';
|
||||
|
||||
jest.mock('~/utils/envConstants', () => ({
|
||||
DOMAIN_SERVER: 'mock-server',
|
||||
SHOW_GOOGLE_LOGIN_OPTION: true
|
||||
}));
|
||||
|
||||
jest.mock('~/data-provider');
|
||||
|
||||
const setup = ({
|
||||
|
|
@ -22,6 +17,15 @@ const setup = ({
|
|||
mutate: jest.fn(),
|
||||
data: {},
|
||||
isSuccess: false
|
||||
},
|
||||
useGetStartupCongfigReturnValue = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
data: {
|
||||
googleLoginEnabled: true,
|
||||
registrationEnabled: true,
|
||||
serverDomain: 'mock-server'
|
||||
}
|
||||
}
|
||||
} = {}) => {
|
||||
const mockUseRegisterUserMutation = jest
|
||||
|
|
@ -32,13 +36,18 @@ const setup = ({
|
|||
.spyOn(mockDataProvider, 'useGetUserQuery')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useGetUserQueryReturnValue);
|
||||
const mockUseGetStartupConfig = jest
|
||||
.spyOn(mockDataProvider, 'useGetStartupConfig')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useGetStartupCongfigReturnValue);
|
||||
|
||||
const renderResult = render(<Registration />);
|
||||
|
||||
return {
|
||||
...renderResult,
|
||||
mockUseRegisterUserMutation,
|
||||
mockUseGetUserQuery
|
||||
mockUseGetUserQuery,
|
||||
mockUseGetStartupConfig
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
|
||||
export default function Footer() {
|
||||
const { data: config } = useGetStartupConfig();
|
||||
return (
|
||||
<div className="hidden px-3 pb-1 pt-2 text-center text-xs text-black/50 dark:text-white/50 md:block md:px-4 md:pb-4 md:pt-3">
|
||||
<a
|
||||
|
|
@ -9,7 +11,7 @@ export default function Footer() {
|
|||
rel="noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
{import.meta.env.VITE_APP_TITLE || 'LibreChat'}
|
||||
{config?.appTitle || 'LibreChat'}
|
||||
</a>
|
||||
. Serves and searches all conversations reliably. All AI convos under one house. Pay per call
|
||||
and not per month (cents compared to dollars).
|
||||
|
|
@ -1,20 +1,24 @@
|
|||
import React from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||
import SunIcon from '../svg/SunIcon';
|
||||
import LightningIcon from '../svg/LightningIcon';
|
||||
import CautionIcon from '../svg/CautionIcon';
|
||||
import store from '~/store';
|
||||
import { useGetStartupConfig } from '~/data-provider';
|
||||
|
||||
export default function Landing() {
|
||||
const { data: config } = useGetStartupConfig();
|
||||
const setText = useSetRecoilState(store.text);
|
||||
const conversation = useRecoilValue(store.conversation);
|
||||
// @ts-ignore TODO: Fix anti-pattern - requires refactoring conversation store
|
||||
const { title = 'New Chat' } = conversation || {};
|
||||
|
||||
useDocumentTitle(title);
|
||||
|
||||
const clickHandler = (e) => {
|
||||
const clickHandler = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
const { innerText } = e.target;
|
||||
const { innerText } = e.target as HTMLButtonElement;
|
||||
const quote = innerText.split('"')[1].trim();
|
||||
setText(quote);
|
||||
};
|
||||
|
|
@ -26,7 +30,7 @@ export default function Landing() {
|
|||
id="landing-title"
|
||||
className="mb-10 ml-auto mr-auto mt-6 flex items-center justify-center gap-2 text-center text-4xl font-semibold sm:mb-16 md:mt-[10vh]"
|
||||
>
|
||||
{import.meta.env.VITE_APP_TITLE || 'LibreChat'}
|
||||
{config?.appTitle || 'LibreChat'}
|
||||
</h1>
|
||||
<div className="items-start gap-3.5 text-center md:flex">
|
||||
<div className="mb-8 flex flex-1 flex-col gap-3.5 md:mb-auto">
|
||||
|
|
@ -89,3 +89,7 @@ export const resetPassword = () => {
|
|||
export const plugins = () => {
|
||||
return '/api/plugins';
|
||||
};
|
||||
|
||||
export const config = () => {
|
||||
return '/api/config';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,3 +111,7 @@ export const getAvailablePlugins = (): Promise<t.TPlugin[]> => {
|
|||
export const updateUserPlugins = (payload: t.TUpdateUserPlugins) => {
|
||||
return request.post(endpoints.userPlugins(), payload);
|
||||
};
|
||||
|
||||
export const getStartupConfig = (): Promise<t.TStartupConfig> => {
|
||||
return request.get(endpoints.config());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ export enum QueryKeys {
|
|||
presets = 'presets',
|
||||
searchResults = 'searchResults',
|
||||
tokenCount = 'tokenCount',
|
||||
availablePlugins = 'availablePlugins'
|
||||
availablePlugins = 'availablePlugins',
|
||||
startupConfig = 'startupConfig',
|
||||
}
|
||||
|
||||
export const useAbortRequestWithMessage = (): UseMutationResult<
|
||||
|
|
@ -336,3 +337,11 @@ export const useUpdateUserPluginsMutation = (): UseMutationResult<
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetStartupConfig = (): QueryObserverResult<t.TStartupConfig> => {
|
||||
return useQuery<t.TStartupConfig>([QueryKeys.startupConfig], () => dataService.getStartupConfig(), {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,3 +233,10 @@ export type TResetPassword = {
|
|||
token: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type TStartupConfig = {
|
||||
appTitle: boolean;
|
||||
googleLoginEnabled: boolean;
|
||||
serverDomain: string;
|
||||
registrationEnabled: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ import Messages from '../components/Messages';
|
|||
import TextChat from '../components/Input';
|
||||
|
||||
import store from '~/store';
|
||||
import { useGetMessagesByConvoId, useGetConversationByIdMutation } from '~/data-provider';
|
||||
import {
|
||||
useGetMessagesByConvoId,
|
||||
useGetConversationByIdMutation,
|
||||
useGetStartupConfig
|
||||
} from '~/data-provider';
|
||||
|
||||
export default function Chat() {
|
||||
const searchQuery = useRecoilValue(store.searchQuery);
|
||||
|
|
@ -21,6 +25,7 @@ export default function Chat() {
|
|||
//disabled by default, we only enable it when messagesTree is null
|
||||
const messagesQuery = useGetMessagesByConvoId(conversationId, { enabled: false });
|
||||
const getConversationMutation = useGetConversationByIdMutation(conversationId);
|
||||
const { data: config } = useGetStartupConfig();
|
||||
|
||||
// when conversation changed or conversationId (in url) changed
|
||||
useEffect(() => {
|
||||
|
|
@ -53,8 +58,8 @@ export default function Chat() {
|
|||
// conversationId (in url) should always follow conversation?.conversationId, unless conversation is null
|
||||
navigate(`/chat/${conversation?.conversationId}`);
|
||||
}
|
||||
document.title = conversation?.title || import.meta.env.VITE_APP_TITLE || 'Chat';
|
||||
}, [conversation, conversationId]);
|
||||
document.title = conversation?.title || config?.appTitle || 'Chat';
|
||||
}, [conversation, conversationId, config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (messagesTree === null && conversation?.conversationId) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Search from './Search';
|
|||
import { Login, Registration, RequestPasswordReset, ResetPassword } from '../components/Auth';
|
||||
import { AuthContextProvider } from '../hooks/AuthContext';
|
||||
import ApiErrorWatcher from '../components/Auth/ApiErrorWatcher';
|
||||
import { ALLOW_REGISTRATION } from '../utils/envConstants';
|
||||
|
||||
const AuthLayout = () => (
|
||||
<AuthContextProvider>
|
||||
|
|
@ -17,7 +16,7 @@ const AuthLayout = () => (
|
|||
export const router = createBrowserRouter([
|
||||
{
|
||||
path: 'register',
|
||||
element: ALLOW_REGISTRATION ? <Registration /> : <Navigate to="/login" replace={true} />
|
||||
element: <Registration />
|
||||
},
|
||||
{
|
||||
path: 'forgot-password',
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
const ALLOW_REGISTRATION = import.meta.env.ALLOW_REGISTRATION === 'true';
|
||||
const DOMAIN_SERVER = import.meta.env.DOMAIN_SERVER;
|
||||
const SHOW_GOOGLE_LOGIN_OPTION = import.meta.env.VITE_SHOW_GOOGLE_LOGIN_OPTION === 'true';
|
||||
|
||||
export {
|
||||
ALLOW_REGISTRATION,
|
||||
DOMAIN_SERVER,
|
||||
SHOW_GOOGLE_LOGIN_OPTION
|
||||
};
|
||||
|
|
@ -60,7 +60,7 @@ let env = {};
|
|||
const title = await askQuestion(
|
||||
'Enter the app title (default: "LibreChat"): '
|
||||
);
|
||||
env['VITE_APP_TITLE'] = title || 'LibreChat';
|
||||
env['APP_TITLE'] = title || 'LibreChat';
|
||||
|
||||
// Ask for OPENAI_API_KEY
|
||||
const key = await askQuestion(
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ const removeEnvs = {
|
|||
'SERVER_URL_PROD': 'remove',
|
||||
'JWT_SECRET_DEV': 'remove', // Lets regen
|
||||
'JWT_SECRET_PROD': 'remove', // Lets regen
|
||||
'VITE_APP_TITLE': 'remove',
|
||||
// Comments to remove:
|
||||
'#JWT:': 'remove',
|
||||
'# Add a secure secret for production if deploying to live domain.': 'remove',
|
||||
|
|
@ -120,11 +121,10 @@ loader.addSecureEnvVar(rootEnvPath, 'JWT_SECRET', 32);
|
|||
// Lets update the openai key name, not the best spot in the env file but who cares ¯\_(ツ)_/¯
|
||||
loader.writeEnvFile(rootEnvPath, {'OPENAI_API_KEY': initEnv['OPENAI_KEY']})
|
||||
|
||||
// TODO: we need to copy over the value of: VITE_SHOW_GOOGLE_LOGIN_OPTION & VITE_APP_TITLE
|
||||
// TODO: we need to copy over the value of: APP_TITLE
|
||||
fs.appendFileSync(rootEnvPath, '\n\n##########################\n# Frontend Vite Variables:\n##########################\n');
|
||||
const frontend = {
|
||||
'VITE_APP_TITLE': initEnv['VITE_APP_TITLE'] || '"LibreChat"',
|
||||
'VITE_SHOW_GOOGLE_LOGIN_OPTION': initEnv['VITE_SHOW_GOOGLE_LOGIN_OPTION'] || 'false',
|
||||
'APP_TITLE': initEnv['VITE_APP_TITLE'] || '"LibreChat"',
|
||||
'ALLOW_REGISTRATION': 'true'
|
||||
}
|
||||
loader.writeEnvFile(rootEnvPath, frontend)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ When the first account is registered, the application will automatically migrate
|
|||
|
||||
The application is setup to support OAuth2/Social Login with Google. All of the code is in place for Facebook login as well, but this has not been tested because the setup process with Facebook was honestly just too painful for me to deal with. I plan to add support for other OAuth2 providers including Github and Discord at a later time.
|
||||
|
||||
To enable Google login, you must create an application in the [Google Cloud Console](https://cloud.google.com) and provide the client ID and client secret in the `/.env` file, then set `VITE_SHOW_GOOGLE_LOGIN_OPTION=true`.
|
||||
To enable Google login, you must create an application in the [Google Cloud Console](https://cloud.google.com) and provide the client ID and client secret in the `/.env` file.
|
||||
|
||||
### *Instructions for setting up Google login are provided below.*
|
||||
```
|
||||
|
|
|
|||
|
|
@ -50,11 +50,10 @@ To update LibreChat. enter these commands one after the other from the root dir:
|
|||
- MEILI_HOST=http://meilisearch:7700
|
||||
- MEILI_HTTP_ADDR=meilisearch:7700
|
||||
```
|
||||
- If you'd like to change the app title or disable/enable google login, edit the following lines (the ones in your .env file are not read during building)
|
||||
- If you'd like to change the app title, edit the following lines (the ones in your .env file are not read during building)
|
||||
```yaml
|
||||
args:
|
||||
VITE_APP_TITLE: LibreChat # default, change to your desired app name
|
||||
VITE_SHOW_GOOGLE_LOGIN_OPTION: false # default, change to true if you have google auth setup
|
||||
APP_TITLE: LibreChat # default, change to your desired app name
|
||||
```
|
||||
|
||||
- If for some reason you're not able to build the app image, you can pull the latest image from **Dockerhub**.
|
||||
|
|
@ -67,8 +66,7 @@ To update LibreChat. enter these commands one after the other from the root dir:
|
|||
context: .
|
||||
target: node
|
||||
args:
|
||||
VITE_APP_TITLE: LibreChat # default, change to your desired app name
|
||||
VITE_SHOW_GOOGLE_LOGIN_OPTION: false # default, change to true if you have google auth setup
|
||||
APP_TITLE: LibreChat # default, change to your desired app name
|
||||
```
|
||||
|
||||
- Comment this line in (remove the `#` key)
|
||||
|
|
|
|||
255
package-lock.json
generated
255
package-lock.json
generated
|
|
@ -81,7 +81,8 @@
|
|||
"devDependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"path": "^0.12.7"
|
||||
"path": "^0.12.7",
|
||||
"supertest": "^6.3.3"
|
||||
}
|
||||
},
|
||||
"api/node_modules/ansi-styles": {
|
||||
|
|
@ -8340,6 +8341,12 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
|
|
@ -9883,6 +9890,12 @@
|
|||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
|
@ -10013,6 +10026,12 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/copy-anything": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
|
||||
|
|
@ -10638,6 +10657,16 @@
|
|||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
|
@ -12087,6 +12116,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-text-encoding": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz",
|
||||
|
|
@ -12486,6 +12521,21 @@
|
|||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
|
@ -13273,6 +13323,15 @@
|
|||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.8.0",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
|
||||
|
|
@ -23640,6 +23699,72 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "8.0.9",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz",
|
||||
"integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0 <13 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/semver": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
|
||||
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/superjson": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz",
|
||||
|
|
@ -23652,6 +23777,19 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/supertest": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
|
||||
"integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^8.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
|
@ -29339,7 +29477,8 @@
|
|||
"path": "^0.12.7",
|
||||
"pino": "^8.12.1",
|
||||
"sanitize": "^2.1.2",
|
||||
"sharp": "^0.32.1"
|
||||
"sharp": "^0.32.1",
|
||||
"supertest": "^6.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
|
|
@ -31371,6 +31510,12 @@
|
|||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
|
||||
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="
|
||||
},
|
||||
"asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"asn1.js": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
|
||||
|
|
@ -32534,6 +32679,12 @@
|
|||
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||
"dev": true
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
|
@ -32647,6 +32798,12 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"copy-anything": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
|
||||
|
|
@ -33104,6 +33261,16 @@
|
|||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||
},
|
||||
"dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
|
@ -34216,6 +34383,12 @@
|
|||
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.2.0.tgz",
|
||||
"integrity": "sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw=="
|
||||
},
|
||||
"fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-text-encoding": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz",
|
||||
|
|
@ -34512,6 +34685,18 @@
|
|||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="
|
||||
},
|
||||
"formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
|
@ -35090,6 +35275,12 @@
|
|||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"dev": true
|
||||
},
|
||||
"highlight.js": {
|
||||
"version": "11.8.0",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
|
||||
|
|
@ -42241,6 +42432,56 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"superagent": {
|
||||
"version": "8.0.9",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz",
|
||||
"integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
|
||||
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"superjson": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz",
|
||||
|
|
@ -42250,6 +42491,16 @@
|
|||
"copy-anything": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"supertest": {
|
||||
"version": "6.3.3",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
|
||||
"integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^8.0.5"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue