diff --git a/waspc/cli/src/Wasp/Cli/Command/Build.hs b/waspc/cli/src/Wasp/Cli/Command/Build.hs index 98e9d866f4..591fc1256e 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Build.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Build.hs @@ -22,13 +22,7 @@ import qualified Wasp.Generator import Wasp.Generator.Monad (GeneratorWarning (GeneratorNeedsMigrationWarning)) import qualified Wasp.Message as Msg import Wasp.Project (CompileError, CompileWarning, WaspProjectDir) -import Wasp.Project.Common - ( buildDirInDotWaspDir, - dotWaspDirInWaspProjectDir, - extClientCodeDirInWaspProjectDir, - extServerCodeDirInWaspProjectDir, - extSharedCodeDirInWaspProjectDir, - ) +import Wasp.Project.Common (buildDirInDotWaspDir, dotWaspDirInWaspProjectDir) -- | Builds Wasp project that the current working directory is part of. -- Does all the steps, from analysis to generation, and at the end writes generated code @@ -71,9 +65,7 @@ buildIO waspProjectDir buildDir = compileIOWithOptions options waspProjectDir bu where options = CompileOptions - { externalClientCodeDirPath = waspProjectDir extClientCodeDirInWaspProjectDir, - externalServerCodeDirPath = waspProjectDir extServerCodeDirInWaspProjectDir, - externalSharedCodeDirPath = waspProjectDir extSharedCodeDirInWaspProjectDir, + { waspProjectDirPath = waspProjectDir, isBuild = True, sendMessage = cliSendMessage, -- Ignore "DB needs migration warnings" during build, as that is not a required step. diff --git a/waspc/cli/src/Wasp/Cli/Command/Compile.hs b/waspc/cli/src/Wasp/Cli/Command/Compile.hs index 0d56a97e21..c1bb97d15f 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Compile.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Compile.hs @@ -26,7 +26,7 @@ import qualified Wasp.Generator import qualified Wasp.Message as Msg import Wasp.Project (CompileError, CompileWarning, WaspProjectDir) import qualified Wasp.Project -import Wasp.Project.Common (dotWaspDirInWaspProjectDir, extClientCodeDirInWaspProjectDir, extServerCodeDirInWaspProjectDir, extSharedCodeDirInWaspProjectDir, generatedCodeDirInDotWaspDir) +import Wasp.Project.Common (dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir) -- | Same like 'compileWithOptions', but with default compile options. compile :: Command [CompileWarning] @@ -115,9 +115,7 @@ compileIOWithOptions options waspProjectDir outDir = defaultCompileOptions :: Path' Abs (Dir WaspProjectDir) -> CompileOptions defaultCompileOptions waspProjectDir = CompileOptions - { externalServerCodeDirPath = waspProjectDir extServerCodeDirInWaspProjectDir, - externalClientCodeDirPath = waspProjectDir extClientCodeDirInWaspProjectDir, - externalSharedCodeDirPath = waspProjectDir extSharedCodeDirInWaspProjectDir, + { waspProjectDirPath = waspProjectDir, isBuild = False, sendMessage = cliSendMessage, generatorWarningsFilter = id diff --git a/waspc/cli/src/Wasp/Cli/Command/Test.hs b/waspc/cli/src/Wasp/Cli/Command/Test.hs index e9378bfbce..ed7bee49e7 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Test.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Test.hs @@ -15,9 +15,12 @@ import Wasp.Cli.Command.Message (cliSendMessageC) import Wasp.Cli.Command.Require (InWaspProject (InWaspProject), require) import Wasp.Cli.Command.Watch (watch) import qualified Wasp.Generator -import Wasp.Generator.Common (ProjectRootDir) import qualified Wasp.Message as Msg -import Wasp.Project.Common (dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir) +import Wasp.Project.Common + ( WaspProjectDir, + dotWaspDirInWaspProjectDir, + generatedCodeDirInDotWaspDir, + ) test :: [String] -> Command () test [] = throwError $ CommandError "Not enough arguments" "Expected: wasp test client " @@ -25,7 +28,7 @@ test ("client" : args) = watchAndTest $ Wasp.Generator.testWebApp args test ("server" : _args) = throwError $ CommandError "Invalid arguments" "Server testing not yet implemented." test _ = throwError $ CommandError "Invalid arguments" "Expected: wasp test client " -watchAndTest :: (Path' Abs (Dir ProjectRootDir) -> IO (Either String ())) -> Command () +watchAndTest :: (Path' Abs (Dir WaspProjectDir) -> IO (Either String ())) -> Command () watchAndTest testRunner = do InWaspProject waspRoot <- require let outDir = waspRoot dotWaspDirInWaspProjectDir generatedCodeDirInDotWaspDir @@ -39,7 +42,7 @@ watchAndTest testRunner = do watchOrStartResult <- liftIO $ do ongoingCompilationResultMVar <- newMVar (warnings, []) let watchWaspProjectSource = watch waspRoot outDir ongoingCompilationResultMVar - watchWaspProjectSource `race` testRunner outDir + watchWaspProjectSource `race` testRunner waspRoot case watchOrStartResult of Left () -> error "This should never happen, listening for file changes should never end but it did." diff --git a/waspc/cli/src/Wasp/Cli/Command/Watch.hs b/waspc/cli/src/Wasp/Cli/Command/Watch.hs index 6372d776f9..81ca7df6c6 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Watch.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Watch.hs @@ -19,7 +19,7 @@ import Wasp.Cli.Message (cliSendMessage) import qualified Wasp.Generator.Common as Wasp.Generator import qualified Wasp.Message as Msg import Wasp.Project (CompileError, CompileWarning, WaspProjectDir) -import Wasp.Project.Common (srcDirInWaspProjectDir) +import Wasp.Project.Common (extPublicDirInWaspProjectDir, srcDirInWaspProjectDir) -- TODO: Idea: Read .gitignore file, and ignore everything from it. This will then also cover the -- .wasp dir, and users can easily add any custom stuff they want ignored. But, we also have to @@ -39,7 +39,8 @@ watch :: watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mgr -> do chan <- newChan _ <- watchFilesAtTopLevelOfWaspProjectDir mgr chan - _ <- watchFilesAtAllLevelsOfSrcDirInWaspProjectDir mgr chan + _ <- watchFilesAtAllLevelsOfDirInWaspProjectDir mgr chan srcDirInWaspProjectDir + _ <- watchFilesAtAllLevelsOfDirInWaspProjectDir mgr chan extPublicDirInWaspProjectDir listenForEvents chan =<< getCurrentTime where watchFilesAtTopLevelOfWaspProjectDir mgr chan = @@ -53,8 +54,8 @@ watch waspProjectDir outDir ongoingCompilationResultMVar = FSN.withManager $ \mg where filename = FP.takeFileName $ FSN.eventPath event - watchFilesAtAllLevelsOfSrcDirInWaspProjectDir mgr chan = - FSN.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir srcDirInWaspProjectDir) eventFilter chan + watchFilesAtAllLevelsOfDirInWaspProjectDir mgr chan dirInWaspProjectDir = + FSN.watchTreeChan mgr (SP.fromAbsDir $ waspProjectDir dirInWaspProjectDir) eventFilter chan where eventFilter :: FSN.Event -> Bool eventFilter event = diff --git a/waspc/data/Cli/templates/basic/package.json b/waspc/data/Cli/templates/basic/package.json index 1a40fbd90c..d9b1ec1fd9 100644 --- a/waspc/data/Cli/templates/basic/package.json +++ b/waspc/data/Cli/templates/basic/package.json @@ -5,6 +5,8 @@ "react": "^18.2.0" }, "devDependencies": { + "typescript": "^5.1.0", + "vite": "^4.3.9", "@types/react": "^18.0.37", "prisma": "4.16.2" }, diff --git a/waspc/data/Cli/templates/basic/src/client/vite.config.ts b/waspc/data/Cli/templates/basic/vite.config.ts similarity index 100% rename from waspc/data/Cli/templates/basic/src/client/vite.config.ts rename to waspc/data/Cli/templates/basic/vite.config.ts diff --git a/waspc/data/Generator/templates/react-app/scripts/validate-env.mjs b/waspc/data/Generator/templates/react-app/scripts/validate-env.mjs index 27d6a9fd59..18ee507c9e 100644 --- a/waspc/data/Generator/templates/react-app/scripts/validate-env.mjs +++ b/waspc/data/Generator/templates/react-app/scripts/validate-env.mjs @@ -1,4 +1,4 @@ -import { throwIfNotValidAbsoluteURL } from './universal/validators.mjs'; +import { throwIfNotValidAbsoluteURL } from 'wasp/universal/validators'; console.info("🔍 Validating environment variables..."); throwIfNotValidAbsoluteURL(process.env.REACT_APP_API_URL, 'Environemnt variable REACT_APP_API_URL'); diff --git a/waspc/data/Generator/templates/react-app/src/actions/_action.ts b/waspc/data/Generator/templates/react-app/src/actions/_action.ts deleted file mode 100644 index 57a158956d..0000000000 --- a/waspc/data/Generator/templates/react-app/src/actions/_action.ts +++ /dev/null @@ -1,10 +0,0 @@ -{{={= =}=}} -import { createAction } from './core' -{=& operationTypeImportStmt =} - -const action = createAction<{= operationTypeName =}>( - '{= actionRoute =}', - {=& entitiesArray =}, -) - -export default action diff --git a/waspc/data/Generator/templates/react-app/src/actions/core.d.ts b/waspc/data/Generator/templates/react-app/src/actions/core.d.ts deleted file mode 100644 index fa31f329ff..0000000000 --- a/waspc/data/Generator/templates/react-app/src/actions/core.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type Action } from '.' -import type { Expand, _Awaited, _ReturnType } from '../universal/types' - -export function createAction( - actionRoute: string, - entitiesUsed: unknown[] -): ActionFor - -type ActionFor = Expand< - Action[0], _Awaited<_ReturnType>> -> - -type GenericBackendAction = (args: never, context: any) => unknown diff --git a/waspc/data/Generator/templates/react-app/src/actions/core.js b/waspc/data/Generator/templates/react-app/src/actions/core.js deleted file mode 100644 index 440e906140..0000000000 --- a/waspc/data/Generator/templates/react-app/src/actions/core.js +++ /dev/null @@ -1,35 +0,0 @@ -import { callOperation, makeOperationRoute } from '../operations' -import { - registerActionInProgress, - registerActionDone, -} from '../operations/resources' - -export function createAction(relativeActionRoute, entitiesUsed) { - const actionRoute = makeOperationRoute(relativeActionRoute) - - async function internalAction(args, specificOptimisticUpdateDefinitions) { - registerActionInProgress(specificOptimisticUpdateDefinitions) - try { - // The `return await` is not redundant here. If we removed the await, the - // `finally` block would execute before the action finishes, prematurely - // registering the action as done. - return await callOperation(actionRoute, args) - } finally { - await registerActionDone(entitiesUsed, specificOptimisticUpdateDefinitions) - } - } - - // We expose (and document) a restricted version of the API for our users, - // while also attaching the full "internal" API to the exposed action. By - // doing this, we can easily use the internal API of an action a users passes - // into our system (e.g., through the `useAction` hook) without needing a - // lookup table. - // - // While it does technically allow our users to access the interal API, it - // shouldn't be a problem in practice. Still, if it turns out to be a problem, - // we can always hide it using a Symbol. - const action = (args) => internalAction(args, []) - action.internal = internalAction - - return action -} diff --git a/waspc/data/Generator/templates/react-app/src/actions/index.ts b/waspc/data/Generator/templates/react-app/src/actions/index.ts deleted file mode 100644 index 7fb2de2f9e..0000000000 --- a/waspc/data/Generator/templates/react-app/src/actions/index.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { - QueryClient, - QueryKey, - useMutation, - UseMutationOptions, - useQueryClient, -} from '@tanstack/react-query' -import { type Query } from '../queries'; - -export type Action = - [Input] extends [never] ? - (args?: unknown) => Promise : - (args: Input) => Promise - -/** - * An options object passed into the `useAction` hook and used to enhance the - * action with extra options. - * - */ -export type ActionOptions = { - optimisticUpdates: OptimisticUpdateDefinition[] -} - -/** - * A documented (public) way to define optimistic updates. - */ -export type OptimisticUpdateDefinition = { - getQuerySpecifier: GetQuerySpecifier - updateQuery: UpdateQuery -} - -/** - * A function that takes an item and returns a Wasp Query specifier. - */ -export type GetQuerySpecifier = (item: ActionInput) => QuerySpecifier - -/** - * A function that takes an item and the previous state of the cache, and returns - * the desired (new) state of the cache. - */ -export type UpdateQuery = (item: ActionInput, oldData: CachedData | undefined) => CachedData - -/** - * A public query specifier used for addressing Wasp queries. See our docs for details: - * https://wasp-lang.dev/docs/data-model/operations/actions#the-useaction-hook-and-optimistic-updates - */ -export type QuerySpecifier = [Query, ...any[]] - -/** - * A hook for adding extra behavior to a Wasp Action (e.g., optimistic updates). - * - * @param actionFn The Wasp Action you wish to enhance/decorate. - * @param actionOptions An options object for enhancing/decorating the given Action. - * @returns A decorated Action with added behavior but an unchanged API. - */ -export function useAction( - actionFn: Action, - actionOptions?: ActionOptions -): typeof actionFn { - const queryClient = useQueryClient(); - - let mutationFn = actionFn - let options = {} - if (actionOptions?.optimisticUpdates) { - const optimisticUpdatesDefinitions = actionOptions.optimisticUpdates.map(translateToInternalDefinition) - mutationFn = makeOptimisticUpdateMutationFn(actionFn, optimisticUpdatesDefinitions) - options = makeRqOptimisticUpdateOptions(queryClient, optimisticUpdatesDefinitions) - } - - // NOTE: We decided to hide React Query's extra mutation features (e.g., - // isLoading, onSuccess and onError callbacks, synchronous mutate) and only - // expose a simple async function whose API matches the original Action. - // We did this to avoid cluttering the API with stuff we're not sure we need - // yet (e.g., isLoading), to postpone the action vs mutation dilemma, and to - // clearly separate our opinionated API from React Query's lower-level - // advanced API (which users can also use) - const mutation = useMutation(mutationFn, options) - return (args) => mutation.mutateAsync(args) -} - -/** - * An internal (undocumented, private, desugared) way of defining optimistic updates. - */ -type InternalOptimisticUpdateDefinition = { - getQueryKey: (item: ActionInput) => QueryKey, - updateQuery: UpdateQuery; - -} - -/** - * An UpdateQuery function "instantiated" with a specific item. It only takes - * the current state of the cache and returns the desired (new) state of the - * cache. - */ -type SpecificUpdateQuery = (oldData: CachedData) => CachedData - -/** - * A specific, "instantiated" optimistic update definition which contains a - * fully-constructed query key and a specific update function. - */ -type SpecificOptimisticUpdateDefinition = { - queryKey: QueryKey; - updateQuery: SpecificUpdateQuery; -} - -type InternalAction = Action & { - internal( - item: Input, - optimisticUpdateDefinitions: SpecificOptimisticUpdateDefinition[] - ): Promise -} - -/** - * Translates/Desugars a public optimistic update definition object into a - * definition object our system uses internally. - * - * @param publicOptimisticUpdateDefinition An optimistic update definition - * object that's a part of the public API: - * https://wasp-lang.dev/docs/data-model/operations/actions#the-useaction-hook-and-optimistic-updates - * @returns An internally-used optimistic update definition object. - */ -function translateToInternalDefinition( - publicOptimisticUpdateDefinition: OptimisticUpdateDefinition -): InternalOptimisticUpdateDefinition { - const { getQuerySpecifier, updateQuery } = publicOptimisticUpdateDefinition - - const definitionErrors = [] - if (typeof getQuerySpecifier !== 'function') { - definitionErrors.push('`getQuerySpecifier` is not a function.') - } - if (typeof updateQuery !== 'function') { - definitionErrors.push('`updateQuery` is not a function.') - } - if (definitionErrors.length) { - throw new TypeError(`Invalid optimistic update definition: ${definitionErrors.join(', ')}.`) - } - - return { - getQueryKey: (item) => getRqQueryKeyFromSpecifier(getQuerySpecifier(item)), - updateQuery, - } -} - -/** - * Creates a function that performs an action while telling it about the - * optimistic updates it caused. - * - * @param actionFn The Wasp Action. - * @param optimisticUpdateDefinitions The optimisitc updates the action causes. - * @returns An decorated action which performs optimistic updates. - */ -function makeOptimisticUpdateMutationFn( - actionFn: Action, - optimisticUpdateDefinitions: InternalOptimisticUpdateDefinition[] -): typeof actionFn { - return function performActionWithOptimisticUpdates(item) { - const specificOptimisticUpdateDefinitions = optimisticUpdateDefinitions.map( - generalDefinition => getOptimisticUpdateDefinitionForSpecificItem(generalDefinition, item) - ) - return (actionFn as InternalAction).internal(item, specificOptimisticUpdateDefinitions) - } -} - -/** - * Given a ReactQuery query client and our internal definition of optimistic - * updates, this function constructs an object describing those same optimistic - * updates in a format we can pass into React Query's useMutation hook. In other - * words, it translates our optimistic updates definition into React Query's - * optimistic updates definition. Check their docs for details: - * https://tanstack.com/query/v4/docs/guides/optimistic-updates?from=reactQueryV3&original=https://react-query-v3.tanstack.com/guides/optimistic-updates - * - * @param queryClient The QueryClient instance used by React Query. - * @param optimisticUpdateDefinitions A list containing internal optimistic - * updates definition objects (i.e., a list where each object carries the - * instructions for performing particular optimistic update). - * @returns An object containing 'onMutate' and 'onError' functions - * corresponding to the given optimistic update definitions (check the docs - * linked above for details). - */ -function makeRqOptimisticUpdateOptions( - queryClient: QueryClient, - optimisticUpdateDefinitions: InternalOptimisticUpdateDefinition[] -): Pick { - async function onMutate(item) { - const specificOptimisticUpdateDefinitions = optimisticUpdateDefinitions.map( - generalDefinition => getOptimisticUpdateDefinitionForSpecificItem(generalDefinition, item) - ) - - // Cancel any outgoing refetches (so they don't overwrite our optimistic update). - // Theoretically, we can be a bit faster. Instead of awaiting the - // cancellation of all queries, we could cancel and update them in parallel. - // However, awaiting cancellation hasn't yet proven to be a performance bottleneck. - await Promise.all(specificOptimisticUpdateDefinitions.map( - ({ queryKey }) => queryClient.cancelQueries(queryKey) - )) - - // We're using a Map to correctly serialize query keys that contain objects. - const previousData = new Map() - specificOptimisticUpdateDefinitions.forEach(({ queryKey, updateQuery }) => { - // Snapshot the currently cached value. - const previousDataForQuery: CachedData = queryClient.getQueryData(queryKey) - - // Attempt to optimistically update the cache using the new value. - try { - queryClient.setQueryData(queryKey, updateQuery) - } catch (e) { - console.error("The `updateQuery` function threw an exception, skipping optimistic update:") - console.error(e) - } - - // Remember the snapshotted value to restore in case of an error. - previousData.set(queryKey, previousDataForQuery) - }) - - return { previousData } - } - - function onError(_err, _item, context) { - // All we do in case of an error is roll back all optimistic updates. We ensure - // not to do anything else because React Query rethrows the error. This allows - // the programmer to handle the error as they usually would (i.e., we want the - // error handling to work as it would if the programmer wasn't using optimistic - // updates). - context.previousData.forEach(async (data, queryKey) => { - await queryClient.cancelQueries(queryKey) - queryClient.setQueryData(queryKey, data) - }) - } - - return { - onMutate, - onError, - } -} - -/** - * Constructs the definition for optimistically updating a specific item. It - * uses a closure over the updated item to construct an item-specific query key - * (e.g., useful when the query key depends on an ID). - * - * @param optimisticUpdateDefinition The general, "uninstantiated" optimistic - * update definition with a function for constructing the query key. - * @param item The item triggering the Action/optimistic update (i.e., the - * argument passed to the Action). - * @returns A specific optimistic update definition which corresponds to the - * provided definition and closes over the provided item. - */ -function getOptimisticUpdateDefinitionForSpecificItem( - optimisticUpdateDefinition: InternalOptimisticUpdateDefinition, - item: ActionInput -): SpecificOptimisticUpdateDefinition { - const { getQueryKey, updateQuery } = optimisticUpdateDefinition - return { - queryKey: getQueryKey(item), - updateQuery: (old) => updateQuery(item, old) - } -} - -/** - * Translates a Wasp query specifier to a query cache key used by React Query. - * - * @param querySpecifier A query specifier that's a part of the public API: - * https://wasp-lang.dev/docs/data-model/operations/actions#the-useaction-hook-and-optimistic-updates - * @returns A cache key React Query internally uses for addressing queries. - */ -function getRqQueryKeyFromSpecifier(querySpecifier: QuerySpecifier): QueryKey { - const [queryFn, ...otherKeys] = querySpecifier - return [...(queryFn as any).queryCacheKey, ...otherKeys] -} diff --git a/waspc/data/Generator/templates/react-app/src/api.ts b/waspc/data/Generator/templates/react-app/src/api.ts deleted file mode 100644 index 17e36c1248..0000000000 --- a/waspc/data/Generator/templates/react-app/src/api.ts +++ /dev/null @@ -1,104 +0,0 @@ -import axios, { type AxiosError } from 'axios' - -import config from './config' -import { storage } from './storage' -import { apiEventsEmitter } from './api/events' - -const api = axios.create({ - baseURL: config.apiUrl, -}) - -const WASP_APP_AUTH_SESSION_ID_NAME = 'sessionId' - -let waspAppAuthSessionId = storage.get(WASP_APP_AUTH_SESSION_ID_NAME) as string | undefined - -export function setSessionId(sessionId: string): void { - waspAppAuthSessionId = sessionId - storage.set(WASP_APP_AUTH_SESSION_ID_NAME, sessionId) - apiEventsEmitter.emit('sessionId.set') -} - -export function getSessionId(): string | undefined { - return waspAppAuthSessionId -} - -export function clearSessionId(): void { - waspAppAuthSessionId = undefined - storage.remove(WASP_APP_AUTH_SESSION_ID_NAME) - apiEventsEmitter.emit('sessionId.clear') -} - -export function removeLocalUserData(): void { - waspAppAuthSessionId = undefined - storage.clear() - apiEventsEmitter.emit('sessionId.clear') -} - -api.interceptors.request.use((request) => { - const sessionId = getSessionId() - if (sessionId) { - request.headers['Authorization'] = `Bearer ${sessionId}` - } - return request -}) - -api.interceptors.response.use(undefined, (error) => { - if (error.response?.status === 401) { - clearSessionId() - } - return Promise.reject(error) -}) - -// This handler will run on other tabs (not the active one calling API functions), -// and will ensure they know about auth session ID changes. -// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event -// "Note: This won't work on the same page that is making the changes — it is really a way -// for other pages on the domain using the storage to sync any changes that are made." -window.addEventListener('storage', (event) => { - if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) { - if (!!event.newValue) { - waspAppAuthSessionId = event.newValue - apiEventsEmitter.emit('sessionId.set') - } else { - waspAppAuthSessionId = undefined - apiEventsEmitter.emit('sessionId.clear') - } - } -}) - -/** - * Takes an error returned by the app's API (as returned by axios), and transforms into a more - * standard format to be further used by the client. It is also assumed that given API - * error has been formatted as implemented by HttpError on the server. - */ -export function handleApiError(error: AxiosError<{ message?: string, data?: unknown }>): void { - if (error?.response) { - // If error came from HTTP response, we capture most informative message - // and also add .statusCode information to it. - // If error had JSON response, we assume it is of format { message, data } and - // add that info to the error. - // TODO: We might want to use HttpError here instead of just Error, since - // HttpError is also used on server to throw errors like these. - // That would require copying HttpError code to web-app also and using it here. - const responseJson = error.response?.data - const responseStatusCode = error.response.status - throw new WaspHttpError(responseStatusCode, responseJson?.message ?? error.message, responseJson) - } else { - // If any other error, we just propagate it. - throw error - } -} - -class WaspHttpError extends Error { - statusCode: number - - data: unknown - - constructor (statusCode: number, message: string, data: unknown) { - super(message) - this.statusCode = statusCode - this.data = data - } -} - -export default api diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/Form.tsx b/waspc/data/Generator/templates/react-app/src/auth/forms/internal/Form.tsx deleted file mode 100644 index 24d6c586d0..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/Form.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { styled } from '../../../stitches.config' - -export const Form = styled('form', { - marginTop: '1.5rem', -}) - -export const FormItemGroup = styled('div', { - '& + div': { - marginTop: '1.5rem', - }, -}) - -export const FormLabel = styled('label', { - display: 'block', - fontSize: '$sm', - fontWeight: '500', - marginBottom: '0.5rem', -}) - -const commonInputStyles = { - display: 'block', - lineHeight: '1.5rem', - fontSize: '$sm', - borderWidth: '1px', - borderColor: '$gray600', - backgroundColor: '#f8f4ff', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - '&:focus': { - borderWidth: '1px', - borderColor: '$gray700', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - }, - '&:disabled': { - opacity: 0.5, - cursor: 'not-allowed', - backgroundColor: '$gray400', - borderColor: '$gray400', - color: '$gray500', - }, - - borderRadius: '0.375rem', - width: '100%', - - paddingTop: '0.375rem', - paddingBottom: '0.375rem', - paddingLeft: '0.75rem', - paddingRight: '0.75rem', - margin: 0, -} - -export const FormInput = styled('input', commonInputStyles) - -export const FormTextarea = styled('textarea', commonInputStyles) - -export const FormError = styled('div', { - display: 'block', - fontSize: '$sm', - fontWeight: '500', - color: '$formErrorText', - marginTop: '0.5rem', -}) - -export const SubmitButton = styled('button', { - display: 'flex', - justifyContent: 'center', - - width: '100%', - borderWidth: '1px', - borderColor: '$brand', - backgroundColor: '$brand', - color: '$submitButtonText', - - padding: '0.5rem 0.75rem', - boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', - - fontWeight: '600', - fontSize: '$sm', - lineHeight: '1.25rem', - borderRadius: '0.375rem', - - // TODO(matija): extract this into separate BaseButton component and then inherit it. - '&:hover': { - backgroundColor: '$brandAccent', - borderColor: '$brandAccent', - }, - '&:disabled': { - opacity: 0.5, - cursor: 'not-allowed', - backgroundColor: '$gray400', - borderColor: '$gray400', - color: '$gray500', - }, - transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', - transitionDuration: '100ms', -}) diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/Message.tsx b/waspc/data/Generator/templates/react-app/src/auth/forms/internal/Message.tsx deleted file mode 100644 index 3a38c1cd96..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/Message.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { styled } from '../../../stitches.config' - -export const Message = styled('div', { - padding: '0.5rem 0.75rem', - borderRadius: '0.375rem', - marginTop: '1rem', - background: '$gray400', -}) - -export const MessageError = styled(Message, { - background: '$errorBackground', - color: '$errorText', -}) - -export const MessageSuccess = styled(Message, { - background: '$successBackground', - color: '$successText', -}) diff --git a/waspc/data/Generator/templates/react-app/src/auth/helpers/user.ts b/waspc/data/Generator/templates/react-app/src/auth/helpers/user.ts deleted file mode 100644 index a6b06299ce..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/helpers/user.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { setSessionId } from '../../api' -import { invalidateAndRemoveQueries } from '../../operations/resources' - -export async function initSession(sessionId: string): Promise { - setSessionId(sessionId) - // We need to invalidate queries after login in order to get the correct user - // data in the React components (using `useAuth`). - // Redirects after login won't work properly without this. - - // TODO(filip): We are currently removing all the queries, but we should - // remove only non-public, user-dependent queries - public queries are - // expected not to change in respect to the currently logged in user. - await invalidateAndRemoveQueries() -} diff --git a/waspc/data/Generator/templates/react-app/src/auth/logout.ts b/waspc/data/Generator/templates/react-app/src/auth/logout.ts deleted file mode 100644 index 715f99a49b..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/logout.ts +++ /dev/null @@ -1,17 +0,0 @@ -import api, { removeLocalUserData } from '../api' -import { invalidateAndRemoveQueries } from '../operations/resources' - -export default async function logout(): Promise { - try { - await api.post('/auth/logout') - } finally { - // Even if the logout request fails, we still want to remove the local user data - // in case the logout failed because of a network error and the user walked away - // from the computer. - removeLocalUserData() - - // TODO(filip): We are currently invalidating and removing all the queries, but - // we should remove only the non-public, user-dependent ones. - await invalidateAndRemoveQueries() - } -} diff --git a/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx b/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx index 10b3ed4d44..295cf5fa71 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx +++ b/waspc/data/Generator/templates/react-app/src/auth/pages/OAuthCodeExchange.jsx @@ -2,9 +2,9 @@ import React, { useEffect, useRef } from 'react' import { useHistory } from 'react-router-dom' -import config from '../../config.js' -import api from '../../api' -import { initSession } from '../helpers/user' +import config from 'wasp/core/config' +import api from 'wasp/api' +import { initSession } from 'wasp/auth/helpers/user' // After a user authenticates via an Oauth 2.0 provider, this is the page that // the provider should redirect them to, while providing query string parameters diff --git a/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx b/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx index 5bc4791231..8725da1835 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx +++ b/waspc/data/Generator/templates/react-app/src/auth/pages/createAuthRequiredPage.jsx @@ -2,7 +2,7 @@ import React from 'react' import { Redirect } from 'react-router-dom' -import useAuth from '../useAuth' +import useAuth from 'wasp/auth/useAuth' const createAuthRequiredPage = (Page) => { diff --git a/waspc/data/Generator/templates/react-app/src/auth/types.ts b/waspc/data/Generator/templates/react-app/src/auth/types.ts deleted file mode 100644 index 637a2e13d4..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -// todo(filip): turn into a proper import/path -export type { SanitizedUser as User, ProviderName, DeserializedAuthIdentity } from '../../../server/src/_types/' diff --git a/waspc/data/Generator/templates/react-app/src/auth/useAuth.ts b/waspc/data/Generator/templates/react-app/src/auth/useAuth.ts deleted file mode 100644 index 3e2b65d0ec..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/useAuth.ts +++ /dev/null @@ -1,40 +0,0 @@ -{{={= =}=}} -import { deserialize as superjsonDeserialize } from 'superjson' -import { useQuery } from '../queries' -import api, { handleApiError } from '../api' -import { HttpMethod } from '../types' -import type { User } from './types' -import { addMetadataToQuery } from '../queries/core' - - -export const getMe = createUserGetter() - -export default function useAuth(queryFnArgs?: unknown, config?: any) { - return useQuery(getMe, queryFnArgs, config) -} - -function createUserGetter() { - const getMeRelativePath = 'auth/me' - const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` } - async function getMe(): Promise { - try { - const response = await api.get(getMeRoute.path) - - return superjsonDeserialize(response.data) - } catch (error) { - if (error.response?.status === 401) { - return null - } else { - handleApiError(error) - } - } - } - - addMetadataToQuery(getMe, { - relativeQueryPath: getMeRelativePath, - queryRoute: getMeRoute, - entitiesUsed: {=& entitiesGetMeDependsOn =}, - }) - - return getMe -} diff --git a/waspc/data/Generator/templates/react-app/src/auth/user.ts b/waspc/data/Generator/templates/react-app/src/auth/user.ts deleted file mode 100644 index aa0da24824..0000000000 --- a/waspc/data/Generator/templates/react-app/src/auth/user.ts +++ /dev/null @@ -1,27 +0,0 @@ -// We decided not to deduplicate these helper functions in the server and the client. -// We have them duplicated in this file and in data/Generator/templates/server/src/auth/user.ts -// If you are changing the logic here, make sure to change it there as well. - -import type { User, ProviderName, DeserializedAuthIdentity } from './types' - -export function getEmail(user: User): string | null { - return findUserIdentity(user, "email")?.providerUserId ?? null; -} - -export function getUsername(user: User): string | null { - return findUserIdentity(user, "username")?.providerUserId ?? null; -} - -export function getFirstProviderUserId(user?: User): string | null { - if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { - return null; - } - - return user.auth.identities[0].providerUserId ?? null; -} - -export function findUserIdentity(user: User, providerName: ProviderName): DeserializedAuthIdentity | undefined { - return user.auth.identities.find( - (identity) => identity.providerName === providerName - ); -} diff --git a/waspc/data/Generator/templates/react-app/src/index.tsx b/waspc/data/Generator/templates/react-app/src/index.tsx index da29899438..6ecad2d2a4 100644 --- a/waspc/data/Generator/templates/react-app/src/index.tsx +++ b/waspc/data/Generator/templates/react-app/src/index.tsx @@ -14,7 +14,7 @@ import { {=/ setupFn.isDefined =} {=# areWebSocketsUsed =} -import { WebSocketProvider } from './webSocket/WebSocketProvider' +import { WebSocketProvider } from 'wasp/webSocket/WebSocketProvider' {=/ areWebSocketsUsed =} startApp() diff --git a/waspc/data/Generator/templates/react-app/src/operations/index.ts b/waspc/data/Generator/templates/react-app/src/operations/index.ts deleted file mode 100644 index 2d4494b050..0000000000 --- a/waspc/data/Generator/templates/react-app/src/operations/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import api, { handleApiError } from '../api' -import { HttpMethod } from '../types' -import { - serialize as superjsonSerialize, - deserialize as superjsonDeserialize, - } from 'superjson' - -export type OperationRoute = { method: HttpMethod, path: string } - -export async function callOperation(operationRoute: OperationRoute & { method: HttpMethod.Post }, args: any) { - try { - const superjsonArgs = superjsonSerialize(args) - const response = await api.post(operationRoute.path, superjsonArgs) - return superjsonDeserialize(response.data) - } catch (error) { - handleApiError(error) - } -} - -export function makeOperationRoute(relativeOperationRoute: string): OperationRoute { - return { method: HttpMethod.Post, path: `/${relativeOperationRoute}` } -} diff --git a/waspc/data/Generator/templates/react-app/src/operations/resources.js b/waspc/data/Generator/templates/react-app/src/operations/resources.js deleted file mode 100644 index 779cd55a5f..0000000000 --- a/waspc/data/Generator/templates/react-app/src/operations/resources.js +++ /dev/null @@ -1,81 +0,0 @@ -import { queryClientInitialized } from '../queryClient' -import { makeUpdateHandlersMap } from './updateHandlersMap' -import { hashQueryKey } from '@tanstack/react-query' - -// Map where key is resource name and value is Set -// containing query ids of all the queries that use -// that resource. -const resourceToQueryCacheKeys = new Map() - -const updateHandlers = makeUpdateHandlersMap(hashQueryKey) -/** - * Remembers that specified query is using specified resources. - * If called multiple times for same query, resources are added, not reset. - * @param {string[]} queryCacheKey - Unique key under used to identify query in the cache. - * @param {string[]} resources - Names of resources that query is using. - */ -export function addResourcesUsedByQuery(queryCacheKey, resources) { - for (const resource of resources) { - let cacheKeys = resourceToQueryCacheKeys.get(resource) - if (!cacheKeys) { - cacheKeys = new Set() - resourceToQueryCacheKeys.set(resource, cacheKeys) - } - cacheKeys.add(queryCacheKey) - } -} - -export function registerActionInProgress(optimisticUpdateTuples) { - optimisticUpdateTuples.forEach( - ({ queryKey, updateQuery }) => updateHandlers.add(queryKey, updateQuery) - ) -} - -export async function registerActionDone(resources, optimisticUpdateTuples) { - optimisticUpdateTuples.forEach(({ queryKey }) => updateHandlers.remove(queryKey)) - await invalidateQueriesUsing(resources) -} - -export function getActiveOptimisticUpdates(queryKey) { - return updateHandlers.getUpdateHandlers(queryKey) -} - -export async function invalidateAndRemoveQueries() { - const queryClient = await queryClientInitialized - // If we don't reset the queries before removing them, Wasp will stay on - // the same page. The user would have to manually refresh the page to "finish" - // logging out. - // When a query is removed, the `Observer` is removed as well, and the components - // that are using the query are not re-rendered. This is why we need to reset - // the queries, so that the `Observer` is re-created and the components are re-rendered. - // For more details: https://github.com/wasp-lang/wasp/pull/1014/files#r1111862125 - queryClient.resetQueries() - // If we don't remove the queries after invalidating them, the old query data - // remains in the cache, casuing a potential privacy issue. - queryClient.removeQueries() -} - -/** - * Invalidates all queries that are using specified resources. - * @param {string[]} resources - Names of resources. - */ -async function invalidateQueriesUsing(resources) { - const queryClient = await queryClientInitialized - - const queryCacheKeysToInvalidate = getQueriesUsingResources(resources) - queryCacheKeysToInvalidate.forEach( - queryCacheKey => queryClient.invalidateQueries(queryCacheKey) - ) -} - -/** - * @param {string} resource - Resource name. - * @returns {string[]} Array of "query cache keys" of queries that use specified resource. - */ -function getQueriesUsingResource(resource) { - return Array.from(resourceToQueryCacheKeys.get(resource) || []) -} - -function getQueriesUsingResources(resources) { - return Array.from(new Set(resources.flatMap(getQueriesUsingResource))) -} diff --git a/waspc/data/Generator/templates/react-app/src/queries/_query.ts b/waspc/data/Generator/templates/react-app/src/queries/_query.ts deleted file mode 100644 index c5abe7d9e4..0000000000 --- a/waspc/data/Generator/templates/react-app/src/queries/_query.ts +++ /dev/null @@ -1,11 +0,0 @@ -{{={= =}=}} -import { createQuery } from './core' -{=& operationTypeImportStmt =} - - -const query = createQuery<{= operationTypeName =}>( - '{= queryRoute =}', - {=& entitiesArray =}, -) - -export default query diff --git a/waspc/data/Generator/templates/react-app/src/queries/core.d.ts b/waspc/data/Generator/templates/react-app/src/queries/core.d.ts deleted file mode 100644 index e1bdbe4783..0000000000 --- a/waspc/data/Generator/templates/react-app/src/queries/core.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { type Query } from '.' -import { Route } from '../types'; -import type { Expand, _Awaited, _ReturnType } from '../universal/types' - -export function createQuery( - queryRoute: string, - entitiesUsed: any[] -): QueryFor - -export function addMetadataToQuery( - query: (...args: any[]) => Promise, - metadata: { - relativeQueryPath: string; - queryRoute: Route; - entitiesUsed: string[]; - }, -): void - -type QueryFor = Expand< - Query[0], _Awaited<_ReturnType>> -> - -type GenericBackendQuery = (args: never, context: any) => unknown diff --git a/waspc/data/Generator/templates/react-app/src/queries/core.js b/waspc/data/Generator/templates/react-app/src/queries/core.js deleted file mode 100644 index 5103db1d8b..0000000000 --- a/waspc/data/Generator/templates/react-app/src/queries/core.js +++ /dev/null @@ -1,27 +0,0 @@ -import { callOperation, makeOperationRoute } from '../operations' -import { - addResourcesUsedByQuery, - getActiveOptimisticUpdates, -} from '../operations/resources' - -export function createQuery(relativeQueryPath, entitiesUsed) { - const queryRoute = makeOperationRoute(relativeQueryPath) - - async function query(queryKey, queryArgs) { - const serverResult = await callOperation(queryRoute, queryArgs) - return getActiveOptimisticUpdates(queryKey).reduce( - (result, update) => update(result), - serverResult, - ) - } - - addMetadataToQuery(query, { relativeQueryPath, queryRoute, entitiesUsed }) - - return query -} - -export function addMetadataToQuery(query, { relativeQueryPath, queryRoute, entitiesUsed }) { - query.queryCacheKey = [relativeQueryPath] - query.route = queryRoute - addResourcesUsedByQuery(query.queryCacheKey, entitiesUsed) -} diff --git a/waspc/data/Generator/templates/react-app/src/queries/index.d.ts b/waspc/data/Generator/templates/react-app/src/queries/index.d.ts deleted file mode 100644 index c007ff4c92..0000000000 --- a/waspc/data/Generator/templates/react-app/src/queries/index.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UseQueryResult } from "@tanstack/react-query"; - -export type Query = { - (queryCacheKey: string[], args: Input): Promise -} - -export function useQuery( - queryFn: Query, - queryFnArgs?: Input, options?: any -): UseQueryResult diff --git a/waspc/data/Generator/templates/react-app/src/queries/index.js b/waspc/data/Generator/templates/react-app/src/queries/index.js deleted file mode 100644 index 03e52ce0f9..0000000000 --- a/waspc/data/Generator/templates/react-app/src/queries/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import { useQuery as rqUseQuery } from '@tanstack/react-query' -export { configureQueryClient } from '../queryClient' - -export function useQuery(queryFn, queryFnArgs, options) { - if (typeof queryFn !== 'function') { - throw new TypeError('useQuery requires queryFn to be a function.') - } - if (!queryFn.queryCacheKey) { - throw new TypeError('queryFn needs to have queryCacheKey property defined.') - } - - const queryKey = queryFnArgs !== undefined ? [...queryFn.queryCacheKey, queryFnArgs] : queryFn.queryCacheKey - return rqUseQuery({ - queryKey, - queryFn: () => queryFn(queryKey, queryFnArgs), - ...options - }) -} diff --git a/waspc/data/Generator/templates/react-app/src/router.tsx b/waspc/data/Generator/templates/react-app/src/router.tsx index ed1de164a0..8379bfa3e1 100644 --- a/waspc/data/Generator/templates/react-app/src/router.tsx +++ b/waspc/data/Generator/templates/react-app/src/router.tsx @@ -1,18 +1,12 @@ {{={= =}=}} import React from 'react' import { Route, Switch, BrowserRouter as Router } from 'react-router-dom' -import { interpolatePath } from './router/linkHelpers' -import type { - RouteDefinitionsToRoutes, - OptionalRouteOptions, - ParamValue, -} from './router/types' {=# rootComponent.isDefined =} {=& rootComponent.importStatement =} {=/ rootComponent.isDefined =} {=# isAuthEnabled =} -import createAuthRequiredPage from "wasp/auth/pages/createAuthRequiredPage" +import createAuthRequiredPage from "./auth/pages/createAuthRequiredPage" {=/ isAuthEnabled =} {=# pagesToImport =} @@ -23,29 +17,14 @@ import createAuthRequiredPage from "wasp/auth/pages/createAuthRequiredPage" import OAuthCodeExchange from "./auth/pages/OAuthCodeExchange" {=/ isExternalAuthEnabled =} -export const routes = { +import { routes } from 'wasp/router' + +export const routeNameToRouteComponent = { {=# routes =} - {= name =}: { - to: "{= urlPath =}", - component: {= targetComponent =}, - {=# hasUrlParams =} - build: ( - options: { - params: {{=# urlParams =}{= name =}{=# isOptional =}?{=/ isOptional =}: ParamValue;{=/ urlParams =}} - } & OptionalRouteOptions, - ) => interpolatePath("{= urlPath =}", options.params, options.search, options.hash), - {=/ hasUrlParams =} - {=^ hasUrlParams =} - build: ( - options?: OptionalRouteOptions, - ) => interpolatePath("{= urlPath =}", undefined, options.search, options.hash), - {=/ hasUrlParams =} - }, + {= name =}: {= targetComponent =}, {=/ routes =} } as const; -export type Routes = RouteDefinitionsToRoutes - const router = ( {=# rootComponent.isDefined =} @@ -57,7 +36,7 @@ const router = ( exact key={routeKey} path={route.to} - component={route.component} + component={routeNameToRouteComponent[routeKey]} /> ))} {=# isExternalAuthEnabled =} @@ -77,5 +56,3 @@ const router = ( ) export default router - -export { Link } from './router/Link' diff --git a/waspc/data/Generator/templates/react-app/src/test/vitest/setup.ts b/waspc/data/Generator/templates/react-app/src/test/vitest/setup.ts index d263e51f4f..f62fc46590 100644 --- a/waspc/data/Generator/templates/react-app/src/test/vitest/setup.ts +++ b/waspc/data/Generator/templates/react-app/src/test/vitest/setup.ts @@ -1,4 +1,8 @@ -import matchers from '@testing-library/jest-dom/matchers' -import { expect } from 'vitest' +import { afterEach } from 'vitest' +import { cleanup } from '@testing-library/react' +import '@testing-library/jest-dom/vitest' -expect.extend(matchers) +// runs a clean after each test case (e.g. clearing jsdom) +afterEach(() => { + cleanup(); +}) diff --git a/waspc/data/Generator/templates/react-app/src/types.ts b/waspc/data/Generator/templates/react-app/src/types.ts deleted file mode 100644 index 982b766e37..0000000000 --- a/waspc/data/Generator/templates/react-app/src/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -// NOTE: This is enough to cover Operations and our APIs (src/Wasp/AppSpec/Api.hs). -export enum HttpMethod { - Get = 'GET', - Post = 'POST', - Put = 'PUT', - Delete = 'DELETE', -} - -export type Route = { method: HttpMethod; path: string } diff --git a/waspc/data/Generator/templates/react-app/tsconfig.json b/waspc/data/Generator/templates/react-app/tsconfig.json index 263338d1ce..4e7d14e1cd 100644 --- a/waspc/data/Generator/templates/react-app/tsconfig.json +++ b/waspc/data/Generator/templates/react-app/tsconfig.json @@ -6,7 +6,7 @@ "allowJs": true, "strict": false, // Allow importing pages with the .tsx extension. - "allowImportingTsExtensions": true + "allowImportingTsExtensions": true, }, "include": [ "src" diff --git a/waspc/data/Generator/templates/react-app/vite.config.ts b/waspc/data/Generator/templates/react-app/vite.config.ts index 8bc0157ed7..ef54e30a86 100644 --- a/waspc/data/Generator/templates/react-app/vite.config.ts +++ b/waspc/data/Generator/templates/react-app/vite.config.ts @@ -2,8 +2,11 @@ /// import { mergeConfig } from "vite"; import react from "@vitejs/plugin-react"; +import { defaultExclude } from "vitest/config" {=# customViteConfig.isDefined =} +// Ignoring the TS error because we are importing a file outside of TS root dir. +// @ts-ignore {=& customViteConfig.importStatement =} const _waspUserProvidedConfig = {=& customViteConfig.importIdentifier =} {=/ customViteConfig.isDefined =} @@ -27,12 +30,16 @@ const defaultViteConfig = { outDir: "build", }, test: { + globals: true, environment: "jsdom", - setupFiles: ["./src/test/vitest/setup.ts"], + // vitest is running from the root of the project, so we need + // to specify the path to the setup file relative to the root. + setupFiles: {=& vitest.setupFilesArray =}, + exclude: [ + ...defaultExclude, + "{= vitest.excludeWaspArtefactsPattern =}", + ] }, - // resolve: { - // dedupe: ["react", "react-dom"], - // }, }; // https://vitejs.dev/config/ diff --git a/waspc/data/Generator/templates/react-app/src/api/events.ts b/waspc/data/Generator/templates/sdk/api/events.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/api/events.ts rename to waspc/data/Generator/templates/sdk/api/events.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/api/index.ts b/waspc/data/Generator/templates/sdk/api/index.ts similarity index 98% rename from waspc/data/Generator/templates/sdk/wasp/api/index.ts rename to waspc/data/Generator/templates/sdk/api/index.ts index 8b22dd7ebc..971f010160 100644 --- a/waspc/data/Generator/templates/sdk/wasp/api/index.ts +++ b/waspc/data/Generator/templates/sdk/api/index.ts @@ -2,7 +2,7 @@ import axios, { type AxiosError } from 'axios' import config from 'wasp/core/config' import { storage } from 'wasp/core/storage' -import { apiEventsEmitter } from 'wasp/api/events' +import { apiEventsEmitter } from './events.js' const api = axios.create({ baseURL: config.apiUrl, diff --git a/waspc/data/Generator/templates/react-app/src/auth/email/actions/login.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts similarity index 86% rename from waspc/data/Generator/templates/react-app/src/auth/email/actions/login.ts rename to waspc/data/Generator/templates/sdk/auth/email/actions/login.ts index dafb5d9ac9..72194361aa 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/email/actions/login.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/login.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from '../../../api'; +import api, { handleApiError } from 'wasp/api'; import { initSession } from '../../helpers/user'; export async function login(data: { email: string; password: string }): Promise { diff --git a/waspc/data/Generator/templates/react-app/src/auth/email/actions/passwordReset.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts similarity index 91% rename from waspc/data/Generator/templates/react-app/src/auth/email/actions/passwordReset.ts rename to waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts index b37cb27610..34c773ce23 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/email/actions/passwordReset.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/passwordReset.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from '../../../api'; +import api, { handleApiError } from 'wasp/api'; export async function requestPasswordReset(data: { email: string; }): Promise<{ success: boolean }> { try { diff --git a/waspc/data/Generator/templates/react-app/src/auth/email/actions/signup.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts similarity index 84% rename from waspc/data/Generator/templates/react-app/src/auth/email/actions/signup.ts rename to waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts index ad2a37eb8d..60c0da2cff 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/email/actions/signup.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/signup.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from '../../../api'; +import api, { handleApiError } from 'wasp/api'; export async function signup(data: { email: string; password: string }): Promise<{ success: boolean }> { try { diff --git a/waspc/data/Generator/templates/react-app/src/auth/email/actions/verifyEmail.ts b/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts similarity index 84% rename from waspc/data/Generator/templates/react-app/src/auth/email/actions/verifyEmail.ts rename to waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts index 4c2cfff583..10fa4308c0 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/email/actions/verifyEmail.ts +++ b/waspc/data/Generator/templates/sdk/auth/email/actions/verifyEmail.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from '../../../api' +import api, { handleApiError } from 'wasp/api' export async function verifyEmail(data: { token: string diff --git a/waspc/data/Generator/templates/react-app/src/auth/email/index.ts b/waspc/data/Generator/templates/sdk/auth/email/index.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/email/index.ts rename to waspc/data/Generator/templates/sdk/auth/email/index.ts diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/Auth.tsx b/waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx similarity index 98% rename from waspc/data/Generator/templates/react-app/src/auth/forms/Auth.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx index 5c50ba4109..99568c40da 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/forms/Auth.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/Auth.tsx @@ -1,7 +1,7 @@ {{={= =}=}} import { useState, createContext } from 'react' import { createTheme } from '@stitches/react' -import { styled } from '../../stitches.config' +import { styled } from 'wasp/core/stitches.config' import { type State, diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/ForgotPassword.tsx b/waspc/data/Generator/templates/sdk/auth/forms/ForgotPassword.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/ForgotPassword.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/ForgotPassword.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/Login.tsx b/waspc/data/Generator/templates/sdk/auth/forms/Login.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/Login.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/Login.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/ResetPassword.tsx b/waspc/data/Generator/templates/sdk/auth/forms/ResetPassword.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/ResetPassword.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/ResetPassword.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/Signup.tsx b/waspc/data/Generator/templates/sdk/auth/forms/Signup.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/Signup.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/Signup.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/VerifyEmail.tsx b/waspc/data/Generator/templates/sdk/auth/forms/VerifyEmail.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/VerifyEmail.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/VerifyEmail.tsx diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/Form.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/Form.tsx similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/Form.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/Form.tsx diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/Message.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/Message.tsx similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/Message.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/Message.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx similarity index 99% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/common/LoginSignupForm.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx index 8fd6348c58..e76e56bb7c 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/common/LoginSignupForm.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/common/LoginSignupForm.tsx @@ -1,8 +1,8 @@ {{={= =}=}} import { useContext } from 'react' import { useForm, UseFormReturn } from 'react-hook-form' -import { styled } from '../../../../stitches.config' -import config from '../../../../config' +import { styled } from 'wasp/core/stitches.config' +import config from 'wasp/core/config' import { AuthContext } from '../../Auth' import { diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/ForgotPasswordForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ForgotPasswordForm.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/ForgotPasswordForm.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/email/ForgotPasswordForm.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/ResetPasswordForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/ResetPasswordForm.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/ResetPasswordForm.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/email/ResetPasswordForm.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/VerifyEmailForm.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/VerifyEmailForm.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/VerifyEmailForm.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/email/VerifyEmailForm.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/useEmail.ts b/waspc/data/Generator/templates/sdk/auth/forms/internal/email/useEmail.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/email/useEmail.ts rename to waspc/data/Generator/templates/sdk/auth/forms/internal/email/useEmail.ts diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/social/SocialButton.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx similarity index 92% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/social/SocialButton.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx index 79284d0354..47044884bc 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/social/SocialButton.tsx +++ b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialButton.tsx @@ -1,4 +1,4 @@ -import { styled } from '../../../../stitches.config' +import { styled } from 'wasp/core/stitches.config' export const SocialButton = styled('a', { display: 'flex', diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/social/SocialIcons.tsx b/waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialIcons.tsx similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/social/SocialIcons.tsx rename to waspc/data/Generator/templates/sdk/auth/forms/internal/social/SocialIcons.tsx diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts b/waspc/data/Generator/templates/sdk/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts rename to waspc/data/Generator/templates/sdk/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts diff --git a/waspc/data/Generator/templates/react-app/src/auth/forms/types.ts b/waspc/data/Generator/templates/sdk/auth/forms/types.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/auth/forms/types.ts rename to waspc/data/Generator/templates/sdk/auth/forms/types.ts diff --git a/waspc/data/Generator/templates/react-app/src/auth/helpers/Generic.jsx b/waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx similarity index 90% rename from waspc/data/Generator/templates/react-app/src/auth/helpers/Generic.jsx rename to waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx index 7c52d93aa4..b54464da18 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/helpers/Generic.jsx +++ b/waspc/data/Generator/templates/sdk/auth/helpers/Generic.tsx @@ -1,6 +1,6 @@ {{={= =}=}} -import config from '../../config.js' +import config from 'wasp/core/config' import { SocialButton } from '../forms/internal/social/SocialButton' import * as SocialIcons from '../forms/internal/social/SocialIcons' diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/helpers/user.ts b/waspc/data/Generator/templates/sdk/auth/helpers/user.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/auth/helpers/user.ts rename to waspc/data/Generator/templates/sdk/auth/helpers/user.ts diff --git a/waspc/data/Generator/templates/server/src/auth/index.ts b/waspc/data/Generator/templates/sdk/auth/index.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/auth/index.ts rename to waspc/data/Generator/templates/sdk/auth/index.ts diff --git a/waspc/data/Generator/templates/server/src/auth/jwt.ts b/waspc/data/Generator/templates/sdk/auth/jwt.ts similarity index 89% rename from waspc/data/Generator/templates/server/src/auth/jwt.ts rename to waspc/data/Generator/templates/sdk/auth/jwt.ts index 5d2f4ae6fa..b244990158 100644 --- a/waspc/data/Generator/templates/server/src/auth/jwt.ts +++ b/waspc/data/Generator/templates/sdk/auth/jwt.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken' import util from 'util' -import config from '../config.js' +import config from 'wasp/server/config' const jwtSign = util.promisify(jwt.sign) const jwtVerify = util.promisify(jwt.verify) diff --git a/waspc/data/Generator/templates/react-app/src/auth/login.ts b/waspc/data/Generator/templates/sdk/auth/login.ts similarity index 88% rename from waspc/data/Generator/templates/react-app/src/auth/login.ts rename to waspc/data/Generator/templates/sdk/auth/login.ts index aa808309f3..b18a09ec16 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/login.ts +++ b/waspc/data/Generator/templates/sdk/auth/login.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from '../api' +import api, { handleApiError } from 'wasp/api' import { initSession } from './helpers/user' export default async function login(username: string, password: string): Promise { diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/logout.ts b/waspc/data/Generator/templates/sdk/auth/logout.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/auth/logout.ts rename to waspc/data/Generator/templates/sdk/auth/logout.ts diff --git a/waspc/data/Generator/templates/server/src/auth/lucia.ts b/waspc/data/Generator/templates/sdk/auth/lucia.ts similarity index 92% rename from waspc/data/Generator/templates/server/src/auth/lucia.ts rename to waspc/data/Generator/templates/sdk/auth/lucia.ts index 12587d6494..9d53af0a1c 100644 --- a/waspc/data/Generator/templates/server/src/auth/lucia.ts +++ b/waspc/data/Generator/templates/sdk/auth/lucia.ts @@ -1,9 +1,8 @@ {{={= =}=}} import { Lucia } from "lucia"; import { PrismaAdapter } from "@lucia-auth/adapter-prisma"; -import prisma from '../dbClient.js' -import config from '../config.js' -import { type {= userEntityUpper =} } from "../entities/index.js" +import prisma from 'wasp/server/dbClient' +import { type {= userEntityUpper =} } from "wasp/entities" const prismaAdapter = new PrismaAdapter( // Using `as any` here since Lucia's model types are not compatible with Prisma 4 diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/password.ts b/waspc/data/Generator/templates/sdk/auth/password.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/auth/password.ts rename to waspc/data/Generator/templates/sdk/auth/password.ts diff --git a/waspc/data/Generator/templates/server/src/auth/providers/types.ts b/waspc/data/Generator/templates/sdk/auth/providers/types.ts similarity index 96% rename from waspc/data/Generator/templates/server/src/auth/providers/types.ts rename to waspc/data/Generator/templates/sdk/auth/providers/types.ts index e9ce7bfe56..f040c1c490 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/types.ts +++ b/waspc/data/Generator/templates/sdk/auth/providers/types.ts @@ -1,7 +1,7 @@ {{={= =}=}} import type { Router, Request } from 'express' import type { Prisma } from '@prisma/client' -import type { Expand } from '../../universal/types' +import type { Expand } from 'wasp/universal/types' import type { ProviderName } from '../utils' type UserEntityCreateInput = Prisma.{= userEntityUpper =}CreateInput diff --git a/waspc/data/Generator/templates/server/src/auth/session.ts b/waspc/data/Generator/templates/sdk/auth/session.ts similarity index 94% rename from waspc/data/Generator/templates/server/src/auth/session.ts rename to waspc/data/Generator/templates/sdk/auth/session.ts index 636c8f49f1..b7ddebc3ea 100644 --- a/waspc/data/Generator/templates/server/src/auth/session.ts +++ b/waspc/data/Generator/templates/sdk/auth/session.ts @@ -1,8 +1,8 @@ {{={= =}=}} import { Request as ExpressRequest } from "express"; -import { type {= userEntityUpper =} } from "../entities/index.js" -import { type SanitizedUser } from '../_types/index.js' +import { type {= userEntityUpper =} } from "wasp/entities" +import { type SanitizedUser } from 'wasp/server/_types' import { auth } from "./lucia.js"; import type { Session } from "lucia"; @@ -11,7 +11,7 @@ import { deserializeAndSanitizeProviderData, } from "./utils.js"; -import prisma from '../dbClient.js'; +import prisma from 'wasp/server/dbClient'; // Creates a new session for the `authId` in the database export async function createSession(authId: string): Promise { diff --git a/waspc/data/Generator/templates/react-app/src/auth/signup.ts b/waspc/data/Generator/templates/sdk/auth/signup.ts similarity index 83% rename from waspc/data/Generator/templates/react-app/src/auth/signup.ts rename to waspc/data/Generator/templates/sdk/auth/signup.ts index d9c004d718..4fe93239cf 100644 --- a/waspc/data/Generator/templates/react-app/src/auth/signup.ts +++ b/waspc/data/Generator/templates/sdk/auth/signup.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import api, { handleApiError } from '../api' +import api, { handleApiError } from 'wasp/api' export default async function signup(userFields: { username: string; password: string }): Promise { try { diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/types.ts b/waspc/data/Generator/templates/sdk/auth/types.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/auth/types.ts rename to waspc/data/Generator/templates/sdk/auth/types.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/useAuth.ts b/waspc/data/Generator/templates/sdk/auth/useAuth.ts similarity index 91% rename from waspc/data/Generator/templates/sdk/wasp/auth/useAuth.ts rename to waspc/data/Generator/templates/sdk/auth/useAuth.ts index 29b95f62a0..b2b7a5b06e 100644 --- a/waspc/data/Generator/templates/sdk/wasp/auth/useAuth.ts +++ b/waspc/data/Generator/templates/sdk/auth/useAuth.ts @@ -1,8 +1,9 @@ +{{={= =}=}} import { deserialize as superjsonDeserialize } from 'superjson' import { useQuery } from 'wasp/rpc' import api, { handleApiError } from 'wasp/api' import { HttpMethod } from 'wasp/types' -import type { User } from './types' +import type { User } from './types' import { addMetadataToQuery } from 'wasp/rpc/queries' export const getMe = createUserGetter() @@ -31,7 +32,7 @@ function createUserGetter() { addMetadataToQuery(getMe, { relativeQueryPath: getMeRelativePath, queryRoute: getMeRoute, - entitiesUsed: ['User'], + entitiesUsed: {=& entitiesGetMeDependsOn =}, }) return getMe diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/user.ts b/waspc/data/Generator/templates/sdk/auth/user.ts similarity index 73% rename from waspc/data/Generator/templates/sdk/wasp/auth/user.ts rename to waspc/data/Generator/templates/sdk/auth/user.ts index aa0da24824..a7ac86827e 100644 --- a/waspc/data/Generator/templates/sdk/wasp/auth/user.ts +++ b/waspc/data/Generator/templates/sdk/auth/user.ts @@ -1,8 +1,4 @@ -// We decided not to deduplicate these helper functions in the server and the client. -// We have them duplicated in this file and in data/Generator/templates/server/src/auth/user.ts -// If you are changing the logic here, make sure to change it there as well. - -import type { User, ProviderName, DeserializedAuthIdentity } from './types' +import type { User, ProviderName, DeserializedAuthIdentity } from './types' export function getEmail(user: User): string | null { return findUserIdentity(user, "email")?.providerUserId ?? null; diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/utils.ts b/waspc/data/Generator/templates/sdk/auth/utils.ts similarity index 86% rename from waspc/data/Generator/templates/sdk/wasp/auth/utils.ts rename to waspc/data/Generator/templates/sdk/auth/utils.ts index 603e9a4b11..08b7798840 100644 --- a/waspc/data/Generator/templates/sdk/wasp/auth/utils.ts +++ b/waspc/data/Generator/templates/sdk/auth/utils.ts @@ -1,14 +1,15 @@ +{{={= =}=}} import { hashPassword } from './password.js' import { verify } from './jwt.js' -import AuthError from '../core/AuthError.js' -import HttpError from '../core/HttpError.js' -import prisma from '../server/dbClient.js' -import { sleep } from '../server/utils' +import AuthError from 'wasp/core/AuthError' +import HttpError from 'wasp/core/HttpError' +import prisma from 'wasp/server/dbClient' +import { sleep } from 'wasp/server/utils' import { - type User, - type Auth, - type AuthIdentity, -} from '../entities' + type {= userEntityUpper =}, + type {= authEntityUpper =}, + type {= authIdentityEntityUpper =}, +} from 'wasp/entities' import { Prisma } from '@prisma/client'; import { throwValidationError } from './validation.js' @@ -46,13 +47,13 @@ export type ProviderName = keyof PossibleProviderData export const contextWithUserEntity = { entities: { - User: prisma.user + {= userEntityUpper =}: prisma.{= userEntityLower =} } } export const authConfig = { - failureRedirectPath: "/login", - successRedirectPath: "/", + failureRedirectPath: "{= failureRedirectPath =}", + successRedirectPath: "{= successRedirectPath =}", } /** @@ -76,8 +77,8 @@ export function createProviderId(providerName: ProviderName, providerUserId: str } } -export async function findAuthIdentity(providerId: ProviderId): Promise { - return prisma.authIdentity.findUnique({ +export async function findAuthIdentity(providerId: ProviderId): Promise<{= authIdentityEntityUpper =} | null> { + return prisma.{= authIdentityEntityLower =}.findUnique({ where: { providerName_providerUserId: providerId, } @@ -96,7 +97,7 @@ export async function updateAuthIdentityProviderData( providerId: ProviderId, existingProviderData: PossibleProviderData[PN], providerDataUpdates: Partial, -): Promise { +): Promise<{= authIdentityEntityUpper =}> { // We are doing the sanitization here only on updates to avoid // hashing the password multiple times. const sanitizedProviderDataUpdates = await sanitizeProviderData(providerDataUpdates); @@ -105,7 +106,7 @@ export async function updateAuthIdentityProviderData( ...sanitizedProviderDataUpdates, } const serializedProviderData = await serializeProviderData(newProviderData); - return prisma.authIdentity.update({ + return prisma.{= authIdentityEntityLower =}.update({ where: { providerName_providerUserId: providerId, }, @@ -113,31 +114,31 @@ export async function updateAuthIdentityProviderData( }); } -type FindAuthWithUserResult = Auth & { - user: User +type FindAuthWithUserResult = {= authEntityUpper =} & { + {= userFieldOnAuthEntityName =}: {= userEntityUpper =} } export async function findAuthWithUserBy( - where: Prisma.AuthWhereInput + where: Prisma.{= authEntityUpper =}WhereInput ): Promise { - return prisma.auth.findFirst({ where, include: { user: true }}); + return prisma.{= authEntityLower =}.findFirst({ where, include: { {= userFieldOnAuthEntityName =}: true }}); } export async function createUser( providerId: ProviderId, serializedProviderData?: string, userFields?: PossibleUserFields, -): Promise { - return prisma.user.create({ + return prisma.{= userEntityLower =}.create({ data: { // Using any here to prevent type errors when userFields are not // defined. We want Prisma to throw an error in that case. ...(userFields ?? {} as any), - auth: { + {= authFieldOnUserEntityName =}: { create: { - identities: { + {= identitiesFieldOnAuthEntityName =}: { create: { providerName: providerId.providerName, providerUserId: providerId.providerUserId, @@ -150,13 +151,13 @@ export async function createUser( // We need to include the Auth entity here because we need `authId` // to be able to create a session. include: { - auth: true, + {= authFieldOnUserEntityName =}: true, }, }) } export async function deleteUserByAuthId(authId: string): Promise<{ count: number }> { - return prisma.user.deleteMany({ where: { auth: { + return prisma.{= userEntityLower =}.deleteMany({ where: { auth: { id: authId, } } }) } @@ -213,7 +214,7 @@ export function rethrowPossibleAuthError(e: unknown): void { // Prisma code P2003 is for foreign key constraint failure if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2003') { console.error(e) - console.info(`🐝 This error can happen if you have some relation on your User entity + console.info(`🐝 This error can happen if you have some relation on your {= userEntityUpper =} entity but you didn't specify the "onDelete" behaviour to either "Cascade" or "SetNull". Read more at: https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/referential-actions`) throw new HttpError(500, 'Save failed', { diff --git a/waspc/data/Generator/templates/server/src/auth/validation.ts b/waspc/data/Generator/templates/sdk/auth/validation.ts similarity index 98% rename from waspc/data/Generator/templates/server/src/auth/validation.ts rename to waspc/data/Generator/templates/sdk/auth/validation.ts index 96f755cac3..73bac13e21 100644 --- a/waspc/data/Generator/templates/server/src/auth/validation.ts +++ b/waspc/data/Generator/templates/sdk/auth/validation.ts @@ -1,4 +1,4 @@ -import HttpError from 'wasp/core/HttpError' +import HttpError from 'wasp/core/HttpError'; export const PASSWORD_FIELD = 'password'; const USERNAME_FIELD = 'username'; diff --git a/waspc/data/Generator/templates/sdk/wasp/core/AuthError.js b/waspc/data/Generator/templates/sdk/core/AuthError.ts similarity index 73% rename from waspc/data/Generator/templates/sdk/wasp/core/AuthError.js rename to waspc/data/Generator/templates/sdk/core/AuthError.ts index 2d965c168e..3ea4b536ea 100644 --- a/waspc/data/Generator/templates/sdk/wasp/core/AuthError.js +++ b/waspc/data/Generator/templates/sdk/core/AuthError.ts @@ -1,5 +1,7 @@ class AuthError extends Error { - constructor (message, data, ...params) { + public data: unknown + + constructor (message: string, data?: unknown, ...params: unknown[]) { super(message, ...params) if (Error.captureStackTrace) { diff --git a/waspc/data/Generator/templates/sdk/wasp/core/HttpError.js b/waspc/data/Generator/templates/sdk/core/HttpError.ts similarity index 74% rename from waspc/data/Generator/templates/sdk/wasp/core/HttpError.js rename to waspc/data/Generator/templates/sdk/core/HttpError.ts index 8a2cb04db5..4f229882e4 100644 --- a/waspc/data/Generator/templates/sdk/wasp/core/HttpError.js +++ b/waspc/data/Generator/templates/sdk/core/HttpError.ts @@ -1,5 +1,8 @@ class HttpError extends Error { - constructor (statusCode, message, data, ...params) { + public statusCode: number + public data: unknown + + constructor (statusCode: number, message?: string, data?: Record, ...params: unknown[]) { super(message, ...params) if (Error.captureStackTrace) { diff --git a/waspc/data/Generator/templates/sdk/wasp/core/auth.js b/waspc/data/Generator/templates/sdk/core/auth.ts similarity index 90% rename from waspc/data/Generator/templates/sdk/wasp/core/auth.js rename to waspc/data/Generator/templates/sdk/core/auth.ts index 6908bfb517..2408af794c 100644 --- a/waspc/data/Generator/templates/sdk/wasp/core/auth.js +++ b/waspc/data/Generator/templates/sdk/core/auth.ts @@ -1,7 +1,4 @@ -import { randomInt } from 'node:crypto' - -import prisma from '../server/dbClient.js' -import { handleRejection } from '../utils.js' +import { handleRejection } from 'wasp/server/utils' import { getSessionAndUserFromBearerToken } from 'wasp/auth/session' import { throwInvalidCredentialsError } from 'wasp/auth/utils' diff --git a/waspc/data/Generator/templates/sdk/wasp/core/config.js b/waspc/data/Generator/templates/sdk/core/config.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/core/config.js rename to waspc/data/Generator/templates/sdk/core/config.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/core/stitches.config.js b/waspc/data/Generator/templates/sdk/core/stitches.config.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/core/stitches.config.js rename to waspc/data/Generator/templates/sdk/core/stitches.config.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/core/storage.ts b/waspc/data/Generator/templates/sdk/core/storage.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/core/storage.ts rename to waspc/data/Generator/templates/sdk/core/storage.ts diff --git a/waspc/data/Generator/templates/react-app/src/crud/_crud.ts b/waspc/data/Generator/templates/sdk/crud/_crud.ts similarity index 92% rename from waspc/data/Generator/templates/react-app/src/crud/_crud.ts rename to waspc/data/Generator/templates/sdk/crud/_crud.ts index d2e66b58de..24a14bd26f 100644 --- a/waspc/data/Generator/templates/react-app/src/crud/_crud.ts +++ b/waspc/data/Generator/templates/sdk/crud/_crud.ts @@ -1,8 +1,8 @@ {{={= =}=}} -import { createAction } from "../actions/core"; -import { useAction } from "../actions"; -import { createQuery } from "../queries/core"; -import { useQuery } from "../queries"; +import { createAction } from "wasp/rpc/actions/core"; +import { useAction } from "wasp/rpc"; +import { createQuery } from "wasp/rpc/queries/core"; +import { useQuery } from "wasp/rpc"; import { {=# operations.Get =} GetQueryResolved, @@ -19,7 +19,7 @@ import { {=# operations.Delete =} DeleteActionResolved, {=/ operations.Delete =} -} from '../../../server/src/crud/{= name =}' +} from 'wasp/server/crud/{= name =}' function createCrud() { {=# operations.Get =} diff --git a/waspc/data/Generator/templates/server/src/dbSeed/types.ts b/waspc/data/Generator/templates/sdk/dbSeed/types.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/dbSeed/types.ts rename to waspc/data/Generator/templates/sdk/dbSeed/types.ts diff --git a/waspc/data/Generator/templates/server/src/email/core/helpers.ts b/waspc/data/Generator/templates/sdk/email/core/helpers.ts similarity index 93% rename from waspc/data/Generator/templates/server/src/email/core/helpers.ts rename to waspc/data/Generator/templates/sdk/email/core/helpers.ts index b1c2e911d2..9fc5652ba6 100644 --- a/waspc/data/Generator/templates/server/src/email/core/helpers.ts +++ b/waspc/data/Generator/templates/sdk/email/core/helpers.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import { EmailFromField } from "./types"; +import { EmailFromField } from "wasp/email/core/types"; // Formats an email address and an optional name into a string that can be used // as the "from" field in an email. diff --git a/waspc/data/Generator/templates/server/src/email/core/index.ts b/waspc/data/Generator/templates/sdk/email/core/index.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/email/core/index.ts rename to waspc/data/Generator/templates/sdk/email/core/index.ts diff --git a/waspc/data/Generator/templates/server/src/email/core/providers/dummy.ts b/waspc/data/Generator/templates/sdk/email/core/providers/dummy.ts similarity index 94% rename from waspc/data/Generator/templates/server/src/email/core/providers/dummy.ts rename to waspc/data/Generator/templates/sdk/email/core/providers/dummy.ts index f7b244e873..48d0f08fe2 100644 --- a/waspc/data/Generator/templates/server/src/email/core/providers/dummy.ts +++ b/waspc/data/Generator/templates/sdk/email/core/providers/dummy.ts @@ -1,4 +1,4 @@ -import { DummyEmailProvider, EmailSender } from "../types.js"; +import { DummyEmailProvider, EmailSender } from "wasp/email/core/types"; import { getDefaultFromField } from "../helpers.js"; const yellowColor = "\x1b[33m%s\x1b[0m"; diff --git a/waspc/data/Generator/templates/server/src/email/core/providers/mailgun.ts b/waspc/data/Generator/templates/sdk/email/core/providers/mailgun.ts similarity index 87% rename from waspc/data/Generator/templates/server/src/email/core/providers/mailgun.ts rename to waspc/data/Generator/templates/sdk/email/core/providers/mailgun.ts index 19a8098d54..549b393589 100644 --- a/waspc/data/Generator/templates/server/src/email/core/providers/mailgun.ts +++ b/waspc/data/Generator/templates/sdk/email/core/providers/mailgun.ts @@ -1,6 +1,6 @@ import { NodeMailgun } from "ts-mailgun"; import { getDefaultFromField } from "../helpers.js"; -import type { MailgunEmailProvider, EmailSender } from "../types.js"; +import type { MailgunEmailProvider, EmailSender } from "wasp/email/core/types"; export function initMailgunEmailSender( config: MailgunEmailProvider diff --git a/waspc/data/Generator/templates/server/src/email/core/providers/sendgrid.ts b/waspc/data/Generator/templates/sdk/email/core/providers/sendgrid.ts similarity index 88% rename from waspc/data/Generator/templates/server/src/email/core/providers/sendgrid.ts rename to waspc/data/Generator/templates/sdk/email/core/providers/sendgrid.ts index 3ce8d39cd4..3afcc42bd1 100644 --- a/waspc/data/Generator/templates/server/src/email/core/providers/sendgrid.ts +++ b/waspc/data/Generator/templates/sdk/email/core/providers/sendgrid.ts @@ -1,6 +1,6 @@ import SendGrid from "@sendgrid/mail"; import { getDefaultFromField } from "../helpers.js"; -import type { SendGridProvider, EmailSender } from "../types.js"; +import type { SendGridProvider, EmailSender } from "wasp/email/core/types"; export function initSendGridEmailSender( provider: SendGridProvider diff --git a/waspc/data/Generator/templates/server/src/email/core/providers/smtp.ts b/waspc/data/Generator/templates/sdk/email/core/providers/smtp.ts similarity index 89% rename from waspc/data/Generator/templates/server/src/email/core/providers/smtp.ts rename to waspc/data/Generator/templates/sdk/email/core/providers/smtp.ts index b09fbc5ec3..43d8d6c211 100644 --- a/waspc/data/Generator/templates/server/src/email/core/providers/smtp.ts +++ b/waspc/data/Generator/templates/sdk/email/core/providers/smtp.ts @@ -1,6 +1,6 @@ import { createTransport } from "nodemailer"; import { formatFromField, getDefaultFromField } from "../helpers.js"; -import type { SMTPEmailProvider, EmailSender } from "../types.js"; +import type { SMTPEmailProvider, EmailSender } from "wasp/email/core/types"; export function initSmtpEmailSender(config: SMTPEmailProvider): EmailSender { const transporter = createTransport({ diff --git a/waspc/data/Generator/templates/server/src/email/core/types.ts b/waspc/data/Generator/templates/sdk/email/core/types.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/email/core/types.ts rename to waspc/data/Generator/templates/sdk/email/core/types.ts diff --git a/waspc/data/Generator/templates/server/src/email/index.ts b/waspc/data/Generator/templates/sdk/email/index.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/email/index.ts rename to waspc/data/Generator/templates/sdk/email/index.ts diff --git a/waspc/data/Generator/templates/server/src/entities/index.ts b/waspc/data/Generator/templates/sdk/entities/index.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/entities/index.ts rename to waspc/data/Generator/templates/sdk/entities/index.ts diff --git a/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts b/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts new file mode 100644 index 0000000000..ce67cbfc9c --- /dev/null +++ b/waspc/data/Generator/templates/sdk/jobs/_jobTypes.ts @@ -0,0 +1,14 @@ +{{={= =}=}} +import prisma from 'wasp/server/dbClient' +import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' +import { type JobFn } from '{= jobExecutorTypesImportPath =}' + +{=! Used in framework code, shouldn't be public =} +export const entities = { + {=# entities =} + {= name =}: prisma.{= prismaIdentifier =}, + {=/ entities =} +}; + +{=! Used by users, should be public =} +export type {= typeName =} = JobFn diff --git a/waspc/data/Generator/templates/sdk/jobs/pgBoss/types.ts b/waspc/data/Generator/templates/sdk/jobs/pgBoss/types.ts new file mode 100644 index 0000000000..3e9e685bc5 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/jobs/pgBoss/types.ts @@ -0,0 +1,8 @@ +import { PrismaDelegate } from 'wasp/server/_types' +import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' + +export type JobFn< + Input extends JSONObject, + Output extends JSONValue | void, + Entities extends Partial +> = (data: Input, context: { entities: Entities }) => Promise diff --git a/waspc/data/Generator/templates/sdk/wasp/operations/index.ts b/waspc/data/Generator/templates/sdk/operations/index.ts similarity index 97% rename from waspc/data/Generator/templates/sdk/wasp/operations/index.ts rename to waspc/data/Generator/templates/sdk/operations/index.ts index 31e70ae98b..1da917cb2c 100644 --- a/waspc/data/Generator/templates/sdk/wasp/operations/index.ts +++ b/waspc/data/Generator/templates/sdk/operations/index.ts @@ -3,7 +3,7 @@ import { HttpMethod } from 'wasp/types' import { serialize as superjsonSerialize, deserialize as superjsonDeserialize, -} from 'superjson' + } from 'superjson' export type OperationRoute = { method: HttpMethod, path: string } diff --git a/waspc/data/Generator/templates/sdk/wasp/operations/resources.js b/waspc/data/Generator/templates/sdk/operations/resources.js similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/operations/resources.js rename to waspc/data/Generator/templates/sdk/operations/resources.js diff --git a/waspc/data/Generator/templates/react-app/src/operations/updateHandlersMap.js b/waspc/data/Generator/templates/sdk/operations/updateHandlersMap.js similarity index 100% rename from waspc/data/Generator/templates/react-app/src/operations/updateHandlersMap.js rename to waspc/data/Generator/templates/sdk/operations/updateHandlersMap.js diff --git a/waspc/data/Generator/templates/sdk/package.json b/waspc/data/Generator/templates/sdk/package.json index 320be0c4bd..363f3f11f4 100644 --- a/waspc/data/Generator/templates/sdk/package.json +++ b/waspc/data/Generator/templates/sdk/package.json @@ -9,23 +9,142 @@ "types": "tsc --declaration --emitDeclarationOnly --stripInternal --declarationDir dist" }, "exports": { - "./core/HttpError": "./core/HttpError.js", - "./core/AuthError": "./core/AuthError.js", - "./core/config": "./core/config.js", - "./core/stitches.config": "./core/stitches.config.js", - "./core/storage": "./core/storage.ts", - "./rpc": "./rpc/index.ts", - "./rpc/queries": "./rpc/queries/index.ts", - "./rpc/actions": "./rpc/actions/index.ts", - "./rpc/queryClient": "./rpc/queryClient.ts", - "./types": "./types/index.ts", - "./auth/*": "./auth/*", - "./api": "./api/index.ts", - "./api/*": "./api/*", - "./operations": "./operations/index.ts", - "./operations/*": "./operations/*", - "./universal/url": "./universal/url.ts", - "./universal/types": "./universal/url.ts" + {=! todo(filip): Check all exports when done with SDK generation =} + {=! Some of the statements in the comments might become incorrect. =} + {=! "our code" means: "web-app", "server" or "SDK", or "some combination of the three". =} + {=! Used by users, documented. =} + "./core/HttpError": "./dist/core/HttpError.js", + {=! Used by users, documented. =} + "./core/AuthError": "./dist/core/AuthError.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./core/config": "./dist/core/config.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./core/stitches.config": "./dist/core/stitches.config.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./core/storage": "./dist/core/storage.js", + "./core/auth": "./dist/core/auth.js", + {=! Used by users, documented. =} + "./rpc": "./dist/rpc/index.js", + {=! Used by users, documented. =} + "./rpc/queries": "./dist/rpc/queries/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./rpc/queries/core": "./dist/rpc/queries/core.js", + {=! Used by users, documented. =} + "./rpc/actions": "./dist/rpc/actions/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./rpc/actions/core": "./dist/rpc/actions/core.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./rpc/queryClient": "./dist/rpc/queryClient.js", + {=! Used by users, documented. =} + "./types": "./dist/types/index.js", + {=! Used by user, documented. =} + "./auth": "./dist/auth/index.js", + {=! Used by users, documented. =} + "./auth/types": "./dist/auth/types.js", + {=! Used by users, documented. =} + "./auth/login": "./dist/auth/login.js", + {=! Used by users, documented. =} + "./auth/logout": "./dist/auth/logout.js", + {=! Used by users, documented. =} + "./auth/signup": "./dist/auth/signup.js", + {=! Used by users, documented. =} + "./auth/useAuth": "./dist/auth/useAuth.js", + {=! Used by users, documented. =} + "./auth/user": "./dist/auth/user.js", + {=! Used by users, documented. =} + "./auth/email": "./dist/auth/email/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./auth/helpers/user": "./dist/auth/helpers/user.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./auth/session": "./dist/auth/session.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./auth/providers/types": "./dist/auth/providers/types.js", + {=! Used by user, documented. =} + "./auth/utils": "./dist/auth/utils.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./auth/password": "./dist/auth/password.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./auth/jwt": "./dist/auth/jwt.js", + {=! Used by user, documented. =} + "./auth/validation": "./dist/auth/validation.js", + {=! Used by users, documented. =} + "./auth/forms/Login": "./dist/auth/forms/Login.jsx", + {=! Used by users, documented. =} + "./auth/forms/Signup": "./dist/auth/forms/Signup.jsx", + {=! Used by users, documented. =} + "./auth/forms/VerifyEmail": "./dist/auth/forms/VerifyEmail.jsx", + {=! Used by users, documented. =} + "./auth/forms/ForgotPassword": "./dist/auth/forms/ForgotPassword.jsx", + {=! Used by users, documented. =} + "./auth/forms/ResetPassword": "./dist/auth/forms/ResetPassword.jsx", + {=! Used by users, documented. =} + "./auth/forms/internal/Form": "./dist/auth/forms/internal/Form.jsx", + {=! Used by users, documented. =} + "./auth/helpers/*": "./dist/auth/helpers/*.jsx", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./auth/pages/createAuthRequiredPage": "./dist/auth/pages/createAuthRequiredPage.jsx", + {=! Used by users, documented. =} + "./api": "./dist/api/index.js", + {=! Used by our framework code (Websockets), undocumented (but accessible) for users. =} + "./api/events": "./dist/api/events.js", + {=! Used by users, documented. =} + "./operations": "./dist/operations/index.js", + {=! If we import a symbol like "import something form 'wasp/something'", we must =} + {=! expose it here (which leaks it to our users). We could avoid this by =} + {=! using relative imports inside SDK code (instead of library imports), =} + {=! but I didn't have time to implement it. =} + "./ext-src/*": "./dist/ext-src/*.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./operations/*": "./dist/operations/*", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./universal/url": "./dist/universal/url.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./universal/types": "./dist/universal/types.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./universal/validators": "./dist/universal/validators.js", + {=! Used by users and by our code, documented =} + "./server/dbClient": "./dist/server/dbClient.js", + {=! Used by users and by our code, documented. =} + "./server/config": "./dist/server/config.js", + {=! Used by users and by our code, documented. =} + "./server/types": "./dist/server/types/index.js", + {=! Used by users and by our code, documented. =} + "./server/middleware": "./dist/server/middleware/index.js", + {=! Parts are used by users, documented. Parts are probably used by our code, undocumented (but accessible). =} + "./server/utils": "./dist/server/utils.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./server/actions": "./dist/server/actions/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./server/queries": "./dist/server/queries/index.js", + {=! Used by users and also by our code, documented. =} + "./server/auth/email": "./dist/server/auth/email/index.js", + {=! Used by users, documented. =} + "./dbSeed/types": "./dist/dbSeed/types.js", + {=! Used by users, documented. =} + "./test": "./dist/test/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./test/*": "./dist/test/*.js", + "./crud/*": "./dist/crud/*.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./server/crud/*": "./dist/server/crud/*", + "./email": "./dist/email/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./email/core/types": "./dist/email/core/types.js", + {=! Used by users, documented. =} + "./server/auth/email/utils": "./dist/server/auth/email/utils.js", + {=! Parts are used by users and documented (types), other parts are used by the framework code (entities). =} + "./jobs/*": "./dist/jobs/*.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + {=! Todo(filip): This export becomes problematic once we start supporting different executors =} + "./jobs/pgBoss/types": "./dist/jobs/pgBoss/types.js", + {=! Used by users, documented. =} + "./router": "./dist/router/index.js", + {=! Used by users, documented. =} + "./server/webSocket": "./dist/server/webSocket/index.js", + {=! Used by users, documented. =} + "./webSocket": "./dist/webSocket/index.js", + {=! Used by our code, uncodumented (but accessible) for users. =} + "./webSocket/WebSocketProvider": "./dist/webSocket/WebSocketProvider.jsx" }, "license": "ISC", "include": [ diff --git a/waspc/data/Generator/templates/react-app/src/router/Link.tsx b/waspc/data/Generator/templates/sdk/router/Link.tsx similarity index 93% rename from waspc/data/Generator/templates/react-app/src/router/Link.tsx rename to waspc/data/Generator/templates/sdk/router/Link.tsx index df29edb857..571262cd57 100644 --- a/waspc/data/Generator/templates/react-app/src/router/Link.tsx +++ b/waspc/data/Generator/templates/sdk/router/Link.tsx @@ -1,7 +1,7 @@ import { useMemo } from 'react' import { Link as RouterLink } from 'react-router-dom' -import { type Routes } from '../router' import { interpolatePath } from './linkHelpers' +import { type Routes } from './index' type RouterLinkProps = Parameters[0] diff --git a/waspc/data/Generator/templates/sdk/router/index.ts b/waspc/data/Generator/templates/sdk/router/index.ts new file mode 100644 index 0000000000..b1dfa68ab8 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/router/index.ts @@ -0,0 +1,31 @@ +{{={= =}=}} +import { interpolatePath } from './linkHelpers' +import type { + RouteDefinitionsToRoutes, + OptionalRouteOptions, + ParamValue, +} from './types' + +export const routes = { + {=# routes =} + {= name =}: { + to: "{= urlPath =}", + {=# hasUrlParams =} + build: ( + options: { + params: {{=# urlParams =}{= name =}{=# isOptional =}?{=/ isOptional =}: ParamValue;{=/ urlParams =}} + } & OptionalRouteOptions, + ) => interpolatePath("{= urlPath =}", options.params, options?.search, options?.hash), + {=/ hasUrlParams =} + {=^ hasUrlParams =} + build: ( + options?: OptionalRouteOptions, + ) => interpolatePath("{= urlPath =}", undefined, options?.search, options?.hash), + {=/ hasUrlParams =} + }, + {=/ routes =} +} as const; + +export type Routes = RouteDefinitionsToRoutes + +export { Link } from './Link' diff --git a/waspc/data/Generator/templates/react-app/src/router/linkHelpers.ts b/waspc/data/Generator/templates/sdk/router/linkHelpers.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/router/linkHelpers.ts rename to waspc/data/Generator/templates/sdk/router/linkHelpers.ts diff --git a/waspc/data/Generator/templates/react-app/src/router/types.ts b/waspc/data/Generator/templates/sdk/router/types.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/router/types.ts rename to waspc/data/Generator/templates/sdk/router/types.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/actions/core.d.ts b/waspc/data/Generator/templates/sdk/rpc/actions/core.d.ts similarity index 93% rename from waspc/data/Generator/templates/sdk/wasp/rpc/actions/core.d.ts rename to waspc/data/Generator/templates/sdk/rpc/actions/core.d.ts index ea41a0eed3..fbd0cc1d0a 100644 --- a/waspc/data/Generator/templates/sdk/wasp/rpc/actions/core.d.ts +++ b/waspc/data/Generator/templates/sdk/rpc/actions/core.d.ts @@ -1,4 +1,4 @@ -import { type Action } from '.' +import { type Action } from '..' import type { Expand, _Awaited, _ReturnType } from 'wasp/universal/types' export function createAction( diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/actions/core.js b/waspc/data/Generator/templates/sdk/rpc/actions/core.js similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/rpc/actions/core.js rename to waspc/data/Generator/templates/sdk/rpc/actions/core.js diff --git a/waspc/data/Generator/templates/sdk/rpc/actions/index.ts b/waspc/data/Generator/templates/sdk/rpc/actions/index.ts new file mode 100644 index 0000000000..d9d58fc527 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/rpc/actions/index.ts @@ -0,0 +1,12 @@ +{{={= =}=}} +import { createAction } from './core' +{=# actions =} +{=& operationTypeImportStmt =} +{=/ actions =} +{=# actions =} + +export const {= operationName =} = createAction<{= operationTypeName =}>( + '{= actionRoute =}', + {=& entitiesArray =}, +) +{=/ actions =} \ No newline at end of file diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/index.ts b/waspc/data/Generator/templates/sdk/rpc/index.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/rpc/index.ts rename to waspc/data/Generator/templates/sdk/rpc/index.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/queries/core.d.ts b/waspc/data/Generator/templates/sdk/rpc/queries/core.d.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/rpc/queries/core.d.ts rename to waspc/data/Generator/templates/sdk/rpc/queries/core.d.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/queries/core.js b/waspc/data/Generator/templates/sdk/rpc/queries/core.js similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/rpc/queries/core.js rename to waspc/data/Generator/templates/sdk/rpc/queries/core.js diff --git a/waspc/data/Generator/templates/sdk/rpc/queries/index.ts b/waspc/data/Generator/templates/sdk/rpc/queries/index.ts new file mode 100644 index 0000000000..631e787cf0 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/rpc/queries/index.ts @@ -0,0 +1,14 @@ +{{={= =}=}} +import { createQuery } from './core' +{=# queries =} +{=& operationTypeImportStmt =} +{=/ queries =} +{=# queries =} + +export const {= operationName =} = createQuery<{= operationTypeName =}>( + '{= queryRoute =}', + {=& entitiesArray =}, +) +{=/ queries =} + +export { addMetadataToQuery } from './core' diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/queryClient.ts b/waspc/data/Generator/templates/sdk/rpc/queryClient.ts similarity index 79% rename from waspc/data/Generator/templates/sdk/wasp/rpc/queryClient.ts rename to waspc/data/Generator/templates/sdk/rpc/queryClient.ts index 448be4c5ce..cf33c071ee 100644 --- a/waspc/data/Generator/templates/sdk/wasp/rpc/queryClient.ts +++ b/waspc/data/Generator/templates/sdk/rpc/queryClient.ts @@ -1,6 +1,4 @@ -import { QueryClient } from "@tanstack/react-query"; - -type QueryClientConfig = object; +import { QueryClient, QueryClientConfig } from '@tanstack/react-query' const defaultQueryClientConfig = {}; @@ -8,12 +6,14 @@ let queryClientConfig: QueryClientConfig, resolveQueryClientInitialized: (...args: any[]) => any, isQueryClientInitialized: boolean; +// Used in framework code, shouldn't be public export const queryClientInitialized: Promise = new Promise( (resolve) => { resolveQueryClientInitialized = resolve; } ); +// Used by users, should be public export function configureQueryClient(config: QueryClientConfig): void { if (isQueryClientInitialized) { throw new Error( @@ -24,6 +24,7 @@ export function configureQueryClient(config: QueryClientConfig): void { queryClientConfig = config; } +// Used in framework code, shouldn't be public export function initializeQueryClient(): void { const queryClient = new QueryClient( queryClientConfig ?? defaultQueryClientConfig diff --git a/waspc/data/Generator/templates/server/src/_types/index.ts b/waspc/data/Generator/templates/sdk/server/_types/index.ts similarity index 94% rename from waspc/data/Generator/templates/server/src/_types/index.ts rename to waspc/data/Generator/templates/sdk/server/_types/index.ts index f7f7f04d3a..24d5011c91 100644 --- a/waspc/data/Generator/templates/server/src/_types/index.ts +++ b/waspc/data/Generator/templates/sdk/server/_types/index.ts @@ -1,19 +1,19 @@ {{={= =}=}} -import { type Expand } from "../universal/types.js"; +import { type Expand } from 'wasp/universal/types'; import { type Request, type Response } from 'express' import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' -import prisma from "../dbClient.js" +import prisma from "wasp/server/dbClient" {=# isAuthEnabled =} import { type {= userEntityName =}, type {= authEntityName =}, type {= authIdentityEntityName =}, -} from "../entities" +} from "wasp/entities" import { type EmailProviderData, type UsernameProviderData, type OAuthProviderData, -} from '../auth/utils.js' +} from 'wasp/auth/utils' {=/ isAuthEnabled =} import { type _Entity } from "./taggedEntities" import { type Payload } from "./serialization"; @@ -103,5 +103,5 @@ export type SanitizedUser = {= userEntityName =} & { } | null } -export type { ProviderName } from '../auth/utils.js' +export type { ProviderName } from 'wasp/auth/utils' {=/ isAuthEnabled =} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/_types/serialization.ts b/waspc/data/Generator/templates/sdk/server/_types/serialization.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/server/_types/serialization.ts rename to waspc/data/Generator/templates/sdk/server/_types/serialization.ts diff --git a/waspc/data/Generator/templates/server/src/_types/taggedEntities.ts b/waspc/data/Generator/templates/sdk/server/_types/taggedEntities.ts similarity index 96% rename from waspc/data/Generator/templates/server/src/_types/taggedEntities.ts rename to waspc/data/Generator/templates/sdk/server/_types/taggedEntities.ts index 4e38484729..eda8037c0f 100644 --- a/waspc/data/Generator/templates/server/src/_types/taggedEntities.ts +++ b/waspc/data/Generator/templates/sdk/server/_types/taggedEntities.ts @@ -10,7 +10,7 @@ import { {=# entities =} type {= name =}, {=/ entities =} -} from '../entities' +} from 'wasp/entities' {=# entities =} export type {= internalTypeName =} = WithName<{= name =}, "{= name =}"> diff --git a/waspc/data/Generator/templates/sdk/server/actions/index.ts b/waspc/data/Generator/templates/sdk/server/actions/index.ts new file mode 100644 index 0000000000..c1832fd6d6 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/actions/index.ts @@ -0,0 +1,26 @@ +{{={= =}=}} +import prisma from 'wasp/server/dbClient' +{=! TODO: This template is exactly the same at the moment as one for queries, + consider in the future if it is worth removing this duplication. =} + +{=! TODO: This will generate multiple import statements even though they're + importing symbols from the same file. We should improve our importing machinery + to support multiple imports from the same file =} +{=# operations =} +{=& jsFn.importStatement =} +{=/ operations =} +{=# operations =} + +export type {= operationTypeName =} = typeof {= jsFn.importIdentifier =} + +export const {= operationName =} = async (args, context) => { + return ({= jsFn.importIdentifier =} as any)(args, { + ...context, + entities: { + {=# entities =} + {= name =}: prisma.{= prismaIdentifier =}, + {=/ entities =} + }, + }) +} +{=/ operations =} diff --git a/waspc/data/Generator/templates/sdk/server/actions/types.ts b/waspc/data/Generator/templates/sdk/server/actions/types.ts new file mode 100644 index 0000000000..15a045acdf --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/actions/types.ts @@ -0,0 +1,34 @@ +{{={= =}=}} +{=! TODO: This template is exactly the same at the moment as one for query + types, consider whether it makes sense to address this in the future. =} +import { + {=# allEntities =} + type {= internalTypeName =}, + {=/ allEntities =} + {=# shouldImportNonAuthenticatedOperation =} + type Action, + {=/ shouldImportNonAuthenticatedOperation =} + {=# shouldImportAuthenticatedOperation =} + type AuthenticatedAction, + {=/ shouldImportAuthenticatedOperation =} + type Payload, +} from 'wasp/server/_types' + +{=# operations =} +export type {= typeName =} = + {=# usesAuth =} + AuthenticatedAction< + {=/ usesAuth =} + {=^ usesAuth =} + Action< + {=/ usesAuth =} + [ + {=# entities =} + {= internalTypeName =}, + {=/ entities =} + ], + Input, + Output + > + +{=/ operations =} \ No newline at end of file diff --git a/waspc/data/Generator/templates/server/src/apis/types.ts b/waspc/data/Generator/templates/sdk/server/apis/types.ts similarity index 100% rename from waspc/data/Generator/templates/server/src/apis/types.ts rename to waspc/data/Generator/templates/sdk/server/apis/types.ts diff --git a/waspc/data/Generator/templates/sdk/server/auth/email/index.ts b/waspc/data/Generator/templates/sdk/server/auth/email/index.ts new file mode 100644 index 0000000000..a37dca08d4 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/auth/email/index.ts @@ -0,0 +1,9 @@ +export type GetVerificationEmailContentFn = (params: { verificationLink: string }) => EmailContent; + +export type GetPasswordResetEmailContentFn = (params: { passwordResetLink: string }) => EmailContent; + +type EmailContent = { + subject: string; + html: string; + text: string; +} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts similarity index 91% rename from waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts rename to waspc/data/Generator/templates/sdk/server/auth/email/utils.ts index 433768d273..168ff61262 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/utils.ts +++ b/waspc/data/Generator/templates/sdk/server/auth/email/utils.ts @@ -1,16 +1,16 @@ {{={= =}=}} -import { signData } from '../../jwt.js' -import { emailSender } from '../../../email/index.js'; -import { Email } from '../../../email/core/types.js'; +import { signData } from 'wasp/auth/jwt' +import { emailSender } from 'wasp/email'; +import { Email } from 'wasp/email/core/types'; import { createProviderId, updateAuthIdentityProviderData, findAuthIdentity, deserializeAndSanitizeProviderData, type EmailProviderData, -} from '../../utils.js'; -import waspServerConfig from '../../../config.js'; -import { type {= userEntityUpper =}, type {= authEntityUpper =} } from '../../../entities/index.js' +} from 'wasp/auth/utils'; +import waspServerConfig from 'wasp/server/config'; +import { type {= userEntityUpper =}, type {= authEntityUpper =} } from 'wasp/entities' export async function createEmailVerificationLink( email: string, diff --git a/waspc/data/Generator/templates/server/src/config.js b/waspc/data/Generator/templates/sdk/server/config.ts similarity index 63% rename from waspc/data/Generator/templates/server/src/config.js rename to waspc/data/Generator/templates/sdk/server/config.ts index e38a10e965..0ee4ca96f3 100644 --- a/waspc/data/Generator/templates/server/src/config.js +++ b/waspc/data/Generator/templates/sdk/server/config.ts @@ -1,7 +1,7 @@ {{={= =}=}} import merge from 'lodash.merge' -import { stripTrailingSlash } from "./universal/url.js"; +import { stripTrailingSlash } from "wasp/universal/url"; const env = process.env.NODE_ENV || 'development' @@ -10,13 +10,38 @@ const env = process.env.NODE_ENV || 'development' // - Use convict library to define schema and validate env vars. // https://codingsans.com/blog/node-config-best-practices -const config = { +type BaseConfig = { + allowedCORSOrigins: string | string[]; + {=# isAuthEnabled =} + auth: { + jwtSecret: string | undefined; + } + {=/ isAuthEnabled =} +} + +type CommonConfig = BaseConfig & { + env: string; + isDevelopment: boolean; + port: number; + databaseUrl: string | undefined; +} + +type EnvConfig = BaseConfig & { + frontendUrl: string; +} + +type Config = CommonConfig & EnvConfig + +const config: { + all: CommonConfig, + development: EnvConfig, + production: EnvConfig, +} = { all: { env, isDevelopment: env === 'development', port: parseInt(process.env.PORT) || 3001, databaseUrl: process.env.{= databaseUrlEnvVarName =}, - frontendUrl: undefined, allowedCORSOrigins: [], {=# isAuthEnabled =} auth: { @@ -28,10 +53,10 @@ const config = { production: getProductionConfig(), } -const resolvedConfig = merge(config.all, config[env]) +const resolvedConfig: Config = merge(config.all, config[env]) export default resolvedConfig -function getDevelopmentConfig() { +function getDevelopmentConfig(): EnvConfig { const frontendUrl = stripTrailingSlash(process.env.WASP_WEB_CLIENT_URL || '{= defaultClientUrl =}'); return { frontendUrl, @@ -44,7 +69,7 @@ function getDevelopmentConfig() { } } -function getProductionConfig() { +function getProductionConfig(): EnvConfig { const frontendUrl = stripTrailingSlash(process.env.WASP_WEB_CLIENT_URL); return { frontendUrl, diff --git a/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts b/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts new file mode 100644 index 0000000000..6fd34aea45 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/crud/_operationTypes.ts @@ -0,0 +1,105 @@ +{{={= =}=}} +import type { + {=# isAuthEnabled =} + AuthenticatedAction, + AuthenticatedQuery, + {=/ isAuthEnabled =} + {=^ isAuthEnabled =} + Action, + Query, + {=/ isAuthEnabled =} + _{= crud.entityUpper =}, +} from "wasp/server/_types"; +import type { Prisma } from "@prisma/client"; +import { Payload } from "wasp/server/_types/serialization"; +import type { + {= crud.entityUpper =}, +} from "wasp/entities"; +{=# overrides.GetAll.isDefined =} +{=& overrides.GetAll.importStatement =} +{=/ overrides.GetAll.isDefined =} +{=# overrides.Get.isDefined =} +{=& overrides.Get.importStatement =} +{=/ overrides.Get.isDefined =} +{=# overrides.Create.isDefined =} +{=& overrides.Create.importStatement =} +{=/ overrides.Create.isDefined =} +{=# overrides.Update.isDefined =} +{=& overrides.Update.importStatement =} +{=/ overrides.Update.isDefined =} +{=# overrides.Delete.isDefined =} +{=& overrides.Delete.importStatement =} +{=/ overrides.Delete.isDefined =} + +type _WaspEntityTagged = _{= crud.entityUpper =} +type _WaspEntity = {= crud.entityUpper =} + +{=# crud.operations.GetAll =} +// Get All query +export type GetAllQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.GetAll.isDefined =} +type GetAllInput = {} +type GetAllOutput = _WaspEntity[] +export type GetAllQueryResolved = GetAllQuery +{=/ overrides.GetAll.isDefined =} +{=# overrides.GetAll.isDefined =} +const _waspGetAllQuery = {= overrides.GetAll.importIdentifier =} +export type GetAllQueryResolved = typeof _waspGetAllQuery +{=/ overrides.GetAll.isDefined =} +{=/ crud.operations.GetAll =} + +{=# crud.operations.Get =} +// Get query +export type GetQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Get.isDefined =} +type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput +type GetOutput = _WaspEntity | null +export type GetQueryResolved = GetQuery +{=/ overrides.Get.isDefined =} +{=# overrides.Get.isDefined =} +const _waspGetQuery = {= overrides.Get.importIdentifier =} +export type GetQueryResolved = typeof _waspGetQuery +{=/ overrides.Get.isDefined =} +{=/ crud.operations.Get =} + +{=# crud.operations.Create =} +// Create action +export type CreateAction= {= actionType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Create.isDefined =} +type CreateInput = Prisma.{= crud.entityUpper =}CreateInput +type CreateOutput = _WaspEntity +export type CreateActionResolved = CreateAction +{=/ overrides.Create.isDefined =} +{=# overrides.Create.isDefined =} +const _waspCreateAction = {= overrides.Create.importIdentifier =} +export type CreateActionResolved = typeof _waspCreateAction +{=/ overrides.Create.isDefined =} +{=/ crud.operations.Create =} + +{=# crud.operations.Update =} +// Update action +export type UpdateAction = {= actionType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Update.isDefined =} +type UpdateInput = Prisma.{= crud.entityUpper =}UpdateInput & Prisma.{= crud.entityUpper =}WhereUniqueInput +type UpdateOutput = _WaspEntity +export type UpdateActionResolved = UpdateAction +{=/ overrides.Update.isDefined =} +{=# overrides.Update.isDefined =} +const _waspUpdateAction = {= overrides.Update.importIdentifier =} +export type UpdateActionResolved = typeof _waspUpdateAction +{=/ overrides.Update.isDefined =} +{=/ crud.operations.Update =} + +{=# crud.operations.Delete =} +// Delete action +export type DeleteAction = {= actionType =}<[_WaspEntityTagged], Input, Output> +{=^ overrides.Delete.isDefined =} +type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput +type DeleteOutput = _WaspEntity +export type DeleteActionResolved = DeleteAction +{=/ overrides.Delete.isDefined =} +{=# overrides.Delete.isDefined =} +const _waspDeleteAction = {= overrides.Delete.importIdentifier =} +export type DeleteActionResolved = typeof _waspDeleteAction +{=/ overrides.Delete.isDefined =} +{=/ crud.operations.Delete =} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/dbClient.ts b/waspc/data/Generator/templates/sdk/server/dbClient.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/server/dbClient.ts rename to waspc/data/Generator/templates/sdk/server/dbClient.ts diff --git a/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts b/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts new file mode 100644 index 0000000000..28c2631149 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/middleware/globalMiddleware.ts @@ -0,0 +1,6 @@ +import { type RequestHandler } from 'express' + +export type MiddlewareConfig = Map + +export type MiddlewareConfigFn = (middlewareConfig: MiddlewareConfig) => MiddlewareConfig + diff --git a/waspc/data/Generator/templates/sdk/server/middleware/index.ts b/waspc/data/Generator/templates/sdk/server/middleware/index.ts new file mode 100644 index 0000000000..50996ec27d --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/middleware/index.ts @@ -0,0 +1 @@ +export * from './globalMiddleware.js' diff --git a/waspc/data/Generator/templates/sdk/server/queries/index.ts b/waspc/data/Generator/templates/sdk/server/queries/index.ts new file mode 100644 index 0000000000..92cb7b7f4f --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/queries/index.ts @@ -0,0 +1,26 @@ +{{={= =}=}} +import prisma from 'wasp/server/dbClient' +{=! TODO: This template is exactly the same at the moment as one for actions, + consider in the future if it is worth removing this duplication. =} + +{=! TODO: This will generate multiple import statements even though they're + importing symbols from the same file. We should improve our importing machinery + to support multiple imports from the same file =} +{=# operations =} +{=& jsFn.importStatement =} +{=/ operations =} +{=# operations =} + +export type {= operationTypeName =} = typeof {= jsFn.importIdentifier =} + +export const {= operationName =} = async (args, context) => { + return ({= jsFn.importIdentifier =} as any)(args, { + ...context, + entities: { + {=# entities =} + {= name =}: prisma.{= prismaIdentifier =}, + {=/ entities =} + }, + }) +} +{=/ operations =} diff --git a/waspc/data/Generator/templates/sdk/server/queries/types.ts b/waspc/data/Generator/templates/sdk/server/queries/types.ts new file mode 100644 index 0000000000..3ae08913a3 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/server/queries/types.ts @@ -0,0 +1,35 @@ +{{={= =}=}} +{=! TODO: This template is exactly the same at the moment as one for action + types, consider whether it makes sense to address this in the future. =} + +import { + {=# allEntities =} + type {= internalTypeName =}, + {=/ allEntities =} + {=# shouldImportNonAuthenticatedOperation =} + type Query, + {=/ shouldImportNonAuthenticatedOperation =} + {=# shouldImportAuthenticatedOperation =} + type AuthenticatedQuery, + {=/ shouldImportAuthenticatedOperation =} + type Payload, +} from 'wasp/server/_types' + +{=# operations =} +export type {= typeName =} = + {=# usesAuth =} + AuthenticatedQuery< + {=/ usesAuth =} + {=^ usesAuth =} + Query< + {=/ usesAuth =} + [ + {=# entities =} + {= internalTypeName =}, + {=/ entities =} + ], + Input, + Output + > + +{=/ operations =} \ No newline at end of file diff --git a/waspc/data/Generator/templates/server/src/types/index.ts b/waspc/data/Generator/templates/sdk/server/types/index.ts similarity index 62% rename from waspc/data/Generator/templates/server/src/types/index.ts rename to waspc/data/Generator/templates/sdk/server/types/index.ts index 205014216e..fda96a4528 100644 --- a/waspc/data/Generator/templates/server/src/types/index.ts +++ b/waspc/data/Generator/templates/sdk/server/types/index.ts @@ -1,5 +1,3 @@ -{{={= =}=}} - import { type Application } from 'express' import { Server } from 'http' @@ -12,7 +10,3 @@ export type ServerSetupFnContext = { export type { Application } from 'express' export type { Server } from 'http' - -{=# isEmailAuthEnabled =} -export type { GetVerificationEmailContentFn, GetPasswordResetEmailContentFn } from '../auth/providers/email/types'; -{=/ isEmailAuthEnabled =} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/utils.ts b/waspc/data/Generator/templates/sdk/server/utils.ts similarity index 92% rename from waspc/data/Generator/templates/sdk/wasp/server/utils.ts rename to waspc/data/Generator/templates/sdk/server/utils.ts index b0744f3129..6ca262decd 100644 --- a/waspc/data/Generator/templates/sdk/wasp/server/utils.ts +++ b/waspc/data/Generator/templates/sdk/server/utils.ts @@ -1,3 +1,4 @@ +{{={= =}=}} import crypto from 'crypto' import { Request, Response, NextFunction } from 'express' @@ -5,11 +6,15 @@ import { readdir } from 'fs' import { dirname } from 'path' import { fileURLToPath } from 'url' -import { type SanitizedUser } from './_types/index.js' +{=# isAuthEnabled =} +import { type SanitizedUser } from 'wasp/server/_types/index.js' +{=/ isAuthEnabled =} type RequestWithExtraFields = Request & { + {=# isAuthEnabled =} user?: SanitizedUser; sessionId?: string; + {=/ isAuthEnabled =} } /** diff --git a/waspc/data/Generator/templates/server/src/webSocket/index.ts b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts similarity index 92% rename from waspc/data/Generator/templates/server/src/webSocket/index.ts rename to waspc/data/Generator/templates/sdk/server/webSocket/index.ts index 3393b500bc..1074eda872 100644 --- a/waspc/data/Generator/templates/server/src/webSocket/index.ts +++ b/waspc/data/Generator/templates/sdk/server/webSocket/index.ts @@ -3,9 +3,9 @@ import { Server } from 'socket.io' import { EventsMap, DefaultEventsMap } from '@socket.io/component-emitter' -import prisma from '../dbClient.js' +import prisma from 'wasp/server/dbClient' {=# isAuthEnabled =} -import { type SanitizedUser } from '../_types/index.js' +import { type SanitizedUser } from 'wasp/server/_types/index.js' {=/ isAuthEnabled =} {=& userWebSocketFn.importStatement =} diff --git a/waspc/data/Generator/templates/react-app/src/test/index.ts b/waspc/data/Generator/templates/sdk/test/index.ts similarity index 100% rename from waspc/data/Generator/templates/react-app/src/test/index.ts rename to waspc/data/Generator/templates/sdk/test/index.ts diff --git a/waspc/data/Generator/templates/react-app/src/test/vitest/helpers.tsx b/waspc/data/Generator/templates/sdk/test/vitest/helpers.tsx similarity index 94% rename from waspc/data/Generator/templates/react-app/src/test/vitest/helpers.tsx rename to waspc/data/Generator/templates/sdk/test/vitest/helpers.tsx index 4152191129..6c4eadbec3 100644 --- a/waspc/data/Generator/templates/react-app/src/test/vitest/helpers.tsx +++ b/waspc/data/Generator/templates/sdk/test/vitest/helpers.tsx @@ -6,11 +6,11 @@ import { BrowserRouter as Router } from 'react-router-dom' import { render, RenderResult, cleanup } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { beforeAll, afterEach, afterAll } from 'vitest' -import { Query } from '../../queries' -import config from '../../config' -import { HttpMethod, Route } from '../../types' +import { Query } from 'wasp/rpc' +import config from 'wasp/core/config' +import { HttpMethod, Route } from 'wasp/types' -export type { Route } from '../../types' +export type { Route } from 'wasp/types' export type MockQuery = ( query: Query, diff --git a/waspc/data/Generator/templates/sdk/tsconfig.json b/waspc/data/Generator/templates/sdk/tsconfig.json new file mode 100644 index 0000000000..069f06acd9 --- /dev/null +++ b/waspc/data/Generator/templates/sdk/tsconfig.json @@ -0,0 +1,44 @@ +{{={= =}=}} +{ + "extends": "@tsconfig/node{= majorNodeVersion =}/tsconfig.json", + "compilerOptions": { + "jsx": "preserve", + "lib": [ + "esnext", + "dom", + "DOM.Iterable" + ], + "strict": false, + // Overriding this because we want to use top-level await + "module": "esnext", + "target": "es2017", + // Enable source map for debugging and go-to-definition + "sourceMap": true, + // The remaining settings should match the extended nodeXY/tsconfig.json, but I kept + // them here to be explicit. + // Enable default imports in TypeScript. + "esModuleInterop": true, + "moduleResolution": "node", + "outDir": "dist", + "allowJs": true, + "types": [ + // This is needed to properly support Vitest testing with jest-dom matchers. + // Types for jest-dom are not recognized automatically and Typescript complains + // about missing types e.g. when using `toBeInTheDocument` and other matchers. + "@testing-library/jest-dom" + ], + // todo(filip): Only works with common js, see https://www.typescriptlang.org/tsconfig#paths and daily-article. + // "paths": { + // "@wasp/*": [ + // "./*.js" + // ] + // } + }, + "include": [ + "." + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/waspc/data/Generator/templates/sdk/wasp/types/index.ts b/waspc/data/Generator/templates/sdk/types/index.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/types/index.ts rename to waspc/data/Generator/templates/sdk/types/index.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/universal/types.ts b/waspc/data/Generator/templates/sdk/universal/types.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/universal/types.ts rename to waspc/data/Generator/templates/sdk/universal/types.ts diff --git a/waspc/data/Generator/templates/sdk/wasp/universal/url.ts b/waspc/data/Generator/templates/sdk/universal/url.ts similarity index 100% rename from waspc/data/Generator/templates/sdk/wasp/universal/url.ts rename to waspc/data/Generator/templates/sdk/universal/url.ts diff --git a/waspc/data/Generator/templates/universal/validators.js b/waspc/data/Generator/templates/sdk/universal/validators.ts similarity index 78% rename from waspc/data/Generator/templates/universal/validators.js rename to waspc/data/Generator/templates/sdk/universal/validators.ts index 9e3605d3b5..3d30d9622a 100644 --- a/waspc/data/Generator/templates/universal/validators.js +++ b/waspc/data/Generator/templates/sdk/universal/validators.ts @@ -1,4 +1,4 @@ -export function isValidAbsoluteURL(rawUrl) { +export function isValidAbsoluteURL(rawUrl: string): boolean { try { const url = new URL(rawUrl); /* @@ -14,7 +14,7 @@ export function isValidAbsoluteURL(rawUrl) { } } -export function throwIfNotValidAbsoluteURL(value, name) { +export function throwIfNotValidAbsoluteURL(value: string | undefined, name: string): void { if (value && !isValidAbsoluteURL(value)) { throw new Error(`${name} must be a valid absolute URL`); } diff --git a/waspc/data/Generator/templates/sdk/wasp/api/events.ts b/waspc/data/Generator/templates/sdk/wasp/api/events.ts deleted file mode 100644 index a72e48dda8..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/api/events.ts +++ /dev/null @@ -1,11 +0,0 @@ -import mitt, { Emitter } from 'mitt'; - -type ApiEvents = { - // key: Event name - // type: Event payload type - 'sessionId.set': void; - 'sessionId.clear': void; -}; - -// Used to allow API clients to register for auth session ID change events. -export const apiEventsEmitter: Emitter = mitt(); diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/Auth.tsx b/waspc/data/Generator/templates/sdk/wasp/auth/forms/Auth.tsx deleted file mode 100644 index 92c58131f6..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/Auth.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useState, createContext } from 'react' -import { createTheme } from '@stitches/react' -import { styled } from 'wasp/core/stitches.config' - -import { - type State, - type CustomizationOptions, - type ErrorMessage, - type AdditionalSignupFields, -} from './types' -import { LoginSignupForm } from './internal/common/LoginSignupForm' -import { MessageError, MessageSuccess } from './internal/Message' - -const logoStyle = { - height: '3rem' -} - -const Container = styled('div', { - display: 'flex', - flexDirection: 'column', -}) - -const HeaderText = styled('h2', { - fontSize: '1.875rem', - fontWeight: '700', - marginTop: '1.5rem' -}) - - -export const AuthContext = createContext({ - isLoading: false, - setIsLoading: (isLoading: boolean) => {}, - setErrorMessage: (errorMessage: ErrorMessage | null) => {}, - setSuccessMessage: (successMessage: string | null) => {}, -}) - -function Auth ({ state, appearance, logo, socialLayout = 'horizontal', additionalSignupFields }: { - state: State; -} & CustomizationOptions & { - additionalSignupFields?: AdditionalSignupFields; -}) { - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const [isLoading, setIsLoading] = useState(false); - - // TODO(matija): this is called on every render, is it a problem? - // If we do it in useEffect(), then there is a glitch between the default color and the - // user provided one. - const customTheme = createTheme(appearance ?? {}) - - const titles: Record = { - login: 'Log in to your account', - signup: 'Create a new account', - } - const title = titles[state] - - const socialButtonsDirection = socialLayout === 'vertical' ? 'vertical' : 'horizontal' - - return ( - -
- {logo && (Your Company)} - {title} -
- - {errorMessage && ( - - {errorMessage.title}{errorMessage.description && ': '}{errorMessage.description} - - )} - {successMessage && {successMessage}} - - {(state === 'login' || state === 'signup') && ( - - )} - -
- ) -} - -export default Auth; diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/Login.tsx b/waspc/data/Generator/templates/sdk/wasp/auth/forms/Login.tsx deleted file mode 100644 index 2ea532d9c5..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/Login.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import Auth from './Auth' -import { type CustomizationOptions, State } from './types' - -export function LoginForm({ - appearance, - logo, - socialLayout, -}: CustomizationOptions) { - return ( - - ) -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/Signup.tsx b/waspc/data/Generator/templates/sdk/wasp/auth/forms/Signup.tsx deleted file mode 100644 index 66ffab4503..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/Signup.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Auth from './Auth' -import { - type CustomizationOptions, - type AdditionalSignupFields, - State, -} from './types' - -export function SignupForm({ - appearance, - logo, - socialLayout, - additionalFields, -}: CustomizationOptions & { additionalFields?: AdditionalSignupFields; }) { - return ( - - ) -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx deleted file mode 100644 index 30665b4759..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { useContext } from 'react' -import { useForm, UseFormReturn } from 'react-hook-form' -import { styled } from 'wasp/core/stitches.config' -import config from 'wasp/core/config' - -import { AuthContext } from '../../Auth' -import { - Form, - FormInput, - FormItemGroup, - FormLabel, - FormError, - FormTextarea, - SubmitButton, -} from '../Form' -import type { - AdditionalSignupFields, - AdditionalSignupField, - AdditionalSignupFieldRenderFn, - FormState, -} from '../../types' -import { useHistory } from 'react-router-dom' -import { useUsernameAndPassword } from '../usernameAndPassword/useUsernameAndPassword' - - -export type LoginSignupFormFields = { - [key: string]: string; -} - -export const LoginSignupForm = ({ - state, - socialButtonsDirection = 'horizontal', - additionalSignupFields, -}: { - state: 'login' | 'signup' - socialButtonsDirection?: 'horizontal' | 'vertical' - additionalSignupFields?: AdditionalSignupFields -}) => { - const { - isLoading, - setErrorMessage, - setSuccessMessage, - setIsLoading, - } = useContext(AuthContext) - const isLogin = state === 'login' - const cta = isLogin ? 'Log in' : 'Sign up'; - const history = useHistory(); - const onErrorHandler = (error) => { - setErrorMessage({ title: error.message, description: error.data?.data?.message }) - }; - const hookForm = useForm() - const { register, formState: { errors }, handleSubmit: hookFormHandleSubmit } = hookForm - const { handleSubmit } = useUsernameAndPassword({ - isLogin, - onError: onErrorHandler, - onSuccess() { - history.push('/') - }, - }); - async function onSubmit (data) { - setIsLoading(true); - setErrorMessage(null); - setSuccessMessage(null); - try { - await handleSubmit(data); - } finally { - setIsLoading(false); - } - } - - return (<> -
- - Username - - {errors.username && {errors.username.message}} - - - Password - - {errors.password && {errors.password.message}} - - - - {cta} - - - ) -} - -function AdditionalFormFields({ - hookForm, - formState: { isLoading }, - additionalSignupFields, -}: { - hookForm: UseFormReturn; - formState: FormState; - additionalSignupFields: AdditionalSignupFields; -}) { - const { - register, - formState: { errors }, - } = hookForm; - - function renderField>( - field: AdditionalSignupField, - // Ideally we would use ComponentType here, but it doesn't work with react-hook-form - Component: any, - props?: React.ComponentProps - ) { - return ( - - {field.label} - - {errors[field.name] && ( - {errors[field.name].message} - )} - - ); - } - - if (areAdditionalFieldsRenderFn(additionalSignupFields)) { - return additionalSignupFields(hookForm, { isLoading }) - } - - return ( - additionalSignupFields && - additionalSignupFields.map((field) => { - if (isFieldRenderFn(field)) { - return field(hookForm, { isLoading }) - } - switch (field.type) { - case 'input': - return renderField(field, FormInput, { - type: 'text', - }) - case 'textarea': - return renderField(field, FormTextarea) - default: - throw new Error( - `Unsupported additional signup field type: ${field.type}` - ) - } - }) - ) -} - -function isFieldRenderFn( - additionalSignupField: AdditionalSignupField | AdditionalSignupFieldRenderFn -): additionalSignupField is AdditionalSignupFieldRenderFn { - return typeof additionalSignupField === 'function' -} - -function areAdditionalFieldsRenderFn( - additionalSignupFields: AdditionalSignupFields -): additionalSignupFields is AdditionalSignupFieldRenderFn { - return typeof additionalSignupFields === 'function' -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts b/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts deleted file mode 100644 index 247c1faeb4..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/usernameAndPassword/useUsernameAndPassword.ts +++ /dev/null @@ -1,29 +0,0 @@ -import signup from '../../../signup' -import login from '../../../login' - -export function useUsernameAndPassword({ - onError, - onSuccess, - isLogin, -}: { - onError: (error: Error) => void - onSuccess: () => void - isLogin: boolean -}) { - async function handleSubmit(data) { - try { - if (!isLogin) { - await signup(data) - } - await login(data.username, data.password) - - onSuccess() - } catch (err: unknown) { - onError(err as Error) - } - } - - return { - handleSubmit, - } -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/types.ts b/waspc/data/Generator/templates/sdk/wasp/auth/forms/types.ts deleted file mode 100644 index 14d61ad51e..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createTheme } from '@stitches/react' -import { UseFormReturn, RegisterOptions } from 'react-hook-form' -import type { LoginSignupFormFields } from './internal/common/LoginSignupForm' - -export enum State { - Login = 'login', - Signup = 'signup', -} - -export type CustomizationOptions = { - logo?: string - socialLayout?: 'horizontal' | 'vertical' - appearance?: Parameters[0] -} - -export type ErrorMessage = { - title: string - description?: string -} - -export type FormState = { - isLoading: boolean -} - -export type AdditionalSignupFieldRenderFn = ( - hookForm: UseFormReturn, - formState: FormState -) => React.ReactNode - -export type AdditionalSignupField = { - name: string - label: string - type: 'input' | 'textarea' - validations?: RegisterOptions -} - -export type AdditionalSignupFields = - | (AdditionalSignupField | AdditionalSignupFieldRenderFn)[] - | AdditionalSignupFieldRenderFn diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/jwt.ts b/waspc/data/Generator/templates/sdk/wasp/auth/jwt.ts deleted file mode 100644 index 06c0f10d3a..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/jwt.ts +++ /dev/null @@ -1,12 +0,0 @@ -import jwt from 'jsonwebtoken' -import util from 'util' - -import config from 'wasp/core/config' - -const jwtSign = util.promisify(jwt.sign) -const jwtVerify = util.promisify(jwt.verify) - -const JWT_SECRET = config.auth.jwtSecret - -export const signData = (data, options) => jwtSign(data, JWT_SECRET, options) -export const verify = (token) => jwtVerify(token, JWT_SECRET) diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/login.ts b/waspc/data/Generator/templates/sdk/wasp/auth/login.ts deleted file mode 100644 index 2b4ec4b9fe..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/login.ts +++ /dev/null @@ -1,13 +0,0 @@ -import api, { handleApiError } from 'wasp/api' -import { initSession } from './helpers/user' - -export default async function login(username: string, password: string): Promise { - try { - const args = { username, password } - const response = await api.post('/auth/username/login', args) - - await initSession(response.data.sessionId) - } catch (error) { - handleApiError(error) - } -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/lucia.ts b/waspc/data/Generator/templates/sdk/wasp/auth/lucia.ts deleted file mode 100644 index 690d090d44..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/lucia.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Lucia } from "lucia"; -import { PrismaAdapter } from "@lucia-auth/adapter-prisma"; -import prisma from '../server/dbClient.js' -import config from 'wasp/core/config' -import { type User } from "../entities/index.js" - -const prismaAdapter = new PrismaAdapter( - // Using `as any` here since Lucia's model types are not compatible with Prisma 4 - // model types. This is a temporary workaround until we migrate to Prisma 5. - // This **works** in runtime, but Typescript complains about it. - prisma.session as any, - prisma.auth as any -); - -/** - * We are using Lucia for session management. - * - * Some details: - * 1. We are using the Prisma adapter for Lucia. - * 2. We are not using cookies for session management. Instead, we are using - * the Authorization header to send the session token. - * 3. Our `Session` entity is connected to the `Auth` entity. - * 4. We are exposing the `userId` field from the `Auth` entity to - * make fetching the User easier. - */ -export const auth = new Lucia<{}, { - userId: User['id'] -}>(prismaAdapter, { - // Since we are not using cookies, we don't need to set any cookie options. - // But in the future, if we decide to use cookies, we can set them here. - - // sessionCookie: { - // name: "session", - // expires: true, - // attributes: { - // secure: !config.isDevelopment, - // sameSite: "lax", - // }, - // }, - getUserAttributes({ userId }) { - return { - userId, - }; - }, -}); - -declare module "lucia" { - interface Register { - Lucia: typeof auth; - DatabaseSessionAttributes: {}; - DatabaseUserAttributes: { - userId: User['id'] - }; - } -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/pages/createAuthRequiredPage.jsx b/waspc/data/Generator/templates/sdk/wasp/auth/pages/createAuthRequiredPage.jsx deleted file mode 100644 index 621ef393d9..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/pages/createAuthRequiredPage.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' - -import { Redirect } from 'react-router-dom' -import useAuth from '../useAuth' - - -const createAuthRequiredPage = (Page) => { - return (props) => { - const { data: user, isError, isSuccess, isLoading } = useAuth() - - if (isSuccess) { - if (user) { - return ( - - ) - } else { - return - } - } else if (isLoading) { - return Loading... - } else if (isError) { - return An error ocurred. Please refresh the page. - } else { - return An unknown error ocurred. Please refresh the page. - } - } -} - -export default createAuthRequiredPage - diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/providers/types.ts b/waspc/data/Generator/templates/sdk/wasp/auth/providers/types.ts deleted file mode 100644 index 76e1114850..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/providers/types.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Router, Request } from 'express' -import type { Prisma } from '@prisma/client' -import type { Expand } from 'wasp/universal/types' -import type { ProviderName } from '../utils' - -type UserEntityCreateInput = Prisma.UserCreateInput - -export type ProviderConfig = { - // Unique provider identifier, used as part of URL paths - id: ProviderName; - displayName: string; - // Each provider config can have an init method which is ran on setup time - // e.g. for oAuth providers this is the time when the Passport strategy is registered. - init?(provider: ProviderConfig): Promise; - // Every provider must have a setupRouter method which returns the Express router. - // In this function we are flexibile to do what ever is necessary to make the provider work. - createRouter(provider: ProviderConfig, initData: InitData): Router; -}; - -export type InitData = { - [key: string]: any; -} - -export type RequestWithWasp = Request & { wasp?: { [key: string]: any } } - -export type PossibleUserFields = Expand> - -export type UserSignupFields = { - [key in keyof PossibleUserFields]: FieldGetter< - PossibleUserFields[key] - > -} - -type FieldGetter = ( - data: { [key: string]: unknown } -) => Promise | T | undefined - -export function defineUserSignupFields(fields: UserSignupFields) { - return fields -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/session.ts b/waspc/data/Generator/templates/sdk/wasp/auth/session.ts deleted file mode 100644 index ed9154120b..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/session.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Request as ExpressRequest } from "express"; - -import { type User } from "../entities/index.js" -import { type SanitizedUser } from '../server/_types/index.js' - -import { auth } from "./lucia.js"; -import type { Session } from "lucia"; -import { - throwInvalidCredentialsError, - deserializeAndSanitizeProviderData, -} from "./utils.js"; - -import prisma from '../server/dbClient.js' - -// Creates a new session for the `authId` in the database -export async function createSession(authId: string): Promise { - return auth.createSession(authId, {}); -} - -export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<{ - user: SanitizedUser | null, - session: Session | null, -}> { - const authorizationHeader = req.headers["authorization"]; - - if (typeof authorizationHeader !== "string") { - return { - user: null, - session: null, - }; - } - - const sessionId = auth.readBearerToken(authorizationHeader); - if (!sessionId) { - return { - user: null, - session: null, - }; - } - - return getSessionAndUserFromSessionId(sessionId); -} - -export async function getSessionAndUserFromSessionId(sessionId: string): Promise<{ - user: SanitizedUser | null, - session: Session | null, -}> { - const { session, user: authEntity } = await auth.validateSession(sessionId); - - if (!session || !authEntity) { - return { - user: null, - session: null, - }; - } - - return { - session, - user: await getUser(authEntity.userId) - } -} - -async function getUser(userId: User['id']): Promise { - const user = await prisma.user - .findUnique({ - where: { id: userId }, - include: { - auth: { - include: { - identities: true - } - } - } - }) - - if (!user) { - throwInvalidCredentialsError() - } - - // TODO: This logic must match the type in _types/index.ts (if we remove the - // password field from the object here, we must to do the same there). - // Ideally, these two things would live in the same place: - // https://github.com/wasp-lang/wasp/issues/965 - const deserializedIdentities = user.auth.identities.map((identity) => { - const deserializedProviderData = deserializeAndSanitizeProviderData( - identity.providerData, - { - shouldRemovePasswordField: true, - } - ) - return { - ...identity, - providerData: deserializedProviderData, - } - }) - return { - ...user, - auth: { - ...user.auth, - identities: deserializedIdentities, - }, - } -} - -export function invalidateSession(sessionId: string): Promise { - return auth.invalidateSession(sessionId); -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/signup.ts b/waspc/data/Generator/templates/sdk/wasp/auth/signup.ts deleted file mode 100644 index bde50c5ebd..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/signup.ts +++ /dev/null @@ -1,9 +0,0 @@ -import api, { handleApiError } from 'wasp/api' - -export default async function signup(userFields: { username: string; password: string }): Promise { - try { - await api.post('/auth/username/signup', userFields) - } catch (error) { - handleApiError(error) - } -} diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/validation.ts b/waspc/data/Generator/templates/sdk/wasp/auth/validation.ts deleted file mode 100644 index f384a28c87..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/auth/validation.ts +++ /dev/null @@ -1,77 +0,0 @@ -import HttpError from '../core/HttpError.js'; - -export const PASSWORD_FIELD = 'password'; -const USERNAME_FIELD = 'username'; -const EMAIL_FIELD = 'email'; -const TOKEN_FIELD = 'token'; - -export function ensureValidEmail(args: unknown): void { - validate(args, [ - { validates: EMAIL_FIELD, message: 'email must be present', validator: email => !!email }, - { validates: EMAIL_FIELD, message: 'email must be a valid email', validator: email => isValidEmail(email) }, - ]); -} - -export function ensureValidUsername(args: unknown): void { - validate(args, [ - { validates: USERNAME_FIELD, message: 'username must be present', validator: username => !!username } - ]); -} - -export function ensurePasswordIsPresent(args: unknown): void { - validate(args, [ - { validates: PASSWORD_FIELD, message: 'password must be present', validator: password => !!password }, - ]); -} - -export function ensureValidPassword(args: unknown): void { - validate(args, [ - { validates: PASSWORD_FIELD, message: 'password must be at least 8 characters', validator: password => isMinLength(password, 8) }, - { validates: PASSWORD_FIELD, message: 'password must contain a number', validator: password => containsNumber(password) }, - ]); -} - -export function ensureTokenIsPresent(args: unknown): void { - validate(args, [ - { validates: TOKEN_FIELD, message: 'token must be present', validator: token => !!token }, - ]); -} - -export function throwValidationError(message: string): void { - throw new HttpError(422, 'Validation failed', { message }) -} - -function validate(args: unknown, validators: { validates: string, message: string, validator: (value: unknown) => boolean }[]): void { - for (const { validates, message, validator } of validators) { - if (!validator(args[validates])) { - throwValidationError(message); - } - } -} - -// NOTE(miho): it would be good to replace our custom validations with e.g. Zod - -const validEmailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ -function isValidEmail(input: unknown): boolean { - if (typeof input !== 'string') { - return false - } - - return input.match(validEmailRegex) !== null -} - -function isMinLength(input: unknown, minLength: number): boolean { - if (typeof input !== 'string') { - return false - } - - return input.length >= minLength -} - -function containsNumber(input: unknown): boolean { - if (typeof input !== 'string') { - return false - } - - return /\d/.test(input) -} diff --git a/waspc/data/Generator/templates/sdk/wasp/entities/index.ts b/waspc/data/Generator/templates/sdk/wasp/entities/index.ts deleted file mode 100644 index 5febac3804..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/entities/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - type User, - type Task, -} from "@prisma/client" - -export { - type User, - type Task, - type Auth, - type AuthIdentity, -} from "@prisma/client" - -export type Entity = - | User - | Task - | never - -export type EntityName = - | "User" - | "Task" - | never diff --git a/waspc/data/Generator/templates/sdk/wasp/ext-src/actions.ts b/waspc/data/Generator/templates/sdk/wasp/ext-src/actions.ts deleted file mode 100644 index c03bfac62b..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/ext-src/actions.ts +++ /dev/null @@ -1,56 +0,0 @@ -import HttpError from 'wasp/core/HttpError' -import type { - CreateTask, - UpdateTask, - DeleteTasks, -} from 'wasp/server/actions/types' -import type { Task } from 'wasp/entities' - -type CreateArgs = Pick - -export const createTask: CreateTask = async ( - { description }, - context -) => { - if (!context.user) { - throw new HttpError(401) - } - - return context.entities.Task.create({ - data: { - description, - user: { connect: { id: context.user.id } }, - }, - }) -} - -type UpdateArgs = Pick - -export const updateTask: UpdateTask = async ( - { id, isDone }, - context -) => { - if (!context.user) { - throw new HttpError(401) - } - - return context.entities.Task.update({ - where: { - id, - }, - data: { isDone }, - }) -} - -export const deleteTasks: DeleteTasks = async ( - idsToDelete, - context -) => { - return context.entities.Task.deleteMany({ - where: { - id: { - in: idsToDelete, - }, - }, - }) -} diff --git a/waspc/data/Generator/templates/sdk/wasp/ext-src/queries.ts b/waspc/data/Generator/templates/sdk/wasp/ext-src/queries.ts deleted file mode 100644 index ac49e0a7a7..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/ext-src/queries.ts +++ /dev/null @@ -1,15 +0,0 @@ -import HttpError from 'wasp/core/HttpError' -import type { GetTasks } from 'wasp/server/queries/types' -import type { Task } from 'wasp/entities' - -//Using TypeScript's new 'satisfies' keyword, it will infer the types of the arguments and return value -export const getTasks = ((_args, context) => { - if (!context.user) { - throw new HttpError(401) - } - - return context.entities.Task.findMany({ - where: { user: { id: context.user.id } }, - orderBy: { id: 'asc' }, - }) -}) satisfies GetTasks diff --git a/waspc/data/Generator/templates/sdk/wasp/operations/updateHandlersMap.js b/waspc/data/Generator/templates/sdk/wasp/operations/updateHandlersMap.js deleted file mode 100644 index 8c43c0b1ba..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/operations/updateHandlersMap.js +++ /dev/null @@ -1,37 +0,0 @@ -export function makeUpdateHandlersMap(calculateHash) { - const updateHandlers = new Map() - - function getHandlerTuples(queryKeyHash) { - return updateHandlers.get(queryKeyHash) || []; - } - - function add(queryKey, updateQuery) { - const queryKeyHash = calculateHash(queryKey) - const handlers = getHandlerTuples(queryKeyHash); - updateHandlers.set(queryKeyHash, [...handlers, { queryKey, updateQuery }]) - } - - function getUpdateHandlers(queryKey) { - const queryKeyHash = calculateHash(queryKey) - return getHandlerTuples(queryKeyHash).map(({ updateQuery }) => updateQuery) - } - - function remove(queryKeyToRemove) { - const queryKeyHash = calculateHash(queryKeyToRemove) - const filteredHandlers = getHandlerTuples(queryKeyHash).filter( - ({ queryKey }) => queryKey !== queryKeyToRemove - ) - - if (filteredHandlers.length > 0) { - updateHandlers.set(queryKeyHash, filteredHandlers) - } else { - updateHandlers.delete(queryKeyHash) - } - } - - return { - add, - remove, - getUpdateHandlers, - } -} diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/actions/index.ts b/waspc/data/Generator/templates/sdk/wasp/rpc/actions/index.ts deleted file mode 100644 index 2be33b3d65..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/rpc/actions/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createAction } from './core' -import { CreateTask, UpdateTask } from 'wasp/server/actions' - -export const updateTask = createAction('operations/update-task', [ - 'Task', -]) - -export const createTask = createAction('operations/create-task', [ - 'Task', -]) - -export const deleteTasks = createAction('operations/delete-tasks', [ - 'Task', -]) diff --git a/waspc/data/Generator/templates/sdk/wasp/rpc/queries/index.ts b/waspc/data/Generator/templates/sdk/wasp/rpc/queries/index.ts deleted file mode 100644 index a03221553d..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/rpc/queries/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createQuery } from './core' -import { GetTasks } from 'wasp/server/queries' - -export const getTasks = createQuery('operations/get-tasks', ['Task']) - -export { addMetadataToQuery } from './core' diff --git a/waspc/data/Generator/templates/sdk/wasp/server/_types/index.ts b/waspc/data/Generator/templates/sdk/wasp/server/_types/index.ts deleted file mode 100644 index 4a2afbd4bc..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/server/_types/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { type Expand } from 'wasp/universal/types'; -import { type Request, type Response } from 'express' -import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core' -import prisma from "wasp/server/dbClient" -import { - type User, - type Auth, - type AuthIdentity, -} from "wasp/entities" -import { - type EmailProviderData, - type UsernameProviderData, - type OAuthProviderData, - // todo(filip): marker -} from 'wasp/auth/utils' -import { type _Entity } from "./taggedEntities" -import { type Payload } from "./serialization"; - -export * from "./taggedEntities" -export * from "./serialization" - -export type Query = - Operation - -export type Action = - Operation - -export type AuthenticatedQuery = - AuthenticatedOperation - -export type AuthenticatedAction = - AuthenticatedOperation - -type AuthenticatedOperation = ( - args: Input, - context: ContextWithUser, -) => Output | Promise - -export type AuthenticatedApi< - Entities extends _Entity[], - Params extends ExpressParams, - ResBody, - ReqBody, - ReqQuery extends ExpressQuery, - Locals extends Record -> = ( - req: Request, - res: Response, - context: ContextWithUser, -) => void - -type Operation = ( - args: Input, - context: Context, -) => Output | Promise - -export type Api< - Entities extends _Entity[], - Params extends ExpressParams, - ResBody, - ReqBody, - ReqQuery extends ExpressQuery, - Locals extends Record -> = ( - req: Request, - res: Response, - context: Context, -) => void - -type EntityMap = { - [EntityName in Entities[number]["_entityName"]]: PrismaDelegate[EntityName] -} - -export type PrismaDelegate = { - "User": typeof prisma.user, - "Task": typeof prisma.task, -} - -type Context = Expand<{ - entities: Expand> -}> - -type ContextWithUser = Expand & { user?: SanitizedUser }> - -// TODO: This type must match the logic in auth/session.js (if we remove the -// password field from the object there, we must do the same here). Ideally, -// these two things would live in the same place: -// https://github.com/wasp-lang/wasp/issues/965 - -export type DeserializedAuthIdentity = Expand & { - providerData: Omit | Omit | OAuthProviderData -}> - -export type SanitizedUser = User & { - auth: Auth & { - identities: DeserializedAuthIdentity[] - } | null -} - -// todo(filip): marker -export type { ProviderName } from 'wasp/auth/utils' diff --git a/waspc/data/Generator/templates/sdk/wasp/server/_types/taggedEntities.ts b/waspc/data/Generator/templates/sdk/wasp/server/_types/taggedEntities.ts deleted file mode 100644 index 3331b3f893..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/server/_types/taggedEntities.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Wasp internally uses the types defined in this file for typing entity maps in -// operation contexts. -// -// We must explicitly tag all entities with their name to avoid issues with -// structural typing. See https://github.com/wasp-lang/wasp/pull/982 for details. -import { - type Entity, - type EntityName, - type User, - type Task, -} from '../../entities' - -export type _User = WithName -export type _Task = WithName - -export type _Entity = - | _User - | _Task - | never - -type WithName = - E & { _entityName: Name } diff --git a/waspc/data/Generator/templates/sdk/wasp/server/actions/index.ts b/waspc/data/Generator/templates/sdk/wasp/server/actions/index.ts deleted file mode 100644 index 5f9800cf8d..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/server/actions/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import prisma from 'wasp/server/dbClient.js' -import { - updateTask as updateTaskUser, - createTask as createTaskUser, - deleteTasks as deleteTasksUser, -} from 'wasp/ext-src/actions.js' - -export type UpdateTask = typeof updateTask - -export const updateTask = async (args, context) => { - return (updateTaskUser as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type CreateTask = typeof createTask - -export const createTask = async (args, context) => { - return (createTaskUser as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} - -export type DeleteTasks = typeof deleteTasks - -export const deleteTasks = async (args, context) => { - return (deleteTasksUser as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/actions/types.ts b/waspc/data/Generator/templates/sdk/wasp/server/actions/types.ts deleted file mode 100644 index 8e03d4963e..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/server/actions/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - type _Task, - type AuthenticatedAction, - type Payload, -} from '../_types' - -export type CreateTask = - AuthenticatedAction< - [ - _Task, - ], - Input, - Output - > - -export type UpdateTask = - AuthenticatedAction< - [ - _Task, - ], - Input, - Output - > - -export type DeleteTasks = - AuthenticatedAction< - [ - _Task, - ], - Input, - Output - > - - diff --git a/waspc/data/Generator/templates/sdk/wasp/server/queries/index.ts b/waspc/data/Generator/templates/sdk/wasp/server/queries/index.ts deleted file mode 100644 index 3c49adc9dc..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/server/queries/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import prisma from 'wasp/server/dbClient.js' -import { getTasks as getTasksUser } from 'wasp/ext-src/queries.js' - -export type GetTasks = typeof getTasksUser - -export const getTasks = async (args, context) => { - return (getTasksUser as any)(args, { - ...context, - entities: { - Task: prisma.task, - }, - }) -} diff --git a/waspc/data/Generator/templates/sdk/wasp/server/queries/types.ts b/waspc/data/Generator/templates/sdk/wasp/server/queries/types.ts deleted file mode 100644 index 0617ad4559..0000000000 --- a/waspc/data/Generator/templates/sdk/wasp/server/queries/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type _Task, type AuthenticatedQuery, type Payload } from "../_types"; - -export type GetTasks< - Input extends Payload = never, - Output extends Payload = Payload -> = AuthenticatedQuery<[_Task], Input, Output>; diff --git a/waspc/data/Generator/templates/react-app/src/webSocket/WebSocketProvider.tsx b/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx similarity index 91% rename from waspc/data/Generator/templates/react-app/src/webSocket/WebSocketProvider.tsx rename to waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx index ef6e4b1e2a..d650c1b80d 100644 --- a/waspc/data/Generator/templates/react-app/src/webSocket/WebSocketProvider.tsx +++ b/waspc/data/Generator/templates/sdk/webSocket/WebSocketProvider.tsx @@ -2,11 +2,11 @@ import { createContext, useState, useEffect } from 'react' import { io, Socket } from 'socket.io-client' -import { getSessionId } from '../api' -import { apiEventsEmitter } from '../api/events' -import config from '../config' +import { getSessionId } from 'wasp/api' +import { apiEventsEmitter } from 'wasp/api/events' +import config from 'wasp/core/config' -import type { ClientToServerEvents, ServerToClientEvents } from '../webSocket'; +import type { ClientToServerEvents, ServerToClientEvents } from 'wasp/server/webSocket'; // TODO: In the future, it would be nice if users could pass more // options to `io`, likely via some `configFn`. diff --git a/waspc/data/Generator/templates/react-app/src/webSocket.ts b/waspc/data/Generator/templates/sdk/webSocket/index.ts similarity index 85% rename from waspc/data/Generator/templates/react-app/src/webSocket.ts rename to waspc/data/Generator/templates/sdk/webSocket/index.ts index 22cf322783..3dce7da720 100644 --- a/waspc/data/Generator/templates/react-app/src/webSocket.ts +++ b/waspc/data/Generator/templates/sdk/webSocket/index.ts @@ -1,14 +1,9 @@ import { useContext, useEffect } from 'react' -import { WebSocketContext } from './webSocket/WebSocketProvider' +import { WebSocketContext } from './WebSocketProvider' import type { ClientToServerEvents, ServerToClientEvents, -} from '../../server/src/webSocket' - -export type { - ClientToServerEvents, - ServerToClientEvents, -} from '../../server/src/webSocket' +} from 'wasp/server/webSocket' export type ServerToClientPayload = Parameters[0] diff --git a/waspc/data/Generator/templates/server/package.json b/waspc/data/Generator/templates/server/package.json index 1a80a9ee47..528e8a44f8 100644 --- a/waspc/data/Generator/templates/server/package.json +++ b/waspc/data/Generator/templates/server/package.json @@ -11,7 +11,7 @@ "build-and-start": "npm run build && npm run start", "watch": "nodemon --exec 'npm run build-and-start || exit 1'", "validate-env": "node -r dotenv/config ./scripts/validate-env.mjs", - "db-seed": "npm run build && NODE_PATH=dist node -r dotenv/config dist/dbSeed.js", + "db-seed": "npm run build && NODE_PATH=dist node -r dotenv/config dist/.wasp/out/server/src/dbSeed.js", "db-migrate-prod": "prisma migrate deploy --schema=../db/schema.prisma", "start-production": "{=& startProductionScript =}", "standard": "standard", diff --git a/waspc/data/Generator/templates/server/scripts/validate-env.mjs b/waspc/data/Generator/templates/server/scripts/validate-env.mjs index fb68580bbb..ac264b7961 100644 --- a/waspc/data/Generator/templates/server/scripts/validate-env.mjs +++ b/waspc/data/Generator/templates/server/scripts/validate-env.mjs @@ -1,4 +1,4 @@ -import { throwIfNotValidAbsoluteURL } from './universal/validators.mjs'; +import { throwIfNotValidAbsoluteURL } from 'wasp/universal/validators'; console.info("🔍 Validating environment variables..."); throwIfNotValidAbsoluteURL(process.env.WASP_WEB_CLIENT_URL, 'Environment variable WASP_WEB_CLIENT_URL'); diff --git a/waspc/data/Generator/templates/server/src/_types/serialization.ts b/waspc/data/Generator/templates/server/src/_types/serialization.ts deleted file mode 100644 index 595b5ba69f..0000000000 --- a/waspc/data/Generator/templates/server/src/_types/serialization.ts +++ /dev/null @@ -1,43 +0,0 @@ -export type Payload = void | SuperJSONValue - -// The part below was copied from SuperJSON and slightly modified: -// https://github.com/blitz-js/superjson/blob/ae7dbcefe5d3ece5b04be0c6afe6b40f3a44a22a/src/types.ts -// -// We couldn't use SuperJSON's types directly because: -// 1. They aren't exported publicly. -// 2. They have a werid quirk that turns `SuperJSONValue` into `any`. -// See why here: -// https://github.com/blitz-js/superjson/pull/36#issuecomment-669239876 -// -// We changed the code as little as possible to make future comparisons easier. -export type JSONValue = PrimitiveJSONValue | JSONArray | JSONObject - -export interface JSONObject { - [key: string]: JSONValue -} - -type PrimitiveJSONValue = string | number | boolean | undefined | null - -interface JSONArray extends Array {} - -type SerializableJSONValue = - | Symbol - | Set - | Map - | undefined - | bigint - | Date - | RegExp - -// Here's where we excluded `ClassInstance` (which was `any`) from the union. -type SuperJSONValue = - | JSONValue - | SerializableJSONValue - | SuperJSONArray - | SuperJSONObject - -interface SuperJSONArray extends Array {} - -interface SuperJSONObject { - [key: string]: SuperJSONValue -} diff --git a/waspc/data/Generator/templates/server/src/actions/_action.ts b/waspc/data/Generator/templates/server/src/actions/_action.ts index 1bc6c0ad40..11b092cb1a 100644 --- a/waspc/data/Generator/templates/server/src/actions/_action.ts +++ b/waspc/data/Generator/templates/server/src/actions/_action.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from '../dbClient.js' +import prisma from 'wasp/server/dbClient' {=& jsFn.importStatement =} diff --git a/waspc/data/Generator/templates/server/src/actions/types.ts b/waspc/data/Generator/templates/server/src/actions/types.ts index c56080ce20..e0fa2e9237 100644 --- a/waspc/data/Generator/templates/server/src/actions/types.ts +++ b/waspc/data/Generator/templates/server/src/actions/types.ts @@ -12,7 +12,7 @@ import { type AuthenticatedAction, {=/ shouldImportAuthenticatedOperation =} type Payload, -} from '../_types' +} from 'wasp/server/_types' {=# operations =} export type {= typeName =} = diff --git a/waspc/data/Generator/templates/server/src/auth/password.ts b/waspc/data/Generator/templates/server/src/auth/password.ts deleted file mode 100644 index a359892b5e..0000000000 --- a/waspc/data/Generator/templates/server/src/auth/password.ts +++ /dev/null @@ -1,15 +0,0 @@ -import SecurePassword from 'secure-password' - -const SP = new SecurePassword() - -export const hashPassword = async (password: string): Promise => { - const hashedPwdBuffer = await SP.hash(Buffer.from(password)) - return hashedPwdBuffer.toString("base64") -} - -export const verifyPassword = async (hashedPassword: string, password: string): Promise => { - const result = await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64")) - if (result !== SecurePassword.VALID) { - throw new Error('Invalid password.') - } -} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/_oauth.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/_oauth.ts index 5d6a8c6c2d..6a88b5500f 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/_oauth.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/_oauth.ts @@ -3,7 +3,7 @@ import { createRouter } from "../oauth/createRouter.js"; import { makeOAuthInit } from "../oauth/init.js"; -import type { ProviderConfig } from "../types.js"; +import type { ProviderConfig } from "wasp/auth/providers/types"; import type { OAuthConfig } from "../oauth/types.js"; {=# userSignupFields.isDefined =} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts index 979514bb24..0957768722 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/email.ts @@ -1,16 +1,16 @@ {{={= =}=}} import { Router, Request, Response, NextFunction } from "express"; -import { ProviderConfig } from "../types.js"; -import type { EmailFromField } from '../../../email/core/types.js'; +import { ProviderConfig } from "wasp/auth/providers/types"; +import type { EmailFromField } from "wasp/email/core/types"; import { getLoginRoute } from "../email/login.js"; import { getSignupRoute } from "../email/signup.js"; import { getRequestPasswordResetRoute } from "../email/requestPasswordReset.js"; import { resetPassword } from "../email/resetPassword.js"; import { verifyEmail } from "../email/verifyEmail.js"; -import { GetVerificationEmailContentFn, GetPasswordResetEmailContentFn } from "../email/types.js"; -import { handleRejection } from "../../../utils.js"; +import { GetVerificationEmailContentFn, GetPasswordResetEmailContentFn } from "wasp/server/auth/email"; +import { handleRejection } from "wasp/server/utils"; {=# userSignupFields.isDefined =} {=& userSignupFields.importStatement =} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/username.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/username.ts index c9846f5187..f75e693549 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/config/username.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/config/username.ts @@ -4,7 +4,7 @@ import { Router } from "express"; import login from "../username/login.js"; import { getSignupRoute } from "../username/signup.js"; -import { ProviderConfig } from "../types.js"; +import { ProviderConfig } from "wasp/auth/providers/types"; {=# userSignupFields.isDefined =} {=& userSignupFields.importStatement =} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts index ee987ca193..fb60110f98 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/login.ts @@ -1,14 +1,14 @@ import { Request, Response } from 'express'; -import { throwInvalidCredentialsError } from '../../utils.js' -import { verifyPassword } from '../../password.js' +import { throwInvalidCredentialsError } from 'wasp/auth/utils' +import { verifyPassword } from 'wasp/auth/password' import { createProviderId, findAuthIdentity, findAuthWithUserBy, deserializeAndSanitizeProviderData, -} from '../../utils.js' -import { createSession } from '../../session.js' -import { ensureValidEmail, ensurePasswordIsPresent } from '../../validation.js' +} from 'wasp/auth/utils' +import { createSession } from 'wasp/auth/session' +import { ensureValidEmail, ensurePasswordIsPresent } from 'wasp/auth/validation' export function getLoginRoute() { return async function login( diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts index 194da085d5..92d3dea0fb 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/requestPasswordReset.ts @@ -4,16 +4,16 @@ import { findAuthIdentity, doFakeWork, deserializeAndSanitizeProviderData, -} from "../../utils.js"; +} from 'wasp/auth/utils'; import { createPasswordResetLink, sendPasswordResetEmail, isEmailResendAllowed, -} from "./utils.js"; -import { ensureValidEmail } from "../../validation.js"; -import type { EmailFromField } from '../../../email/core/types.js'; -import { GetPasswordResetEmailContentFn } from './types.js'; -import HttpError from 'wasp/core/HttpError' +} from "wasp/server/auth/email/utils"; +import { ensureValidEmail } from 'wasp/auth/validation'; +import type { EmailFromField } from 'wasp/email/core/types'; +import { GetPasswordResetEmailContentFn } from 'wasp/server/auth/email'; +import HttpError from 'wasp/core/HttpError'; export function getRequestPasswordResetRoute({ fromField, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts index 6d8f0d6753..ba00a4ae9f 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/resetPassword.ts @@ -5,8 +5,8 @@ import { updateAuthIdentityProviderData, verifyToken, deserializeAndSanitizeProviderData, -} from "../../utils.js"; -import { ensureTokenIsPresent, ensurePasswordIsPresent, ensureValidPassword } from "../../validation.js"; +} from 'wasp/auth/utils'; +import { ensureTokenIsPresent, ensurePasswordIsPresent, ensureValidPassword } from 'wasp/auth/validation'; import { tokenVerificationErrors } from "./types.js"; import HttpError from 'wasp/core/HttpError'; diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts index 984f38362d..3039f44746 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/signup.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { EmailFromField } from "../../../email/core/types.js"; +import { EmailFromField } from "wasp/email/core/types"; import { createUser, createProviderId, @@ -9,17 +9,17 @@ import { deserializeAndSanitizeProviderData, sanitizeAndSerializeProviderData, rethrowPossibleAuthError, -} from "../../utils.js"; +} from 'wasp/auth/utils'; import { createEmailVerificationLink, sendEmailVerificationEmail, isEmailResendAllowed, -} from "./utils.js"; -import { ensureValidEmail, ensureValidPassword, ensurePasswordIsPresent } from "../../validation.js"; -import { GetVerificationEmailContentFn } from './types.js'; -import { validateAndGetUserFields } from '../../utils.js' +} from "wasp/server/auth/email/utils"; +import { ensureValidEmail, ensureValidPassword, ensurePasswordIsPresent } from 'wasp/auth/validation'; +import { GetVerificationEmailContentFn } from 'wasp/server/auth/email'; +import { validateAndGetUserFields } from 'wasp/auth/utils' import HttpError from 'wasp/core/HttpError'; -import { type UserSignupFields } from '../types.js'; +import { type UserSignupFields } from 'wasp/auth/providers/types'; export function getSignupRoute({ userSignupFields, diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/types.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/types.ts index f213c80e93..67e2ec8273 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/types.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/types.ts @@ -1,13 +1,3 @@ -export type GetVerificationEmailContentFn = (params: { verificationLink: string }) => EmailContent; - -export type GetPasswordResetEmailContentFn = (params: { passwordResetLink: string }) => EmailContent; - -type EmailContent = { - subject: string; - html: string; - text: string; -} - export const tokenVerificationErrors = { TokenExpiredError: 'TokenExpiredError', }; diff --git a/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts b/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts index de73a05ddf..c54963e411 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/email/verifyEmail.ts @@ -5,7 +5,7 @@ import { findAuthIdentity, updateAuthIdentityProviderData, deserializeAndSanitizeProviderData, -} from '../../utils.js'; +} from 'wasp/auth/utils'; import { tokenVerificationErrors } from './types.js'; import HttpError from 'wasp/core/HttpError'; diff --git a/waspc/data/Generator/templates/server/src/auth/providers/index.ts b/waspc/data/Generator/templates/server/src/auth/providers/index.ts index f3e3326fbe..c3a2c2b463 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/index.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/index.ts @@ -3,9 +3,9 @@ import { join } from 'path' import { Router } from "express"; -import { getDirPathFromFileUrl, importJsFilesFromDir } from "../../utils.js"; +import { getDirPathFromFileUrl, importJsFilesFromDir } from "wasp/server/utils"; -import { ProviderConfig } from "./types"; +import { ProviderConfig } from "wasp/auth/providers/types"; const whitelistedProviderConfigFileNames = [ {=# enabledProviderIds =} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts index caa0b3a8a3..a8032b717d 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/createRouter.ts @@ -3,8 +3,8 @@ import { Router } from "express" import passport from "passport" -import prisma from '../../../dbClient.js' -import waspServerConfig from '../../../config.js' +import prisma from 'wasp/server/dbClient' +import waspServerConfig from 'wasp/server/config' import { type ProviderName, type ProviderId, @@ -15,11 +15,11 @@ import { rethrowPossibleAuthError, sanitizeAndSerializeProviderData, validateAndGetUserFields, -} from "../../utils.js" -import { createSession } from "../../session.js" -import { type {= authEntityUpper =} } from "../../../entities/index.js" -import type { ProviderConfig, RequestWithWasp, UserSignupFields } from "../types.js" -import { handleRejection } from "../../../utils.js" +} from 'wasp/auth/utils' +import { createSession } from "wasp/auth/session" +import { type {= authEntityUpper =} } from "wasp/entities" +import type { ProviderConfig, RequestWithWasp, UserSignupFields } from "wasp/auth/providers/types" +import { handleRejection } from "wasp/server/utils" // For oauth providers, we have an endpoint /login to get the auth URL, // and the /callback endpoint which is used to get the actual access_token and the user info. diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts index 1462f3a2f7..a104eac299 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/init.ts @@ -1,8 +1,8 @@ import passport from "passport"; -import waspServerConfig from '../../../config.js'; +import waspServerConfig from 'wasp/server/config'; -import type { InitData, ProviderConfig, RequestWithWasp, UserSignupFields } from "../types.js"; +import type { InitData, ProviderConfig, RequestWithWasp, UserSignupFields } from "wasp/auth/providers/types"; import type { OAuthConfig, UserDefinedConfigFn } from "./types.js"; export function makeOAuthInit({ userDefinedConfigFn, userSignupFields, npmPackage, oAuthConfig }: OAuthImports) { diff --git a/waspc/data/Generator/templates/server/src/auth/providers/oauth/types.ts b/waspc/data/Generator/templates/server/src/auth/providers/oauth/types.ts index d18295c676..c60f3d3c80 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/oauth/types.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/oauth/types.ts @@ -1,7 +1,7 @@ {{={= =}=}} import type { Prisma } from "@prisma/client" -import { contextWithUserEntity } from '../../utils.js' +import { contextWithUserEntity } from 'wasp/auth/utils' export type OAuthConfig = { clientID?: string; diff --git a/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts b/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts index 8fdab1f29c..f461965c4e 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/username/login.ts @@ -1,16 +1,16 @@ {{={= =}=}} -import { throwInvalidCredentialsError } from '../../utils.js' -import { handleRejection } from '../../../utils.js' -import { verifyPassword } from '../../password.js' +import { throwInvalidCredentialsError } from 'wasp/auth/utils' +import { handleRejection } from 'wasp/server/utils' +import { verifyPassword } from 'wasp/auth/password' import { createProviderId, findAuthIdentity, findAuthWithUserBy, deserializeAndSanitizeProviderData, -} from '../../utils.js' -import { createSession } from '../../session.js' -import { ensureValidUsername, ensurePasswordIsPresent } from '../../validation.js' +} from 'wasp/auth/utils' +import { createSession } from 'wasp/auth/session' +import { ensureValidUsername, ensurePasswordIsPresent } from 'wasp/auth/validation' export default handleRejection(async (req, res) => { const fields = req.body ?? {} diff --git a/waspc/data/Generator/templates/server/src/auth/providers/username/signup.ts b/waspc/data/Generator/templates/server/src/auth/providers/username/signup.ts index 6e014d86f4..6b0fc73162 100644 --- a/waspc/data/Generator/templates/server/src/auth/providers/username/signup.ts +++ b/waspc/data/Generator/templates/server/src/auth/providers/username/signup.ts @@ -1,18 +1,18 @@ {{={= =}=}} -import { handleRejection } from '../../../utils.js' +import { handleRejection } from 'wasp/server/utils' import { createProviderId, createUser, rethrowPossibleAuthError, sanitizeAndSerializeProviderData, -} from '../../utils.js' +} from 'wasp/auth/utils' import { ensureValidUsername, ensurePasswordIsPresent, ensureValidPassword, -} from '../../validation.js' -import { validateAndGetUserFields } from '../../utils.js' -import { type UserSignupFields } from '../types.js' +} from 'wasp/auth/validation' +import { validateAndGetUserFields } from 'wasp/auth/utils' +import { type UserSignupFields } from 'wasp/auth/providers/types' export function getSignupRoute({ userSignupFields, diff --git a/waspc/data/Generator/templates/server/src/auth/user.ts b/waspc/data/Generator/templates/server/src/auth/user.ts deleted file mode 100644 index 410e92058b..0000000000 --- a/waspc/data/Generator/templates/server/src/auth/user.ts +++ /dev/null @@ -1,27 +0,0 @@ -// We decided not to deduplicate these helper functions in the server and the client. -// We have them duplicated in this file and in data/Generator/templates/react-app/src/auth/user.ts -// If you are changing the logic here, make sure to change it there as well. - -import type { SanitizedUser as User, ProviderName, DeserializedAuthIdentity } from '../_types/index' - -export function getEmail(user: User): string | null { - return findUserIdentity(user, "email")?.providerUserId ?? null; -} - -export function getUsername(user: User): string | null { - return findUserIdentity(user, "username")?.providerUserId ?? null; -} - -export function getFirstProviderUserId(user?: User): string | null { - if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) { - return null; - } - - return user.auth.identities[0].providerUserId ?? null; -} - -export function findUserIdentity(user: User, providerName: ProviderName): DeserializedAuthIdentity | undefined { - return user.auth.identities.find( - (identity) => identity.providerName === providerName - ); -} diff --git a/waspc/data/Generator/templates/server/src/auth/utils.ts b/waspc/data/Generator/templates/server/src/auth/utils.ts index bd63be6c79..d3ede94ce6 100644 --- a/waspc/data/Generator/templates/server/src/auth/utils.ts +++ b/waspc/data/Generator/templates/server/src/auth/utils.ts @@ -1,20 +1,20 @@ {{={= =}=}} -import { hashPassword } from './password.js' -import { verify } from './jwt.js' +import { hashPassword } from 'wasp/auth/password' +import { verify } from 'wasp/auth/jwt' import AuthError from 'wasp/core/AuthError' import HttpError from 'wasp/core/HttpError' -import prisma from '../dbClient.js' -import { sleep } from '../utils.js' +import prisma from 'wasp/server/dbClient' +import { sleep } from 'wasp/server/utils' import { type {= userEntityUpper =}, type {= authEntityUpper =}, type {= authIdentityEntityUpper =}, -} from '../entities/index.js' +} from 'wasp/entities/index' import { Prisma } from '@prisma/client'; -import { throwValidationError } from './validation.js' +import { throwValidationError } from 'wasp/auth/validation' -import { type UserSignupFields, type PossibleUserFields } from './providers/types.js' +import { type UserSignupFields, type PossibleUserFields } from 'wasp/auth/providers/types' export type EmailProviderData = { hashedPassword: string; @@ -180,7 +180,7 @@ export async function doFakeWork(): Promise { export function rethrowPossibleAuthError(e: unknown): void { if (e instanceof AuthError) { - throwValidationError(e.message); + throwValidationError((e as any).message); } // Prisma code P2002 is for unique constraint violations. diff --git a/waspc/data/Generator/templates/server/src/core/auth.js b/waspc/data/Generator/templates/server/src/core/auth.js deleted file mode 100644 index 85e702baea..0000000000 --- a/waspc/data/Generator/templates/server/src/core/auth.js +++ /dev/null @@ -1,42 +0,0 @@ -{{={= =}=}} -import { randomInt } from 'node:crypto' - -import prisma from '../dbClient.js' -import { handleRejection } from '../utils.js' -import { getSessionAndUserFromBearerToken } from '../auth/session.js' -import { throwInvalidCredentialsError } from '../auth/utils.js' - -/** - * Auth middleware - * - * If the request includes an `Authorization` header it will try to authenticate the request, - * otherwise it will let the request through. - * - * - If authentication succeeds it sets `req.sessionId` and `req.user` - * - `req.user` is the user that made the request and it's used in - * all Wasp features that need to know the user that made the request. - * - `req.sessionId` is the ID of the session that authenticated the request. - * - If the request is not authenticated, it throws an error. - */ -const auth = handleRejection(async (req, res, next) => { - const authHeader = req.get('Authorization') - if (!authHeader) { - // NOTE(matija): for now we let tokenless requests through and make it operation's - // responsibility to verify whether the request is authenticated or not. In the future - // we will develop our own system at Wasp-level for that. - return next() - } - - const { session, user } = await getSessionAndUserFromBearerToken(req); - - if (!session || !user) { - throwInvalidCredentialsError() - } - - req.sessionId = session.id - req.user = user - - next() -}) - -export default auth diff --git a/waspc/data/Generator/templates/server/src/crud/_operations.ts b/waspc/data/Generator/templates/server/src/crud/_operations.ts index fd6cf6cb5d..31237b2dc9 100644 --- a/waspc/data/Generator/templates/server/src/crud/_operations.ts +++ b/waspc/data/Generator/templates/server/src/crud/_operations.ts @@ -1,25 +1,14 @@ {{={= =}=}} -import prisma from "../dbClient.js"; +import prisma from "wasp/server/dbClient"; -import type { - {=# isAuthEnabled =} - AuthenticatedAction, - AuthenticatedQuery, - {=/ isAuthEnabled =} - {=^ isAuthEnabled =} - Action, - Query, - {=/ isAuthEnabled =} - _{= crud.entityUpper =}, -} from "../_types"; import type { Prisma } from "@prisma/client"; -import { Payload } from "../_types/serialization.js"; import type { {= crud.entityUpper =}, -} from "../entities"; +} from "wasp/entities"; {=# isAuthEnabled =} -import { throwInvalidCredentialsError } from '../auth/utils.js' +import { throwInvalidCredentialsError } from 'wasp/auth/utils' {=/ isAuthEnabled =} +import type { GetAllQuery, GetQuery, CreateAction, UpdateAction, DeleteAction } from "{= crudTypesImportPath =}"; {=# overrides.GetAll.isDefined =} {=& overrides.GetAll.importStatement =} {=/ overrides.GetAll.isDefined =} @@ -36,7 +25,6 @@ import { throwInvalidCredentialsError } from '../auth/utils.js' {=& overrides.Delete.importStatement =} {=/ overrides.Delete.isDefined =} -type _WaspEntityTagged = _{= crud.entityUpper =} type _WaspEntity = {= crud.entityUpper =} const entities = { {= crud.entityUpper =}: prisma.{= crud.entityLower =}, @@ -48,12 +36,7 @@ const entities = { {=# crud.operations.GetAll =} // Get All query {=! -// 1. We define the type for the operation using "queryType" template variable which is either -// AuthenticatedQuery or Query (it depends on whether auth is enabled or not). -=} -export type GetAllQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> -{=! -// 2. Then, we either use the default implementation of the operation... +// 1. We either use the default implementation of the operation... =} {=^ overrides.GetAll.isDefined =} type GetAllInput = {} @@ -74,13 +57,7 @@ const _waspGetAllQuery = {= overrides.GetAll.importIdentifier =} {=/ overrides.GetAll.isDefined =} {=! -// 3. We then define the final type for the operation, which is the type of the function we defined in the previous step. -// It will pick up either the default implementation or the one from the overrides. -=} -export type GetAllQueryResolved = typeof _waspGetAllQuery - -{=! -// 4. We define a function that is used as the Express route handler +// 2. We define a function that is used as the Express route handler =} export async function getAllFn(args, context) { return (_waspGetAllQuery as any)(args, { @@ -95,7 +72,6 @@ export async function getAllFn(args, context) { {=# crud.operations.Get =} // Get query -export type GetQuery = {= queryType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Get.isDefined =} type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type GetOutput = _WaspEntity | null @@ -109,7 +85,6 @@ const _waspGetQuery: GetQuery = ((args, context) => { {=# overrides.Get.isDefined =} const _waspGetQuery = {= overrides.Get.importIdentifier =} {=/ overrides.Get.isDefined =} -export type GetQueryResolved = typeof _waspGetQuery export async function getFn(args, context) { return (_waspGetQuery as any)(args, { @@ -121,7 +96,6 @@ export async function getFn(args, context) { {=# crud.operations.Create =} // Create action -export type CreateAction= {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Create.isDefined =} type CreateInput = Prisma.{= crud.entityUpper =}CreateInput type CreateOutput = _WaspEntity @@ -136,8 +110,6 @@ const _waspCreateAction: CreateAction = ((args, conte const _waspCreateAction = {= overrides.Create.importIdentifier =} {=/ overrides.Create.isDefined =} -export type CreateActionResolved = typeof _waspCreateAction - export async function createFn(args, context) { return (_waspCreateAction as any)(args, { ...context, @@ -148,7 +120,6 @@ export async function createFn(args, context) { {=# crud.operations.Update =} // Update action -export type UpdateAction = {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Update.isDefined =} type UpdateInput = Prisma.{= crud.entityUpper =}UpdateInput & Prisma.{= crud.entityUpper =}WhereUniqueInput type UpdateOutput = _WaspEntity @@ -167,8 +138,6 @@ const _waspUpdateAction: UpdateAction = ((args, conte const _waspUpdateAction = {= overrides.Update.importIdentifier =} {=/ overrides.Update.isDefined =} -export type UpdateActionResolved = typeof _waspUpdateAction - export async function updateFn(args, context) { return (_waspUpdateAction as any)(args, { ...context, @@ -179,7 +148,6 @@ export async function updateFn(args, context) { {=# crud.operations.Delete =} // Delete action -export type DeleteAction = {= actionType =}<[_WaspEntityTagged], Input, Output> {=^ overrides.Delete.isDefined =} type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput type DeleteOutput = _WaspEntity @@ -195,8 +163,6 @@ const _waspDeleteAction: DeleteAction = ((args, conte const _waspDeleteAction = {= overrides.Delete.importIdentifier =} {=/ overrides.Delete.isDefined =} -export type DeleteActionResolved = typeof _waspDeleteAction - export async function deleteFn(args, context) { return (_waspDeleteAction as any)(args, { ...context, diff --git a/waspc/data/Generator/templates/server/src/dbClient.ts b/waspc/data/Generator/templates/server/src/dbClient.ts deleted file mode 100644 index e6319ff696..0000000000 --- a/waspc/data/Generator/templates/server/src/dbClient.ts +++ /dev/null @@ -1,13 +0,0 @@ -{{={= =}=}} -import Prisma from '@prisma/client' - - -const createDbClient = () => { - const prismaClient = new Prisma.PrismaClient() - - return prismaClient -} - -const dbClient = createDbClient() - -export default dbClient diff --git a/waspc/data/Generator/templates/server/src/dbSeed.ts b/waspc/data/Generator/templates/server/src/dbSeed.ts index a4d89ddd6b..204480f456 100644 --- a/waspc/data/Generator/templates/server/src/dbSeed.ts +++ b/waspc/data/Generator/templates/server/src/dbSeed.ts @@ -6,8 +6,8 @@ // TODO: Consider in the future moving it into a a separate project (maybe db/ ?), while still // maintaining access to logic from the server/ . -import prismaClient from './dbClient.js' -import type { DbSeedFn } from './dbSeed/types.js' +import prismaClient from 'wasp/server/dbClient' +import type { DbSeedFn } from 'wasp/dbSeed/types' {=# dbSeeds =} {=& importStatement =} diff --git a/waspc/data/Generator/templates/server/src/jobs/_job.ts b/waspc/data/Generator/templates/server/src/jobs/_job.ts index ef619f17d8..6e6e1636d6 100644 --- a/waspc/data/Generator/templates/server/src/jobs/_job.ts +++ b/waspc/data/Generator/templates/server/src/jobs/_job.ts @@ -1,21 +1,12 @@ {{={= =}=}} -import prisma from '../dbClient.js' -import type { JSONValue, JSONObject } from '../_types/serialization.js' -import { createJob, type JobFn } from './{= jobExecutorRelativePath =}' +import { createJob } from './{= jobExecutorRelativePath =}' +{=& jobEntitiesImportStatement =} {=& jobPerformFnImportStatement =} -const entities = { - {=# entities =} - {= name =}: prisma.{= prismaIdentifier =}, - {=/ entities =} -}; - -export type {= typeName =} = JobFn - export const {= jobName =} = createJob({ jobName: "{= jobName =}", jobFn: {= jobPerformFnName =}, defaultJobOptions: {=& jobPerformOptions =}, jobSchedule: {=& jobSchedule =}, - entities, + {= jobEntitiesIdentifier =}, }) diff --git a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts index e7600991ed..b0c1622f66 100644 --- a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts +++ b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBoss.ts @@ -1,5 +1,5 @@ import PgBoss from 'pg-boss' -import config from '../../../config.js' +import config from 'wasp/server/config' const boss = createPgBoss() diff --git a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts index 5e50f97e73..34b36ae470 100644 --- a/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts +++ b/waspc/data/Generator/templates/server/src/jobs/core/pgBoss/pgBossJob.ts @@ -1,8 +1,9 @@ import PgBoss from 'pg-boss' import { pgBossStarted } from './pgBoss.js' import { Job, SubmittedJob } from '../job.js' -import type { JSONValue, JSONObject } from '../../../_types/serialization.js' -import { PrismaDelegate } from '../../../_types/index.js' +import type { JSONValue, JSONObject } from 'wasp/server/_types/serialization' +import { PrismaDelegate } from 'wasp/server/_types' +import type { JobFn } from 'wasp/jobs/pgBoss/types' export const PG_BOSS_EXECUTOR_NAME = Symbol('PgBoss') @@ -77,12 +78,6 @@ export function createJob< return new PgBossJob(jobName, defaultJobOptions) } -export type JobFn< - Input extends JSONObject, - Output extends JSONValue | void, - Entities extends Partial -> = (data: Input, context: { entities: Entities }) => Promise - /** * This is an interface repesenting a job that can be submitted to pg-boss. * It is not yet submitted until the caller invokes `submit()` on an instance. diff --git a/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts b/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts index cf274f4d8b..7f576b1353 100644 --- a/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts +++ b/waspc/data/Generator/templates/server/src/middleware/globalMiddleware.ts @@ -5,7 +5,9 @@ import logger from 'morgan' import cors from 'cors' import helmet from 'helmet' -import config from '../config.js' +import config from 'wasp/server/config' +import type { MiddlewareConfig, MiddlewareConfigFn } from 'wasp/server/middleware' +export type { MiddlewareConfig, MiddlewareConfigFn } from 'wasp/server/middleware' {=# globalMiddlewareConfigFn.isDefined =} {=& globalMiddlewareConfigFn.importStatement =} @@ -14,10 +16,6 @@ import config from '../config.js' const {=& globalMiddlewareConfigFn.importAlias =} = (mc: MiddlewareConfig) => mc {=/ globalMiddlewareConfigFn.isDefined =} -export type MiddlewareConfig = Map - -export type MiddlewareConfigFn = (middlewareConfig: MiddlewareConfig) => MiddlewareConfig - // This is the set of middleware Wasp supplies by default. // NOTE: Remember to update the docs of these change. const defaultGlobalMiddlewareConfig: MiddlewareConfig = new Map([ diff --git a/waspc/data/Generator/templates/server/src/middleware/operations.ts b/waspc/data/Generator/templates/server/src/middleware/operations.ts index 677ac113c8..856256c79a 100644 --- a/waspc/data/Generator/templates/server/src/middleware/operations.ts +++ b/waspc/data/Generator/templates/server/src/middleware/operations.ts @@ -3,7 +3,7 @@ import { deserialize as superjsonDeserialize, serialize as superjsonSerialize, } from 'superjson' -import { handleRejection } from '../utils.js' +import { handleRejection } from 'wasp/server/utils' export function createOperation (handlerFn) { return handleRejection(async (req, res) => { diff --git a/waspc/data/Generator/templates/server/src/queries/_query.ts b/waspc/data/Generator/templates/server/src/queries/_query.ts index 2d6c17d482..24118fd432 100644 --- a/waspc/data/Generator/templates/server/src/queries/_query.ts +++ b/waspc/data/Generator/templates/server/src/queries/_query.ts @@ -1,5 +1,5 @@ {{={= =}=}} -import prisma from '../dbClient.js' +import prisma from 'wasp/server/dbClient' {=& jsFn.importStatement =} diff --git a/waspc/data/Generator/templates/server/src/queries/types.ts b/waspc/data/Generator/templates/server/src/queries/types.ts index 405b05d9c8..33055e7c74 100644 --- a/waspc/data/Generator/templates/server/src/queries/types.ts +++ b/waspc/data/Generator/templates/server/src/queries/types.ts @@ -13,7 +13,7 @@ import { type AuthenticatedQuery, {=/ shouldImportAuthenticatedOperation =} type Payload, -} from '../_types' +} from 'wasp/server/_types' {=# operations =} export type {= typeName =} = diff --git a/waspc/data/Generator/templates/server/src/routes/apis/index.ts b/waspc/data/Generator/templates/server/src/routes/apis/index.ts index 5c928f81dc..3e87cd3643 100644 --- a/waspc/data/Generator/templates/server/src/routes/apis/index.ts +++ b/waspc/data/Generator/templates/server/src/routes/apis/index.ts @@ -1,11 +1,11 @@ {{={= =}=}} import express from 'express' -import prisma from '../../dbClient.js' -import { handleRejection } from '../../utils.js' +import prisma from 'wasp/server/dbClient' +import { handleRejection } from 'wasp/server/utils' import { MiddlewareConfigFn, globalMiddlewareConfigForExpress } from '../../middleware/index.js' {=# isAuthEnabled =} -import auth from '../../core/auth.js' -import { type SanitizedUser } from '../../_types' +import auth from 'wasp/core/auth' +import { type SanitizedUser } from 'wasp/server/_types' {=/ isAuthEnabled =} {=# apiNamespaces =} diff --git a/waspc/data/Generator/templates/server/src/routes/auth/index.js b/waspc/data/Generator/templates/server/src/routes/auth/index.js index b64c407651..da3bc79016 100644 --- a/waspc/data/Generator/templates/server/src/routes/auth/index.js +++ b/waspc/data/Generator/templates/server/src/routes/auth/index.js @@ -1,7 +1,7 @@ {{={= =}=}} import express from 'express' -import auth from '../../core/auth.js' +import auth from 'wasp/core/auth' import me from './me.js' import logout from './logout.js' diff --git a/waspc/data/Generator/templates/server/src/routes/auth/logout.ts b/waspc/data/Generator/templates/server/src/routes/auth/logout.ts index 10a24a7bc5..7966e56c03 100644 --- a/waspc/data/Generator/templates/server/src/routes/auth/logout.ts +++ b/waspc/data/Generator/templates/server/src/routes/auth/logout.ts @@ -1,6 +1,6 @@ -import { handleRejection } from '../../utils.js' -import { throwInvalidCredentialsError } from '../../auth/utils.js' -import { invalidateSession } from '../../auth/session.js' +import { handleRejection } from 'wasp/server/utils' +import { throwInvalidCredentialsError } from 'wasp/auth/utils' +import { invalidateSession } from 'wasp/auth/session' export default handleRejection(async (req, res) => { if (req.sessionId) { diff --git a/waspc/data/Generator/templates/server/src/routes/auth/me.js b/waspc/data/Generator/templates/server/src/routes/auth/me.js index e84aff221a..8c182835d1 100644 --- a/waspc/data/Generator/templates/server/src/routes/auth/me.js +++ b/waspc/data/Generator/templates/server/src/routes/auth/me.js @@ -1,6 +1,6 @@ import { serialize as superjsonSerialize } from 'superjson' -import { handleRejection } from '../../utils.js' -import { throwInvalidCredentialsError } from '../../auth/utils.js' +import { handleRejection } from 'wasp/server/utils' +import { throwInvalidCredentialsError } from 'wasp/auth/utils' export default handleRejection(async (req, res) => { if (req.user) { diff --git a/waspc/data/Generator/templates/server/src/routes/crud/_crud.ts b/waspc/data/Generator/templates/server/src/routes/crud/_crud.ts index 72d860e7e3..a0cdf9efcd 100644 --- a/waspc/data/Generator/templates/server/src/routes/crud/_crud.ts +++ b/waspc/data/Generator/templates/server/src/routes/crud/_crud.ts @@ -3,7 +3,7 @@ import express from 'express' import * as crud from '../../crud/{= crud.name =}.js' import { createAction, createQuery } from '../../middleware/operations.js' {=# isAuthEnabled =} -import auth from '../../core/auth.js' +import auth from 'wasp/core/auth' {=/ isAuthEnabled =} const _waspRouter = express.Router() diff --git a/waspc/data/Generator/templates/server/src/routes/operations/index.js b/waspc/data/Generator/templates/server/src/routes/operations/index.js index 747e43af86..394cb78a7f 100644 --- a/waspc/data/Generator/templates/server/src/routes/operations/index.js +++ b/waspc/data/Generator/templates/server/src/routes/operations/index.js @@ -2,7 +2,7 @@ import express from 'express' {=# isAuthEnabled =} -import auth from '../../core/auth.js' +import auth from 'wasp/core/auth' {=/ isAuthEnabled =} {=# operationRoutes =} diff --git a/waspc/data/Generator/templates/server/src/server.ts b/waspc/data/Generator/templates/server/src/server.ts index cbc5738ea9..e4b16898b6 100644 --- a/waspc/data/Generator/templates/server/src/server.ts +++ b/waspc/data/Generator/templates/server/src/server.ts @@ -2,11 +2,11 @@ import http from 'http' import app from './app.js' -import config from './config.js' +import config from 'wasp/server/config' {=# setupFn.isDefined =} {=& setupFn.importStatement =} -import { ServerSetupFn, ServerSetupFnContext } from './types' +import { ServerSetupFn, ServerSetupFnContext } from 'wasp/server/types' {=/ setupFn.isDefined =} {=# isPgBossJobExecutorUsed =} diff --git a/waspc/data/Generator/templates/server/src/utils.ts b/waspc/data/Generator/templates/server/src/utils.ts index 9b5d71d153..6ca262decd 100644 --- a/waspc/data/Generator/templates/server/src/utils.ts +++ b/waspc/data/Generator/templates/server/src/utils.ts @@ -7,7 +7,7 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' {=# isAuthEnabled =} -import { type SanitizedUser } from './_types/index.js' +import { type SanitizedUser } from 'wasp/server/_types/index.js' {=/ isAuthEnabled =} type RequestWithExtraFields = Request & { diff --git a/waspc/data/Generator/templates/server/src/webSocket/initialization.ts b/waspc/data/Generator/templates/server/src/webSocket/initialization.ts index bbc83774de..28467910c1 100644 --- a/waspc/data/Generator/templates/server/src/webSocket/initialization.ts +++ b/waspc/data/Generator/templates/server/src/webSocket/initialization.ts @@ -2,13 +2,13 @@ import http from 'http' import { Server, Socket } from 'socket.io' -import type { ServerType } from './index.js' +import type { ServerType } from 'wasp/server/webSocket' -import config from '../config.js' -import prisma from '../dbClient.js' +import config from 'wasp/server/config' +import prisma from 'wasp/server/dbClient' {=# isAuthEnabled =} -import { getSessionAndUserFromSessionId } from '../auth/session.js' +import { getSessionAndUserFromSessionId } from 'wasp/auth/session' {=/ isAuthEnabled =} {=& userWebSocketFn.importStatement =} diff --git a/waspc/data/Generator/templates/universal/types.ts b/waspc/data/Generator/templates/universal/types.ts deleted file mode 100644 index 8cadbd740d..0000000000 --- a/waspc/data/Generator/templates/universal/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -// This is a helper type used exclusively for DX purposes. It's a No-op for the -// compiler, but expands the type's representatoin in IDEs (i.e., inlines all -// type constructors) to make it more readable for the user. -// -// It expands this SO answer to functions: https://stackoverflow.com/a/57683652 -export type Expand = T extends (...args: infer A) => infer R - ? (...args: A) => R - : T extends infer O - ? { [K in keyof O]: O[K] } - : never - -// TypeScript's native Awaited type exhibits strange behavior in VS Code (see -// https://github.com/wasp-lang/wasp/pull/1090#discussion_r1159687537 for -// details). Until it's fixed, we're using our own type for this. -// -// TODO: investigate further. This most likely has something to do with an -// unsatisfied 'extends' constraints. A mismatch is probably happening with -// function parameter types and/or return types (check '_ReturnType' below for -// more). -export type _Awaited = T extends Promise - ? _Awaited - : T - -// TypeScript's native ReturnType does not work for functions of type '(...args: -// never[]) => unknown' (and that's what operations currently use). -// -// TODO: investigate how to properly specify the 'extends' constraint for function -// type (i.e., any vs never and unknown) and stick with that. Take DX into -// consideration. -export type _ReturnType unknown> = - T extends (...args: never[]) => infer R ? R : never diff --git a/waspc/data/Generator/templates/universal/url.ts b/waspc/data/Generator/templates/universal/url.ts deleted file mode 100644 index d21c06c65c..0000000000 --- a/waspc/data/Generator/templates/universal/url.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function stripTrailingSlash(url?: string): string | undefined { - return url?.replace(/\/$/, ""); -} diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/session.ts b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/session.ts index 1f27623697..ed9154120b 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/session.ts +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/server/src/auth/session.ts @@ -1,7 +1,7 @@ import { Request as ExpressRequest } from "express"; import { type User } from "../entities/index.js" -import { type SanitizedUser } from '../_types/index.js' +import { type SanitizedUser } from '../server/_types/index.js' import { auth } from "./lucia.js"; import type { Session } from "lucia"; @@ -10,7 +10,7 @@ import { deserializeAndSanitizeProviderData, } from "./utils.js"; -import prisma from '../dbClient.js'; +import prisma from '../server/dbClient.js' // Creates a new session for the `authId` in the database export async function createSession(authId: string): Promise { diff --git a/waspc/examples/todo-typescript/.gitignore b/waspc/examples/todo-typescript/.gitignore index ab7cafccec..6577e45061 100644 --- a/waspc/examples/todo-typescript/.gitignore +++ b/waspc/examples/todo-typescript/.gitignore @@ -1,4 +1,4 @@ /.wasp/ /.env.server /.env.client -/node_modules/ +node_modules/ diff --git a/waspc/examples/todo-typescript/.prettierrc b/waspc/examples/todo-typescript/.prettierrc new file mode 100644 index 0000000000..00fbdb1856 --- /dev/null +++ b/waspc/examples/todo-typescript/.prettierrc @@ -0,0 +1,4 @@ +{ + "semi": false, + "singleQuote": true +} \ No newline at end of file diff --git a/waspc/examples/todo-typescript/cleanstart b/waspc/examples/todo-typescript/cleanstart index 3c7987deb5..e32b3aecac 100755 --- a/waspc/examples/todo-typescript/cleanstart +++ b/waspc/examples/todo-typescript/cleanstart @@ -1 +1,3 @@ -rm -r .wasp node_modules package-lock.json migrations; cabal run wasp-cli db migrate-dev -- --name init && ./fix; cabal run wasp-cli start +#!/bin/bash +cabal run wasp-cli clean; cabal run wasp-cli db migrate-dev && ./fix; cabal run wasp-cli start + diff --git a/waspc/examples/todo-typescript/main.wasp b/waspc/examples/todo-typescript/main.wasp index a2dd70db1c..4acb2bafa4 100644 --- a/waspc/examples/todo-typescript/main.wasp +++ b/waspc/examples/todo-typescript/main.wasp @@ -7,12 +7,49 @@ app TodoTypescript { auth: { userEntity: User, methods: { - usernameAndPassword: {}, // this is a very naive implementation, use 'email' in production instead - //google: {}, //https://wasp-lang.dev/docs/integrations/google - //gitHub: {}, //https://wasp-lang.dev/docs/integrations/github - //email: {} //https://wasp-lang.dev/docs/guides/email-auth + // usernameAndPassword: { + // userSignupFields: import { userSignupFields } from "@src/user/userSignupFields.js" + // }, + // google: { + // userSignupFields: import { googleUserSignupFields } from "@src/user/userSignupFields.js" + // }, + // gitHub: { + // userSignupFields: import { githubUserSignupFields } from "@src/user/userSignupFields.js" + // }, + email: { + userSignupFields: import { userSignupFields } from "@src/user/userSignupFields.js", + fromField: { + email: "waspy@app.com" + }, + emailVerification: { + clientRoute: EmailVerificationRoute, + }, + passwordReset: { + clientRoute: PasswordResetRoute, + } + } // https://wasp-lang.dev/docs/guides/email-auth }, onAuthFailedRedirectTo: "/login", + }, + emailSender: { + provider: Dummy + }, + db: { + seeds: [ + import { seedMyDb } from "@src/db/seeds.js" + ], + system: PostgreSQL + }, + webSocket: { + fn: import { webSocketFn } from "@src/websocket/index.js", + autoConnect: true, + }, + server: { + setupFn: import { serverSetup } from "@src/setup/serverSetup.js", + middlewareConfigFn: import { serverMiddlewareFn } from "@src/setup/serverSetup.js" + }, + client: { + setupFn: import setup from "@src/setup/clientSetup.js" } } @@ -21,6 +58,7 @@ app TodoTypescript { // Then run `wasp db studio` to open Prisma Studio and view your db models entity User {=psl id Int @id @default(autoincrement()) + address String tasks Task[] psl=} @@ -32,6 +70,19 @@ entity Task {=psl userId Int psl=} +crud Tasks { + entity: Task, + operations: { + getAll: { + overrideFn: import { getAllQuery } from "@src/task/crud.js", + }, + create: {}, + update: {}, + get: {}, + delete: {} + } +} + route RootRoute { path: "/", to: MainPage } page MainPage { authRequired: true, @@ -41,12 +92,38 @@ page MainPage { route LoginRoute { path: "/login", to: LoginPage } page LoginPage { - component: import { LoginPage } from "@src/user/LoginPage.tsx" + component: import { LoginPage } from "@src/user/auth.tsx" } route SignupRoute { path: "/signup", to: SignupPage } page SignupPage { - component: import { SignupPage } from "@src/user/SignupPage.tsx" + component: import { SignupPage } from "@src/user/auth.tsx" +} + +route EmailVerificationRoute { path: "/email-verify", to: EmailVerification } +page EmailVerification { + component: import { EmailVerificationPage } from "@src/user/auth.tsx" +} + +route ChatRoute { path: "/chat", to: Chat } +page Chat { + component: import { ChatPage } from "@src/ChatPage.tsx" +} + +// route EmailVerificationRoute { path: "/email-verify", to: EmailVerification } +// page EmailVerification { +// component: import { EmailVerificationPage } from "@src/user/auth.tsx" +// } + +route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordReset } +page RequestPasswordReset { + component: import { RequestPasswordResetPage } from "@src/user/auth.tsx" +} + + +route PasswordResetRoute { path: "/password-reset", to: PasswordReset } +page PasswordReset { + component: import { PasswordResetPage } from "@src/user/auth.tsx" } query getTasks { @@ -73,3 +150,29 @@ action deleteTasks { fn: import { deleteTasks } from "@src/task/actions.js", entities: [Task], } + +job PrintTimeAndNumberOfTasks { + executor: PgBoss, + perform: { + fn: import { printTimeAndNumberOfTasks } from "@src/jobs/print.js" + }, + entities: [Task], + schedule: { + cron: "* * * * *" + } +} + +api fooBar { + fn: import { fooBar } from "@src/api.js", + httpRoute: (GET, "/foo/bar") +} + +apiNamespace fooBarNamespace { + middlewareConfigFn: import { fooBarNamespace } from "@src/setup/serverSetup.js", + path: "/foo/bar" +} + +action customEmailSending { + fn: import { send } from "@src/user/customEmailSending.js", + entities: [User] +} \ No newline at end of file diff --git a/waspc/examples/todo-typescript/migrations/20240119151915_init/migration.sql b/waspc/examples/todo-typescript/migrations/20240119151915_init/migration.sql deleted file mode 100644 index 919941fb15..0000000000 --- a/waspc/examples/todo-typescript/migrations/20240119151915_init/migration.sql +++ /dev/null @@ -1,48 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT -); - --- CreateTable -CREATE TABLE "Task" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "description" TEXT NOT NULL, - "isDone" BOOLEAN NOT NULL DEFAULT false, - "userId" INTEGER NOT NULL, - CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "Auth" ( - "id" TEXT NOT NULL PRIMARY KEY, - "userId" INTEGER, - CONSTRAINT "Auth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "AuthIdentity" ( - "providerName" TEXT NOT NULL, - "providerUserId" TEXT NOT NULL, - "providerData" TEXT NOT NULL DEFAULT '{}', - "authId" TEXT NOT NULL, - - PRIMARY KEY ("providerName", "providerUserId"), - CONSTRAINT "AuthIdentity_authId_fkey" FOREIGN KEY ("authId") REFERENCES "Auth" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "Session" ( - "id" TEXT NOT NULL PRIMARY KEY, - "expiresAt" DATETIME NOT NULL, - "userId" TEXT NOT NULL, - CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Auth" ("id") ON DELETE CASCADE ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "Auth_userId_key" ON "Auth"("userId"); - --- CreateIndex -CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id"); - --- CreateIndex -CREATE INDEX "Session_userId_idx" ON "Session"("userId"); diff --git a/waspc/examples/todo-typescript/migrations/20240123113845_initial/migration.sql b/waspc/examples/todo-typescript/migrations/20240123113845_initial/migration.sql new file mode 100644 index 0000000000..45dabb98a4 --- /dev/null +++ b/waspc/examples/todo-typescript/migrations/20240123113845_initial/migration.sql @@ -0,0 +1,65 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "address" TEXT NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Task" ( + "id" SERIAL NOT NULL, + "description" TEXT NOT NULL, + "isDone" BOOLEAN NOT NULL DEFAULT false, + "userId" INTEGER NOT NULL, + + CONSTRAINT "Task_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Auth" ( + "id" TEXT NOT NULL, + "userId" INTEGER, + + CONSTRAINT "Auth_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AuthIdentity" ( + "providerName" TEXT NOT NULL, + "providerUserId" TEXT NOT NULL, + "providerData" TEXT NOT NULL DEFAULT '{}', + "authId" TEXT NOT NULL, + + CONSTRAINT "AuthIdentity_pkey" PRIMARY KEY ("providerName","providerUserId") +); + +-- CreateTable +CREATE TABLE "Session" ( + "id" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "userId" TEXT NOT NULL, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Auth_userId_key" ON "Auth"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id"); + +-- CreateIndex +CREATE INDEX "Session_userId_idx" ON "Session"("userId"); + +-- AddForeignKey +ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Auth" ADD CONSTRAINT "Auth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AuthIdentity" ADD CONSTRAINT "AuthIdentity_authId_fkey" FOREIGN KEY ("authId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "Auth"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/waspc/examples/todo-typescript/migrations/migration_lock.toml b/waspc/examples/todo-typescript/migrations/migration_lock.toml index e5e5c4705a..fbffa92c2b 100644 --- a/waspc/examples/todo-typescript/migrations/migration_lock.toml +++ b/waspc/examples/todo-typescript/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) -provider = "sqlite" \ No newline at end of file +provider = "postgresql" \ No newline at end of file diff --git a/waspc/examples/todo-typescript/package-lock.json b/waspc/examples/todo-typescript/package-lock.json new file mode 100644 index 0000000000..9f31de3c23 --- /dev/null +++ b/waspc/examples/todo-typescript/package-lock.json @@ -0,0 +1,6234 @@ +{ + "name": "prototype", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prototype", + "dependencies": { + "cors": "^2.8.5", + "react": "^18.2.0", + "wasp": "file:.wasp/out/sdk/wasp" + }, + "devDependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.17.13", + "@types/react": "^18.0.37", + "prisma": "4.16.2", + "typescript": "^5.1.0", + "vite": "^4.3.9" + } + }, + ".wasp/out/sdk/wasp": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@lucia-auth/adapter-prisma": "^4.0.0-beta.9", + "@prisma/client": "4.16.2", + "@socket.io/component-emitter": "^4.0.0", + "@stitches/react": "^1.2.8", + "@tanstack/react-query": "^4.29.0", + "@testing-library/jest-dom": "^6.3.0", + "@testing-library/react": "^14.1.2", + "@types/express-serve-static-core": "^4.17.13", + "@vitest/ui": "^1.2.1", + "axios": "^1.4.0", + "express": "~4.18.1", + "jsdom": "^21.1.1", + "jsonwebtoken": "^8.5.1", + "lodash.merge": "^4.6.2", + "lucia": "^3.0.0-beta.14", + "mitt": "3.0.0", + "msw": "^1.1.0", + "prisma": "4.16.2", + "react": "^18.2.0", + "react-hook-form": "^7.45.4", + "react-router-dom": "^5.3.3", + "secure-password": "^4.0.0", + "socket.io": "^4.6.1", + "socket.io-client": "^4.6.1", + "superjson": "^1.12.2", + "vitest": "^1.2.1" + }, + "devDependencies": { + "@tsconfig/node18": "latest" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==" + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.8", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@lucia-auth/adapter-prisma": { + "version": "4.0.0-beta.9", + "license": "MIT", + "peerDependencies": { + "@prisma/client": "^4.2.0 || ^5.0.0", + "lucia": "3.0.0-beta.14" + } + }, + "node_modules/@mswjs/cookies": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.2.tgz", + "integrity": "sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==", + "dependencies": { + "@types/set-cookie-parser": "^2.4.0", + "set-cookie-parser": "^2.4.6" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.17.10.tgz", + "integrity": "sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==", + "dependencies": { + "@open-draft/until": "^1.0.3", + "@types/debug": "^4.1.7", + "@xmldom/xmldom": "^0.8.3", + "debug": "^4.3.3", + "headers-polyfill": "3.2.5", + "outvariant": "^1.2.1", + "strict-event-emitter": "^0.2.4", + "web-encoding": "^1.1.5" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@mswjs/interceptors/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@mswjs/interceptors/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/@mswjs/interceptors/node_modules/strict-event-emitter": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz", + "integrity": "sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==", + "dependencies": { + "events": "^3.3.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==" + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==" + }, + "node_modules/@prisma/client": { + "version": "4.16.2", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines-version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "4.16.2", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines-version": { + "version": "4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81", + "license": "Apache-2.0" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", + "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", + "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", + "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", + "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", + "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", + "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", + "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", + "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", + "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", + "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", + "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", + "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", + "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@socket.io/component-emitter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-4.0.0.tgz", + "integrity": "sha512-2WNKxlj1JdjMDc3REbalzBNL/3fPu1gqA87/aU3BmDJGDAy66zjCNHAwa5WuWU3IXJwwqN8s4c5j4XfneMEsqg==" + }, + "node_modules/@stitches/react": { + "version": "1.2.8", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "4.36.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.36.1", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "4.36.1", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.3.0.tgz", + "integrity": "sha512-hJVIrkFizEQxoWsGBlycTcQhrpoCH4DhXfrnHFFXgkx3Xdm15zycsq5Ep+vpw4W8S0NJa8cxDHcuJib+1tEbhg==", + "dependencies": { + "@adobe/css-tools": "^4.3.2", + "@babel/runtime": "^7.9.2", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + }, + "peerDependencies": { + "@jest/globals": ">= 28", + "@types/bun": "latest", + "@types/jest": ">= 28", + "jest": ">= 28", + "vitest": ">= 0.32" + }, + "peerDependenciesMeta": { + "@jest/globals": { + "optional": true + }, + "@types/bun": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "jest": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@testing-library/react": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz", + "integrity": "sha512-z4p7DVBTPjKM5qDZ0t5ZjzkpSNb+fZy1u6bzO7kk8oeGagpPCAtgh4cx1syrfp7a+QWkM021jGqjJaxJJnXAZg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node18": { + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.2.tgz", + "integrity": "sha512-d6McJeGsuoRlwWZmVIeE8CUA27lu6jLjvv1JzqmpsytOYYbVi1tHZEnwCNVOXnj4pyLvneZlFlpXUK+X9wBWyw==", + "dev": true + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-levenshtein": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz", + "integrity": "sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/node": { + "version": "20.10.8", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.11", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.2.47", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", + "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.7.tgz", + "integrity": "sha512-+ge/loa0oTozxip6zmhRIk8Z/boU51wl9Q6QdLZcokIGMzY5lFXYy/x7Htj2HTC6/KZP1hUbZ1ekx8DYXICvWg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.1.tgz", + "integrity": "sha512-/bqGXcHfyKgFWYwIgFr1QYDaR9e64pRKxgBNWNXPefPFRhgm+K3+a/dS0cUGEreWngets3dlr8w8SBRw2fCfFQ==", + "dependencies": { + "@vitest/spy": "1.2.1", + "@vitest/utils": "1.2.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.1.tgz", + "integrity": "sha512-zc2dP5LQpzNzbpaBt7OeYAvmIsRS1KpZQw4G3WM/yqSV1cQKNKwLGmnm79GyZZjMhQGlRcSFMImLjZaUQvNVZQ==", + "dependencies": { + "@vitest/utils": "1.2.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.1.tgz", + "integrity": "sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/snapshot/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@vitest/spy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.1.tgz", + "integrity": "sha512-vG3a/b7INKH7L49Lbp0IWrG6sw9j4waWAucwnksPB1r1FTJgV7nkBByd9ufzu6VWya/QTvQW4V9FShZbZIB2UQ==", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.2.1.tgz", + "integrity": "sha512-5kyEDpH18TB13Keutk5VScWG+LUDfPJOL2Yd1hqX+jv6+V74tp4ZYcmTgx//WDngiZA5PvX3qCHQ5KrhGzPbLg==", + "dependencies": { + "@vitest/utils": "1.2.1", + "fast-glob": "^3.3.2", + "fflate": "^0.8.1", + "flatted": "^3.2.9", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "sirv": "^2.0.4" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "^1.0.0" + } + }, + "node_modules/@vitest/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-bsH6WVZYe/J2v3+81M5LDU8kW76xWObKIURpPrOXm2pjBniBu2MERI/XP60GpS4PHU3jyK50LUutOwrx4CyHUg==", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "optional": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead" + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.6.5", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==" + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/esbuild": { + "version": "0.18.20", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/express": { + "version": "4.18.2", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", + "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==" + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" + }, + "node_modules/follow-redirects": { + "version": "1.15.4", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphql": { + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/headers-polyfill": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.2.5.tgz", + "integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==" + }, + "node_modules/history": { + "version": "4.10.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.2.tgz", + "integrity": "sha512-sCpFmK2jv+1sjff4u7fzft+pUh2KSUbUrEHYHyfSIbGTIcmnjyp83qg6qLwdJ/I3LpTXx33ACxeRL7Lsyc6lGQ==", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.2", + "acorn-globals": "^7.0.0", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.4", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" + }, + "node_modules/jsonwebtoken": { + "version": "8.5.1", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.1", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lucia": { + "version": "3.0.0-beta.14", + "license": "MIT", + "dependencies": { + "oslo": "^0.27.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mitt": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/msw": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/msw/-/msw-1.3.2.tgz", + "integrity": "sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==", + "hasInstallScript": true, + "dependencies": { + "@mswjs/cookies": "^0.2.2", + "@mswjs/interceptors": "^0.17.10", + "@open-draft/until": "^1.0.3", + "@types/cookie": "^0.4.1", + "@types/js-levenshtein": "^1.1.1", + "chalk": "^4.1.1", + "chokidar": "^3.4.2", + "cookie": "^0.4.2", + "graphql": "^16.8.1", + "headers-polyfill": "3.2.5", + "inquirer": "^8.2.0", + "is-node-process": "^1.2.0", + "js-levenshtein": "^1.1.6", + "node-fetch": "^2.6.7", + "outvariant": "^1.4.0", + "path-to-regexp": "^6.2.0", + "strict-event-emitter": "^0.4.3", + "type-fest": "^2.19.0", + "yargs": "^17.3.1" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.4.x <= 5.2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/msw/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/msw/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "node_modules/nanoassert": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oslo": { + "version": "0.27.1", + "license": "MIT", + "dependencies": { + "@node-rs/argon2": "^1.5.2", + "@node-rs/bcrypt": "^1.7.3" + } + }, + "node_modules/outvariant": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.2.tgz", + "integrity": "sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==" + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/prisma": { + "version": "4.16.2", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "4.16.2" + }, + "bin": { + "prisma": "build/index.js", + "prisma2": "build/index.js" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "18.2.0", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-hook-form": { + "version": "7.49.3", + "license": "MIT", + "engines": { + "node": ">=18", + "pnpm": "8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/react-router": { + "version": "5.3.4", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router/node_modules/path-to-regexp": { + "version": "1.8.0", + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secure-password": { + "version": "4.0.0", + "license": "ISC", + "dependencies": { + "nanoassert": "^1.0.0", + "sodium-native": "^3.1.1" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socket.io": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz", + "integrity": "sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz", + "integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/sodium-native": { + "version": "3.4.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superjson": { + "version": "1.13.3", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.6.0.tgz", + "integrity": "sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==" + }, + "node_modules/tinypool": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.2.tgz", + "integrity": "sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "4.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.1.tgz", + "integrity": "sha512-fNzHmQUSOY+y30naohBvSW7pPn/xn3Ib/uqm+5wAJQJiqQsU0NBR78XdRJb04l4bOFKjpTWld0XAfkKlrDbySg==", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/vite-node/node_modules/rollup": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", + "fsevents": "~2.3.2" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.1.tgz", + "integrity": "sha512-TRph8N8rnSDa5M2wKWJCMnztCZS9cDcgVTQ6tsTFTG/odHJ4l5yNVqvbeDJYJRZ6is3uxaEpFs8LL6QM+YFSdA==", + "dependencies": { + "@vitest/expect": "1.2.1", + "@vitest/runner": "1.2.1", + "@vitest/snapshot": "1.2.1", + "@vitest/spy": "1.2.1", + "@vitest/utils": "1.2.1", + "acorn-walk": "^8.3.2", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^1.3.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.1", + "vite": "^5.0.0", + "vite-node": "1.2.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "^1.0.0", + "@vitest/ui": "^1.0.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/vitest/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/vitest/node_modules/rollup": { + "version": "4.9.6", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", + "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.9.6", + "@rollup/rollup-android-arm64": "4.9.6", + "@rollup/rollup-darwin-arm64": "4.9.6", + "@rollup/rollup-darwin-x64": "4.9.6", + "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", + "@rollup/rollup-linux-arm64-gnu": "4.9.6", + "@rollup/rollup-linux-arm64-musl": "4.9.6", + "@rollup/rollup-linux-riscv64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-gnu": "4.9.6", + "@rollup/rollup-linux-x64-musl": "4.9.6", + "@rollup/rollup-win32-arm64-msvc": "4.9.6", + "@rollup/rollup-win32-ia32-msvc": "4.9.6", + "@rollup/rollup-win32-x64-msvc": "4.9.6", + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.32", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/wasp": { + "resolved": ".wasp/out/sdk/wasp", + "link": true + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/waspc/examples/todo-typescript/package.json b/waspc/examples/todo-typescript/package.json index 7e6222db84..bad70f6765 100644 --- a/waspc/examples/todo-typescript/package.json +++ b/waspc/examples/todo-typescript/package.json @@ -1,11 +1,16 @@ { "name": "prototype", "dependencies": { - "wasp": "file:.wasp/out/sdk/wasp", - "react": "^18.2.0" + "cors": "^2.8.5", + "react": "^18.2.0", + "wasp": "file:.wasp/out/sdk/wasp" }, "devDependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.17.13", "@types/react": "^18.0.37", - "prisma": "4.16.2" + "prisma": "4.16.2", + "typescript": "^5.1.0", + "vite": "^4.3.9" } } diff --git a/waspc/examples/todo-typescript/src/ChatPage.tsx b/waspc/examples/todo-typescript/src/ChatPage.tsx new file mode 100644 index 0000000000..c1d424e2c1 --- /dev/null +++ b/waspc/examples/todo-typescript/src/ChatPage.tsx @@ -0,0 +1,67 @@ +import React, { useEffect, useRef, useState } from 'react' +import api from 'wasp/api' +import { + useSocket, + useSocketListener, + ServerToClientPayload, +} from 'wasp/webSocket' + +async function fetchCustomRoute() { + const res = await api.get('/foo/bar') + console.log(res.data) +} + +export const ChatPage = () => { + const [messages, setMessages] = useState< + ServerToClientPayload<'chatMessage'>[] + >([]) + const { socket, isConnected } = useSocket() + const inputRef = useRef(null) + + useEffect(() => { + fetchCustomRoute() + }, []) + + useSocketListener('chatMessage', (msg) => + setMessages((priorMessages) => [msg, ...priorMessages]) + ) + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() + + if (inputRef.current !== null) { + socket.emit('chatMessage', inputRef.current.value) + inputRef.current.value = '' + } + } + + const messageList = messages.map((msg) => ( +
  • + {msg.username}: {msg.text} +
  • + )) + const connectionIcon = isConnected ? '🟢' : '🔴' + + + return ( + <> +

    Chat

    +
    +
    +
    +
    {connectionIcon}
    +
    + +
    +
    + +
    +
    +
    +
      {messageList}
    +
    + + ) +} diff --git a/waspc/examples/todo-typescript/src/Main.css b/waspc/examples/todo-typescript/src/Main.css index 15a61f2399..f9a79594cb 100644 --- a/waspc/examples/todo-typescript/src/Main.css +++ b/waspc/examples/todo-typescript/src/Main.css @@ -57,7 +57,7 @@ code { justify-content: center; width: 300px; margin-top: 1rem; - padding: 0 + padding: 0; } li { diff --git a/waspc/examples/todo-typescript/src/MainPage.tsx b/waspc/examples/todo-typescript/src/MainPage.tsx index 3d43e28deb..582eb2527d 100644 --- a/waspc/examples/todo-typescript/src/MainPage.tsx +++ b/waspc/examples/todo-typescript/src/MainPage.tsx @@ -3,31 +3,54 @@ import React, { useEffect, FormEventHandler, FormEvent } from 'react' import logout from 'wasp/auth/logout' import { useQuery, useAction } from 'wasp/rpc' // Wasp uses a thin wrapper around react-query import { getTasks } from 'wasp/rpc/queries' -import { createTask, updateTask, deleteTasks } from 'wasp/rpc/actions' +import { + createTask, + updateTask, + deleteTasks, + customEmailSending, +} from 'wasp/rpc/actions' import waspLogo from './waspLogo.png' import type { Task } from 'wasp/entities' import type { User } from 'wasp/auth/types' -import { getUsername } from 'wasp/auth/user' +import { getFirstProviderUserId } from 'wasp/auth/user' +import { Link } from 'react-router-dom' +import { Tasks } from 'wasp/crud/Tasks' +// import login from 'wasp/auth/login' +// import signup from 'wasp/auth/signup' +import useAuth from 'wasp/auth/useAuth' +import { Todo } from './Todo' export const MainPage = ({ user }: { user: User }) => { const { data: tasks, isLoading, error } = useQuery(getTasks) + const { data: userAgain } = useAuth() + + const { data: allTasks } = Tasks.getAll.useQuery() if (isLoading) return 'Loading...' if (error) return 'Error: ' + error + // console.log(login) + // console.log(signup) + const completed = tasks?.filter((task) => task.isDone).map((task) => task.id) return (
    wasp logo + + Wonna chat? {user && (

    - {getUsername(user)} + {getFirstProviderUserId(user)} {`'s tasks :)`}

    )} {tasks && } +

    All

    + {allTasks && }
    - - - ) -} - function TasksList({ tasks }: { tasks: Task[] }) { if (tasks.length === 0) return

    No tasks yet.

    return ( diff --git a/waspc/examples/todo-typescript/src/Todo.test.tsx b/waspc/examples/todo-typescript/src/Todo.test.tsx new file mode 100644 index 0000000000..47dee23f7b --- /dev/null +++ b/waspc/examples/todo-typescript/src/Todo.test.tsx @@ -0,0 +1,72 @@ +import { test, expect } from 'vitest' +import { screen } from '@testing-library/react' + +import { mockServer, renderInContext } from 'wasp/test' +import { getTasks } from 'wasp/rpc/queries' +import { Todo, areThereAnyTasks } from './Todo' +import { MainPage } from './MainPage' +import type { User } from 'wasp/auth/types' +import { getMe } from 'wasp/auth/useAuth' +import { Tasks } from 'wasp/crud/Tasks' + +const mockTasks = [ + { + id: 1, + description: 'test todo 1', + isDone: true, + userId: 1, + }, +] + +const mockAllTasks = [ + { + id: 2, + description: 'test todo 2', + isDone: true, + userId: 1, + }, +] + +test('handles unit testing', () => { + expect(areThereAnyTasks([])).toBe(false) +}) + +test('handles rendering in context', () => { + renderInContext() + + expect(screen.getByText('test todo 1')).toBeInTheDocument() +}) + +const { mockQuery } = mockServer() + +const mockUser = { + id: 12, + auth: { + id: '123', + userId: 12, + identities: [ + { + authId: '123', + providerName: 'email', + providerUserId: 'elon@tesla.com', + providerData: '', + }, + ], + }, + address: '', +} satisfies User + +test('handles mock data', async () => { + mockQuery(getTasks, mockTasks) + mockQuery(getMe, mockUser) + mockQuery(Tasks.getAll.query, mockAllTasks) + + renderInContext() + + await screen.findByText('test todo 1') + await screen.findByText('test todo 2') + + expect(screen.getAllByRole('checkbox')[0]).toBeChecked() + + screen.debug() +}) diff --git a/waspc/examples/todo-typescript/src/Todo.tsx b/waspc/examples/todo-typescript/src/Todo.tsx new file mode 100644 index 0000000000..f40dc10ca9 --- /dev/null +++ b/waspc/examples/todo-typescript/src/Todo.tsx @@ -0,0 +1,37 @@ +import { FormEventHandler } from "react" +import { Task } from "wasp/entities" +import { updateTask, deleteTasks } from 'wasp/rpc/actions' + +export function Todo({ id, isDone, description }: Task) { + const handleIsDoneChange: FormEventHandler = async ( + event + ) => { + try { + await updateTask({ + id, + isDone: event.currentTarget.checked, + }) + } catch (err: any) { + window.alert('Error while updating task ' + err?.message) + } + } + + return ( +
  • + + + {description} + + +
  • + ) +} + +export function areThereAnyTasks(tasks: Task[]) { + return tasks.length > 0 +} diff --git a/waspc/examples/todo-typescript/src/api.ts b/waspc/examples/todo-typescript/src/api.ts new file mode 100644 index 0000000000..5157cd7a59 --- /dev/null +++ b/waspc/examples/todo-typescript/src/api.ts @@ -0,0 +1,6 @@ +import type { FooBar } from "wasp/server/apis/types"; // This type is generated by Wasp based on the `api` declaration above. + +export const fooBar: FooBar = (req, res, context) => { + res.set("Access-Control-Allow-Origin", "*"); // Example of modifying headers to override Wasp default CORS middleware. + res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` }); +}; diff --git a/waspc/examples/todo-typescript/src/db/seeds.ts b/waspc/examples/todo-typescript/src/db/seeds.ts new file mode 100644 index 0000000000..854a53c313 --- /dev/null +++ b/waspc/examples/todo-typescript/src/db/seeds.ts @@ -0,0 +1,21 @@ +import { DbSeedFn } from "wasp/dbSeed/types"; + +export const seedMyDb: DbSeedFn = async (prisma) => { + const user = await prisma.user.findFirst({}); + + if (!user) { + console.log("no user found"); + return; + } + + await prisma.task.create({ + data: { + description: "My first task", + user: { + connect: { + id: user.id, + }, + }, + }, + }); +}; diff --git a/waspc/examples/todo-typescript/src/jobs/print.ts b/waspc/examples/todo-typescript/src/jobs/print.ts new file mode 100644 index 0000000000..a98941b9f3 --- /dev/null +++ b/waspc/examples/todo-typescript/src/jobs/print.ts @@ -0,0 +1,9 @@ +import { PrintTimeAndNumberOfTasks } from "wasp/jobs/PrintTimeAndNumberOfTasks"; + +export const printTimeAndNumberOfTasks: PrintTimeAndNumberOfTasks< + {}, + void +> = async (data, context) => { + const count = await context.entities.Task.count(); + console.log(Date.now(), "Number of tasks:", count); +}; diff --git a/waspc/examples/todo-typescript/src/setup/clientSetup.ts b/waspc/examples/todo-typescript/src/setup/clientSetup.ts new file mode 100644 index 0000000000..361f2892bc --- /dev/null +++ b/waspc/examples/todo-typescript/src/setup/clientSetup.ts @@ -0,0 +1,12 @@ +import { configureQueryClient } from 'wasp/rpc' + +export default async function mySetupFunction(): Promise { + console.log('Setting up client...') + configureQueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + }, + }, + }) +} diff --git a/waspc/examples/todo-typescript/src/setup/serverSetup.ts b/waspc/examples/todo-typescript/src/setup/serverSetup.ts new file mode 100644 index 0000000000..ddfc8ab036 --- /dev/null +++ b/waspc/examples/todo-typescript/src/setup/serverSetup.ts @@ -0,0 +1,33 @@ +import express from 'express' +import cors from 'cors' +import type { MiddlewareConfigFn } from 'wasp/server/middleware' +import config from 'wasp/server/config' +import type { Application, ServerSetupFn } from 'wasp/server/types' +import prismaClient from 'wasp/server/dbClient' + +export const serverMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => { + // Example of adding an extra domains to CORS. + middlewareConfig.set('cors', cors({ + origin: [config.frontendUrl, 'https://example1.com', 'https://example2.com'] + })) + return middlewareConfig +} + +export const fooBarNamespace: MiddlewareConfigFn = (middlewareConfig) => { + const customMiddleware: express.RequestHandler = (_req, _res, next) => { + console.log('fooBarNamespaceMiddlewareFn: custom middleware') + next() + } + + middlewareConfig.set('custom.middleware', customMiddleware) + + return middlewareConfig +} + +export const serverSetup: ServerSetupFn = async ({ app }: { app: Application}) => { + app.get('/customRoute', (_req, res) => { + res.send('I am a custom route') + }) + console.log("I am a server setup function!"); + console.log("Executed raw prisma client call: ", await prismaClient.$executeRaw``); +} diff --git a/waspc/examples/todo-typescript/src/task/actions.ts b/waspc/examples/todo-typescript/src/task/actions.ts index c03bfac62b..5c9811f356 100644 --- a/waspc/examples/todo-typescript/src/task/actions.ts +++ b/waspc/examples/todo-typescript/src/task/actions.ts @@ -5,6 +5,7 @@ import type { DeleteTasks, } from 'wasp/server/actions/types' import type { Task } from 'wasp/entities' +import { emailSender } from 'wasp/email' type CreateArgs = Pick @@ -16,6 +17,17 @@ export const createTask: CreateTask = async ( throw new HttpError(401) } + emailSender.send({ + to: 'test@test.com', + from: { + name: 'Test', + email: 'test@test.com' + }, + subject: 'Test email', + text: 'Thank you for using our app!', + html: '

    Thank you for using our app!

    ', + }) + return context.entities.Task.create({ data: { description, diff --git a/waspc/examples/todo-typescript/src/task/crud.ts b/waspc/examples/todo-typescript/src/task/crud.ts new file mode 100644 index 0000000000..042cdf7563 --- /dev/null +++ b/waspc/examples/todo-typescript/src/task/crud.ts @@ -0,0 +1,6 @@ +import { Task } from "wasp/entities"; +import { GetAllQuery } from "wasp/server/crud/Tasks"; + +export const getAllQuery = ((args, context) => { + return context.entities.Task.findMany({}); +}) satisfies GetAllQuery<{}, Task[]>; diff --git a/waspc/examples/todo-typescript/src/task/queries.ts b/waspc/examples/todo-typescript/src/task/queries.ts index ac49e0a7a7..e14f410f7b 100644 --- a/waspc/examples/todo-typescript/src/task/queries.ts +++ b/waspc/examples/todo-typescript/src/task/queries.ts @@ -1,6 +1,9 @@ import HttpError from 'wasp/core/HttpError' +import AuthError from 'wasp/core/AuthError' import type { GetTasks } from 'wasp/server/queries/types' import type { Task } from 'wasp/entities' +import { ensureValidEmail } from 'wasp/auth/validation' +import { createProviderId } from 'wasp/auth/utils' //Using TypeScript's new 'satisfies' keyword, it will infer the types of the arguments and return value export const getTasks = ((_args, context) => { @@ -8,6 +11,11 @@ export const getTasks = ((_args, context) => { throw new HttpError(401) } + console.log(AuthError); + + console.log(createProviderId); + ensureValidEmail({ email: "wasp@gmail.com"}); + return context.entities.Task.findMany({ where: { user: { id: context.user.id } }, orderBy: { id: 'asc' }, diff --git a/waspc/examples/todo-typescript/src/user/LoginPage.tsx b/waspc/examples/todo-typescript/src/user/LoginPage.tsx deleted file mode 100644 index fa198154ab..0000000000 --- a/waspc/examples/todo-typescript/src/user/LoginPage.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Link } from 'react-router-dom' -import { LoginForm } from 'wasp/auth/forms/Login' - -export function LoginPage() { - return ( -
    - {/** Wasp has built-in auth forms & flows, which you can customize or opt-out of, if you wish :) - * https://wasp-lang.dev/docs/guides/auth-ui - */} - -
    - - I don't have an account yet (go to signup). - -
    - ) -} diff --git a/waspc/examples/todo-typescript/src/user/SignupPage.tsx b/waspc/examples/todo-typescript/src/user/SignupPage.tsx deleted file mode 100644 index e3599729d6..0000000000 --- a/waspc/examples/todo-typescript/src/user/SignupPage.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Link } from 'react-router-dom' -import { SignupForm } from 'wasp/auth/forms/Signup' - -export function SignupPage() { - return ( -
    - {/** Wasp has built-in auth forms & flows, which you can customize or opt-out of, if you wish :) - * https://wasp-lang.dev/docs/guides/auth-ui - */} - -
    - - I already have an account (go to login). - -
    - ) -} diff --git a/waspc/examples/todo-typescript/src/user/auth.tsx b/waspc/examples/todo-typescript/src/user/auth.tsx new file mode 100644 index 0000000000..2d6b341aa0 --- /dev/null +++ b/waspc/examples/todo-typescript/src/user/auth.tsx @@ -0,0 +1,86 @@ +import { ResetPasswordForm } from "wasp/auth/forms/ResetPassword"; +import { LoginForm } from "wasp/auth/forms/Login"; +import { VerifyEmailForm } from "wasp/auth/forms/VerifyEmail"; +import { SignupForm } from "wasp/auth/forms/Signup"; +import { + FormError, + FormInput, + FormItemGroup, + FormLabel, +} from "wasp/auth/forms/internal/Form"; +// import { +// SignInButton as GitHubSignInButton, +// signInUrl as gitHubSignInUrl, +// } from "wasp/auth/helpers/GitHub"; +// import { +// SignInButton as GoogleSignInButton, +// signInUrl as googleSignInUrl, +// } from "wasp/auth/helpers/Google"; + +import { ForgotPasswordForm } from "wasp/auth/forms/ForgotPassword"; +import { Link, routes } from "wasp/router"; + +export function SignupPage() { + return ( +
    + {/** Wasp has built-in auth forms & flows, which you can customize or opt-out of, if you wish :) + * https://wasp-lang.dev/docs/guides/auth-ui + */} + { + return ( + + Address + + {errors.address && ( + {errors.address.message} + )} + + ); + }} + /> + {/*
    + Extra Github Button: with link {gitHubSignInUrl} +
    +
    + Extra Google Button: with link {googleSignInUrl} +
    */} +
    + + I already have an account (go to login). + + The link to the login page is {routes.LoginRoute.build()}. +
    + ); +} + +export function LoginPage() { + return ( +
    + {/** Wasp has built-in auth forms & flows, which you can customize or opt-out of, if you wish :) + * https://wasp-lang.dev/docs/guides/auth-ui + */} + +
    + + I don't have an account yet (go to signup). + +
    + ); +} + +export function RequestPasswordResetPage() { + return ; +} + +export function PasswordResetPage() { + return ; +} + +export function EmailVerificationPage() { + return ; +} diff --git a/waspc/examples/todo-typescript/src/user/customEmailSending.ts b/waspc/examples/todo-typescript/src/user/customEmailSending.ts new file mode 100644 index 0000000000..ab6a729409 --- /dev/null +++ b/waspc/examples/todo-typescript/src/user/customEmailSending.ts @@ -0,0 +1,41 @@ +import { + createPasswordResetLink, + createEmailVerificationLink, + sendEmailVerificationEmail, + sendPasswordResetEmail, +} from 'wasp/server/auth/email/utils' + +export async function send() { + const link = await createPasswordResetLink( + 'mihovil@ilakovac.com', + '/password-reset' + ) + const secondLink = await createEmailVerificationLink( + 'mihovil@ilakovac.com', + '/email-verify' + ) + + // Send email verification email. + await sendEmailVerificationEmail('mihovil@ilakovac.com', { + from: { + name: 'Wasp', + email: 'mihovil@ilakovac.com', + }, + to: 'mihovil@ilakovac.com', + subject: 'Email verification', + text: 'Click on the link to verify your email. ' + secondLink, + html: `Click here to verify your email.`, + }) + + // Send password reset email. + await sendPasswordResetEmail('mihovil@ilakovac.com', { + from: { + name: 'Wasp', + email: 'mihovil@ilakovac.com', + }, + to: 'mihovil@ilakovac.com', + subject: 'Password reset', + text: 'Click on the link to reset your password.' + link, + html: `Click here to reset your password.`, + }) +} diff --git a/waspc/examples/todo-typescript/src/user/userSignupFields.ts b/waspc/examples/todo-typescript/src/user/userSignupFields.ts new file mode 100644 index 0000000000..7c40562cc5 --- /dev/null +++ b/waspc/examples/todo-typescript/src/user/userSignupFields.ts @@ -0,0 +1,18 @@ +import { defineUserSignupFields } from "wasp/auth"; + +export const googleUserSignupFields = defineUserSignupFields({ + address: () => "Placeholder address", +}); + +export const githubUserSignupFields = defineUserSignupFields({ + address: () => "Placeholder address", +}); + +export const userSignupFields = defineUserSignupFields({ + address: (data) => { + if (typeof data.address !== "string") { + throw new Error("Address must be provided on signup."); + } + return data.address; + }, +}); diff --git a/waspc/examples/todo-typescript/src/websocket/index.ts b/waspc/examples/todo-typescript/src/websocket/index.ts new file mode 100644 index 0000000000..502856a824 --- /dev/null +++ b/waspc/examples/todo-typescript/src/websocket/index.ts @@ -0,0 +1,26 @@ +import { WebSocketDefinition } from "wasp/server/webSocket"; +import { getFirstProviderUserId } from "wasp/auth/user"; + +export const webSocketFn: WebSocketDefinition< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents +> = (io, context) => { + io.on("connection", (socket) => { + const username = getFirstProviderUserId(socket.data.user) ?? "Unknown"; + console.log("a user connected: ", username); + + socket.on("chatMessage", async (msg) => { + console.log("message: ", msg); + io.emit("chatMessage", { id: "random", username, text: msg }); + }); + }); +}; + +interface ServerToClientEvents { + chatMessage: (msg: { id: string; username: string; text: string }) => void; +} +interface ClientToServerEvents { + chatMessage: (msg: string) => void; +} +interface InterServerEvents {} diff --git a/waspc/examples/todo-typescript/tsconfig.json b/waspc/examples/todo-typescript/tsconfig.json index 93c79bf3d8..a38370e8ee 100644 --- a/waspc/examples/todo-typescript/tsconfig.json +++ b/waspc/examples/todo-typescript/tsconfig.json @@ -16,11 +16,17 @@ "esnext" ], "allowJs": true, + "types": [ + // This is needed to properly support Vitest testing with jest-dom matchers. + // Types for jest-dom are not recognized automatically and Typescript complains + // about missing types e.g. when using `toBeInTheDocument` and other matchers. + "@testing-library/jest-dom" + ], // Since this TS config is used only for IDE support and not for // compilation, the following directory doesn't exist. We need to specify // it to prevent this error: // https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file - "outDir": "phantom" + "outDir": "phantom", }, "exclude": [ "phantom" diff --git a/waspc/examples/todo-typescript/vite.config.ts b/waspc/examples/todo-typescript/vite.config.ts new file mode 100644 index 0000000000..6e16a4c901 --- /dev/null +++ b/waspc/examples/todo-typescript/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + server: { + open: false, + }, +}); diff --git a/waspc/src/Wasp/AppSpec.hs b/waspc/src/Wasp/AppSpec.hs index 555dbe724e..60c2f5bfdd 100644 --- a/waspc/src/Wasp/AppSpec.hs +++ b/waspc/src/Wasp/AppSpec.hs @@ -38,8 +38,7 @@ import Wasp.AppSpec.Core.Decl (Decl, IsDecl, takeDecls) import Wasp.AppSpec.Core.Ref (Ref, refName) import Wasp.AppSpec.Crud (Crud) import Wasp.AppSpec.Entity (Entity) -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) -import qualified Wasp.AppSpec.ExternalCode as ExternalCode +import qualified Wasp.AppSpec.ExternalFiles as ExternalFiles import Wasp.AppSpec.Job (Job) import Wasp.AppSpec.Operation (Operation) import qualified Wasp.AppSpec.Operation as AS.Operation @@ -64,13 +63,9 @@ data AppSpec = AppSpec packageJson :: PackageJson, -- | Absolute path to the directory containing the wasp project. waspProjectDir :: Path' Abs (Dir WaspProjectDir), - -- | List of external server code files (they are referenced/used in the declarations). - externalServerFiles :: [ExternalCode.File], - -- | List of external client code files (they are referenced/used in the declarations). - externalClientFiles :: [ExternalCode.File], - -- | List of files with external code shared between the server and the client. - externalSharedFiles :: [ExternalCode.File], - -- | Absolute path to the directory in wasp project source that contains external code files. + -- | List of external code files (they are referenced/used in the declarations). + externalCodeFiles :: [ExternalFiles.CodeFile], + externalPublicFiles :: [ExternalFiles.PublicFile], migrationsDir :: Maybe (Path' Abs (Dir DbMigrationsDir)), -- | Env variables to be provided to the server only during the development. devEnvVarsServer :: [EnvVar], @@ -86,7 +81,7 @@ data AppSpec = AppSpec -- | Connection URL for a database used during development. If provided, generated app will -- make sure to use it when run in development mode. devDatabaseUrl :: Maybe String, - customViteConfigPath :: Maybe (Path' (Rel SourceExternalCodeDir) File') + customViteConfigPath :: Maybe (Path' (Rel WaspProjectDir) File') } -- TODO: Make this return "Named" declarations? diff --git a/waspc/src/Wasp/AppSpec/ExtImport.hs b/waspc/src/Wasp/AppSpec/ExtImport.hs index 4680d75e37..cc2b347826 100644 --- a/waspc/src/Wasp/AppSpec/ExtImport.hs +++ b/waspc/src/Wasp/AppSpec/ExtImport.hs @@ -13,7 +13,7 @@ import Data.Aeson (FromJSON, ToJSON) import Data.Data (Data) import GHC.Generics (Generic) import StrongPath (File', Path, Posix, Rel) -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) +import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir) data ExtImport = ExtImport { -- | What is being imported. diff --git a/waspc/src/Wasp/AppSpec/ExternalCode.hs b/waspc/src/Wasp/AppSpec/ExternalCode.hs deleted file mode 100644 index 7f83db7310..0000000000 --- a/waspc/src/Wasp/AppSpec/ExternalCode.hs +++ /dev/null @@ -1,52 +0,0 @@ -{-# LANGUAGE DeriveDataTypeable #-} - -module Wasp.AppSpec.ExternalCode - ( -- | Wasp project consists of Wasp code (.wasp files) and external code (e.g. .js files) that is - -- used/referenced by the Wasp code. - -- Therefore, the whole specification of the web app is not just Wasp code, but a combination of - -- Wasp code and external code. - -- Main unit of external code is File, and external code is currently all organized in a single - -- directory in Wasp project which we call source external code dir (source because it is in the - -- Wasp project \/ source dir, and not in the generated project \/ source). - File (..), - filePathInExtCodeDir, - fileAbsPath, - fileText, - SourceExternalCodeDir, - ) -where - -import Data.Data (Data) -import Data.Text (Text) -import qualified Data.Text.Lazy as TextL -import StrongPath (Abs, Dir, File', Path', Rel, ()) - --- | Directory in Wasp source that contains external code. --- External code files are obtained from it. -data SourceExternalCodeDir deriving (Data) - -data File = File - { _pathInExtCodeDir :: !(Path' (Rel SourceExternalCodeDir) File'), - _extCodeDirPath :: !(Path' Abs (Dir SourceExternalCodeDir)), - -- | File content. Since it is lazy, it might throw error when evaluated, - -- since reading will happen only then. E.g. it will throw error if file is not a textual file. - _text :: TextL.Text - } - -instance Show File where - show = show . _pathInExtCodeDir - -instance Eq File where - f1 == f2 = _pathInExtCodeDir f1 == _pathInExtCodeDir f2 - --- | Returns path relative to the external code directory. -filePathInExtCodeDir :: File -> Path' (Rel SourceExternalCodeDir) File' -filePathInExtCodeDir = _pathInExtCodeDir - --- | Unsafe method: throws error if text could not be read (if file is not a textual file)! -fileText :: File -> Text -fileText = TextL.toStrict . _text - --- | Returns absolute path of the external code file. -fileAbsPath :: File -> Path' Abs File' -fileAbsPath file = _extCodeDirPath file _pathInExtCodeDir file diff --git a/waspc/src/Wasp/AppSpec/ExternalFiles.hs b/waspc/src/Wasp/AppSpec/ExternalFiles.hs new file mode 100644 index 0000000000..f75b39c69d --- /dev/null +++ b/waspc/src/Wasp/AppSpec/ExternalFiles.hs @@ -0,0 +1,67 @@ +{-# LANGUAGE DeriveDataTypeable #-} + +module Wasp.AppSpec.ExternalFiles + ( -- | Wasp project consists of Wasp code (.wasp files) and external files. + -- External files can be either: + -- - External code files (e.g., JS, TS) used/referenced by Wasp code. These + -- files reside in the \/src directory which we call the "source external code dir". + -- - External (static) public files served unaltered by Wasp. These + -- files reside in the \/public directory which we call the "source external public dir". + -- We call these directories "source" directories because they're found in + -- the project's source dir, not in the generated project's source dir. + -- + -- Therefore, the whole specification of the web app is not just Wasp code, but a combination of + -- Wasp code and external files. + CodeFile (..), + PublicFile (..), + filePathInExtCodeDir, + fileAbsPath, + fileText, + SourceExternalCodeDir, + SourceExternalPublicDir, + ) +where + +import Data.Data (Data) +import Data.Text (Text) +import qualified Data.Text.Lazy as TextL +import StrongPath (Abs, Dir, File', Path', Rel, ()) + +-- | Directory in the Wasp project that contains external code. +-- External code files are obtained from it. +data SourceExternalCodeDir deriving (Data) + +-- | Directory in Wasp project that contains external public static files. +-- Public files are obtained from it. +data SourceExternalPublicDir deriving (Data) + +data CodeFile = CodeFile + { _pathInExtCodeDir :: !(Path' (Rel SourceExternalCodeDir) File'), + _extCodeDirPath :: !(Path' Abs (Dir SourceExternalCodeDir)), + -- | File content. Since it is lazy, it might throw error when evaluated, + -- since reading will happen only then. E.g. it will throw error if file is not a textual file. + _text :: TextL.Text + } + +data PublicFile = PublicFile + { _pathInPublicDir :: !(Path' (Rel SourceExternalPublicDir) File'), + _publicDirPath :: !(Path' Abs (Dir SourceExternalPublicDir)) + } + +instance Show CodeFile where + show = show . _pathInExtCodeDir + +instance Eq CodeFile where + f1 == f2 = _pathInExtCodeDir f1 == _pathInExtCodeDir f2 + +-- | Returns path relative to the external code directory. +filePathInExtCodeDir :: CodeFile -> Path' (Rel SourceExternalCodeDir) File' +filePathInExtCodeDir = _pathInExtCodeDir + +-- | Unsafe method: throws error if text could not be read (if file is not a textual file)! +fileText :: CodeFile -> Text +fileText = TextL.toStrict . _text + +-- | Returns absolute path of the external code file. +fileAbsPath :: CodeFile -> Path' Abs File' +fileAbsPath file = _extCodeDirPath file _pathInExtCodeDir file diff --git a/waspc/src/Wasp/AppSpec/Valid.hs b/waspc/src/Wasp/AppSpec/Valid.hs index c6a7859291..6880edd293 100644 --- a/waspc/src/Wasp/AppSpec/Valid.hs +++ b/waspc/src/Wasp/AppSpec/Valid.hs @@ -307,7 +307,7 @@ validateWebAppBaseDir :: AppSpec -> [ValidationError] validateWebAppBaseDir spec = case maybeBaseDir of Just baseDir | not (startsWithSlash baseDir) -> - [GenericValidationError "The app.client.baseDir should start with a slash e.g. \"/test\""] + [GenericValidationError "The app.client.baseDir should start with a slash e.g. \"/test\""] _anyOtherCase -> [] where maybeBaseDir = Client.baseDir =<< AS.App.client (snd $ getApp spec) diff --git a/waspc/src/Wasp/CompileOptions.hs b/waspc/src/Wasp/CompileOptions.hs index d0174cd9b7..02eb4d0ff3 100644 --- a/waspc/src/Wasp/CompileOptions.hs +++ b/waspc/src/Wasp/CompileOptions.hs @@ -4,17 +4,15 @@ module Wasp.CompileOptions where import StrongPath (Abs, Dir, Path') -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) import Wasp.Generator.Monad (GeneratorWarning) import Wasp.Message (SendMessage) +import Wasp.Project.Common (WaspProjectDir) -- TODO(martin): Should these be merged with Wasp data? Is it really a separate thing or not? -- It would be easier to pass around if it is part of Wasp data. But is it semantically correct? -- Maybe it is, even more than this! data CompileOptions = CompileOptions - { externalServerCodeDirPath :: !(Path' Abs (Dir SourceExternalCodeDir)), - externalClientCodeDirPath :: !(Path' Abs (Dir SourceExternalCodeDir)), - externalSharedCodeDirPath :: !(Path' Abs (Dir SourceExternalCodeDir)), + { waspProjectDirPath :: !(Path' Abs (Dir WaspProjectDir)), isBuild :: !Bool, -- We give the compiler the ability to send messages. The code that -- invokes the compiler (such as the CLI) can then implement a way diff --git a/waspc/src/Wasp/Generator/Common.hs b/waspc/src/Wasp/Generator/Common.hs index 5ce25639ca..cecdd011d8 100644 --- a/waspc/src/Wasp/Generator/Common.hs +++ b/waspc/src/Wasp/Generator/Common.hs @@ -10,14 +10,18 @@ module Wasp.Generator.Common makeJsonWithEntityData, GeneratedSrcDir, makeJsArrayFromHaskellList, + dropExtensionFromImportPath, ) where import Data.Aeson (KeyValue ((.=)), object) import qualified Data.Aeson as Aeson import Data.List (intercalate) -import StrongPath (Dir, Rel, reldir) +import Data.Maybe (fromJust) +import StrongPath (Dir, File, Path, Posix, Rel, reldir) +import qualified StrongPath as SP import StrongPath.Types (Path') +import System.FilePath (splitExtension) import Wasp.Generator.Templates (TemplatesDir) import qualified Wasp.SemanticVersion as SV import Wasp.Util (toLowerFirst) @@ -72,3 +76,8 @@ makeJsArrayFromHaskellList :: [String] -> String makeJsArrayFromHaskellList list = "[" ++ intercalate ", " listOfJsStrings ++ "]" where listOfJsStrings = map (\s -> "'" ++ s ++ "'") list + +dropExtensionFromImportPath :: Path Posix (Rel r) (File f) -> Path Posix (Rel r) (File f) +dropExtensionFromImportPath = fromJust . SP.parseRelFileP . dropExtension . SP.fromRelFileP + where + dropExtension = fst . splitExtension diff --git a/waspc/src/Wasp/Generator/DbGenerator.hs b/waspc/src/Wasp/Generator/DbGenerator.hs index 0340c10a48..dda90bd13d 100644 --- a/waspc/src/Wasp/Generator/DbGenerator.hs +++ b/waspc/src/Wasp/Generator/DbGenerator.hs @@ -198,7 +198,7 @@ generatePrismaClient spec projectRootDir = generatePrismaClientIfEntitiesExist :: IO (Maybe GeneratorError) generatePrismaClientIfEntitiesExist | entitiesExist = - either (Just . GenericGeneratorError) (const Nothing) <$> DbOps.generatePrismaClient projectRootDir + either (Just . GenericGeneratorError) (const Nothing) <$> DbOps.generatePrismaClient projectRootDir | otherwise = return Nothing entitiesExist = not . null $ getEntities spec diff --git a/waspc/src/Wasp/Generator/DbGenerator/Jobs.hs b/waspc/src/Wasp/Generator/DbGenerator/Jobs.hs index a60a9db0f3..1a478ed0bc 100644 --- a/waspc/src/Wasp/Generator/DbGenerator/Jobs.hs +++ b/waspc/src/Wasp/Generator/DbGenerator/Jobs.hs @@ -82,7 +82,7 @@ asPrismaCliArgs migrateArgs = do -- to signal if the diff is empty or not (Empty: 0, Error: 1, Not empty: 2) migrateDiff :: Path' Abs (Dir ProjectRootDir) -> J.Job migrateDiff projectRootDir = - runPrismaCommandAsJob + runPrismaCommandAsJobFromWaspServerDir projectRootDir [ "migrate", "diff", @@ -102,7 +102,7 @@ migrateDiff projectRootDir = -- Therefore, this should be checked **after** a command that ensures connectivity. migrateStatus :: Path' Abs (Dir ProjectRootDir) -> J.Job migrateStatus projectRootDir = - runPrismaCommandAsJob + runPrismaCommandAsJobFromWaspServerDir projectRootDir ["migrate", "status", "--schema", SP.fromAbsFile schema] where @@ -112,7 +112,7 @@ migrateStatus projectRootDir = -- reapplies all the migrations. reset :: Path' Abs (Dir ProjectRootDir) -> J.Job reset projectRootDir = - runPrismaCommandAsJob + runPrismaCommandAsJobFromWaspServerDir projectRootDir -- NOTE(martin): We do "--skip-seed" here because I just think seeding happening automatically on -- reset is too aggressive / confusing. @@ -122,13 +122,19 @@ reset projectRootDir = -- | Runs `prisma db seed`, which executes the seeding script specified in package.json in -- prisma.seed field. +-- NOTE: We are running this command from server dir since that's where we defined the "prisma.seed" +-- script in package.json. In the future, we might want to allow users to specify the script name +-- in the project package.json, in which case we would run this command from project root dir. seed :: Path' Abs (Dir ProjectRootDir) -> String -> J.Job -- NOTE: Since v 0.3, Prisma doesn't use --schema parameter for `db seed`. seed projectRootDir seedName = runPrismaCommandAsJobWithExtraEnv + serverDir [(dbSeedNameEnvVarName, seedName)] projectRootDir ["db", "seed"] + where + serverDir = projectRootDir serverRootDirInProjectRootDir -- | Checks if the DB is running and connectable by running -- `prisma db execute --stdin --schema `. @@ -138,34 +144,37 @@ seed projectRootDir seedName = -- SQL command, which works perfectly for checking if the database is running. dbExecuteTest :: Path' Abs (Dir ProjectRootDir) -> J.Job dbExecuteTest projectRootDir = - runPrismaCommandAsJob projectRootDir ["db", "execute", "--stdin", "--schema", SP.fromAbsFile schema] + runPrismaCommandAsJobFromWaspServerDir projectRootDir ["db", "execute", "--stdin", "--schema", SP.fromAbsFile schema] where schema = projectRootDir dbSchemaFileInProjectRootDir -- | Runs `prisma studio` - Prisma's db inspector. runStudio :: Path' Abs (Dir ProjectRootDir) -> J.Job runStudio projectRootDir = - runPrismaCommandAsJob projectRootDir ["studio", "--schema", SP.fromAbsFile schema] + runPrismaCommandAsJobFromWaspServerDir projectRootDir ["studio", "--schema", SP.fromAbsFile schema] where schema = projectRootDir dbSchemaFileInProjectRootDir generatePrismaClient :: Path' Abs (Dir ProjectRootDir) -> J.Job generatePrismaClient projectRootDir = - runPrismaCommandAsJob projectRootDir ["generate", "--schema", SP.fromAbsFile schema] + runPrismaCommandAsJobFromWaspServerDir projectRootDir ["generate", "--schema", SP.fromAbsFile schema] where schema = projectRootDir dbSchemaFileInProjectRootDir -runPrismaCommandAsJob :: Path' Abs (Dir ProjectRootDir) -> [String] -> J.Job -runPrismaCommandAsJob projectRootDir cmdArgs = - runPrismaCommandAsJobWithExtraEnv [] projectRootDir cmdArgs +runPrismaCommandAsJobFromWaspServerDir :: Path' Abs (Dir ProjectRootDir) -> [String] -> J.Job +runPrismaCommandAsJobFromWaspServerDir projectRootDir cmdArgs = + runPrismaCommandAsJobWithExtraEnv serverDir [] projectRootDir cmdArgs + where + serverDir = projectRootDir serverRootDirInProjectRootDir runPrismaCommandAsJobWithExtraEnv :: + Path' Abs (Dir a) -> [(String, String)] -> Path' Abs (Dir ProjectRootDir) -> [String] -> J.Job -runPrismaCommandAsJobWithExtraEnv envVars projectRootDir cmdArgs = - runNodeCommandAsJobWithExtraEnv envVars waspProjectDir (absPrismaExecutableFp waspProjectDir) cmdArgs J.Db +runPrismaCommandAsJobWithExtraEnv fromDir envVars projectRootDir cmdArgs = + runNodeCommandAsJobWithExtraEnv envVars fromDir (absPrismaExecutableFp waspProjectDir) cmdArgs J.Db where waspProjectDir = projectRootDir waspProjectDirFromProjectRootDir diff --git a/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs b/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs deleted file mode 100644 index f2875db8eb..0000000000 --- a/waspc/src/Wasp/Generator/ExternalCodeGenerator.hs +++ /dev/null @@ -1,38 +0,0 @@ -module Wasp.Generator.ExternalCodeGenerator - ( genExternalCodeDir, - ) -where - -import Data.Maybe (mapMaybe) -import qualified StrongPath as SP -import qualified System.FilePath as FP -import qualified Wasp.AppSpec.ExternalCode as EC -import qualified Wasp.Generator.ExternalCodeGenerator.Common as C -import Wasp.Generator.ExternalCodeGenerator.Js (genSourceFile) -import Wasp.Generator.FileDraft (FileDraft) -import qualified Wasp.Generator.FileDraft as FD -import Wasp.Generator.Monad (Generator) - --- | Takes external code files from Wasp and generates them in new location as part of the generated project. --- It might not just copy them but also do some changes on them, as needed. -genExternalCodeDir :: - C.ExternalCodeGeneratorStrategy -> - [EC.File] -> - Generator [FD.FileDraft] -genExternalCodeDir strategy = sequence . mapMaybe (genFile strategy) - -genFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Maybe (Generator FD.FileDraft) -genFile strategy file - | fileName == "tsconfig.json" = Nothing - | extension `elem` [".js", ".jsx", ".ts", ".tsx"] = Just $ genSourceFile strategy file - | otherwise = Just $ genResourceFile strategy file - where - extension = FP.takeExtension filePath - fileName = FP.takeFileName filePath - filePath = SP.toFilePath $ EC.filePathInExtCodeDir file - -genResourceFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Generator FileDraft -genResourceFile strategy file = return $ FD.createCopyFileDraft relDstPath absSrcPath - where - relDstPath = C._resolveDstFilePath strategy $ EC.filePathInExtCodeDir file - absSrcPath = EC.fileAbsPath file diff --git a/waspc/src/Wasp/Generator/ExternalCodeGenerator/Common.hs b/waspc/src/Wasp/Generator/ExternalCodeGenerator/Common.hs index 184f7dc435..e8d44e3f8a 100644 --- a/waspc/src/Wasp/Generator/ExternalCodeGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/ExternalCodeGenerator/Common.hs @@ -1,17 +1,9 @@ module Wasp.Generator.ExternalCodeGenerator.Common - ( ExternalCodeGeneratorStrategy (..), - GeneratedExternalCodeDir, - castRelPathFromSrcToGenExtCodeDir, - asGenExtFile, + ( GeneratedExternalCodeDir, ) where -import Data.Text (Text) -import StrongPath (File', Path', Rel) -import qualified StrongPath as SP -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) -import Wasp.Generator.Common (ProjectRootDir) - +-- todo(filip): Where should I put this? -- TODO: consider refactoring the usage of GeneratedExternalCodeDir since -- generated code might end up in multiple places (e.g. ext-src/ but also public/). -- Name should probably be narrowed down to something that represent only the ext-src/ @@ -19,17 +11,3 @@ import Wasp.Generator.Common (ProjectRootDir) -- | Path to the directory where ext code will be generated. data GeneratedExternalCodeDir - -asGenExtFile :: Path' (Rel d) File' -> Path' (Rel GeneratedExternalCodeDir) File' -asGenExtFile = SP.castRel - -castRelPathFromSrcToGenExtCodeDir :: Path' (Rel SourceExternalCodeDir) a -> Path' (Rel GeneratedExternalCodeDir) a -castRelPathFromSrcToGenExtCodeDir = SP.castRel - -data ExternalCodeGeneratorStrategy = ExternalCodeGeneratorStrategy - { -- | Takes a path where the external code js file will be generated. - -- Also takes text of the file. Returns text where special @wasp imports have been replaced with - -- imports that will work. - _resolveJsFileWaspImports :: Path' (Rel GeneratedExternalCodeDir) File' -> Text -> Text, - _resolveDstFilePath :: Path' (Rel SourceExternalCodeDir) File' -> Path' (Rel ProjectRootDir) File' - } diff --git a/waspc/src/Wasp/Generator/ExternalCodeGenerator/Js.hs b/waspc/src/Wasp/Generator/ExternalCodeGenerator/Js.hs deleted file mode 100644 index 1cd956a56c..0000000000 --- a/waspc/src/Wasp/Generator/ExternalCodeGenerator/Js.hs +++ /dev/null @@ -1,44 +0,0 @@ -module Wasp.Generator.ExternalCodeGenerator.Js - ( genSourceFile, - resolveJsFileWaspImportsForExtCodeDir, - ) -where - -import Data.Maybe (fromJust) -import Data.Text (Text, unpack) -import qualified Data.Text as T -import FilePath.Extra (reversePosixPath) -import StrongPath (Dir, File', Path', Rel, ()) -import qualified StrongPath as SP -import qualified Text.Regex.TDFA as TR -import qualified Wasp.AppSpec.ExternalCode as EC -import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir) -import qualified Wasp.Generator.ExternalCodeGenerator.Common as C -import qualified Wasp.Generator.FileDraft as FD -import Wasp.Generator.Monad (Generator) - -genSourceFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Generator FD.FileDraft -genSourceFile strategy file = return $ FD.createTextFileDraft dstPath text - where - filePathInSrcExtCodeDir = EC.filePathInExtCodeDir file - text = EC.fileText file - dstPath = C._resolveDstFilePath strategy filePathInSrcExtCodeDir - --- | Replaces imports that start with "@wasp/" with imports that start from the src dir of the app. -resolveJsFileWaspImportsForExtCodeDir :: - -- | Relative path of ext code dir in src dir of app (web app, server (app), ...) - Path' (Rel ()) (Dir GeneratedExternalCodeDir) -> - -- | Path where this JS file will be generated. - Path' (Rel GeneratedExternalCodeDir) File' -> - -- | Original text of the file. - Text -> - -- | Text of the file with special "@wasp" imports resolved (replaced with normal JS imports). - Text -resolveJsFileWaspImportsForExtCodeDir extCodeDirInAppSrcDir jsFileDstPathInExtCodeDir jsFileText = - let matches = concat (unpack jsFileText TR.=~ ("(from +['\"]@wasp/)" :: String) :: [[String]]) - in foldr replaceFromWasp jsFileText matches - where - replaceFromWasp fromWasp = T.replace (T.pack fromWasp) (T.pack $ transformFromWasp fromWasp) - transformFromWasp fromWasp = reverse (drop (length ("@wasp/" :: String)) $ reverse fromWasp) ++ pathPrefix ++ "/" - pathPrefix = reversePosixPath $ SP.fromRelDirP $ fromJust $ SP.relDirToPosix $ SP.parent jsFileDstPathInAppSrcDir - jsFileDstPathInAppSrcDir = extCodeDirInAppSrcDir jsFileDstPathInExtCodeDir diff --git a/waspc/src/Wasp/Generator/JsImport.hs b/waspc/src/Wasp/Generator/JsImport.hs index c5da3f7a74..9df2fea89f 100644 --- a/waspc/src/Wasp/Generator/JsImport.hs +++ b/waspc/src/Wasp/Generator/JsImport.hs @@ -1,6 +1,7 @@ module Wasp.Generator.JsImport ( extImportToJsImport, jsImportToImportJson, + extImportNameToJsImportName, ) where @@ -14,6 +15,7 @@ import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir) import Wasp.JsImport ( JsImport, JsImportName (JsImportField, JsImportModule), + JsImportPath (RelativeImportPath), getJsImportStmtAndIdentifier, makeJsImport, ) @@ -24,31 +26,15 @@ extImportToJsImport :: Path Posix (Rel importLocation) (Dir d) -> EI.ExtImport -> JsImport -extImportToJsImport pathFromSrcDirToExtCodeDir pathFromImportLocationToSrcDir extImport = makeJsImport importPath importName +extImportToJsImport pathFromSrcDirToExtCodeDir pathFromImportLocationToSrcDir extImport = makeJsImport (RelativeImportPath importPath) importName where userDefinedPathInExtSrcDir = SP.castRel $ EI.path extImport :: Path Posix (Rel GeneratedExternalCodeDir) File' importName = extImportNameToJsImportName $ EI.name extImport importPath = SP.castRel $ pathFromImportLocationToSrcDir pathFromSrcDirToExtCodeDir userDefinedPathInExtSrcDir - extImportNameToJsImportName :: EI.ExtImportName -> JsImportName - extImportNameToJsImportName (EI.ExtImportModule name) = JsImportModule name - extImportNameToJsImportName (EI.ExtImportField name) = JsImportField name - --- filip: attempt to simplify how we generate imports. I wanted to generate a --- module import (e.g., '@ext-src/something') and couldn't do it --- jsImportToImportJsonRaw :: Maybe (FilePath, JsImportName, Maybe JsImportAlias) -> Aeson.Value --- jsImportToImportJsonRaw importData = maybe notDefinedValue mkTmplData importData --- where --- notDefinedValue = object ["isDefined" .= False] - --- mkTmplData :: (FilePath, JsImportName, Maybe JsImportAlias) -> Aeson.Value --- mkTmplData (importPath, importName, maybeImportAlias) = --- let (jsImportStmt, jsImportIdentifier) = getJsImportStmtAndIdentifierRaw importPath importName maybeImportAlias --- in object --- [ "isDefined" .= True, --- "importStatement" .= jsImportStmt, --- "importIdentifier" .= jsImportIdentifier --- ] +extImportNameToJsImportName :: EI.ExtImportName -> JsImportName +extImportNameToJsImportName (EI.ExtImportModule name) = JsImportModule name +extImportNameToJsImportName (EI.ExtImportField name) = JsImportField name jsImportToImportJson :: Maybe JsImport -> Aeson.Value jsImportToImportJson maybeJsImport = maybe notDefinedValue mkTmplData maybeJsImport diff --git a/waspc/src/Wasp/Generator/NpmInstall.hs b/waspc/src/Wasp/Generator/NpmInstall.hs index c2da605128..0b4d68adaf 100644 --- a/waspc/src/Wasp/Generator/NpmInstall.hs +++ b/waspc/src/Wasp/Generator/NpmInstall.hs @@ -10,7 +10,6 @@ import Control.Monad.IO.Class (liftIO) import Data.Function ((&)) import Data.Functor ((<&>)) import qualified Data.Text as T -import Debug.Pretty.Simple (pTrace) import StrongPath (Abs, Dir, Path') import qualified StrongPath as SP import System.Exit (ExitCode (..)) diff --git a/waspc/src/Wasp/Generator/NpmInstall/Common.hs b/waspc/src/Wasp/Generator/NpmInstall/Common.hs index c1a5d80555..e466d7922c 100644 --- a/waspc/src/Wasp/Generator/NpmInstall/Common.hs +++ b/waspc/src/Wasp/Generator/NpmInstall/Common.hs @@ -10,9 +10,9 @@ import Data.Aeson (FromJSON, ToJSON) import GHC.Generics (Generic) import Wasp.AppSpec (AppSpec) import qualified Wasp.Generator.NpmDependencies as N -import qualified Wasp.Generator.SdkGenerator as SdkGenerator import qualified Wasp.Generator.ServerGenerator as SG import qualified Wasp.Generator.WebAppGenerator as WG +import qualified Wasp.Generator.SdkGenerator as SdkGenerator data AllNpmDeps = AllNpmDeps { _userNpmDeps :: !N.NpmDepsForUser, -- Deps coming from user's package.json . diff --git a/waspc/src/Wasp/Generator/SdkGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator.hs index 7bef87a18c..fe7ae1bbb4 100644 --- a/waspc/src/Wasp/Generator/SdkGenerator.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator.hs @@ -1,54 +1,195 @@ -module Wasp.Generator.SdkGenerator where +{-# LANGUAGE TypeApplications #-} +module Wasp.Generator.SdkGenerator + ( genSdk, + installNpmDependencies, + genExternalCodeDir, + buildSdk, + npmDepsForSdk, + ) +where + +import Control.Concurrent (newChan) +import Control.Concurrent.Async (concurrently) import Data.Aeson (object) -import qualified Data.Aeson as Aeson import Data.Aeson.Types ((.=)) -import GHC.IO (unsafePerformIO) +import Data.Maybe (fromMaybe, isJust, mapMaybe) import StrongPath +import qualified StrongPath as SP +import System.Exit (ExitCode (..)) +import qualified System.FilePath as FP import Wasp.AppSpec +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.App.Auth import qualified Wasp.AppSpec.App.Dependency as AS.Dependency -import Wasp.AppSpec.Valid (isAuthEnabled) -import Wasp.Generator.Common (ProjectRootDir, prismaVersion) -import Wasp.Generator.FileDraft (FileDraft, createCopyDirFileDraft, createTemplateFileDraft) -import Wasp.Generator.FileDraft.CopyDirFileDraft (CopyDirFileDraftDstDirStrategy (RemoveExistingDstDir)) +import qualified Wasp.AppSpec.Entity as AS.Entity +import qualified Wasp.AppSpec.ExternalFiles as EC +import Wasp.AppSpec.Valid (getLowestNodeVersionUserAllows, isAuthEnabled) +import qualified Wasp.AppSpec.Valid as AS.Valid +import Wasp.Generator.Common (ProjectRootDir, makeJsonWithEntityData, prismaVersion) +import qualified Wasp.Generator.DbGenerator.Auth as DbAuth +import Wasp.Generator.FileDraft (FileDraft) +import qualified Wasp.Generator.FileDraft as FD import qualified Wasp.Generator.Job as J +import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed) import Wasp.Generator.Job.Process (runNodeCommandAsJob) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N +import Wasp.Generator.SdkGenerator.ApiRoutesG (genApis) +import Wasp.Generator.SdkGenerator.AuthG (genAuth) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.CrudG (genCrud) +import Wasp.Generator.SdkGenerator.EmailSenderG (depsRequiredByEmail, genEmailSender) +import Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes) +import Wasp.Generator.SdkGenerator.RouterGenerator (genRouter) +import Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) +import Wasp.Generator.SdkGenerator.ServerOpsGenerator (genOperations) +import Wasp.Generator.SdkGenerator.WebSocketGenerator (depsRequiredByWebSockets, genWebSockets) import qualified Wasp.Generator.ServerGenerator.AuthG as ServerAuthG -import Wasp.Generator.Templates (TemplatesDir, getTemplatesDirAbsPath) +import qualified Wasp.Generator.WebAppGenerator.Common as WebApp +import qualified Wasp.Node.Version as NodeVersion import Wasp.Project.Common (WaspProjectDir) +import qualified Wasp.Project.Db as Db import qualified Wasp.SemanticVersion as SV +import Wasp.Util (toLowerFirst, (<++>)) genSdk :: AppSpec -> Generator [FileDraft] -genSdk spec = sequence [genSdkModules, genPackageJson spec] +genSdk spec = genSdkReal spec -data SdkRootDir +buildSdk :: Path' Abs (Dir ProjectRootDir) -> IO (Either String ()) +buildSdk projectRootDir = do + chan <- newChan + (_, exitCode) <- + concurrently + (readJobMessagesAndPrintThemPrefixed chan) + (runNodeCommandAsJob dstDir "npx" ["tsc"] J.Wasp chan) + case exitCode of + ExitSuccess -> return $ Right () + ExitFailure code -> return $ Left $ "SDK build failed with exit code: " ++ show code + where + dstDir = projectRootDir C.sdkRootDirInProjectRootDir -data SdkTemplatesDir +genSdkReal :: AppSpec -> Generator [FileDraft] +genSdkReal spec = + sequence + [ genFileCopy [relfile|api/index.ts|], + genFileCopy [relfile|api/events.ts|], + genFileCopy [relfile|core/config.ts|], + genFileCopy [relfile|core/auth.ts|], + genFileCopy [relfile|core/storage.ts|], + genFileCopy [relfile|core/stitches.config.ts|], + genFileCopy [relfile|core/AuthError.ts|], + genFileCopy [relfile|core/HttpError.ts|], + -- Not migrated to TS yet + genFileCopy [relfile|operations/resources.js|], + genFileCopy [relfile|operations/index.ts|], + -- Not migrated to TS yet + genFileCopy [relfile|operations/updateHandlersMap.js|], + genFileCopy [relfile|server/dbClient.ts|], + genFileCopy [relfile|types/index.ts|], + genFileCopy [relfile|dbSeed/types.ts|], + genFileCopy [relfile|test/vitest/helpers.tsx|], + genFileCopy [relfile|test/index.ts|], + genFileCopy [relfile|jobs/pgBoss/types.ts|], + genServerConfigFile spec, + genTsConfigJson, + genServerUtils spec, + genPackageJson spec + ] + <++> genRpc spec + <++> genAuth spec + <++> genOperations spec + <++> genUniversalDir + <++> genExternalCodeDir (AS.externalCodeFiles spec) + <++> genEntitiesAndServerTypesDirs spec + <++> genCrud spec + <++> genJobTypes spec + <++> genApis spec + <++> genWebSockets spec + <++> genRouter spec + <++> genMiddleware spec + <++> genExportedTypesDir spec + <++> genEmailSender spec + where + genFileCopy = return . C.mkTmplFd -genSdkModules :: Generator FileDraft -genSdkModules = - return $ - createCopyDirFileDraft - RemoveExistingDstDir - sdkRootDirInProjectRootDir - (unsafePerformIO getTemplatesDirAbsPath sdkTemplatesDirInTemplatesDir [reldir|wasp|]) +-- genSdkHardcoded :: Generator [FileDraft] +-- genSdkHardcoded = +-- return [] +-- where +-- copyFile = C.mkTmplFd +-- copyFolder :: Path' (Rel SdkTemplatesDir) (Dir d) -> FileDraft +-- copyFolder modul = +-- createCopyDirFileDraft +-- RemoveExistingDstDir +-- (dstFolder castRel modul) +-- (srcFolder modul) +-- dstFolder = C.sdkRootDirInProjectRootDir +-- srcFolder = absSdkTemplatesDir +-- absSdkTemplatesDir = unsafePerformIO getTemplatesDirAbsPath C.sdkTemplatesDirInTemplatesDir + +genEntitiesAndServerTypesDirs :: AppSpec -> Generator [FileDraft] +genEntitiesAndServerTypesDirs spec = + return + [ entitiesIndexFileDraft, + taggedEntitiesFileDraft, + serializationFileDraft, + typesIndexFileDraft + ] + where + entitiesIndexFileDraft = + C.mkTmplFdWithDstAndData + [relfile|entities/index.ts|] + [relfile|entities/index.ts|] + ( Just $ + object + [ "entities" .= allEntities, + "isAuthEnabled" .= isJust maybeUserEntityName, + "authEntityName" .= DbAuth.authEntityName, + "authIdentityEntityName" .= DbAuth.authIdentityEntityName + ] + ) + taggedEntitiesFileDraft = + C.mkTmplFdWithDstAndData + [relfile|server/_types/taggedEntities.ts|] + [relfile|server/_types/taggedEntities.ts|] + (Just $ object ["entities" .= allEntities]) + serializationFileDraft = + C.mkTmplFd + [relfile|server/_types/serialization.ts|] + typesIndexFileDraft = + C.mkTmplFdWithDstAndData + [relfile|server/_types/index.ts|] + [relfile|server/_types/index.ts|] + ( Just $ + object + [ "entities" .= allEntities, + "isAuthEnabled" .= isJust maybeUserEntityName, + "userEntityName" .= userEntityName, + "authEntityName" .= DbAuth.authEntityName, + "authFieldOnUserEntityName" .= DbAuth.authFieldOnUserEntityName, + "authIdentityEntityName" .= DbAuth.authIdentityEntityName, + "identitiesFieldOnAuthEntityName" .= DbAuth.identitiesFieldOnAuthEntityName, + "userFieldName" .= toLowerFirst userEntityName + ] + ) + userEntityName = fromMaybe "" maybeUserEntityName + allEntities = map (makeJsonWithEntityData . fst) $ AS.getDecls @AS.Entity.Entity spec + maybeUserEntityName = AS.refName . AS.App.Auth.userEntity <$> AS.App.auth (snd $ AS.Valid.getApp spec) genPackageJson :: AppSpec -> Generator FileDraft genPackageJson spec = return $ - mkTmplFdWithDstAndData + C.mkTmplFdWithDstAndData [relfile|package.json|] [relfile|package.json|] ( Just $ object - [ "depsChunk" .= N.getDependenciesPackageJsonEntry npmDeps, - "devDepsChunk" .= N.getDevDependenciesPackageJsonEntry npmDeps + [ "depsChunk" .= N.getDependenciesPackageJsonEntry (npmDepsForSdk spec), + "devDepsChunk" .= N.getDevDependenciesPackageJsonEntry (npmDepsForSdk spec) ] ) - where - npmDeps = npmDepsForSdk spec npmDepsForSdk :: AppSpec -> N.NpmDepsForPackage npmDepsForSdk spec = @@ -63,6 +204,7 @@ npmDepsForSdk spec = ("jsonwebtoken", "^8.5.1"), ("mitt", "3.0.0"), ("react", "^18.2.0"), + ("lodash.merge", "^4.6.2"), ("react-router-dom", "^5.3.3"), ("react-hook-form", "^7.45.4"), ("secure-password", "^4.0.0"), @@ -76,9 +218,53 @@ npmDepsForSdk spec = -- runtime to load the wrong (uninitialized prisma/client) -- TODO(filip): Find a better way to handle duplicate -- dependencies: https://github.com/wasp-lang/wasp/issues/1640 - ++ ServerAuthG.depsRequiredByAuth spec, - N.devDependencies = AS.Dependency.fromList [] + ++ ServerAuthG.depsRequiredByAuth spec + ++ depsRequiredByEmail spec + ++ depsRequiredByWebSockets spec + ++ depsRequiredForTesting, + N.devDependencies = + AS.Dependency.fromList + [ ("@tsconfig/node" <> majorNodeVersionStr, "latest") + ] } + where + majorNodeVersionStr = show (SV.major $ getLowestNodeVersionUserAllows spec) + +depsRequiredForTesting :: [AS.Dependency.Dependency] +depsRequiredForTesting = + AS.Dependency.fromList + [ ("vitest", "^1.2.1"), + ("@vitest/ui", "^1.2.1"), + ("jsdom", "^21.1.1"), + ("@testing-library/react", "^14.1.2"), + ("@testing-library/jest-dom", "^6.3.0"), + ("msw", "^1.1.0") + ] + +genServerConfigFile :: AppSpec -> Generator FileDraft +genServerConfigFile spec = return $ C.mkTmplFdWithData relConfigFilePath tmplData + where + relConfigFilePath = [relfile|server/config.ts|] + tmplData = + object + [ "isAuthEnabled" .= isAuthEnabled spec, + "databaseUrlEnvVarName" .= Db.databaseUrlEnvVarName, + "defaultClientUrl" .= WebApp.getDefaultClientUrl spec + ] + +-- todo(filip): remove this duplication, we have almost the same thing in the +-- ServerGenerator. +genTsConfigJson :: Generator FileDraft +genTsConfigJson = do + return $ + C.mkTmplFdWithDstAndData + [relfile|tsconfig.json|] + [relfile|tsconfig.json|] + ( Just $ + object + [ "majorNodeVersion" .= show (SV.major NodeVersion.oldestWaspSupportedNodeVersion) + ] + ) depsRequiredForAuth :: AppSpec -> [AS.Dependency.Dependency] depsRequiredForAuth spec = @@ -86,25 +272,62 @@ depsRequiredForAuth spec = where versionRange = SV.Range [SV.backwardsCompatibleWith (SV.Version 1 2 8)] -mkTmplFdWithDstAndData :: - Path' (Rel SdkTemplatesDir) File' -> - Path' (Rel SdkRootDir) File' -> - Maybe Aeson.Value -> - FileDraft -mkTmplFdWithDstAndData relSrcPath relDstPath tmplData = - createTemplateFileDraft - (sdkRootDirInProjectRootDir relDstPath) - (sdkTemplatesDirInTemplatesDir relSrcPath) - tmplData - -sdkRootDirInProjectRootDir :: Path' (Rel ProjectRootDir) (Dir SdkRootDir) -sdkRootDirInProjectRootDir = [reldir|sdk/wasp|] - -sdkTemplatesDirInTemplatesDir :: Path' (Rel TemplatesDir) (Dir SdkTemplatesDir) -sdkTemplatesDirInTemplatesDir = [reldir|sdk|] - -- TODO(filip): Figure out where this belongs. Check https://github.com/wasp-lang/wasp/pull/1602#discussion_r1437144166 . -- Also, fix imports for wasp project. installNpmDependencies :: Path' Abs (Dir WaspProjectDir) -> J.Job installNpmDependencies projectDir = runNodeCommandAsJob projectDir "npm" ["install"] J.Wasp + +-- todo(filip): consider reorganizing/splitting the file. + +-- | Takes external code files from Wasp and generates them in new location as part of the generated project. +-- It might not just copy them but also do some changes on them, as needed. +genExternalCodeDir :: [EC.CodeFile] -> Generator [FileDraft] +genExternalCodeDir = sequence . mapMaybe genFile + +genFile :: EC.CodeFile -> Maybe (Generator FileDraft) +genFile file + | fileName == "tsconfig.json" = Nothing + | extension `elem` [".js", ".jsx", ".ts", ".tsx"] = Just $ genSourceFile file + | otherwise = Just $ genResourceFile file + where + extension = FP.takeExtension filePath + fileName = FP.takeFileName filePath + filePath = SP.toFilePath $ EC.filePathInExtCodeDir file + +genResourceFile :: EC.CodeFile -> Generator FileDraft +genResourceFile file = return $ FD.createCopyFileDraft relDstPath absSrcPath + where + relDstPath = C.sdkRootDirInProjectRootDir C.extSrcDirInSdkRootDir SP.castRel (EC._pathInExtCodeDir file) + absSrcPath = EC.fileAbsPath file + +genSourceFile :: EC.CodeFile -> Generator FD.FileDraft +genSourceFile file = return $ FD.createTextFileDraft relDstPath text + where + filePathInSrcExtCodeDir = EC.filePathInExtCodeDir file + text = EC.fileText file + relDstPath = C.sdkRootDirInProjectRootDir C.extSrcDirInSdkRootDir SP.castRel filePathInSrcExtCodeDir + +genUniversalDir :: Generator [FileDraft] +genUniversalDir = + return + [ C.mkTmplFd [relfile|universal/url.ts|], + C.mkTmplFd [relfile|universal/types.ts|], + C.mkTmplFd [relfile|universal/validators.ts|] + ] + +genServerUtils :: AppSpec -> Generator FileDraft +genServerUtils spec = return $ C.mkTmplFdWithData [relfile|server/utils.ts|] tmplData + where + tmplData = object ["isAuthEnabled" .= (isAuthEnabled spec :: Bool)] + +genExportedTypesDir :: AppSpec -> Generator [FileDraft] +genExportedTypesDir _spec = + return [C.mkTmplFd [relfile|server/types/index.ts|]] + +genMiddleware :: AppSpec -> Generator [FileDraft] +genMiddleware _spec = + sequence + [ return $ C.mkTmplFd [relfile|server/middleware/index.ts|], + return $ C.mkTmplFd [relfile|server/middleware/globalMiddleware.ts|] + ] diff --git a/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs b/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs new file mode 100644 index 0000000000..c5f3f048b4 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/ApiRoutesG.hs @@ -0,0 +1,59 @@ +module Wasp.Generator.SdkGenerator.ApiRoutesG + ( genApis, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import Data.List (nub) +import Data.Maybe (fromMaybe) +import StrongPath (File', Path', Rel, relfile) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec, getApis) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.Api as Api +import Wasp.AppSpec.Valid (isAuthEnabled) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.ServerGenerator.ApiRoutesG (getApiEntitiesObject, isAuthEnabledForApi) +import Wasp.Util (toUpperFirst) + +genApis :: AppSpec -> Generator [FileDraft] +genApis spec = + if areThereAnyCustomApiRoutes + then + sequence + [ genApiTypes spec + ] + else return [] + where + areThereAnyCustomApiRoutes = not . null $ getApis spec + +genApiTypes :: AppSpec -> Generator FileDraft +genApiTypes spec = + return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) + where + namedApis = AS.getApis spec + apis = snd <$> namedApis + tmplData = + object + [ "apiRoutes" .= map getTmplData namedApis, + "shouldImportAuthenticatedApi" .= any usesAuth apis, + "shouldImportNonAuthenticatedApi" .= not (all usesAuth apis), + "allEntities" .= nub (concatMap getApiEntitiesObject apis) + ] + usesAuth = fromMaybe (isAuthEnabledGlobally spec) . Api.auth + tmplFile = C.asTmplFile [relfile|server/apis/types.ts|] + dstFile = SP.castRel tmplFile :: Path' (Rel C.SdkRootDir) File' + + getTmplData :: (String, Api.Api) -> Aeson.Value + getTmplData (name, api) = + object + [ "typeName" .= toUpperFirst name, + "entities" .= getApiEntitiesObject api, + "usesAuth" .= isAuthEnabledForApi spec api + ] + + isAuthEnabledGlobally :: AppSpec -> Bool + isAuthEnabledGlobally = isAuthEnabled diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/AuthFormsG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs similarity index 88% rename from waspc/src/Wasp/Generator/WebAppGenerator/Auth/AuthFormsG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs index a208440260..910cd54ace 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/AuthFormsG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs @@ -1,4 +1,4 @@ -module Wasp.Generator.WebAppGenerator.Auth.AuthFormsG +module Wasp.Generator.SdkGenerator.Auth.AuthFormsG ( genAuthForms, ) where @@ -10,8 +10,9 @@ import Wasp.Generator.AuthProviders (gitHubAuthProvider, googleAuthProvider) import qualified Wasp.Generator.AuthProviders.OAuth as OAuth import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common as C +-- todo(filip) -- Should I put this under something like Wasp.Generator.Auth (doesn't exist) or Wasp.Generator.Common? import Wasp.Generator.WebAppGenerator.Auth.Common (getOnAuthSucceededRedirectToOrDefault) -import Wasp.Generator.WebAppGenerator.Common as C import Wasp.Util ((<++>)) genAuthForms :: AS.Auth.Auth -> Generator [FileDraft] @@ -20,19 +21,18 @@ genAuthForms auth = [ genAuthComponent auth, genTypes auth, genFileCopy [relfile|auth/forms/Login.tsx|], - genFileCopy [relfile|auth/forms/Signup.tsx|], - genFileCopy [relfile|stitches.config.js|] + genFileCopy [relfile|auth/forms/Signup.tsx|] ] <++> genEmailForms auth <++> genInternalAuthComponents auth where - genFileCopy = return . C.mkSrcTmplFd + genFileCopy = return . C.mkTmplFd genAuthComponent :: AS.Auth.Auth -> Generator FileDraft genAuthComponent auth = return $ C.mkTmplFdWithData - [relfile|src/auth/forms/Auth.tsx|] + [relfile|auth/forms/Auth.tsx|] tmplData where tmplData = object ["isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth] @@ -41,7 +41,7 @@ genTypes :: AS.Auth.Auth -> Generator FileDraft genTypes auth = return $ C.mkTmplFdWithData - [relfile|src/auth/forms/types.ts|] + [relfile|auth/forms/types.ts|] tmplData where tmplData = object ["isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth] @@ -55,7 +55,7 @@ genEmailForms auth = genFileCopy [relfile|auth/forms/VerifyEmail.tsx|] ] where - genFileCopy = return . C.mkSrcTmplFd + genFileCopy = return . C.mkTmplFd isEmailAuthEnabled = AS.Auth.isEmailAuthEnabled auth genInternalAuthComponents :: AS.Auth.Auth -> Generator [FileDraft] @@ -93,14 +93,14 @@ genInternalAuthComponents auth = isUsernameAndPasswordAuthEnabled = AS.Auth.isUsernameAndPasswordAuthEnabled auth isEmailAuthEnabled = AS.Auth.isEmailAuthEnabled auth - copyInternalAuthComponent = return . C.mkSrcTmplFd . (pathToInternalInAuth ) + copyInternalAuthComponent = return . C.mkTmplFd . (pathToInternalInAuth ) pathToInternalInAuth = [reldir|auth/forms/internal|] genLoginSignupForm :: AS.Auth.Auth -> Generator FileDraft genLoginSignupForm auth = return $ C.mkTmplFdWithData - [relfile|src/auth/forms/internal/common/LoginSignupForm.tsx|] + [relfile|auth/forms/internal/common/LoginSignupForm.tsx|] tmplData where tmplData = diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/EmailAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs similarity index 55% rename from waspc/src/Wasp/Generator/WebAppGenerator/Auth/EmailAuthG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs index 36956e1c2a..9e205cf22b 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/EmailAuthG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/EmailAuthG.hs @@ -1,10 +1,11 @@ -module Wasp.Generator.WebAppGenerator.Auth.EmailAuthG +module Wasp.Generator.SdkGenerator.Auth.EmailAuthG ( genEmailAuth, ) where import Data.Aeson (object, (.=)) import StrongPath (relfile) +import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.App.Auth as AS.Auth import Wasp.Generator.AuthProviders (emailAuthProvider) import Wasp.Generator.AuthProviders.Email @@ -14,22 +15,26 @@ import Wasp.Generator.AuthProviders.Email serverSignupUrl, serverVerifyEmailUrl, ) +import qualified Wasp.Generator.DbGenerator.Auth as DbAuth import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.WebAppGenerator.Common as C +import Wasp.Generator.SdkGenerator.Common as C import Wasp.Util ((<++>)) +import qualified Wasp.Util as Util genEmailAuth :: AS.Auth.Auth -> Generator [FileDraft] genEmailAuth auth | AS.Auth.isEmailAuthEnabled auth = sequence - [ genIndex + [ genIndex, + genServerUtils auth ] <++> genActions + <++> genServer | otherwise = return [] genIndex :: Generator FileDraft -genIndex = return $ C.mkSrcTmplFd [relfile|auth/email/index.ts|] +genIndex = return $ C.mkTmplFd [relfile|auth/email/index.ts|] genActions :: Generator [FileDraft] genActions = @@ -40,25 +45,31 @@ genActions = genVerifyEmailAction ] +genServer :: Generator [FileDraft] +genServer = + return + [ C.mkTmplFd [relfile|server/auth/email/index.ts|] + ] + genLoginAction :: Generator FileDraft genLoginAction = return $ C.mkTmplFdWithData - [relfile|src/auth/email/actions/login.ts|] + [relfile|auth/email/actions/login.ts|] (object ["loginPath" .= serverLoginUrl emailAuthProvider]) genSignupAction :: Generator FileDraft genSignupAction = return $ C.mkTmplFdWithData - [relfile|src/auth/email/actions/signup.ts|] + [relfile|auth/email/actions/signup.ts|] (object ["signupPath" .= serverSignupUrl emailAuthProvider]) genPasswordResetActions :: Generator FileDraft genPasswordResetActions = return $ C.mkTmplFdWithData - [relfile|src/auth/email/actions/passwordReset.ts|] + [relfile|auth/email/actions/passwordReset.ts|] ( object [ "requestPasswordResetPath" .= serverRequestPasswordResetUrl emailAuthProvider, "resetPasswordPath" .= serverResetPasswordUrl emailAuthProvider @@ -69,5 +80,19 @@ genVerifyEmailAction :: Generator FileDraft genVerifyEmailAction = return $ C.mkTmplFdWithData - [relfile|src/auth/email/actions/verifyEmail.ts|] + [relfile|auth/email/actions/verifyEmail.ts|] (object ["verifyEmailPath" .= serverVerifyEmailUrl emailAuthProvider]) + +genServerUtils :: AS.Auth.Auth -> Generator FileDraft +genServerUtils auth = return $ C.mkTmplFdWithData tmplFile tmplData + where + userEntityName = AS.refName $ AS.Auth.userEntity auth + tmplFile = [relfile|server/auth/email/utils.ts|] + tmplData = + object + [ "userEntityUpper" .= (userEntityName :: String), + "userEntityLower" .= (Util.toLowerFirst userEntityName :: String), + "authEntityUpper" .= (DbAuth.authEntityName :: String), + "authEntityLower" .= (Util.toLowerFirst DbAuth.authEntityName :: String), + "userFieldOnAuthEntityName" .= (DbAuth.userFieldOnAuthEntityName :: String) + ] diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/LocalAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/LocalAuthG.hs similarity index 76% rename from waspc/src/Wasp/Generator/WebAppGenerator/Auth/LocalAuthG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Auth/LocalAuthG.hs index 969b8483f5..5ca5f1c4a9 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/LocalAuthG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/LocalAuthG.hs @@ -1,16 +1,17 @@ -module Wasp.Generator.WebAppGenerator.Auth.LocalAuthG +module Wasp.Generator.SdkGenerator.Auth.LocalAuthG ( genLocalAuth, ) where import Data.Aeson (object, (.=)) import StrongPath (relfile) +import qualified StrongPath as SP import qualified Wasp.AppSpec.App.Auth as AS.Auth import Wasp.Generator.AuthProviders (localAuthProvider) import Wasp.Generator.AuthProviders.Local (serverLoginUrl, serverSignupUrl) import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.WebAppGenerator.Common as C +import Wasp.Generator.SdkGenerator.Common as C genLocalAuth :: AS.Auth.Auth -> Generator [FileDraft] genLocalAuth = genActions @@ -26,12 +27,12 @@ genActions auth -- | Generates file with signup function to be used by Wasp developer. genLocalSignupAction :: Generator FileDraft -genLocalSignupAction = return $ C.mkTmplFdWithData (C.asTmplFile [relfile|src/auth/signup.ts|]) tmplData +genLocalSignupAction = return $ C.mkTmplFdWithData (SP.castRel [relfile|auth/signup.ts|]) tmplData where tmplData = object ["signupPath" .= serverSignupUrl localAuthProvider] -- | Generates file with login function to be used by Wasp developer. genLocalLoginAction :: Generator FileDraft -genLocalLoginAction = return $ C.mkTmplFdWithData (C.asTmplFile [relfile|src/auth/login.ts|]) tmplData +genLocalLoginAction = return $ C.mkTmplFdWithData (SP.castRel [relfile|auth/login.ts|]) tmplData where tmplData = object ["loginPath" .= serverLoginUrl localAuthProvider] diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/OAuthAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs similarity index 63% rename from waspc/src/Wasp/Generator/WebAppGenerator/Auth/OAuthAuthG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs index 0a2c6ab909..bddc6fe08a 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Auth/OAuthAuthG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs @@ -1,4 +1,4 @@ -module Wasp.Generator.WebAppGenerator.Auth.OAuthAuthG +module Wasp.Generator.SdkGenerator.Auth.OAuthAuthG ( genOAuthAuth, ) where @@ -12,15 +12,12 @@ import Wasp.Generator.AuthProviders.OAuth (OAuthAuthProvider) import qualified Wasp.Generator.AuthProviders.OAuth as OAuth import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.WebAppGenerator.Auth.Common (getOnAuthSucceededRedirectToOrDefault) -import Wasp.Generator.WebAppGenerator.Common as C -import Wasp.Util ((<++>)) +import Wasp.Generator.SdkGenerator.Common as C genOAuthAuth :: AS.Auth.Auth -> Generator [FileDraft] genOAuthAuth auth | AS.Auth.isExternalAuthEnabled auth = genHelpers auth - <++> sequence [genOAuthCodeExchange auth] | otherwise = return [] genHelpers :: AS.Auth.Auth -> Generator [FileDraft] @@ -31,14 +28,14 @@ genHelpers auth = [googleHelpers | AS.Auth.isGoogleAuthEnabled auth] ] where - gitHubHelpers = mkHelpersFd gitHubAuthProvider [relfile|GitHub.jsx|] - googleHelpers = mkHelpersFd googleAuthProvider [relfile|Google.jsx|] + gitHubHelpers = mkHelpersFd gitHubAuthProvider [relfile|GitHub.tsx|] + googleHelpers = mkHelpersFd googleAuthProvider [relfile|Google.tsx|] mkHelpersFd :: OAuthAuthProvider -> Path' Rel' File' -> FileDraft mkHelpersFd provider helpersFp = mkTmplFdWithDstAndData - [relfile|src/auth/helpers/Generic.jsx|] - (SP.castRel $ [reldir|src/auth/helpers|] SP. helpersFp) + [relfile|auth/helpers/Generic.tsx|] + (SP.castRel $ [reldir|auth/helpers|] SP. helpersFp) (Just tmplData) where tmplData = @@ -46,14 +43,3 @@ genHelpers auth = [ "signInPath" .= OAuth.serverLoginUrl provider, "displayName" .= OAuth.displayName provider ] - -genOAuthCodeExchange :: AS.Auth.Auth -> Generator FileDraft -genOAuthCodeExchange auth = - return $ - C.mkTmplFdWithData - [relfile|src/auth/pages/OAuthCodeExchange.jsx|] - ( object - [ "onAuthSucceededRedirectTo" .= getOnAuthSucceededRedirectToOrDefault auth, - "onAuthFailedRedirectTo" .= AS.Auth.onAuthFailedRedirectTo auth - ] - ) diff --git a/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs new file mode 100644 index 0000000000..3487d2ebd4 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/AuthG.hs @@ -0,0 +1,135 @@ +module Wasp.Generator.SdkGenerator.AuthG + ( genAuth, + ) +where + +import Data.Aeson (object, (.=)) +import StrongPath (File', Path', Rel, relfile) +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.Auth +import Wasp.AppSpec.Valid (getApp) +import Wasp.Generator.Common (makeJsArrayFromHaskellList) +import qualified Wasp.Generator.DbGenerator.Auth as DbAuth +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Auth.AuthFormsG (genAuthForms) +import Wasp.Generator.SdkGenerator.Auth.EmailAuthG (genEmailAuth) +import Wasp.Generator.SdkGenerator.Auth.LocalAuthG (genLocalAuth) +import Wasp.Generator.SdkGenerator.Auth.OAuthAuthG (genOAuthAuth) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.WebAppGenerator.Auth.Common (getOnAuthSucceededRedirectToOrDefault) +import Wasp.Util ((<++>)) +import qualified Wasp.Util as Util + +genAuth :: AppSpec -> Generator [FileDraft] +genAuth spec = + case maybeAuth of + Nothing -> return [] + Just auth -> + -- shared stuff + sequence [genFileCopy [relfile|auth/user.ts|]] + -- client stuff + <++> sequence + [ genFileCopy [relfile|auth/helpers/user.ts|], + genFileCopy [relfile|auth/types.ts|], + genFileCopy [relfile|auth/logout.ts|], + genUseAuth auth + ] + <++> genAuthForms auth + <++> genLocalAuth auth + <++> genOAuthAuth auth + <++> genEmailAuth auth + -- server stuff + <++> sequence + [ genFileCopy [relfile|auth/validation.ts|], + genFileCopy [relfile|auth/password.ts|], + genFileCopy [relfile|auth/jwt.ts|], + genSessionTs auth, + genLuciaTs auth, + genUtils auth, + genProvidersTypes auth + ] + <++> genIndexTs auth + where + maybeAuth = AS.App.auth $ snd $ getApp spec + genFileCopy = return . C.mkTmplFd + +-- | Generates React hook that Wasp developer can use in a component to get +-- access to the currently logged in user (and check whether user is logged in +-- ot not). +genUseAuth :: AS.Auth.Auth -> Generator FileDraft +genUseAuth auth = return $ C.mkTmplFdWithData [relfile|auth/useAuth.ts|] tmplData + where + tmplData = object ["entitiesGetMeDependsOn" .= makeJsArrayFromHaskellList [userEntityName]] + userEntityName = AS.refName $ AS.Auth.userEntity auth + +genLuciaTs :: AS.Auth.Auth -> Generator FileDraft +genLuciaTs auth = return $ C.mkTmplFdWithData [relfile|auth/lucia.ts|] tmplData + where + tmplData = + object + [ "sessionEntityLower" .= (Util.toLowerFirst DbAuth.sessionEntityName :: String), + "authEntityLower" .= (Util.toLowerFirst DbAuth.authEntityName :: String), + "userEntityUpper" .= (userEntityName :: String) + ] + + userEntityName = AS.refName $ AS.Auth.userEntity auth + +genSessionTs :: AS.Auth.Auth -> Generator FileDraft +genSessionTs auth = return $ C.mkTmplFdWithData [relfile|auth/session.ts|] tmplData + where + tmplData = + object + [ "userEntityUpper" .= userEntityName, + "userEntityLower" .= Util.toLowerFirst userEntityName, + "authFieldOnUserEntityName" .= DbAuth.authFieldOnUserEntityName, + "identitiesFieldOnAuthEntityName" .= DbAuth.identitiesFieldOnAuthEntityName + ] + + userEntityName = AS.refName $ AS.Auth.userEntity auth + +genUtils :: AS.Auth.Auth -> Generator FileDraft +genUtils auth = return $ C.mkTmplFdWithData relUtilsFilePath tmplData + where + userEntityName = AS.refName $ AS.Auth.userEntity auth + tmplData = + object + [ "userEntityUpper" .= (userEntityName :: String), + "userEntityLower" .= (Util.toLowerFirst userEntityName :: String), + "authEntityUpper" .= (DbAuth.authEntityName :: String), + "authEntityLower" .= (Util.toLowerFirst DbAuth.authEntityName :: String), + "userFieldOnAuthEntityName" .= (DbAuth.userFieldOnAuthEntityName :: String), + "authIdentityEntityUpper" .= (DbAuth.authIdentityEntityName :: String), + "authIdentityEntityLower" .= (Util.toLowerFirst DbAuth.authIdentityEntityName :: String), + "authFieldOnUserEntityName" .= (DbAuth.authFieldOnUserEntityName :: String), + "identitiesFieldOnAuthEntityName" .= (DbAuth.identitiesFieldOnAuthEntityName :: String), + "failureRedirectPath" .= AS.Auth.onAuthFailedRedirectTo auth, + "successRedirectPath" .= getOnAuthSucceededRedirectToOrDefault auth + ] + + relUtilsFilePath :: Path' (Rel C.SdkTemplatesDir) File' + relUtilsFilePath = [relfile|auth/utils.ts|] + +genIndexTs :: AS.Auth.Auth -> Generator [FileDraft] +genIndexTs auth = + return $ + if isEmailAuthEnabled || isLocalAuthEnabled + then [C.mkTmplFdWithData [relfile|auth/index.ts|] tmplData] + else [] + where + tmplData = + object + [ "isEmailAuthEnabled" .= isEmailAuthEnabled, + "isLocalAuthEnabled" .= isLocalAuthEnabled + ] + isEmailAuthEnabled = AS.Auth.isEmailAuthEnabled auth + isLocalAuthEnabled = AS.Auth.isUsernameAndPasswordAuthEnabled auth + +genProvidersTypes :: AS.Auth.Auth -> Generator FileDraft +genProvidersTypes auth = return $ C.mkTmplFdWithData [relfile|auth/providers/types.ts|] tmplData + where + userEntityName = AS.refName $ AS.Auth.userEntity auth + + tmplData = object ["userEntityUpper" .= (userEntityName :: String)] diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Common.hs b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs new file mode 100644 index 0000000000..ed3cf975f0 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/Common.hs @@ -0,0 +1,62 @@ +module Wasp.Generator.SdkGenerator.Common where + +import qualified Data.Aeson as Aeson +import Data.Maybe (fromJust) +import StrongPath +import qualified StrongPath as SP +import Wasp.Generator.Common (ProjectRootDir) +import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir) +import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft) +import Wasp.Generator.Templates (TemplatesDir) + +data SdkRootDir + +data SdkTemplatesDir + +asTmplFile :: Path' (Rel d) File' -> Path' (Rel SdkTemplatesDir) File' +asTmplFile = SP.castRel + +mkTmplFdWithDstAndData :: + Path' (Rel SdkTemplatesDir) File' -> + Path' (Rel SdkRootDir) File' -> + Maybe Aeson.Value -> + FileDraft +mkTmplFdWithDstAndData relSrcPath relDstPath tmplData = + createTemplateFileDraft + (sdkRootDirInProjectRootDir relDstPath) + (sdkTemplatesDirInTemplatesDir relSrcPath) + tmplData + +mkTmplFdWithDst :: Path' (Rel SdkTemplatesDir) File' -> Path' (Rel SdkRootDir) File' -> FileDraft +mkTmplFdWithDst src dst = mkTmplFdWithDstAndData src dst Nothing + +mkTmplFdWithData :: + Path' (Rel SdkTemplatesDir) File' -> + Aeson.Value -> + FileDraft +mkTmplFdWithData relSrcPath tmplData = mkTmplFdWithDstAndData relSrcPath relDstPath (Just tmplData) + where + relDstPath = castRel relSrcPath + +mkTmplFd :: Path' (Rel SdkTemplatesDir) File' -> FileDraft +mkTmplFd path = mkTmplFdWithDst path (SP.castRel path) + +sdkRootDirInProjectRootDir :: Path' (Rel ProjectRootDir) (Dir SdkRootDir) +sdkRootDirInProjectRootDir = [reldir|sdk/wasp|] + +sdkTemplatesDirInTemplatesDir :: Path' (Rel TemplatesDir) (Dir SdkTemplatesDir) +sdkTemplatesDirInTemplatesDir = [reldir|sdk|] + +extSrcDirInSdkRootDir :: Path' (Rel SdkRootDir) (Dir GeneratedExternalCodeDir) +extSrcDirInSdkRootDir = [reldir|ext-src|] + +relDirToRelFileP :: Path Posix (Rel d) Dir' -> Path Posix (Rel d) File' +relDirToRelFileP path = fromJust $ SP.parseRelFileP $ removeTrailingSlash $ SP.fromRelDirP path + where + removeTrailingSlash = reverse . dropWhile (== '/') . reverse + +makeSdkImportPath :: Path Posix (Rel SdkRootDir) File' -> Path Posix (Rel s) File' +makeSdkImportPath path = [reldirP|wasp|] path + +extCodeDirInSdkRootDir :: Path' (Rel SdkRootDir) Dir' +extCodeDirInSdkRootDir = [reldir|ext-src|] diff --git a/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs new file mode 100644 index 0000000000..6f3b08284c --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/CrudG.hs @@ -0,0 +1,96 @@ +module Wasp.Generator.SdkGenerator.CrudG + ( genCrud, + getCrudTypesImportPathForName, + ) +where + +import Data.Aeson (KeyValue ((.=)), object) +import qualified Data.Aeson.Types as Aeson.Types +import Data.Maybe (fromJust) +import StrongPath + ( File', + Path, + Posix, + Rel, + reldir, + reldirP, + relfile, + (), + ) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec, getCruds) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Auth as AS.Auth +import qualified Wasp.AppSpec.Crud as AS.Crud +import Wasp.AppSpec.Valid (getApp, getIdFieldFromCrudEntity, isAuthEnabled) +import Wasp.Generator.Crud (crudDeclarationToOperationsList, getCrudFilePath, getCrudOperationJson, makeCrudOperationKeyAndJsonPair) +import Wasp.Generator.FileDraft (FileDraft) +import qualified Wasp.Generator.JsImport as GJI +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.ServerOpsGenerator (extImportToJsImport) +import Wasp.Util ((<++>)) + +genCrud :: AppSpec -> Generator [FileDraft] +genCrud spec = + if areThereAnyCruds + then + genCrudOperations spec cruds + <++> genCrudServerOperations spec cruds + else return [] + where + cruds = getCruds spec + areThereAnyCruds = not $ null cruds + +genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] +genCrudOperations spec cruds = return $ map genCrudOperation cruds + where + genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft + genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData) + where + tmplPath = [relfile|crud/_crud.ts|] + destPath = [reldir|crud|] fromJust (SP.parseRelFile (name ++ ".ts")) + tmplData = getCrudOperationJson name crud idField + idField = getIdFieldFromCrudEntity spec crud + +genCrudServerOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] +genCrudServerOperations spec cruds = return $ map genCrudOperation cruds + where + genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft + genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData) + where + tmplPath = [relfile|server/crud/_operationTypes.ts|] + destPath = [reldir|server/crud|] getCrudFilePath name "ts" + tmplData = + object + [ "crud" .= getCrudOperationJson name crud idField, + "isAuthEnabled" .= isAuthEnabled spec, + "userEntityUpper" .= maybeUserEntity, + "overrides" .= object overrides, + "queryType" .= queryTsType, + "actionType" .= actionTsType + ] + idField = getIdFieldFromCrudEntity spec crud + maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth + maybeAuth = AS.App.auth $ snd $ getApp spec + + queryTsType :: String + queryTsType = if isAuthEnabled spec then "AuthenticatedQuery" else "Query" + + actionTsType :: String + actionTsType = if isAuthEnabled spec then "AuthenticatedAction" else "Action" + + overrides :: [Aeson.Types.Pair] + overrides = map operationToOverrideImport crudOperations + + crudOperations = crudDeclarationToOperationsList crud + + operationToOverrideImport :: (AS.Crud.CrudOperation, AS.Crud.CrudOperationOptions) -> Aeson.Types.Pair + operationToOverrideImport (operation, options) = makeCrudOperationKeyAndJsonPair operation importJson + where + importJson = GJI.jsImportToImportJson $ extImportToJsImport <$> AS.Crud.overrideFn options + +getCrudTypesImportPathForName :: String -> Path Posix (Rel r) File' +getCrudTypesImportPathForName crudName = makeSdkImportPath $ [reldirP|server/crud|] fromJust (SP.parseRelFileP crudName) diff --git a/waspc/src/Wasp/Generator/ServerGenerator/EmailSender/Providers.hs b/waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs similarity index 89% rename from waspc/src/Wasp/Generator/ServerGenerator/EmailSender/Providers.hs rename to waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs index f12c26ed36..6bb0e13cd2 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/EmailSender/Providers.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/EmailSender/Providers.hs @@ -1,16 +1,16 @@ -module Wasp.Generator.ServerGenerator.EmailSender.Providers +module Wasp.Generator.SdkGenerator.EmailSender.Providers ( smtp, sendGrid, mailgun, dummy, - providersDirInServerSrc, + providersDirInSdkTemplatesDir, EmailSenderProvider (..), ) where import StrongPath (Dir, File', Path', Rel, reldir, relfile) import qualified Wasp.AppSpec.App.Dependency as AS.Dependency -import qualified Wasp.Generator.ServerGenerator.Common as C +import qualified Wasp.Generator.SdkGenerator.Common as C import qualified Wasp.SemanticVersion as SV data EmailSenderProvider = EmailSenderProvider @@ -24,6 +24,9 @@ data EmailSenderProvider = EmailSenderProvider data ProvidersDir +providersDirInSdkTemplatesDir :: Path' (Rel C.SdkTemplatesDir) (Dir ProvidersDir) +providersDirInSdkTemplatesDir = [reldir|email/core/providers|] + smtp :: EmailSenderProvider smtp = EmailSenderProvider @@ -73,6 +76,3 @@ dummy = setupFnFile = [relfile|dummy.ts|], isEnabledKey = "isDummyProviderUsed" } - -providersDirInServerSrc :: Path' (Rel C.ServerTemplatesSrcDir) (Dir ProvidersDir) -providersDirInServerSrc = [reldir|email/core/providers|] diff --git a/waspc/src/Wasp/Generator/ServerGenerator/EmailSenderG.hs b/waspc/src/Wasp/Generator/SdkGenerator/EmailSenderG.hs similarity index 79% rename from waspc/src/Wasp/Generator/ServerGenerator/EmailSenderG.hs rename to waspc/src/Wasp/Generator/SdkGenerator/EmailSenderG.hs index 0ba63ee8bc..9a669247c9 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/EmailSenderG.hs +++ b/waspc/src/Wasp/Generator/SdkGenerator/EmailSenderG.hs @@ -1,4 +1,4 @@ -module Wasp.Generator.ServerGenerator.EmailSenderG where +module Wasp.Generator.SdkGenerator.EmailSenderG where import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson @@ -13,8 +13,8 @@ import qualified Wasp.AppSpec.App.EmailSender as AS.EmailSender import Wasp.AppSpec.Valid (getApp) import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import qualified Wasp.Generator.ServerGenerator.Common as C -import qualified Wasp.Generator.ServerGenerator.EmailSender.Providers as Providers +import qualified Wasp.Generator.SdkGenerator.Common as C +import qualified Wasp.Generator.SdkGenerator.EmailSender.Providers as Providers import Wasp.Util ((<++>)) genEmailSender :: AppSpec -> Generator [FileDraft] @@ -29,9 +29,9 @@ genEmailSender spec = case maybeEmailSender of maybeEmailSender = AS.App.emailSender $ snd $ getApp spec genIndex :: EmailSender -> Generator FileDraft -genIndex email = return $ C.mkTmplFdWithData tmplPath (Just tmplData) +genIndex email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|src/email/index.ts|] + tmplPath = [relfile|email/index.ts|] tmplData = getEmailProvidersJson email genCore :: EmailSender -> Generator [FileDraft] @@ -44,24 +44,24 @@ genCore email = <++> genEmailSenderProviderSetupFn email genCoreIndex :: EmailSender -> Generator FileDraft -genCoreIndex email = return $ C.mkTmplFdWithData tmplPath (Just tmplData) +genCoreIndex email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|src/email/core/index.ts|] + tmplPath = [relfile|email/core/index.ts|] tmplData = getEmailProvidersJson email genCoreTypes :: EmailSender -> Generator FileDraft -genCoreTypes email = return $ C.mkTmplFdWithData tmplPath (Just tmplData) +genCoreTypes email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|src/email/core/types.ts|] + tmplPath = [relfile|email/core/types.ts|] tmplData = object ["isDefaultFromFieldDefined" .= isDefaultFromFieldDefined] isDefaultFromFieldDefined = isJust defaultFromField defaultFromField = AS.EmailSender.defaultFrom email genCoreHelpers :: EmailSender -> Generator FileDraft -genCoreHelpers email = return $ C.mkTmplFdWithData tmplPath (Just tmplData) +genCoreHelpers email = return $ C.mkTmplFdWithData tmplPath tmplData where - tmplPath = [relfile|src/email/core/helpers.ts|] + tmplPath = [relfile|email/core/helpers.ts|] tmplData = object [ "defaultFromField" @@ -86,7 +86,7 @@ genEmailSenderProviderSetupFn email = provider :: Providers.EmailSenderProvider provider = getEmailSenderProvider email - tmplPath = Providers.providersDirInServerSrc Providers.setupFnFile provider + tmplPath = Providers.providersDirInSdkTemplatesDir Providers.setupFnFile provider depsRequiredByEmail :: AppSpec -> [AS.Dependency.Dependency] depsRequiredByEmail spec = maybeToList maybeNpmDepedency @@ -110,5 +110,5 @@ getEmailSenderProvider email = case AS.EmailSender.provider email of AS.EmailSender.Mailgun -> Providers.mailgun AS.EmailSender.Dummy -> Providers.dummy -genFileCopy :: Path' (Rel C.ServerTemplatesSrcDir) File' -> Generator FileDraft -genFileCopy = return . C.mkSrcTmplFd +genFileCopy :: Path' (Rel C.SdkTemplatesDir) File' -> Generator FileDraft +genFileCopy = return . C.mkTmplFd diff --git a/waspc/src/Wasp/Generator/SdkGenerator/JobGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/JobGenerator.hs new file mode 100644 index 0000000000..4619d787e8 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/JobGenerator.hs @@ -0,0 +1,48 @@ +module Wasp.Generator.SdkGenerator.JobGenerator (genJobTypes, getImportPathForJobName, getJobExecutorTypesImportPath) where + +import Data.Aeson (object, (.=)) +import Data.Maybe (fromJust) +import StrongPath (File', Path, Posix, Rel, reldir, relfile, relfileP, ()) +import qualified StrongPath as SP +import StrongPath.TH (reldirP) +import Wasp.AppSpec (AppSpec, getJobs) +import qualified Wasp.AppSpec as AS +import Wasp.AppSpec.Job (Job, JobExecutor (PgBoss)) +import qualified Wasp.AppSpec.Job as J +import Wasp.Generator.Common (makeJsonWithEntityData) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Util + +genJobTypes :: AppSpec -> Generator [FileDraft] +genJobTypes spec = case getJobs spec of + [] -> return [] + jobs -> return $ map genJobType jobs + +genJobType :: (String, Job) -> FileDraft +genJobType (jobName, job) = + C.mkTmplFdWithDstAndData + tmplFile + dstFile + $ Just tmplData + where + tmplFile = [relfile|jobs/_jobTypes.ts|] + dstFile = [reldir|jobs|] fromJust (SP.parseRelFile $ jobName ++ ".ts") + tmplData = + object + [ "typeName" .= toUpperFirst jobName, + "jobExecutorTypesImportPath" .= SP.fromRelFileP jobExecutorTypesImportPath, + "entities" .= maybe [] (map (makeJsonWithEntityData . AS.refName)) (J.entities job) + ] + + jobExecutorTypesImportPath = getJobExecutorTypesImportPath (J.executor job) + +getImportPathForJobName :: String -> Path Posix (Rel d) File' +getImportPathForJobName jobName = makeSdkImportPath $ [reldirP|jobs|] fromJust (SP.parseRelFileP jobName) + +-- | We are importing relevant types per executor e.g. JobFn, this functions maps +-- the executor to the import path of the relevant types. +getJobExecutorTypesImportPath :: JobExecutor -> Path Posix (Rel r) File' +getJobExecutorTypesImportPath PgBoss = makeSdkImportPath [relfileP|jobs/pgBoss/types|] \ No newline at end of file diff --git a/waspc/src/Wasp/Generator/SdkGenerator/RouterGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/RouterGenerator.hs new file mode 100644 index 0000000000..c5f89fae90 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/RouterGenerator.hs @@ -0,0 +1,49 @@ +module Wasp.Generator.SdkGenerator.RouterGenerator + ( genRouter, + ) +where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import StrongPath (relfile) +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.Route as AS.Route +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Util.WebRouterPath (Param (Optional, Required), extractPathParams) + +genRouter :: AppSpec -> Generator [FileDraft] +genRouter spec = + sequence + [ genRouterTsx spec, + genFileCopy [relfile|router/types.ts|], + genFileCopy [relfile|router/linkHelpers.ts|], + genFileCopy [relfile|router/Link.tsx|] + ] + where + genFileCopy = return . C.mkTmplFd + +genRouterTsx :: AppSpec -> Generator FileDraft +genRouterTsx spec = return $ C.mkTmplFdWithData [relfile|router/index.ts|] tmplData + where + tmplData = + object ["routes" .= map createRouteTemplateData (AS.getRoutes spec)] + +createRouteTemplateData :: (String, AS.Route.Route) -> Aeson.Value +createRouteTemplateData (name, route) = + object + [ "name" .= name, + "urlPath" .= path, + "urlParams" .= map mapPathParamToJson urlParams, + "hasUrlParams" .= (not . null $ urlParams) + ] + where + path = AS.Route.path route + + urlParams = extractPathParams path + + mapPathParamToJson :: Param -> Aeson.Value + mapPathParamToJson (Required paramName) = object ["name" .= paramName, "isOptional" .= False] + mapPathParamToJson (Optional paramName) = object ["name" .= paramName, "isOptional" .= True] diff --git a/waspc/src/Wasp/Generator/SdkGenerator/RpcGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/RpcGenerator.hs new file mode 100644 index 0000000000..d532e00cf0 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/RpcGenerator.hs @@ -0,0 +1,123 @@ +module Wasp.Generator.SdkGenerator.RpcGenerator (genRpc) where + +import Data.Aeson (KeyValue ((.=)), object) +import qualified Data.Aeson as Aeson +import Data.Aeson.Types (Pair) +import Data.Maybe (fromJust) +import StrongPath (relfile) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec (..)) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.Action as AS.Action +import qualified Wasp.AppSpec.Operation as AS.Operation +import qualified Wasp.AppSpec.Query as AS.Query +import Wasp.Generator.Common (makeJsArrayFromHaskellList) +import Wasp.Generator.FileDraft (FileDraft) +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (makeSdkImportPath, relDirToRelFileP) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.ServerOpsGenerator (serverOperationsDirInSdkRootDir) +import qualified Wasp.Generator.ServerGenerator as ServerGenerator +import qualified Wasp.Generator.ServerGenerator.OperationsRoutesG as ServerOperationsRoutesG +import Wasp.JsImport (JsImportName (JsImportField), JsImportPath (ModuleImportPath), getJsImportStmtAndIdentifier, makeJsImport) +import Wasp.Util (toUpperFirst, (<++>)) + +genRpc :: AppSpec -> Generator [FileDraft] +genRpc spec = + sequence + [ genFileCopy [relfile|rpc/index.ts|], + genFileCopy [relfile|rpc/queryClient.ts|] + ] + <++> genQueries spec + <++> genActions spec + where + genFileCopy = return . C.mkTmplFd + +genQueries :: AppSpec -> Generator [FileDraft] +genQueries spec = + (:) <$> genQueriesIndex spec + <*> return + [ C.mkTmplFd [relfile|rpc/queries/core.js|], + C.mkTmplFd [relfile|rpc/queries/core.d.ts|] + ] + +genActions :: AppSpec -> Generator [FileDraft] +genActions spec = + (:) <$> genActionsIndex spec + <*> return + [ C.mkTmplFd [relfile|rpc/actions/core.js|], + C.mkTmplFd [relfile|rpc/actions/core.d.ts|] + ] + +genQueriesIndex :: AppSpec -> Generator FileDraft +genQueriesIndex spec = return $ C.mkTmplFdWithData relPath tmplData + where + relPath = [relfile|rpc/queries/index.ts|] + tmplData = + object + [ "queries" .= map getQueryData (AS.getQueries spec) + ] + +genActionsIndex :: AppSpec -> Generator FileDraft +genActionsIndex spec = return $ C.mkTmplFdWithData relPath tmplData + where + relPath = [relfile|rpc/actions/index.ts|] + tmplData = + object + [ "actions" .= map getActionData (AS.getActions spec) + ] + +getQueryData :: (String, AS.Query.Query) -> Aeson.Value +getQueryData (queryName, query) = + object $ + [ "queryRoute" + .= ( ServerGenerator.operationsRouteInRootRouter + ++ "/" + ++ ServerOperationsRoutesG.operationRouteInOperationsRouter operation + ), + "entitiesArray" .= makeJsArrayOfEntityNames operation + ] + ++ getOperationTypeData operation + where + operation = AS.Operation.QueryOp queryName query + +getActionData :: (String, AS.Action.Action) -> Aeson.Value +getActionData (actionName, action) = + object $ + [ "actionRoute" + .= ( ServerGenerator.operationsRouteInRootRouter + ++ "/" + ++ ServerOperationsRoutesG.operationRouteInOperationsRouter operation + ), + "entitiesArray" .= makeJsArrayOfEntityNames operation + ] + ++ getOperationTypeData operation + where + operation = AS.Operation.ActionOp actionName action + +-- | Generates string that is JS array containing names (as strings) of entities being used by given operation. +-- E.g. "['Task', 'Project']" +makeJsArrayOfEntityNames :: AS.Operation.Operation -> String +makeJsArrayOfEntityNames operation = makeJsArrayFromHaskellList entityNames + where + entityNames = maybe [] (map $ \x -> AS.refName x) (AS.Operation.getEntities operation) + +getOperationTypeData :: AS.Operation.Operation -> [Pair] +getOperationTypeData operation = tmplData + where + tmplData = + [ "operationTypeImportStmt" .= operationTypeImportStmt, + "operationTypeName" .= operationTypeImportIdentifier, + "operationName" .= operationName + ] + + operationName = AS.Operation.getName operation + + (operationTypeImportStmt, operationTypeImportIdentifier) = + getJsImportStmtAndIdentifier $ + makeJsImport (ModuleImportPath serverOpsImportPath) (JsImportField $ toUpperFirst operationName) + serverOpsImportPath = + makeSdkImportPath $ + relDirToRelFileP $ + fromJust $ + SP.relDirToPosix $ serverOperationsDirInSdkRootDir operation diff --git a/waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs new file mode 100644 index 0000000000..63e82fb20e --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/ServerOpsGenerator.hs @@ -0,0 +1,160 @@ +{-# LANGUAGE TypeApplications #-} + +module Wasp.Generator.SdkGenerator.ServerOpsGenerator where + +import Data.Aeson (object, (.=)) +import qualified Data.Aeson as Aeson +import Data.List (nub) +import Data.Maybe (fromJust, fromMaybe) +import StrongPath (Dir', File', Path', Rel, reldir, relfile, ()) +import qualified StrongPath as SP +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.Action as AS.Action +import qualified Wasp.AppSpec.ExtImport as EI +import Wasp.AppSpec.Operation (getName) +import qualified Wasp.AppSpec.Operation as AS.Operation +import qualified Wasp.AppSpec.Query as AS.Query +import Wasp.AppSpec.Valid (isAuthEnabled) +import Wasp.Generator.Common (makeJsonWithEntityData) +import Wasp.Generator.FileDraft (FileDraft) +import qualified Wasp.Generator.JsImport as GJI +import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.Common (mkTmplFdWithData) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.JsImport (JsImport (..), JsImportPath (..)) +import qualified Wasp.JsImport as JI +import Wasp.Util (toUpperFirst) + +genOperations :: AppSpec -> Generator [FileDraft] +genOperations spec = + sequence + [ genQueryTypesFile spec, + genActionTypesFile spec, + genQueriesIndex spec, + genActionsIndex spec + ] + +genQueriesIndex :: AppSpec -> Generator FileDraft +genQueriesIndex spec = return $ mkTmplFdWithData relPath tmplData + where + relPath = [relfile|server/queries/index.ts|] + tmplData = + object + [ "operations" .= map getQueryData (AS.getQueries spec) + ] + +genActionsIndex :: AppSpec -> Generator FileDraft +genActionsIndex spec = return $ mkTmplFdWithData relPath tmplData + where + relPath = [relfile|server/actions/index.ts|] + tmplData = + object + [ "operations" .= map getActionData (AS.getActions spec) + ] + +genQueryTypesFile :: AppSpec -> Generator FileDraft +genQueryTypesFile spec = genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally + where + tmplFile = [relfile|server/queries/types.ts|] + dstFile = [relfile|server/queries/types.ts|] + operations = map (uncurry AS.Operation.QueryOp) $ AS.getQueries spec + isAuthEnabledGlobally = isAuthEnabled spec + +genActionTypesFile :: AppSpec -> Generator FileDraft +genActionTypesFile spec = genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally + where + tmplFile = [relfile|server/actions/types.ts|] + dstFile = [relfile|server/actions/types.ts|] + operations = map (uncurry AS.Operation.ActionOp) $ AS.getActions spec + isAuthEnabledGlobally = isAuthEnabled spec + +-- | Here we generate JS file that basically imports JS query function provided by user, +-- decorates it (mostly injects stuff into it) and exports. Idea is that the rest of the server, +-- and user also, should use this new JS function, and not the old one directly. +getQueryData :: (String, AS.Query.Query) -> Aeson.Value +getQueryData (queryName, query) = getOperationTmplData operation + where + operation = AS.Operation.QueryOp queryName query + +getActionData :: (String, AS.Action.Action) -> Aeson.Value +getActionData (actionName, action) = getOperationTmplData operation + where + operation = AS.Operation.ActionOp actionName action + +genOperationTypesFile :: + Path' (Rel C.SdkTemplatesDir) File' -> + Path' (Rel C.SdkRootDir) File' -> + [AS.Operation.Operation] -> + Bool -> + Generator FileDraft +genOperationTypesFile tmplFile dstFile operations isAuthEnabledGlobally = + return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) + where + tmplData = + object + [ "operations" .= map operationTypeData operations, + "shouldImportAuthenticatedOperation" .= any usesAuth operations, + "shouldImportNonAuthenticatedOperation" .= not (all usesAuth operations), + "allEntities" .= nub (concatMap getEntities operations) + ] + operationTypeData operation = + object + [ "typeName" .= toUpperFirst (getName operation), + "entities" .= getEntities operation, + "usesAuth" .= usesAuth operation + ] + getEntities = map makeJsonWithEntityData . maybe [] (map AS.refName) . AS.Operation.getEntities + usesAuth = fromMaybe isAuthEnabledGlobally . AS.Operation.getAuth + +serverOperationsDirInSdkRootDir :: AS.Operation.Operation -> Path' (Rel C.SdkRootDir) Dir' +serverOperationsDirInSdkRootDir (AS.Operation.QueryOp _ _) = [reldir|server/queries|] +serverOperationsDirInSdkRootDir (AS.Operation.ActionOp _ _) = [reldir|server/actions|] + +getOperationTmplData :: AS.Operation.Operation -> Aeson.Value +getOperationTmplData operation = + object + [ "jsFn" .= extOperationImportToImportJson (AS.Operation.getFn operation), + "operationName" .= getName operation, + "operationTypeName" .= toUpperFirst (getName operation), + "entities" + .= maybe [] (map (makeJsonWithEntityData . AS.refName)) (AS.Operation.getEntities operation) + ] + +extOperationImportToImportJson :: EI.ExtImport -> Aeson.Value +extOperationImportToImportJson = + GJI.jsImportToImportJson + . Just + . applyExtImportAlias + . extImportToJsImport + +applyExtImportAlias :: JsImport -> JsImport +applyExtImportAlias jsImport = + jsImport {_importAlias = Just $ JI.getImportIdentifier jsImport ++ "_ext"} + +extImportToJsImport :: EI.ExtImport -> JsImport +extImportToJsImport extImport@(EI.ExtImport extImportName extImportPath) = + JsImport + { _path = ModuleImportPath importPath, + _name = importName, + _importAlias = Just $ EI.importIdentifier extImport ++ "_ext" + } + where + importPath = C.makeSdkImportPath $ extCodeDirP SP.castRel extImportPath + extCodeDirP = fromJust $ SP.relDirToPosix C.extCodeDirInSdkRootDir + importName = GJI.extImportNameToJsImportName extImportName + +-- extImportToImportJson :: EI.ExtImport -> Aeson.Value +-- extImportToImportJson extImport@(EI.ExtImport importName importPath) = +-- object +-- [ "isDefined" .= True, +-- "importStatement" .= Debug.trace jsImportStmt jsImportStmt, +-- "importIdentifier" .= importAlias +-- ] +-- where +-- jsImportStmt = case importName of +-- EI.ExtImportModule n -> "import " ++ n ++ " from '" ++ importPathStr ++ "'" +-- EI.ExtImportField n -> "import { " ++ n ++ " as " ++ importAlias ++ " } from '" ++ importPathStr ++ "'" +-- importPathStr = C.makeSdkImportPath $ extCodeDirP SP.castRel importPath +-- extCodeDirP = fromJust $ SP.relDirToPosix C.extCodeDirInSdkRootDir +-- importAlias = EI.importIdentifier extImport ++ "User" diff --git a/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs new file mode 100644 index 0000000000..8a97d26025 --- /dev/null +++ b/waspc/src/Wasp/Generator/SdkGenerator/WebSocketGenerator.hs @@ -0,0 +1,58 @@ +module Wasp.Generator.SdkGenerator.WebSocketGenerator + ( genWebSockets, + depsRequiredByWebSockets, + ) +where + +import Data.Aeson (object, (.=)) +import Data.Char (toLower) +import StrongPath (relfile) +import Wasp.AppSpec (AppSpec) +import qualified Wasp.AppSpec as AS +import qualified Wasp.AppSpec.App as AS.App +import qualified Wasp.AppSpec.App.Dependency as AS.Dependency +import qualified Wasp.AppSpec.App.WebSocket as AS.App.WS +import Wasp.AppSpec.Valid (getApp, isAuthEnabled) +import Wasp.Generator.Common (makeJsonWithEntityData) +import Wasp.Generator.FileDraft (FileDraft) +import qualified Wasp.Generator.JsImport as GJI +import Wasp.Generator.Monad (Generator) +import qualified Wasp.Generator.SdkGenerator.Common as C +import Wasp.Generator.SdkGenerator.ServerOpsGenerator (extImportToJsImport) +import qualified Wasp.Generator.WebSocket as AS.WS + +genWebSockets :: AppSpec -> Generator [FileDraft] +genWebSockets spec + | AS.WS.areWebSocketsUsed spec = + sequence + [ genWebSocketServerIndex spec, + genFileCopy [relfile|webSocket/index.ts|], + genWebSocketProvider spec + ] + | otherwise = return [] + where + genFileCopy = return . C.mkTmplFd + +genWebSocketServerIndex :: AppSpec -> Generator FileDraft +genWebSocketServerIndex spec = return $ C.mkTmplFdWithData [relfile|server/webSocket/index.ts|] tmplData + where + tmplData = + object + [ "isAuthEnabled" .= isAuthEnabled spec, + "userWebSocketFn" .= GJI.jsImportToImportJson (extImportToJsImport <$> mayebWebSocketFn), + "allEntities" .= map (makeJsonWithEntityData . fst) (AS.getEntities spec) + ] + maybeWebSocket = AS.App.webSocket $ snd $ getApp spec + mayebWebSocketFn = AS.App.WS.fn <$> maybeWebSocket + +genWebSocketProvider :: AppSpec -> Generator FileDraft +genWebSocketProvider spec = return $ C.mkTmplFdWithData [relfile|webSocket/WebSocketProvider.tsx|] tmplData + where + maybeWebSocket = AS.App.webSocket $ snd $ getApp spec + shouldAutoConnect = (AS.App.WS.autoConnect <$> maybeWebSocket) /= Just (Just False) + tmplData = object ["autoConnect" .= map toLower (show shouldAutoConnect)] + +depsRequiredByWebSockets :: AppSpec -> [AS.Dependency.Dependency] +depsRequiredByWebSockets spec + | AS.WS.areWebSocketsUsed spec = AS.WS.sdkDepsRequiredForWebSockets + | otherwise = [] diff --git a/waspc/src/Wasp/Generator/ServerGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator.hs index 9541069cbf..55c49002a2 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator.hs @@ -15,9 +15,7 @@ import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.UTF8 as ByteStringLazyUTF8 import Data.Maybe - ( fromJust, - fromMaybe, - isJust, + ( isJust, maybeToList, ) import StrongPath @@ -27,7 +25,6 @@ import StrongPath Path', Posix, Rel, - reldir, reldirP, relfile, (), @@ -44,9 +41,7 @@ import Wasp.AppSpec.Valid (getApp, getLowestNodeVersionUserAllows, isAuthEnabled import Wasp.Env (envVarsToDotEnvContent) import Wasp.Generator.Common ( ServerRootDir, - makeJsonWithEntityData, ) -import qualified Wasp.Generator.DbGenerator.Auth as DbAuth import Wasp.Generator.FileDraft (FileDraft, createTextFileDraft) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N @@ -54,10 +49,8 @@ import Wasp.Generator.ServerGenerator.ApiRoutesG (genApis) import Wasp.Generator.ServerGenerator.Auth.OAuthAuthG (depsRequiredByPassport) import Wasp.Generator.ServerGenerator.AuthG (genAuth) import qualified Wasp.Generator.ServerGenerator.Common as C -import Wasp.Generator.ServerGenerator.ConfigG (genConfigFile) import Wasp.Generator.ServerGenerator.CrudG (genCrud) import Wasp.Generator.ServerGenerator.Db.Seed (genDbSeed, getPackageJsonPrismaSeedField) -import Wasp.Generator.ServerGenerator.EmailSenderG (depsRequiredByEmail, genEmailSender) import Wasp.Generator.ServerGenerator.JobGenerator (depsRequiredByJobs, genJobExecutors, genJobs) import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson, getAliasedJsImportStmtAndIdentifier) import Wasp.Generator.ServerGenerator.OperationsG (genOperations) @@ -66,7 +59,7 @@ import Wasp.Generator.ServerGenerator.WebSocketG (depsRequiredByWebSockets, genW import qualified Wasp.Node.Version as NodeVersion import Wasp.Project.Db (databaseUrlEnvVarName) import qualified Wasp.SemanticVersion as SV -import Wasp.Util (toLowerFirst, (<++>)) +import Wasp.Util ((<++>)) genServer :: AppSpec -> Generator [FileDraft] genServer spec = @@ -79,16 +72,11 @@ genServer spec = genGitignore ] <++> genSrcDir spec - -- Filip: I don't generate external source folders as we're importing the user's code direclty (see ServerGenerator/JsImport.hs). - -- <++> genExternalCodeDir extServerCodeGeneratorStrategy (AS.externalServerFiles spec) - -- <++> genExternalCodeDir extSharedCodeGeneratorStrategy (AS.externalSharedFiles spec) <++> genDotEnv spec <++> genJobs spec <++> genJobExecutors spec <++> genPatches spec - <++> genUniversalDir <++> genEnvValidationScript - <++> genExportedTypesDir spec <++> genApis spec <++> genCrud spec where @@ -171,13 +159,11 @@ npmDepsForWasp spec = ("helmet", "^6.0.0"), ("patch-package", "^6.4.7"), ("uuid", "^9.0.0"), - ("lodash.merge", "^4.6.2"), ("rate-limiter-flexible", "^2.4.1"), ("superjson", "^1.12.2") ] ++ depsRequiredByPassport spec ++ depsRequiredByJobs spec - ++ depsRequiredByEmail spec ++ depsRequiredByWebSockets spec, N.waspDevDependencies = AS.Dependency.fromList @@ -217,42 +203,19 @@ genSrcDir :: AppSpec -> Generator [FileDraft] genSrcDir spec = sequence [ genFileCopy [relfile|app.js|], - genDbClient spec, - genConfigFile spec, genServerJs spec, genFileCopy [relfile|polyfill.ts|] ] - <++> genServerUtils spec <++> genRoutesDir spec - <++> genTypesAndEntitiesDirs spec <++> genOperationsRoutes spec <++> genOperations spec <++> genAuth spec - <++> genEmailSender spec <++> genDbSeed spec <++> genMiddleware spec <++> genWebSockets spec where genFileCopy = return . C.mkSrcTmplFd -genDbClient :: AppSpec -> Generator FileDraft -genDbClient spec = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - maybeAuth = AS.App.auth $ snd $ getApp spec - - dbClientRelToSrcP = [relfile|dbClient.ts|] - tmplFile = C.asTmplFile $ [reldir|src|] dbClientRelToSrcP - dstFile = C.serverSrcDirInServerRootDir C.asServerSrcFile dbClientRelToSrcP - - tmplData = - if isJust maybeAuth - then - object - [ "isAuthEnabled" .= True, - "userEntityUpper" .= (AS.refName (AS.App.Auth.userEntity $ fromJust maybeAuth) :: String) - ] - else object [] - genServerJs :: AppSpec -> Generator FileDraft genServerJs spec = return $ @@ -295,55 +258,6 @@ genRoutesIndex spec = "areThereAnyCrudRoutes" .= (not . null $ AS.getCruds spec) ] -genTypesAndEntitiesDirs :: AppSpec -> Generator [FileDraft] -genTypesAndEntitiesDirs spec = - return - [ entitiesIndexFileDraft, - taggedEntitiesFileDraft, - serializationFileDraft, - typesIndexFileDraft - ] - where - entitiesIndexFileDraft = - C.mkTmplFdWithDstAndData - [relfile|src/entities/index.ts|] - [relfile|src/entities/index.ts|] - ( Just $ - object - [ "entities" .= allEntities, - "isAuthEnabled" .= isJust maybeUserEntityName, - "authEntityName" .= DbAuth.authEntityName, - "authIdentityEntityName" .= DbAuth.authIdentityEntityName - ] - ) - taggedEntitiesFileDraft = - C.mkTmplFdWithDstAndData - [relfile|src/_types/taggedEntities.ts|] - [relfile|src/_types/taggedEntities.ts|] - (Just $ object ["entities" .= allEntities]) - serializationFileDraft = - C.mkSrcTmplFd - [relfile|_types/serialization.ts|] - typesIndexFileDraft = - C.mkTmplFdWithDstAndData - [relfile|src/_types/index.ts|] - [relfile|src/_types/index.ts|] - ( Just $ - object - [ "entities" .= allEntities, - "isAuthEnabled" .= isJust maybeUserEntityName, - "userEntityName" .= userEntityName, - "authEntityName" .= DbAuth.authEntityName, - "authFieldOnUserEntityName" .= DbAuth.authFieldOnUserEntityName, - "authIdentityEntityName" .= DbAuth.authIdentityEntityName, - "identitiesFieldOnAuthEntityName" .= DbAuth.identitiesFieldOnAuthEntityName, - "userFieldName" .= toLowerFirst userEntityName - ] - ) - userEntityName = fromMaybe "" maybeUserEntityName - allEntities = map (makeJsonWithEntityData . fst) $ AS.getDecls @AS.Entity.Entity spec - maybeUserEntityName = AS.refName . AS.App.Auth.userEntity <$> AS.App.auth (snd $ getApp spec) - operationsRouteInRootRouter :: String operationsRouteInRootRouter = "operations" @@ -391,35 +305,12 @@ getPackageJsonOverrides = map buildOverrideData (designateLastElement overrides) map (\(x1, x2, x3) -> (x1, x2, x3, False)) (init l) ++ map (\(x1, x2, x3) -> (x1, x2, x3, True)) [last l] -genUniversalDir :: Generator [FileDraft] -genUniversalDir = - return - [ C.mkUniversalTmplFdWithDst [relfile|url.ts|] [relfile|src/universal/url.ts|], - C.mkUniversalTmplFdWithDst [relfile|types.ts|] [relfile|src/universal/types.ts|] - ] - genEnvValidationScript :: Generator [FileDraft] genEnvValidationScript = return - [ C.mkTmplFd [relfile|scripts/validate-env.mjs|], - C.mkUniversalTmplFdWithDst [relfile|validators.js|] [relfile|scripts/universal/validators.mjs|] + [ C.mkTmplFd [relfile|scripts/validate-env.mjs|] ] -genExportedTypesDir :: AppSpec -> Generator [FileDraft] -genExportedTypesDir spec = - return - [ C.mkTmplFdWithData [relfile|src/types/index.ts|] (Just tmplData) - ] - where - tmplData = - object - [ "isExternalAuthEnabled" .= isExternalAuthEnabled, - "isEmailAuthEnabled" .= isEmailAuthEnabled - ] - isExternalAuthEnabled = AS.App.Auth.isExternalAuthEnabled <$> maybeAuth - isEmailAuthEnabled = AS.App.Auth.isEmailAuthEnabled <$> maybeAuth - maybeAuth = AS.App.auth $ snd $ getApp spec - genMiddleware :: AppSpec -> Generator [FileDraft] genMiddleware spec = sequence @@ -453,8 +344,3 @@ genOperationsMiddleware spec = (Just tmplData) where tmplData = object ["isAuthEnabled" .= (isAuthEnabled spec :: Bool)] - -genServerUtils :: AppSpec -> Generator [FileDraft] -genServerUtils spec = return [C.mkTmplFdWithData [relfile|src/utils.ts|] (Just tmplData)] - where - tmplData = object ["isAuthEnabled" .= (isAuthEnabled spec :: Bool)] diff --git a/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs b/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs index 9962cf85da..14e55b68c3 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/ApiRoutesG.hs @@ -1,12 +1,13 @@ module Wasp.Generator.ServerGenerator.ApiRoutesG ( genApis, + getApiEntitiesObject, + isAuthEnabledForApi, ) where import Data.Aeson (object, (.=)) import qualified Data.Aeson as Aeson import Data.Char (toLower) -import Data.List (nub) import Data.Maybe (fromMaybe, isJust) import StrongPath (Dir, File', Path, Path', Posix, Rel, reldirP, relfile) import qualified StrongPath as SP @@ -20,15 +21,13 @@ import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.JsImport (getAliasedJsImportStmtAndIdentifier) -import Wasp.Util (toUpperFirst) genApis :: AppSpec -> Generator [FileDraft] genApis spec = if areThereAnyCustomApiRoutes then sequence - [ genApiRoutes spec, - genApiTypes spec + [ genApiRoutes spec ] else return [] where @@ -88,31 +87,6 @@ genApiRoutes spec = relPathFromApisRoutesToServerSrcDir :: Path Posix (Rel importLocation) (Dir C.ServerSrcDir) relPathFromApisRoutesToServerSrcDir = [reldirP|../..|] -genApiTypes :: AppSpec -> Generator FileDraft -genApiTypes spec = - return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - namedApis = AS.getApis spec - apis = snd <$> namedApis - tmplData = - object - [ "apiRoutes" .= map getTmplData namedApis, - "shouldImportAuthenticatedApi" .= any usesAuth apis, - "shouldImportNonAuthenticatedApi" .= not (all usesAuth apis), - "allEntities" .= nub (concatMap getApiEntitiesObject apis) - ] - usesAuth = fromMaybe (isAuthEnabledGlobally spec) . Api.auth - tmplFile = C.asTmplFile [relfile|src/apis/types.ts|] - dstFile = SP.castRel tmplFile :: Path' (Rel ServerRootDir) File' - - getTmplData :: (String, Api.Api) -> Aeson.Value - getTmplData (name, api) = - object - [ "typeName" .= toUpperFirst name, - "entities" .= getApiEntitiesObject api, - "usesAuth" .= isAuthEnabledForApi spec api - ] - getApiEntitiesObject :: Api.Api -> [Aeson.Value] getApiEntitiesObject api = maybe [] (map (makeJsonWithEntityData . AS.refName)) (Api.entities api) diff --git a/waspc/src/Wasp/Generator/ServerGenerator/Auth/EmailAuthG.hs b/waspc/src/Wasp/Generator/ServerGenerator/Auth/EmailAuthG.hs index 26226783fe..78769f2343 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/Auth/EmailAuthG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/Auth/EmailAuthG.hs @@ -25,21 +25,18 @@ import qualified Wasp.AppSpec.App.EmailSender as AS.EmailSender import Wasp.AppSpec.Util (getRoutePathFromRef) import Wasp.Generator.AuthProviders (emailAuthProvider) import qualified Wasp.Generator.AuthProviders.Email as Email -import qualified Wasp.Generator.DbGenerator.Auth as DbAuth import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson) import Wasp.Util ((<++>)) -import qualified Wasp.Util as Util genEmailAuth :: AS.AppSpec -> AS.Auth.Auth -> Generator [FileDraft] genEmailAuth spec auth = case emailAuth of Just emailAuthConfig -> sequence [ genEmailAuthConfig spec emailAuthConfig, - genTypes emailAuthConfig, - genUtils auth + genTypes emailAuthConfig ] <++> genRoutes Nothing -> return [] @@ -109,17 +106,3 @@ genTypes _emailAuthConfig = return $ C.mkTmplFdWithData tmplFile (Just tmplData) where tmplFile = C.srcDirInServerTemplatesDir [relfile|auth/providers/email/types.ts|] tmplData = object [] - -genUtils :: AS.Auth.Auth -> Generator FileDraft -genUtils auth = return $ C.mkTmplFdWithData tmplFile (Just tmplData) - where - userEntityName = AS.refName $ AS.Auth.userEntity auth - tmplFile = C.srcDirInServerTemplatesDir [relfile|auth/providers/email/utils.ts|] - tmplData = - object - [ "userEntityUpper" .= (userEntityName :: String), - "userEntityLower" .= (Util.toLowerFirst userEntityName :: String), - "authEntityUpper" .= (DbAuth.authEntityName :: String), - "authEntityLower" .= (Util.toLowerFirst DbAuth.authEntityName :: String), - "userFieldOnAuthEntityName" .= (DbAuth.userFieldOnAuthEntityName :: String) - ] diff --git a/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs b/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs index 83560ffc7c..e4affdb9a6 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs @@ -5,18 +5,15 @@ module Wasp.Generator.ServerGenerator.AuthG where import Data.Aeson (object, (.=)) -import Data.Maybe (fromMaybe) import StrongPath ( File', Path', Rel, - reldir, relfile, (), ) import qualified StrongPath as SP import Wasp.AppSpec (AppSpec) -import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.App as AS.App import qualified Wasp.AppSpec.App.Auth as AS.Auth import qualified Wasp.AppSpec.App.Dependency as AS.Dependency @@ -25,7 +22,6 @@ import Wasp.Generator.AuthProviders (emailAuthProvider, gitHubAuthProvider, goog import qualified Wasp.Generator.AuthProviders.Email as EmailProvider import qualified Wasp.Generator.AuthProviders.Local as LocalProvider import qualified Wasp.Generator.AuthProviders.OAuth as OAuthProvider -import qualified Wasp.Generator.DbGenerator.Auth as DbAuth import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) import Wasp.Generator.ServerGenerator.Auth.EmailAuthG (genEmailAuth) @@ -33,50 +29,24 @@ import Wasp.Generator.ServerGenerator.Auth.LocalAuthG (genLocalAuth) import Wasp.Generator.ServerGenerator.Auth.OAuthAuthG (genOAuthAuth) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Util ((<++>)) -import qualified Wasp.Util as Util genAuth :: AppSpec -> Generator [FileDraft] genAuth spec = case maybeAuth of + Nothing -> return [] Just auth -> sequence - [ genCoreAuth auth, - genAuthRoutesIndex auth, + [ genAuthRoutesIndex auth, genFileCopy [relfile|routes/auth/me.js|], genFileCopy [relfile|routes/auth/logout.ts|], - genUtils auth, - genProvidersIndex auth, - genProvidersTypes auth, - genFileCopy [relfile|auth/validation.ts|], - genFileCopy [relfile|auth/user.ts|], - genFileCopy [relfile|auth/password.ts|], - genFileCopy [relfile|auth/jwt.ts|], - genSessionTs auth, - genLuciaTs auth + genProvidersIndex auth ] - <++> genIndexTs auth <++> genLocalAuth auth <++> genOAuthAuth auth <++> genEmailAuth spec auth - Nothing -> return [] where maybeAuth = AS.App.auth $ snd $ getApp spec genFileCopy = return . C.mkSrcTmplFd --- | Generates core/auth file which contains auth middleware and createUser() function. -genCoreAuth :: AS.Auth.Auth -> Generator FileDraft -genCoreAuth auth = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - coreAuthRelToSrc = [relfile|core/auth.js|] - tmplFile = C.asTmplFile $ [reldir|src|] coreAuthRelToSrc - dstFile = C.serverSrcDirInServerRootDir C.asServerSrcFile coreAuthRelToSrc - - tmplData = - let userEntityName = AS.refName $ AS.Auth.userEntity auth - in object - [ "userEntityUpper" .= (userEntityName :: String), - "userEntityLower" .= (Util.toLowerFirst userEntityName :: String) - ] - genAuthRoutesIndex :: AS.Auth.Auth -> Generator FileDraft genAuthRoutesIndex auth = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) where @@ -88,48 +58,6 @@ genAuthRoutesIndex auth = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Ju authIndexFileInSrcDir :: Path' (Rel C.ServerSrcDir) File' authIndexFileInSrcDir = [relfile|routes/auth/index.js|] -genUtils :: AS.Auth.Auth -> Generator FileDraft -genUtils auth = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - userEntityName = AS.refName $ AS.Auth.userEntity auth - tmplFile = C.srcDirInServerTemplatesDir SP.castRel utilsFileInSrcDir - dstFile = C.serverSrcDirInServerRootDir utilsFileInSrcDir - tmplData = - object - [ "userEntityUpper" .= (userEntityName :: String), - "userEntityLower" .= (Util.toLowerFirst userEntityName :: String), - "authEntityUpper" .= (DbAuth.authEntityName :: String), - "authEntityLower" .= (Util.toLowerFirst DbAuth.authEntityName :: String), - "userFieldOnAuthEntityName" .= (DbAuth.userFieldOnAuthEntityName :: String), - "authIdentityEntityUpper" .= (DbAuth.authIdentityEntityName :: String), - "authIdentityEntityLower" .= (Util.toLowerFirst DbAuth.authIdentityEntityName :: String), - "authFieldOnUserEntityName" .= (DbAuth.authFieldOnUserEntityName :: String), - "identitiesFieldOnAuthEntityName" .= (DbAuth.identitiesFieldOnAuthEntityName :: String), - "failureRedirectPath" .= AS.Auth.onAuthFailedRedirectTo auth, - "successRedirectPath" .= getOnAuthSucceededRedirectToOrDefault auth - ] - - utilsFileInSrcDir :: Path' (Rel C.ServerSrcDir) File' - utilsFileInSrcDir = [relfile|auth/utils.ts|] - -genIndexTs :: AS.Auth.Auth -> Generator [FileDraft] -genIndexTs auth = - return $ - if isEmailAuthEnabled || isLocalAuthEnabled - then [C.mkTmplFdWithData [relfile|src/auth/index.ts|] (Just tmplData)] - else [] - where - tmplData = - object - [ "isEmailAuthEnabled" .= isEmailAuthEnabled, - "isLocalAuthEnabled" .= isLocalAuthEnabled - ] - isEmailAuthEnabled = AS.Auth.isEmailAuthEnabled auth - isLocalAuthEnabled = AS.Auth.isUsernameAndPasswordAuthEnabled auth - -getOnAuthSucceededRedirectToOrDefault :: AS.Auth.Auth -> String -getOnAuthSucceededRedirectToOrDefault auth = fromMaybe "/" (AS.Auth.onAuthSucceededRedirectTo auth) - genProvidersIndex :: AS.Auth.Auth -> Generator FileDraft genProvidersIndex auth = return $ C.mkTmplFdWithData [relfile|src/auth/providers/index.ts|] (Just tmplData) where @@ -143,38 +71,6 @@ genProvidersIndex auth = return $ C.mkTmplFdWithData [relfile|src/auth/providers [EmailProvider.providerId emailAuthProvider | AS.Auth.isEmailAuthEnabled auth] ] -genProvidersTypes :: AS.Auth.Auth -> Generator FileDraft -genProvidersTypes auth = return $ C.mkTmplFdWithData [relfile|src/auth/providers/types.ts|] (Just tmplData) - where - userEntityName = AS.refName $ AS.Auth.userEntity auth - - tmplData = object ["userEntityUpper" .= (userEntityName :: String)] - -genLuciaTs :: AS.Auth.Auth -> Generator FileDraft -genLuciaTs auth = return $ C.mkTmplFdWithData [relfile|src/auth/lucia.ts|] (Just tmplData) - where - tmplData = - object - [ "sessionEntityLower" .= (Util.toLowerFirst DbAuth.sessionEntityName :: String), - "authEntityLower" .= (Util.toLowerFirst DbAuth.authEntityName :: String), - "userEntityUpper" .= (userEntityName :: String) - ] - - userEntityName = AS.refName $ AS.Auth.userEntity auth - -genSessionTs :: AS.Auth.Auth -> Generator FileDraft -genSessionTs auth = return $ C.mkTmplFdWithData [relfile|src/auth/session.ts|] (Just tmplData) - where - tmplData = - object - [ "userEntityUpper" .= userEntityName, - "userEntityLower" .= Util.toLowerFirst userEntityName, - "authFieldOnUserEntityName" .= DbAuth.authFieldOnUserEntityName, - "identitiesFieldOnAuthEntityName" .= DbAuth.identitiesFieldOnAuthEntityName - ] - - userEntityName = AS.refName $ AS.Auth.userEntity auth - depsRequiredByAuth :: AppSpec -> [AS.Dependency.Dependency] depsRequiredByAuth spec = maybe [] (const authDeps) maybeAuth where diff --git a/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs b/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs deleted file mode 100644 index 7d55fd8d07..0000000000 --- a/waspc/src/Wasp/Generator/ServerGenerator/ConfigG.hs +++ /dev/null @@ -1,30 +0,0 @@ -module Wasp.Generator.ServerGenerator.ConfigG - ( genConfigFile, - ) -where - -import Data.Aeson (object, (.=)) -import StrongPath (File', Path', Rel, relfile, ()) -import qualified StrongPath as SP -import Wasp.AppSpec (AppSpec) -import Wasp.AppSpec.Valid (isAuthEnabled) -import Wasp.Generator.FileDraft (FileDraft) -import Wasp.Generator.Monad (Generator) -import qualified Wasp.Generator.ServerGenerator.Common as C -import qualified Wasp.Generator.WebAppGenerator.Common as WebApp -import Wasp.Project.Db (databaseUrlEnvVarName) - -genConfigFile :: AppSpec -> Generator FileDraft -genConfigFile spec = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - tmplFile = C.srcDirInServerTemplatesDir SP.castRel configFileInSrcDir - dstFile = C.serverSrcDirInServerRootDir configFileInSrcDir - tmplData = - object - [ "isAuthEnabled" .= isAuthEnabled spec, - "databaseUrlEnvVarName" .= databaseUrlEnvVarName, - "defaultClientUrl" .= WebApp.getDefaultClientUrl spec - ] - -configFileInSrcDir :: Path' (Rel C.ServerSrcDir) File' -configFileInSrcDir = [relfile|config.js|] diff --git a/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs b/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs index 242ce4596f..bdf184a8dd 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/CrudG.hs @@ -24,8 +24,10 @@ import Wasp.Generator.Crud import qualified Wasp.Generator.Crud.Routes as Routes import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.CrudG (getCrudTypesImportPathForName) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson) +import Wasp.JsImport (JsImportPath (RelativeImportPath)) import qualified Wasp.JsImport as JI import Wasp.Util ((<++>)) @@ -59,7 +61,7 @@ genCrudIndexRoute cruds = return $ C.mkTmplFdWithData tmplPath (Just tmplData) JI.getJsImportStmtAndIdentifier JI.JsImport { JI._name = JI.JsImportField name, - JI._path = fromJust . SP.relFileToPosix $ getCrudFilePath name "js", + JI._path = RelativeImportPath (fromJust . SP.relFileToPosix $ getCrudFilePath name "js"), JI._importAlias = Nothing } @@ -94,7 +96,8 @@ genCrudOperations spec cruds = return $ map genCrudOperation cruds "userEntityUpper" .= maybeUserEntity, "overrides" .= object overrides, "queryType" .= queryTsType, - "actionType" .= actionTsType + "actionType" .= actionTsType, + "crudTypesImportPath" .= SP.fromRelFileP (getCrudTypesImportPathForName name) ] idField = getIdFieldFromCrudEntity spec crud maybeUserEntity = AS.refName . AS.Auth.userEntity <$> maybeAuth diff --git a/waspc/src/Wasp/Generator/ServerGenerator/Db/Seed.hs b/waspc/src/Wasp/Generator/ServerGenerator/Db/Seed.hs index 90d5cafc5c..0d218b26e4 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/Db/Seed.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/Db/Seed.hs @@ -22,11 +22,8 @@ import Wasp.Generator.ServerGenerator.JsImport (extImportToImportJson) genDbSeed :: AppSpec -> Generator [FileDraft] genDbSeed spec = - return $ - dbSeedTypesFd : - maybeToList dbSeedFd + return $ maybeToList dbSeedFd where - dbSeedTypesFd = C.mkSrcTmplFd [relfile|dbSeed/types.ts|] dbSeedFd = dbSeedsToTemplateData (getDbSeeds spec) <&> \tmplData -> C.mkTmplFdWithData diff --git a/waspc/src/Wasp/Generator/ServerGenerator/ExternalCodeGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator/ExternalCodeGenerator.hs deleted file mode 100644 index 7d69a5b2a2..0000000000 --- a/waspc/src/Wasp/Generator/ServerGenerator/ExternalCodeGenerator.hs +++ /dev/null @@ -1,43 +0,0 @@ -module Wasp.Generator.ServerGenerator.ExternalCodeGenerator - ( extServerCodeGeneratorStrategy, - extServerCodeDirInServerSrcDir, - extSharedCodeGeneratorStrategy, - ) -where - -import StrongPath (Dir, Path', Rel, reldir, ()) -import qualified StrongPath as SP -import Wasp.Generator.ExternalCodeGenerator.Common - ( ExternalCodeGeneratorStrategy (..), - GeneratedExternalCodeDir, - castRelPathFromSrcToGenExtCodeDir, - ) -import Wasp.Generator.ExternalCodeGenerator.Js (resolveJsFileWaspImportsForExtCodeDir) -import qualified Wasp.Generator.ServerGenerator.Common as C - -extServerCodeGeneratorStrategy :: ExternalCodeGeneratorStrategy -extServerCodeGeneratorStrategy = mkExtCodeGeneratorStrategy extServerCodeDirInServerSrcDir - -extSharedCodeGeneratorStrategy :: ExternalCodeGeneratorStrategy -extSharedCodeGeneratorStrategy = mkExtCodeGeneratorStrategy extSharedCodeDirInServerSrcDir - --- | Relative path to the directory where external server code will be generated. --- Relative to the server src dir. -extServerCodeDirInServerSrcDir :: Path' (Rel C.ServerSrcDir) (Dir GeneratedExternalCodeDir) -extServerCodeDirInServerSrcDir = [reldir|ext-src|] - --- | Relative path to the directory where external shared code will be generated. --- Relative to the server src dir. -extSharedCodeDirInServerSrcDir :: Path' (Rel C.ServerSrcDir) (Dir GeneratedExternalCodeDir) -extSharedCodeDirInServerSrcDir = [reldir|shared|] - -mkExtCodeGeneratorStrategy :: Path' (Rel C.ServerSrcDir) (Dir GeneratedExternalCodeDir) -> ExternalCodeGeneratorStrategy -mkExtCodeGeneratorStrategy extCodeDirInServerSrcDir = - ExternalCodeGeneratorStrategy - { _resolveJsFileWaspImports = resolveJsFileWaspImportsForExtCodeDir (SP.castRel extCodeDirInServerSrcDir), - _resolveDstFilePath = \filePath -> - C.serverRootDirInProjectRootDir - C.serverSrcDirInServerRootDir - extCodeDirInServerSrcDir - castRelPathFromSrcToGenExtCodeDir filePath - } diff --git a/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs b/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs index 8e466bc31b..17de8feedc 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/JobGenerator.hs @@ -18,29 +18,31 @@ import StrongPath Path', Posix, Rel, - parseRelFile, reldir, reldirP, relfile, toFilePath, + (), ) import qualified StrongPath as SP import Wasp.AppSpec (AppSpec, getJobs) -import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.App.Dependency as AS.Dependency import qualified Wasp.AppSpec.JSON as AS.JSON import Wasp.AppSpec.Job (Job, JobExecutor (PgBoss), jobExecutors) import qualified Wasp.AppSpec.Job as J import Wasp.AppSpec.Util (isPgBossJobExecutorUsed) -import Wasp.Generator.Common (ServerRootDir, makeJsonWithEntityData) +import Wasp.Generator.Common (ServerRootDir) import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) +import Wasp.Generator.SdkGenerator.JobGenerator (getImportPathForJobName, getJobExecutorTypesImportPath) import Wasp.Generator.ServerGenerator.Common ( ServerTemplatesDir, srcDirInServerTemplatesDir, ) import qualified Wasp.Generator.ServerGenerator.Common as C -import Wasp.Generator.ServerGenerator.JsImport (getJsImportStmtAndIdentifier) +import qualified Wasp.Generator.ServerGenerator.JsImport as SJI +import Wasp.JsImport (JsImportName (JsImportField), JsImportPath (ModuleImportPath), makeJsImport) +import qualified Wasp.JsImport as JI import qualified Wasp.SemanticVersion as SV import Wasp.Util (toUpperFirst) @@ -66,13 +68,24 @@ genJob (jobName, job) = "jobSchedule" .= Aeson.Text.encodeToLazyText (fromMaybe Aeson.Null maybeJobSchedule), "jobPerformOptions" .= show (fromMaybe AS.JSON.emptyObject maybeJobPerformOptions), "jobExecutorRelativePath" .= toFilePath (executorJobTemplateInJobsDir "js" (J.executor job)), - "entities" .= maybe [] (map (makeJsonWithEntityData . AS.refName)) (J.entities job) + "jobExecutorTypesImportPath" .= SP.fromRelFileP jobExecutorTypesImportPath, + "jobEntitiesImportStatement" .= jobEntitiesImportStatement, + "jobEntitiesIdentifier" .= jobEntitiesIdentifier ] ) where - tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir SP. [relfile|_job.ts|] - dstFile = jobsDirInServerRootDir SP. fromJust (parseRelFile $ jobName ++ ".ts") - (jobPerformFnImportStatement, jobPerformFnName) = getJsImportStmtAndIdentifier relPathFromJobsDirToServerSrcDir $ (J.fn . J.perform) job + tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir [relfile|_job.ts|] + dstFile = jobsDirInServerRootDir fromJust (SP.parseRelFile $ jobName ++ ".ts") + + -- Users import job types from the SDK, so the types for each job are generated + -- separately and imported from the SDK. + (jobEntitiesImportStatement, jobEntitiesIdentifier) = + JI.getJsImportStmtAndIdentifier $ + makeJsImport (ModuleImportPath $ getImportPathForJobName jobName) (JsImportField "entities") + + (jobPerformFnImportStatement, jobPerformFnName) = + SJI.getJsImportStmtAndIdentifier relPathFromJobsDirToServerSrcDir $ (J.fn . J.perform) job + maybeJobPerformOptions = J.performExecutorOptionsJson job jobScheduleTmplData s = object @@ -82,6 +95,8 @@ genJob (jobName, job) = ] maybeJobSchedule = jobScheduleTmplData <$> J.schedule job + jobExecutorTypesImportPath = getJobExecutorTypesImportPath (J.executor job) + relPathFromJobsDirToServerSrcDir :: Path Posix (Rel importLocation) (Dir C.ServerSrcDir) relPathFromJobsDirToServerSrcDir = [reldirP|../|] @@ -89,8 +104,8 @@ genJob (jobName, job) = -- even if they are not referenced by user code. This ensures schedules are started, etc. genAllJobImports :: AppSpec -> FileDraft genAllJobImports spec = - let tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir SP. [relfile|core/_allJobs.ts|] - dstFile = jobsDirInServerRootDir SP. [relfile|core/allJobs.ts|] + let tmplFile = C.asTmplFile $ jobsDirInServerTemplatesDir [relfile|core/_allJobs.ts|] + dstFile = jobsDirInServerRootDir [relfile|core/allJobs.ts|] in C.mkTmplFdWithDstAndData tmplFile dstFile @@ -118,17 +133,17 @@ genJobExecutors spec = case getJobs spec of jobExecutorHelperFds :: [FileDraft] jobExecutorHelperFds = - [ C.mkTmplFd $ jobsDirInServerTemplatesDir SP. [relfile|core/pgBoss/pgBoss.ts|], - C.mkTmplFd $ jobsDirInServerTemplatesDir SP. [relfile|core/job.ts|] + [ C.mkTmplFd $ jobsDirInServerTemplatesDir [relfile|core/pgBoss/pgBoss.ts|], + C.mkTmplFd $ jobsDirInServerTemplatesDir [relfile|core/job.ts|] ] executorJobTemplateInServerTemplatesDir :: JobExecutor -> Path SP.System (Rel ServerTemplatesDir) File' - executorJobTemplateInServerTemplatesDir = (jobsDirInServerTemplatesDir SP.) . executorJobTemplateInJobsDir "ts" + executorJobTemplateInServerTemplatesDir = (jobsDirInServerTemplatesDir ) . executorJobTemplateInJobsDir "ts" data JobsDir jobsDirInServerTemplatesDir :: Path' (Rel ServerTemplatesDir) (Dir JobsDir) -jobsDirInServerTemplatesDir = srcDirInServerTemplatesDir SP. [reldir|jobs|] +jobsDirInServerTemplatesDir = srcDirInServerTemplatesDir [reldir|jobs|] executorJobTemplateInJobsDir :: String -> JobExecutor -> Path' (Rel JobsDir) File' executorJobTemplateInJobsDir ext PgBoss = fromJust $ SP.parseRelFile $ "core/pgBoss/pgBossJob" <> "." <> ext diff --git a/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs b/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs index 4da990b3a1..c2c5869360 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs @@ -8,8 +8,10 @@ where import Data.Aeson (object, (.=)) import Data.Maybe (fromJust, fromMaybe, isJust) -import StrongPath (Dir, File', Path, Path', Posix, Rel, reldir, reldirP, relfile, ()) +import StrongPath (Dir, File', Path, Path', Rel, ()) import qualified StrongPath as SP +import StrongPath.TH +import StrongPath.Types (Posix) import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.Action as AS.Action @@ -21,7 +23,12 @@ import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator, GeneratorError (GenericGeneratorError), logAndThrowGeneratorError) import qualified Wasp.Generator.ServerGenerator.Common as C import Wasp.Generator.ServerGenerator.OperationsG (operationFileInSrcDir) -import Wasp.JsImport (JsImportName (..), getJsImportStmtAndIdentifier, makeJsImport) +import Wasp.JsImport + ( JsImportName (..), + JsImportPath (RelativeImportPath), + getJsImportStmtAndIdentifier, + makeJsImport, + ) import qualified Wasp.Util as U genOperationsRoutes :: AppSpec -> Generator [FileDraft] @@ -53,11 +60,17 @@ genOperationRoute operation tmplFile = return $ C.mkTmplFdWithDstAndData tmplFil object [ "operation" .= object - [ "importIdentifier" .= (operationImportIdentifier :: String), - "importStatement" .= (operationImportStmt :: String) + [ "importIdentifier" .= operationImportIdentifier, + "importStatement" .= operationImportStmt ] ] + operationName = AS.Operation.getName operation + + (operationImportStmt, operationImportIdentifier) = + getJsImportStmtAndIdentifier $ + makeJsImport (RelativeImportPath operationImportPath) (JsImportModule operationName) + pathToOperationFile = relPosixPathFromOperationsRoutesDirToSrcDir fromJust (SP.relFileToPosix $ operationFileInSrcDir operation) @@ -68,14 +81,11 @@ genOperationRoute operation tmplFile = return $ C.mkTmplFdWithDstAndData tmplFil C.toESModulesImportPath $ SP.fromRelFileP pathToOperationFile - operationName = AS.Operation.getName operation - - (operationImportStmt, operationImportIdentifier) = - getJsImportStmtAndIdentifier $ - makeJsImport operationImportPath (JsImportModule operationName) - data OperationsRoutesDir +relPosixPathFromOperationsRoutesDirToSrcDir :: Path Posix (Rel OperationsRoutesDir) (Dir C.ServerSrcDir) +relPosixPathFromOperationsRoutesDirToSrcDir = [reldirP|../..|] + operationsRoutesDirInServerSrcDir :: Path' (Rel C.ServerSrcDir) (Dir OperationsRoutesDir) operationsRoutesDirInServerSrcDir = [reldir|routes/operations/|] @@ -85,9 +95,6 @@ operationsRoutesDirInServerRootDir = C.serverSrcDirInServerRootDir operation operationRouteFileInOperationsRoutesDir :: AS.Operation.Operation -> Path' (Rel OperationsRoutesDir) File' operationRouteFileInOperationsRoutesDir operation = fromJust $ SP.parseRelFile $ AS.Operation.getName operation ++ ".js" -relPosixPathFromOperationsRoutesDirToSrcDir :: Path Posix (Rel OperationsRoutesDir) (Dir C.ServerSrcDir) -relPosixPathFromOperationsRoutesDirToSrcDir = [reldirP|../..|] - genOperationsRouter :: AppSpec -> Generator FileDraft genOperationsRouter spec -- TODO: Right now we are throwing error here, but we should instead perform this check in parsing/analyzer phase, as a semantic check, since we have all the info we need then already. @@ -107,7 +114,7 @@ genOperationsRouter spec makeOperationRoute operation = let operationName = AS.Operation.getName operation importPath = fromJust $ SP.relFileToPosix $ SP.castRel $ operationRouteFileInOperationsRoutesDir operation - (importStmt, importIdentifier) = getJsImportStmtAndIdentifier $ makeJsImport importPath (JsImportModule operationName) + (importStmt, importIdentifier) = getJsImportStmtAndIdentifier $ makeJsImport (RelativeImportPath importPath) (JsImportModule operationName) in object [ "importIdentifier" .= importIdentifier, "importStatement" .= importStmt, diff --git a/waspc/src/Wasp/Generator/ServerGenerator/WebSocketG.hs b/waspc/src/Wasp/Generator/ServerGenerator/WebSocketG.hs index 6b05d54b77..72c40d4adb 100644 --- a/waspc/src/Wasp/Generator/ServerGenerator/WebSocketG.hs +++ b/waspc/src/Wasp/Generator/ServerGenerator/WebSocketG.hs @@ -1,7 +1,6 @@ module Wasp.Generator.ServerGenerator.WebSocketG ( depsRequiredByWebSockets, genWebSockets, - genWebSocketIndex, mkWebSocketFnImport, ) where @@ -41,27 +40,10 @@ genWebSockets :: AppSpec -> Generator [FileDraft] genWebSockets spec | AS.WS.areWebSocketsUsed spec = sequence - [ genWebSocketIndex spec, - genWebSocketInitialization spec + [ genWebSocketInitialization spec ] | otherwise = return [] -genWebSocketIndex :: AppSpec -> Generator FileDraft -genWebSocketIndex spec = - return $ - C.mkTmplFdWithDstAndData - (C.asTmplFile [relfile|src/webSocket/index.ts|]) - (C.asServerFile [relfile|src/webSocket/index.ts|]) - ( Just $ - object - [ "isAuthEnabled" .= isAuthEnabled spec, - "userWebSocketFn" .= mkWebSocketFnImport maybeWebSocket [reldirP|../|], - "allEntities" .= map (makeJsonWithEntityData . fst) (AS.getEntities spec) - ] - ) - where - maybeWebSocket = AS.App.webSocket $ snd $ getApp spec - genWebSocketInitialization :: AppSpec -> Generator FileDraft genWebSocketInitialization spec = return $ diff --git a/waspc/src/Wasp/Generator/Setup.hs b/waspc/src/Wasp/Generator/Setup.hs index 26cd4ce9b3..c89817a008 100644 --- a/waspc/src/Wasp/Generator/Setup.hs +++ b/waspc/src/Wasp/Generator/Setup.hs @@ -10,15 +10,22 @@ import Wasp.Generator.Common (ProjectRootDir) import qualified Wasp.Generator.DbGenerator as DbGenerator import Wasp.Generator.Monad (GeneratorError (..), GeneratorWarning (..)) import Wasp.Generator.NpmInstall (installNpmDependenciesWithInstallRecord) +import qualified Wasp.Generator.SdkGenerator as SdkGenerator import qualified Wasp.Message as Msg runSetup :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> Msg.SendMessage -> IO ([GeneratorWarning], [GeneratorError]) -runSetup spec dstDir sendMessage = do - installNpmDependenciesWithInstallRecord spec dstDir >>= \case +runSetup spec projectRootDir sendMessage = do + installNpmDependenciesWithInstallRecord spec projectRootDir >>= \case Right () -> do sendMessage $ Msg.Success "Successfully completed npm install." - setUpDatabase spec dstDir sendMessage - Left e -> return ([], [e]) + setUpDatabase spec projectRootDir sendMessage >>= \case + setUpDatabaseResults@(_warnings, _errors@[]) -> do + -- todo(filip): Should we consider building SDK as part of code generation? + -- todo(filip): Avoid building on each setup if we don't need to. + buildsSdkResults <- buildSdk projectRootDir sendMessage + return $ setUpDatabaseResults <> buildsSdkResults + setUpDatabaseResults -> return setUpDatabaseResults + Left npmInstallError -> return ([], [npmInstallError]) setUpDatabase :: AppSpec -> Path' Abs (Dir ProjectRootDir) -> Msg.SendMessage -> IO ([GeneratorWarning], [GeneratorError]) setUpDatabase spec dstDir sendMessage = do @@ -26,3 +33,12 @@ setUpDatabase spec dstDir sendMessage = do (dbGeneratorWarnings, dbGeneratorErrors) <- DbGenerator.postWriteDbGeneratorActions spec dstDir when (null dbGeneratorErrors) (sendMessage $ Msg.Success "Database successfully set up.") return (dbGeneratorWarnings, dbGeneratorErrors) + +buildSdk :: Path' Abs (Dir ProjectRootDir) -> Msg.SendMessage -> IO ([GeneratorWarning], [GeneratorError]) +buildSdk projectRootDir sendMessage = do + sendMessage $ Msg.Start "Building SDK..." + SdkGenerator.buildSdk projectRootDir >>= \case + Left errorMesage -> return ([], [GenericGeneratorError errorMesage]) + Right () -> do + sendMessage $ Msg.Success "SDK built successfully." + return ([], []) diff --git a/waspc/src/Wasp/Generator/Test.hs b/waspc/src/Wasp/Generator/Test.hs index df3bee54eb..c266f0ec3b 100644 --- a/waspc/src/Wasp/Generator/Test.hs +++ b/waspc/src/Wasp/Generator/Test.hs @@ -7,11 +7,11 @@ import Control.Concurrent (newChan) import Control.Concurrent.Async (concurrently) import StrongPath (Abs, Dir, Path') import System.Exit (ExitCode (..)) -import Wasp.Generator.Common (ProjectRootDir) import Wasp.Generator.Job.IO (readJobMessagesAndPrintThemPrefixed) import qualified Wasp.Generator.WebAppGenerator.Test as WebAppTest +import Wasp.Project.Common (WaspProjectDir) -testWebApp :: [String] -> Path' Abs (Dir ProjectRootDir) -> IO (Either String ()) +testWebApp :: [String] -> Path' Abs (Dir WaspProjectDir) -> IO (Either String ()) testWebApp args projectDir = do chan <- newChan let testWebAppJob = WebAppTest.testWebApp args projectDir chan @@ -19,4 +19,6 @@ testWebApp args projectDir = do testWebAppJob `concurrently` readJobMessagesAndPrintThemPrefixed chan case testExitCode of ExitSuccess -> return $ Right () + -- Exit code 130 is thrown when user presses Ctrl+C. + ExitFailure 130 -> return $ Right () ExitFailure code -> return $ Left $ "Tests failed with exit code " ++ show code ++ "." diff --git a/waspc/src/Wasp/Generator/WebAppGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator.hs index 0f40cef132..b28705ae6d 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator.hs @@ -7,9 +7,9 @@ module Wasp.Generator.WebAppGenerator where import Data.Aeson (object, (.=)) -import Data.Char (toLower) import Data.List (intercalate) import Data.Maybe (fromJust, isJust) +import qualified FilePath.Extra as FP.Extra import StrongPath ( Dir, File', @@ -24,57 +24,48 @@ import StrongPath import qualified StrongPath as SP import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec as AS -import Wasp.AppSpec.App (App (webSocket)) import qualified Wasp.AppSpec.App as AS.App import qualified Wasp.AppSpec.App.Auth as AS.App.Auth import qualified Wasp.AppSpec.App.Client as AS.App.Client import qualified Wasp.AppSpec.App.Dependency as AS.Dependency -import Wasp.AppSpec.App.WebSocket (WebSocket (..)) import qualified Wasp.AppSpec.Entity as AS.Entity -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) import Wasp.AppSpec.Valid (getApp) import Wasp.Env (envVarsToDotEnvContent) import Wasp.Generator.Common - ( makeJsonWithEntityData, + ( makeJsArrayFromHaskellList, + makeJsonWithEntityData, ) import qualified Wasp.Generator.ConfigFile as G.CF import qualified Wasp.Generator.DbGenerator.Auth as DbAuth -import Wasp.Generator.ExternalCodeGenerator (genExternalCodeDir) -import qualified Wasp.Generator.ExternalCodeGenerator.Common as ECC import Wasp.Generator.FileDraft (FileDraft, createTextFileDraft) import qualified Wasp.Generator.FileDraft as FD import Wasp.Generator.JsImport (jsImportToImportJson) import Wasp.Generator.Monad (Generator) import qualified Wasp.Generator.NpmDependencies as N import Wasp.Generator.WebAppGenerator.AuthG (genAuth) +import Wasp.Generator.WebAppGenerator.Common (webAppRootDirInProjectRootDir, webAppSrcDirInWebAppRootDir) import qualified Wasp.Generator.WebAppGenerator.Common as C -import Wasp.Generator.WebAppGenerator.CrudG (genCrud) -import Wasp.Generator.WebAppGenerator.ExternalCodeGenerator - ( extClientCodeGeneratorStrategy, - ) -import qualified Wasp.Generator.WebAppGenerator.ExternalCodeGenerator as EC import Wasp.Generator.WebAppGenerator.JsImport (extImportToImportJson) -import Wasp.Generator.WebAppGenerator.OperationsGenerator (genOperations) import Wasp.Generator.WebAppGenerator.RouterGenerator (genRouter) import qualified Wasp.Generator.WebSocket as AS.WS import Wasp.JsImport ( JsImport, JsImportName (JsImportModule), + JsImportPath (RelativeImportPath), makeJsImport, ) import qualified Wasp.Node.Version as NodeVersion +import Wasp.Project.Common (dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir) +import qualified Wasp.Project.Common as Project import Wasp.Util ((<++>)) genWebApp :: AppSpec -> Generator [FileDraft] genWebApp spec = do - extClientCodeFileDrafts <- genExternalCodeDir extClientCodeGeneratorStrategy (AS.externalClientFiles spec) sequence [ genFileCopy [relfile|README.md|], genFileCopy [relfile|tsconfig.json|], genFileCopy [relfile|tsconfig.node.json|], genFileCopy [relfile|src/test/vitest/setup.ts|], - genFileCopy [relfile|src/test/vitest/helpers.tsx|], - genFileCopy [relfile|src/test/index.ts|], genFileCopy [relfile|netlify.toml|], genPackageJson spec (npmDepsForWasp spec), genNpmrc, @@ -83,14 +74,9 @@ genWebApp spec = do genViteConfig spec ] <++> genSrcDir spec - -- Filip: I don't generate external source folders as we're importing the user's code direclty (see ServerGenerator/JsImport.hs). - -- <++> return extClientCodeFileDrafts - -- <++> genExternalCodeDir extSharedCodeGeneratorStrategy (AS.externalSharedFiles spec) - <++> genPublicDir spec extClientCodeFileDrafts + <++> genPublicDir spec <++> genDotEnv spec - <++> genUniversalDir <++> genEnvValidationScript - <++> genCrud spec where genFileCopy = return . C.mkTmplFd @@ -147,13 +133,11 @@ npmDepsForWasp spec = -- Used for Auth UI ("react-hook-form", "^7.45.4") ] - ++ depsRequiredByTailwind spec - ++ depsRequiredForWebSockets spec, + ++ depsRequiredByTailwind spec, N.waspDevDependencies = AS.Dependency.fromList [ -- TODO: Allow users to choose whether they want to use TypeScript -- in their projects and install these dependencies accordingly. - ("vite", "^4.3.9"), ("typescript", "^5.1.0"), ("@types/react", "^18.0.37"), ("@types/react-dom", "^18.0.11"), @@ -164,7 +148,6 @@ npmDepsForWasp spec = -- when updating Vite or React versions ("@tsconfig/vite-react", "^2.0.0") ] - ++ depsRequiredForTesting } depsRequiredByTailwind :: AppSpec -> [AS.Dependency.Dependency] @@ -178,22 +161,6 @@ depsRequiredByTailwind spec = ] else [] -depsRequiredForTesting :: [AS.Dependency.Dependency] -depsRequiredForTesting = - AS.Dependency.fromList - [ ("vitest", "^0.29.3"), - ("@vitest/ui", "^0.29.3"), - ("jsdom", "^21.1.1"), - ("@testing-library/react", "^14.0.0"), - ("@testing-library/jest-dom", "^5.16.5"), - ("msw", "^1.1.0") - ] - -depsRequiredForWebSockets :: AppSpec -> [AS.Dependency.Dependency] -depsRequiredForWebSockets spec - | AS.WS.areWebSocketsUsed spec = AS.WS.clientDepsRequiredForWebSockets - | otherwise = [] - genGitignore :: Generator FileDraft genGitignore = return $ @@ -201,25 +168,24 @@ genGitignore = (C.asTmplFile [relfile|gitignore|]) (C.asWebAppFile [relfile|.gitignore|]) -genPublicDir :: AppSpec -> [FileDraft] -> Generator [FileDraft] -genPublicDir spec extCodeFileDrafts = +genPublicDir :: AppSpec -> Generator [FileDraft] +genPublicDir spec = return $ - ifUserDidntProvideFile genFaviconFd + extPublicFileDrafts + ++ ifUserDidntProvideFile genFaviconFd ++ ifUserDidntProvideFile genManifestFd where + publicFiles = AS.externalPublicFiles spec + extPublicFileDrafts = map C.mkPublicFileDraft publicFiles genFaviconFd = C.mkTmplFd (C.asTmplFile [relfile|public/favicon.ico|]) genManifestFd = C.mkTmplFdWithData tmplFile tmplData where tmplData = object ["appName" .= (fst (getApp spec) :: String)] tmplFile = C.asTmplFile [relfile|public/manifest.json|] - ifUserDidntProvideFile fileDraft = - if checkIfFileDraftExists fileDraft - then [] - else [fileDraft] - + ifUserDidntProvideFile fileDraft = [fileDraft | not (checkIfFileDraftExists fileDraft)] checkIfFileDraftExists = (`elem` existingDstPaths) . FD.getDstPath - existingDstPaths = map FD.getDstPath extCodeFileDrafts + existingDstPaths = map FD.getDstPath extPublicFileDrafts genIndexHtml :: AppSpec -> Generator FileDraft genIndexHtml spec = @@ -243,21 +209,13 @@ genSrcDir :: AppSpec -> Generator [FileDraft] genSrcDir spec = sequence [ genFileCopy [relfile|logo.png|], - genFileCopy [relfile|config.js|], genFileCopy [relfile|queryClient.js|], genFileCopy [relfile|utils.js|], - genFileCopy [relfile|types.ts|], genFileCopy [relfile|vite-env.d.ts|], - -- Generates api.js file which contains token management and configured api (e.g. axios) instance. - genFileCopy [relfile|api.ts|], - genFileCopy [relfile|api/events.ts|], - genFileCopy [relfile|storage.ts|], getIndexTs spec ] - <++> genOperations spec <++> genEntitiesDir spec <++> genAuth spec - <++> genWebSockets spec <++> genRouter spec where genFileCopy = return . C.mkSrcTmplFd @@ -298,39 +256,13 @@ getIndexTs spec = relPathToWebAppSrcDir :: Path Posix (Rel importLocation) (Dir C.WebAppSrcDir) relPathToWebAppSrcDir = [reldirP|./|] -genUniversalDir :: Generator [FileDraft] -genUniversalDir = - return - [ C.mkUniversalTmplFdWithDst [relfile|url.ts|] [relfile|src/universal/url.ts|], - C.mkUniversalTmplFdWithDst [relfile|types.ts|] [relfile|src/universal/types.ts|] - ] - genEnvValidationScript :: Generator [FileDraft] genEnvValidationScript = return - [ C.mkTmplFd [relfile|scripts/validate-env.mjs|], - C.mkUniversalTmplFdWithDst [relfile|validators.js|] [relfile|scripts/universal/validators.mjs|] + [ C.mkTmplFd [relfile|scripts/validate-env.mjs|] ] -genWebSockets :: AppSpec -> Generator [FileDraft] -genWebSockets spec - | AS.WS.areWebSocketsUsed spec = - sequence - [ genFileCopy [relfile|webSocket.ts|], - genWebSocketProvider spec - ] - | otherwise = return [] - where - genFileCopy = return . C.mkSrcTmplFd - -genWebSocketProvider :: AppSpec -> Generator FileDraft -genWebSocketProvider spec = return $ C.mkTmplFdWithData tmplFile tmplData - where - maybeWebSocket = webSocket $ snd $ getApp spec - shouldAutoConnect = (autoConnect <$> maybeWebSocket) /= Just (Just False) - tmplData = object ["autoConnect" .= map toLower (show shouldAutoConnect)] - tmplFile = C.asTmplFile [relfile|src/webSocket/WebSocketProvider.tsx|] - +-- todo(filip): Take care of this as well genViteConfig :: AppSpec -> Generator FileDraft genViteConfig spec = return $ C.mkTmplFdWithData tmplFile tmplData where @@ -339,17 +271,33 @@ genViteConfig spec = return $ C.mkTmplFdWithData tmplFile tmplData object [ "customViteConfig" .= jsImportToImportJson (makeCustomViteConfigJsImport <$> AS.customViteConfigPath spec), "baseDir" .= SP.fromAbsDirP (C.getBaseDir spec), - "defaultClientPort" .= C.defaultClientPort + "defaultClientPort" .= C.defaultClientPort, + "vitest" + .= object + [ "setupFilesArray" .= makeJsArrayFromHaskellList vitestSetupFiles, + "excludeWaspArtefactsPattern" .= SP.fromRelFile (dotWaspDirInWaspProjectDir [relfile|**/*|]) + ] ] + vitestSetupFiles = + [ SP.fromRelFile $ + dotWaspDirInWaspProjectDir + generatedCodeDirInDotWaspDir + webAppRootDirInProjectRootDir + webAppSrcDirInWebAppRootDir + [relfile|test/vitest/setup.ts|] + ] - makeCustomViteConfigJsImport :: Path' (Rel SourceExternalCodeDir) File' -> JsImport - makeCustomViteConfigJsImport pathToConfig = makeJsImport importPath importName + makeCustomViteConfigJsImport :: Path' (Rel Project.WaspProjectDir) File' -> JsImport + makeCustomViteConfigJsImport pathToConfig = makeJsImport (RelativeImportPath importPath) importName where - importPath = C.toViteImportPath $ fromJust $ SP.relFileToPosix pathToConfigInSrc - pathToConfigInSrc = - SP.castRel $ - C.webAppSrcDirInWebAppRootDir - EC.extClientCodeDirInWebAppSrcDir - ECC.castRelPathFromSrcToGenExtCodeDir pathToConfig + importPath = SP.castRel $ C.toViteImportPath relPathToConfigInProjectDir + relPathToConfigInProjectDir = relPathFromWebAppRootDirWaspProjectDir (fromJust . SP.relFileToPosix $ pathToConfig) + + relPathFromWebAppRootDirWaspProjectDir :: Path Posix (Rel C.WebAppRootDir) (Dir Project.WaspProjectDir) + relPathFromWebAppRootDirWaspProjectDir = + fromJust $ + SP.parseRelDirP $ + FP.Extra.reversePosixPath $ + SP.fromRelDir (Project.dotWaspDirInWaspProjectDir Project.generatedCodeDirInDotWaspDir C.webAppRootDirInProjectRootDir) importName = JsImportModule "customViteConfig" diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs b/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs index 6a67b77f12..62592fb881 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/AuthG.hs @@ -6,40 +6,23 @@ where import Data.Aeson (object, (.=)) import StrongPath (relfile) import Wasp.AppSpec (AppSpec) -import qualified Wasp.AppSpec as AS import qualified Wasp.AppSpec.App as AS.App import qualified Wasp.AppSpec.App.Auth as AS.Auth import Wasp.AppSpec.Valid (getApp) -import Wasp.Generator.Common (makeJsArrayFromHaskellList) import Wasp.Generator.FileDraft (FileDraft) import Wasp.Generator.Monad (Generator) -import Wasp.Generator.WebAppGenerator.Auth.AuthFormsG (genAuthForms) -import Wasp.Generator.WebAppGenerator.Auth.EmailAuthG (genEmailAuth) -import Wasp.Generator.WebAppGenerator.Auth.LocalAuthG (genLocalAuth) -import Wasp.Generator.WebAppGenerator.Auth.OAuthAuthG (genOAuthAuth) +import Wasp.Generator.WebAppGenerator.Auth.Common (getOnAuthSucceededRedirectToOrDefault) import Wasp.Generator.WebAppGenerator.Common as C -import Wasp.Util ((<++>)) + ( mkTmplFdWithData, + ) genAuth :: AppSpec -> Generator [FileDraft] genAuth spec = case maybeAuth of Nothing -> return [] - Just auth -> - sequence - [ genFileCopy [relfile|auth/helpers/user.ts|], - genFileCopy [relfile|auth/types.ts|], - genFileCopy [relfile|auth/user.ts|], - genFileCopy [relfile|auth/logout.ts|], - genUseAuth auth, - genCreateAuthRequiredPage auth - ] - <++> genAuthForms auth - <++> genLocalAuth auth - <++> genOAuthAuth auth - <++> genEmailAuth auth + Just auth -> (:) <$> genCreateAuthRequiredPage auth <*> genOAuthAuth auth where maybeAuth = AS.App.auth $ snd $ getApp spec - genFileCopy = return . C.mkSrcTmplFd -- | Generates HOC that handles auth for the given page. genCreateAuthRequiredPage :: AS.Auth.Auth -> Generator FileDraft @@ -49,11 +32,16 @@ genCreateAuthRequiredPage auth = [relfile|src/auth/pages/createAuthRequiredPage.jsx|] (object ["onAuthFailedRedirectTo" .= AS.Auth.onAuthFailedRedirectTo auth]) --- | Generates React hook that Wasp developer can use in a component to get --- access to the currently logged in user (and check whether user is logged in --- ot not). -genUseAuth :: AS.Auth.Auth -> Generator FileDraft -genUseAuth auth = return $ C.mkTmplFdWithData [relfile|src/auth/useAuth.ts|] tmplData - where - tmplData = object ["entitiesGetMeDependsOn" .= makeJsArrayFromHaskellList [userEntityName]] - userEntityName = AS.refName $ AS.Auth.userEntity auth +genOAuthAuth :: AS.Auth.Auth -> Generator [FileDraft] +genOAuthAuth auth = sequence [genOAuthCodeExchange auth | AS.Auth.isExternalAuthEnabled auth] + +genOAuthCodeExchange :: AS.Auth.Auth -> Generator FileDraft +genOAuthCodeExchange auth = + return $ + C.mkTmplFdWithData + [relfile|src/auth/pages/OAuthCodeExchange.jsx|] + ( object + [ "onAuthSucceededRedirectTo" .= getOnAuthSucceededRedirectToOrDefault auth, + "onAuthFailedRedirectTo" .= AS.Auth.onAuthFailedRedirectTo auth + ] + ) diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs index 06005ca2bb..96420b860b 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Common.hs @@ -6,6 +6,7 @@ module Wasp.Generator.WebAppGenerator.Common mkTmplFdWithDst, mkTmplFdWithData, mkTmplFdWithDstAndData, + mkPublicFileDraft, webAppSrcDirInProjectRootDir, webAppTemplatesDirInTemplatesDir, asTmplFile, @@ -27,13 +28,14 @@ module Wasp.Generator.WebAppGenerator.Common where import qualified Data.Aeson as Aeson -import Data.Maybe (fromJust, fromMaybe) +import Data.Maybe (fromMaybe) import StrongPath (Abs, Dir, File, File', Path, Path', Posix, Rel, absdirP, reldir, ()) import qualified StrongPath as SP -import System.FilePath (splitExtension) import Wasp.AppSpec (AppSpec) import qualified Wasp.AppSpec.App as AS.App import qualified Wasp.AppSpec.App.Client as AS.App.Client +import Wasp.AppSpec.ExternalFiles (PublicFile (..)) +import qualified Wasp.AppSpec.ExternalFiles as EF import Wasp.AppSpec.Valid (getApp) import Wasp.Generator.Common ( GeneratedSrcDir, @@ -41,9 +43,10 @@ import Wasp.Generator.Common ServerRootDir, UniversalTemplatesDir, WebAppRootDir, + dropExtensionFromImportPath, universalTemplatesDirInTemplatesDir, ) -import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft) +import Wasp.Generator.FileDraft (FileDraft, createCopyFileDraft, createTemplateFileDraft) import Wasp.Generator.Templates (TemplatesDir) data WebAppSrcDir @@ -108,6 +111,14 @@ mkTmplFdWithDst src dst = mkTmplFdWithDstAndData src dst Nothing mkTmplFdWithData :: Path' (Rel WebAppTemplatesDir) File' -> Aeson.Value -> FileDraft mkTmplFdWithData src tmplData = mkTmplFdWithDstAndData src (SP.castRel src) (Just tmplData) +mkPublicFileDraft :: PublicFile -> FileDraft +mkPublicFileDraft (PublicFile _pathInPublicDir _publicDirPath) = createCopyFileDraft dstPath srcPath + where + dstPath = webAppRootDirInProjectRootDir publicDirInWebAppRootDir _pathInPublicDir + srcPath = _publicDirPath _pathInPublicDir + publicDirInWebAppRootDir :: Path' (Rel WebAppRootDir) (Dir EF.SourceExternalPublicDir) + publicDirInWebAppRootDir = [reldir|public|] + mkTmplFdWithDstAndData :: Path' (Rel WebAppTemplatesDir) File' -> Path' (Rel WebAppRootDir) File' -> @@ -127,9 +138,7 @@ mkUniversalTmplFdWithDst relSrcPath relDstPath = Nothing toViteImportPath :: Path Posix (Rel r) (File f) -> Path Posix (Rel r) (File f) -toViteImportPath = fromJust . SP.parseRelFileP . dropExtension . SP.fromRelFileP - where - dropExtension = fst . splitExtension +toViteImportPath = dropExtensionFromImportPath getBaseDir :: AppSpec -> Path Posix Abs (Dir ()) getBaseDir spec = fromMaybe [absdirP|/|] maybeBaseDir diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/CrudG.hs b/waspc/src/Wasp/Generator/WebAppGenerator/CrudG.hs deleted file mode 100644 index 397b85efed..0000000000 --- a/waspc/src/Wasp/Generator/WebAppGenerator/CrudG.hs +++ /dev/null @@ -1,35 +0,0 @@ -module Wasp.Generator.WebAppGenerator.CrudG - ( genCrud, - ) -where - -import Data.Maybe (fromJust) -import StrongPath (reldir, relfile, ()) -import qualified StrongPath as SP -import Wasp.AppSpec (AppSpec, getCruds) -import qualified Wasp.AppSpec.Crud as AS.Crud -import Wasp.AppSpec.Valid (getIdFieldFromCrudEntity) -import Wasp.Generator.Crud (getCrudOperationJson) -import Wasp.Generator.FileDraft (FileDraft) -import Wasp.Generator.Monad (Generator) -import qualified Wasp.Generator.WebAppGenerator.Common as C - -genCrud :: AppSpec -> Generator [FileDraft] -genCrud spec = - if areThereAnyCruds - then genCrudOperations spec cruds - else return [] - where - cruds = getCruds spec - areThereAnyCruds = not $ null cruds - -genCrudOperations :: AppSpec -> [(String, AS.Crud.Crud)] -> Generator [FileDraft] -genCrudOperations spec cruds = return $ map genCrudOperation cruds - where - genCrudOperation :: (String, AS.Crud.Crud) -> FileDraft - genCrudOperation (name, crud) = C.mkTmplFdWithDstAndData tmplPath destPath (Just tmplData) - where - tmplPath = [relfile|src/crud/_crud.ts|] - destPath = C.webAppSrcDirInWebAppRootDir [reldir|crud|] fromJust (SP.parseRelFile (name ++ ".ts")) - tmplData = getCrudOperationJson name crud idField - idField = getIdFieldFromCrudEntity spec crud diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/ExternalCodeGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator/ExternalCodeGenerator.hs deleted file mode 100644 index 7f17a18841..0000000000 --- a/waspc/src/Wasp/Generator/WebAppGenerator/ExternalCodeGenerator.hs +++ /dev/null @@ -1,57 +0,0 @@ -module Wasp.Generator.WebAppGenerator.ExternalCodeGenerator - ( extClientCodeGeneratorStrategy, - extSharedCodeGeneratorStrategy, - extClientCodeDirInWebAppSrcDir, - ) -where - -import Data.Maybe (fromJust) -import StrongPath (Dir, Path', Rel, reldir, ()) -import qualified StrongPath as SP -import Wasp.Generator.ExternalCodeGenerator.Common - ( ExternalCodeGeneratorStrategy (..), - GeneratedExternalCodeDir, - castRelPathFromSrcToGenExtCodeDir, - ) -import Wasp.Generator.ExternalCodeGenerator.Js (resolveJsFileWaspImportsForExtCodeDir) -import qualified Wasp.Generator.WebAppGenerator.Common as C -import Wasp.Util.FilePath (removePathPrefix) - -extClientCodeGeneratorStrategy :: ExternalCodeGeneratorStrategy -extClientCodeGeneratorStrategy = mkExtCodeGeneratorStrategy extClientCodeDirInWebAppSrcDir - -extSharedCodeGeneratorStrategy :: ExternalCodeGeneratorStrategy -extSharedCodeGeneratorStrategy = mkExtCodeGeneratorStrategy extSharedCodeDirInWebAppSrcDir - --- | Relative path to the directory where external client code will be generated. --- Relative to web app src dir. -extClientCodeDirInWebAppSrcDir :: Path' (Rel C.WebAppSrcDir) (Dir GeneratedExternalCodeDir) -extClientCodeDirInWebAppSrcDir = [reldir|ext-src|] - --- | Relative path to the directory where external shared code will be generated. --- Relative to web app src dir. -extSharedCodeDirInWebAppSrcDir :: Path' (Rel C.WebAppSrcDir) (Dir GeneratedExternalCodeDir) -extSharedCodeDirInWebAppSrcDir = [reldir|shared|] - -mkExtCodeGeneratorStrategy :: Path' (Rel C.WebAppSrcDir) (Dir GeneratedExternalCodeDir) -> ExternalCodeGeneratorStrategy -mkExtCodeGeneratorStrategy extCodeDirInWebAppSrcDir = - ExternalCodeGeneratorStrategy - { _resolveJsFileWaspImports = resolveJsFileWaspImportsForExtCodeDir (SP.castRel extCodeDirInWebAppSrcDir), - _resolveDstFilePath = resolveDstFilePath - } - where - resolveDstFilePath filePath = - case maybeFilePathInStaticAssetsDir of - Just filePathInStaticAssetsDir -> - C.webAppRootDirInProjectRootDir - C.staticAssetsDirInWebAppDir - fromJust (SP.parseRelFile filePathInStaticAssetsDir) - Nothing -> - C.webAppRootDirInProjectRootDir - C.webAppSrcDirInWebAppRootDir - extCodeDirInWebAppSrcDir - castRelPathFromSrcToGenExtCodeDir filePath - where - maybeFilePathInStaticAssetsDir = removePathPrefix staticAssetsDir (SP.fromRelFile filePath) - - staticAssetsDir = SP.fromRelDir C.staticAssetsDirInWebAppDir diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/JsImport.hs b/waspc/src/Wasp/Generator/WebAppGenerator/JsImport.hs index 8e83c4c427..850967ee6b 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/JsImport.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/JsImport.hs @@ -9,10 +9,7 @@ import qualified Wasp.Generator.JsImport as GJI import Wasp.Generator.WebAppGenerator.Common (WebAppSrcDir) import Wasp.JsImport ( JsImport, - JsImportIdentifier, - JsImportStatement, ) -import qualified Wasp.JsImport as JI extImportToImportJson :: Path Posix (Rel importLocation) (Dir WebAppSrcDir) -> @@ -22,32 +19,6 @@ extImportToImportJson pathFromImportLocationToSrcDir maybeExtImport = GJI.jsImpo where jsImport = extImportToJsImport pathFromImportLocationToSrcDir <$> maybeExtImport --- extImportToImportJson :: --- Path Posix (Rel importLocation) (Dir WebAppSrcDir) -> --- Maybe ExtImport -> --- Aeson.Value --- extImportToImportJson _ maybeExtImport = case maybeExtImport of --- Nothing -> object ["isDefined" .= False] --- Just extImport -> makeImportObject extImport --- where --- makeImportObject (ExtImport importName importPath) = --- let importClause = makeImportClause importName --- importPathStr = "ext-sdrc/" ++ SP.toFilePath importPath --- in object --- [ "isDefined" .= True, --- "importStatement" .= ("import " ++ importClause ++ "from \"" ++ importPathStr ++ "\""), --- "importIdentifier" .= importName --- ] --- makeImportClause = \case --- EI.ExtImportModule name -> name --- EI.ExtImportField name -> "{ " ++ name ++ " - -getJsImportStmtAndIdentifier :: - Path Posix (Rel importLocation) (Dir WebAppSrcDir) -> - EI.ExtImport -> - (JsImportStatement, JsImportIdentifier) -getJsImportStmtAndIdentifier pathFromImportLocationToSrcDir = JI.getJsImportStmtAndIdentifier . extImportToJsImport pathFromImportLocationToSrcDir - extImportToJsImport :: Path Posix (Rel importLocation) (Dir WebAppSrcDir) -> EI.ExtImport -> @@ -55,4 +26,7 @@ extImportToJsImport :: extImportToJsImport = GJI.extImportToJsImport webAppExtDir where -- filip: read notes in ServerGenerator/JsImport.hs + -- todo(filip): use WaspProjectDirInProjectRootDir (once you add it for + -- Prisma stuff) and other stuff from WebAppGenerator/Common to build this + -- directory. Do the same for the server webAppExtDir = [reldirP|../../../../src|] diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs deleted file mode 100644 index 438f1d60d5..0000000000 --- a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator.hs +++ /dev/null @@ -1,132 +0,0 @@ -{-# LANGUAGE TypeApplications #-} - -module Wasp.Generator.WebAppGenerator.OperationsGenerator - ( genOperations, - ) -where - -import Data.Aeson - ( object, - (.=), - ) -import Data.Aeson.Types (Pair) -import Data.Maybe (fromJust) -import StrongPath (File', Path', Rel', parseRelFile, reldir, relfile, ()) -import qualified StrongPath as SP -import Wasp.AppSpec (AppSpec) -import qualified Wasp.AppSpec as AS -import qualified Wasp.AppSpec.Action as AS.Action -import qualified Wasp.AppSpec.Operation as AS.Operation -import qualified Wasp.AppSpec.Query as AS.Query -import Wasp.Generator.Common (makeJsArrayFromHaskellList) -import Wasp.Generator.FileDraft (FileDraft) -import Wasp.Generator.Monad (Generator) -import qualified Wasp.Generator.ServerGenerator as ServerGenerator -import Wasp.Generator.ServerGenerator.Common (serverSrcDirInServerRootDir) -import Wasp.Generator.ServerGenerator.OperationsG (operationFileInSrcDir) -import qualified Wasp.Generator.ServerGenerator.OperationsRoutesG as ServerOperationsRoutesG -import Wasp.Generator.WebAppGenerator.Common (serverRootDirFromWebAppRootDir, toViteImportPath) -import qualified Wasp.Generator.WebAppGenerator.Common as C -import qualified Wasp.Generator.WebAppGenerator.OperationsGenerator.ResourcesG as Resources -import Wasp.JsImport (JsImportName (JsImportField), getJsImportStmtAndIdentifier, makeJsImport) -import Wasp.Util (toUpperFirst, (<++>)) - -genOperations :: AppSpec -> Generator [FileDraft] -genOperations spec = - genQueries spec - <++> genActions spec - <++> Resources.genResources spec - <++> return - [ C.mkSrcTmplFd [relfile|operations/index.ts|], - C.mkSrcTmplFd [relfile|operations/updateHandlersMap.js|] - ] - -genQueries :: AppSpec -> Generator [FileDraft] -genQueries spec = - mapM (genQuery spec) (AS.getQueries spec) - <++> return - [ C.mkSrcTmplFd [relfile|queries/index.js|], - C.mkSrcTmplFd [relfile|queries/index.d.ts|], - C.mkSrcTmplFd [relfile|queries/core.js|], - C.mkSrcTmplFd [relfile|queries/core.d.ts|] - ] - -genActions :: AppSpec -> Generator [FileDraft] -genActions spec = - mapM (genAction spec) (AS.getActions spec) - <++> return - [ C.mkSrcTmplFd [relfile|actions/index.ts|], - C.mkSrcTmplFd [relfile|actions/core.js|], - C.mkSrcTmplFd [relfile|actions/core.d.ts|] - ] - -genQuery :: AppSpec -> (String, AS.Query.Query) -> Generator FileDraft -genQuery _ (queryName, query) = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - tmplFile = C.asTmplFile [relfile|src/queries/_query.ts|] - - dstFile = C.asWebAppFile $ [reldir|src/queries/|] fromJust (getOperationDstFileName operation) - tmplData = - object $ - [ "queryRoute" - .= ( ServerGenerator.operationsRouteInRootRouter - ++ "/" - ++ ServerOperationsRoutesG.operationRouteInOperationsRouter operation - ), - "entitiesArray" .= makeJsArrayOfEntityNames operation - ] - ++ operationTypeData operation - operation = AS.Operation.QueryOp queryName query - -genAction :: AppSpec -> (String, AS.Action.Action) -> Generator FileDraft -genAction _ (actionName, action) = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData) - where - tmplFile = C.asTmplFile [relfile|src/actions/_action.ts|] - - dstFile = C.asWebAppFile $ [reldir|src/actions/|] fromJust (getOperationDstFileName operation) - tmplData = - object $ - [ "actionRoute" - .= ( ServerGenerator.operationsRouteInRootRouter - ++ "/" - ++ ServerOperationsRoutesG.operationRouteInOperationsRouter operation - ), - "entitiesArray" .= makeJsArrayOfEntityNames operation - ] - ++ operationTypeData operation - operation = AS.Operation.ActionOp actionName action - -operationTypeData :: AS.Operation.Operation -> [Pair] -operationTypeData operation = tmplData - where - tmplData = - [ "operationTypeImportStmt" .= (operationTypeImportStmt :: String), - "operationTypeName" .= (operationTypeImportIdentifier :: String) - ] - - (operationTypeImportStmt, operationTypeImportIdentifier) = - getJsImportStmtAndIdentifier $ - makeJsImport operationImportPath (JsImportField $ toUpperFirst operationName) - - operationName = AS.Operation.getName operation - - operationImportPath = - toViteImportPath $ - fromJust $ - SP.relFileToPosix serverOperationFileFromWebAppOperationsDir - - serverOperationFileFromWebAppOperationsDir = - webAppRootDirFromWebAppOperationsDir serverOperationFileFromWebAppRootDir - webAppRootDirFromWebAppOperationsDir = [reldir|../..|] - serverOperationFileFromWebAppRootDir = serverRootDirFromWebAppRootDir serverOperationFileInServerRootDir - serverOperationFileInServerRootDir = serverSrcDirInServerRootDir operationFileInSrcDir operation - --- | Generates string that is JS array containing names (as strings) of entities being used by given operation. --- E.g. "['Task', 'Project']" -makeJsArrayOfEntityNames :: AS.Operation.Operation -> String -makeJsArrayOfEntityNames operation = makeJsArrayFromHaskellList entityNames - where - entityNames = maybe [] (map $ \x -> AS.refName x) (AS.Operation.getEntities operation) - -getOperationDstFileName :: AS.Operation.Operation -> Maybe (Path' Rel' File') -getOperationDstFileName operation = parseRelFile (AS.Operation.getName operation ++ ".ts") diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs b/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs deleted file mode 100644 index 61d4ab7679..0000000000 --- a/waspc/src/Wasp/Generator/WebAppGenerator/OperationsGenerator/ResourcesG.hs +++ /dev/null @@ -1,18 +0,0 @@ -module Wasp.Generator.WebAppGenerator.OperationsGenerator.ResourcesG - ( genResources, - ) -where - -import Data.Aeson (object) -import StrongPath (relfile) -import Wasp.AppSpec (AppSpec) -import Wasp.Generator.FileDraft (FileDraft) -import Wasp.Generator.Monad (Generator) -import qualified Wasp.Generator.WebAppGenerator.Common as C - -genResources :: AppSpec -> Generator [FileDraft] -genResources _ = return [C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)] - where - tmplFile = C.asTmplFile [relfile|src/operations/resources.js|] - dstFile = C.asWebAppFile [relfile|src/operations/resources.js|] -- TODO: Un-hardcode this by combining path to operations dir with path to resources file in it. - tmplData = object [] diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs b/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs index b7998d03f5..2a06883d9c 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/RouterGenerator.hs @@ -29,7 +29,6 @@ import Wasp.Generator.WebAppGenerator.Common (asTmplFile, asWebAppSrcFile) import qualified Wasp.Generator.WebAppGenerator.Common as C import Wasp.Generator.WebAppGenerator.JsImport (extImportToImportJson, extImportToJsImport) import Wasp.JsImport (applyJsImportAlias, getJsImportStmtAndIdentifier) -import Wasp.Util.WebRouterPath (Param (Optional, Required), extractPathParams) data RouterTemplateData = RouterTemplateData { _routes :: ![RouteTemplateData], @@ -55,8 +54,6 @@ instance ToJSON RouterTemplateData where data RouteTemplateData = RouteTemplateData { _routeName :: !String, - _urlPath :: !String, - _urlParams :: ![Param], _targetComponent :: !String } @@ -64,9 +61,6 @@ instance ToJSON RouteTemplateData where toJSON routeTD = object [ "name" .= _routeName routeTD, - "urlPath" .= _urlPath routeTD, - "urlParams" .= map mapPathParamToJson (_urlParams routeTD), - "hasUrlParams" .= (not . null $ _urlParams routeTD), "targetComponent" .= _targetComponent routeTD ] @@ -99,13 +93,8 @@ instance ToJSON ExternalAuthProviderTemplateData where genRouter :: AppSpec -> Generator [FileDraft] genRouter spec = sequence - [ genRouterTsx spec, - genFileCopy [relfile|src/router/types.ts|], - genFileCopy [relfile|src/router/linkHelpers.ts|], - genFileCopy [relfile|src/router/Link.tsx|] + [ genRouterTsx spec ] - where - genFileCopy = return . C.mkTmplFd genRouterTsx :: AppSpec -> Generator FileDraft genRouterTsx spec = do @@ -154,15 +143,11 @@ createExternalAuthProviderTemplateData maybeAuth (method, provider) = } createRouteTemplateData :: AppSpec -> (String, AS.Route.Route) -> RouteTemplateData -createRouteTemplateData spec namedRoute@(name, route) = +createRouteTemplateData spec namedRoute@(name, _) = RouteTemplateData { _routeName = name, - _urlPath = path, - _urlParams = extractPathParams path, _targetComponent = determineRouteTargetComponent spec namedRoute } - where - path = AS.Route.path route -- NOTE: This should be prevented by Analyzer, so use error since it should not be possible determineRouteTargetComponent :: AppSpec -> (String, AS.Route.Route) -> String @@ -208,7 +193,3 @@ createPageTemplateData page = relPathToWebAppSrcDir :: Path Posix (Rel importLocation) (Dir C.WebAppSrcDir) relPathToWebAppSrcDir = [reldirP|./|] - -mapPathParamToJson :: Param -> Aeson.Value -mapPathParamToJson (Required name) = object ["name" .= name, "isOptional" .= False] -mapPathParamToJson (Optional name) = object ["name" .= name, "isOptional" .= True] diff --git a/waspc/src/Wasp/Generator/WebAppGenerator/Test.hs b/waspc/src/Wasp/Generator/WebAppGenerator/Test.hs index 0770fc2570..6755395503 100644 --- a/waspc/src/Wasp/Generator/WebAppGenerator/Test.hs +++ b/waspc/src/Wasp/Generator/WebAppGenerator/Test.hs @@ -3,13 +3,20 @@ module Wasp.Generator.WebAppGenerator.Test ) where -import StrongPath (Abs, Dir, Path', ()) -import Wasp.Generator.Common (ProjectRootDir) +import StrongPath (Abs, Dir, Path', relfile, ()) +import qualified StrongPath as SP import qualified Wasp.Generator.Job as J import Wasp.Generator.Job.Process (runNodeCommandAsJob) -import qualified Wasp.Generator.WebAppGenerator.Common as Common +import Wasp.Generator.WebAppGenerator.Common (webAppRootDirInProjectRootDir) +import Wasp.Project.Common (WaspProjectDir, dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir) -testWebApp :: [String] -> Path' Abs (Dir ProjectRootDir) -> J.Job +testWebApp :: [String] -> Path' Abs (Dir WaspProjectDir) -> J.Job testWebApp args projectDir = do - let webAppDir = projectDir Common.webAppRootDirInProjectRootDir - runNodeCommandAsJob webAppDir "npx" ("vitest" : args) J.WebApp + runNodeCommandAsJob projectDir "npx" (vitestCommand ++ args) J.WebApp + where + vitestCommand = ["vitest", "--config", SP.fromRelFile viteConfigPath] + viteConfigPath = + dotWaspDirInWaspProjectDir + generatedCodeDirInDotWaspDir + webAppRootDirInProjectRootDir + [relfile|vite.config.ts|] diff --git a/waspc/src/Wasp/Generator/WebSocket.hs b/waspc/src/Wasp/Generator/WebSocket.hs index e48c0a247f..5f55968b77 100644 --- a/waspc/src/Wasp/Generator/WebSocket.hs +++ b/waspc/src/Wasp/Generator/WebSocket.hs @@ -1,7 +1,7 @@ module Wasp.Generator.WebSocket ( areWebSocketsUsed, serverDepsRequiredForWebSockets, - clientDepsRequiredForWebSockets, + sdkDepsRequiredForWebSockets, ) where @@ -27,8 +27,9 @@ serverDepsRequiredForWebSockets = AS.Dependency.make ("@socket.io/component-emitter", show socketIoComponentEmitterVersionRange) ] -clientDepsRequiredForWebSockets :: [AS.Dependency.Dependency] -clientDepsRequiredForWebSockets = - [ AS.Dependency.make ("socket.io-client", show socketIoVersionRange), +sdkDepsRequiredForWebSockets :: [AS.Dependency.Dependency] +sdkDepsRequiredForWebSockets = + [ AS.Dependency.make ("socket.io", show socketIoVersionRange), + AS.Dependency.make ("socket.io-client", show socketIoVersionRange), AS.Dependency.make ("@socket.io/component-emitter", show socketIoComponentEmitterVersionRange) ] diff --git a/waspc/src/Wasp/JsImport.hs b/waspc/src/Wasp/JsImport.hs index 8ef9da26b4..51e4eabef8 100644 --- a/waspc/src/Wasp/JsImport.hs +++ b/waspc/src/Wasp/JsImport.hs @@ -3,12 +3,13 @@ module Wasp.JsImport ( JsImport (..), JsImportName (..), + JsImportPath (..), JsImportAlias, - JsImportPath, JsImportIdentifier, JsImportStatement, makeJsImport, applyJsImportAlias, + getImportIdentifier, getJsImportStmtAndIdentifier, getJsImportStmtAndIdentifierRaw, ) @@ -33,7 +34,10 @@ data JsImport = JsImport } deriving (Show, Eq, Data) -type JsImportPath = Path Posix (Rel Dir') File' +data JsImportPath + = RelativeImportPath (Path Posix (Rel Dir') File') + | ModuleImportPath (Path Posix (Rel Dir') File') + deriving (Show, Eq, Data) -- Note (filip): not a fan of so many aliases for regular types type JsImportAlias = String @@ -54,6 +58,11 @@ type JsImportClause = String -- | Represents the full import statement e.g. @import { Name } from "file.js"@ type JsImportStatement = String +getImportIdentifier :: JsImport -> JsImportIdentifier +getImportIdentifier JsImport {_name = name} = case name of + JsImportModule identifier -> identifier + JsImportField identifier -> identifier + makeJsImport :: JsImportPath -> JsImportName -> JsImport makeJsImport importPath importName = JsImport importPath importName Nothing @@ -62,12 +71,14 @@ applyJsImportAlias importAlias jsImport = jsImport {_importAlias = importAlias} getJsImportStmtAndIdentifier :: JsImport -> (JsImportStatement, JsImportIdentifier) getJsImportStmtAndIdentifier (JsImport importPath importName maybeImportAlias) = - getJsImportStmtAndIdentifierRaw normalizedPath importName maybeImportAlias + getJsImportStmtAndIdentifierRaw filePath importName maybeImportAlias where - filePath = SP.fromRelFileP importPath - normalizedPath = if ".." `isPrefixOf` filePath then filePath else "./" ++ filePath + filePath = case importPath of + RelativeImportPath relPath -> normalizePath $ SP.fromRelFileP relPath + ModuleImportPath pathString -> SP.fromRelFileP pathString + normalizePath path = if ".." `isPrefixOf` path then path else "./" ++ path --- filip: attempt to simplify how we generate imports. I wanted to generate a +-- todo(filip): attempt to simplify how we generate imports. I wanted to generate a -- module import (e.g., '@ext-src/something') and couldn't do it. This is one of -- the funtions I implemented while I was trying to pull it off. getJsImportStmtAndIdentifierRaw :: diff --git a/waspc/src/Wasp/Project/Analyze.hs b/waspc/src/Wasp/Project/Analyze.hs index d32d19ea4d..24e32eb950 100644 --- a/waspc/src/Wasp/Project/Analyze.hs +++ b/waspc/src/Wasp/Project/Analyze.hs @@ -1,5 +1,3 @@ -{-# LANGUAGE DeriveGeneric #-} - module Wasp.Project.Analyze ( analyzeWaspProject, readPackageJsonFile, @@ -22,13 +20,13 @@ import Wasp.CompileOptions (CompileOptions) import qualified Wasp.CompileOptions as CompileOptions import qualified Wasp.ConfigFile as CF import Wasp.Error (showCompilerErrorForTerminal) -import qualified Wasp.ExternalCode as ExternalCode import qualified Wasp.Generator.ConfigFile as G.CF import Wasp.Project.Common (CompileError, CompileWarning, WaspProjectDir, findFileInWaspProjectDir, packageJsonInWaspProjectDir) import Wasp.Project.Db (makeDevDatabaseUrl) import Wasp.Project.Db.Migrations (findMigrationsDir) import Wasp.Project.Deployment (loadUserDockerfileContents) import Wasp.Project.Env (readDotEnvClient, readDotEnvServer) +import qualified Wasp.Project.ExternalFiles as ExternalFiles import Wasp.Project.Vite (findCustomViteConfigPath) import Wasp.Util (maybeToEither) import qualified Wasp.Util.IO as IOUtil @@ -67,14 +65,10 @@ constructAppSpec :: [AS.Decl] -> IO (Either [CompileError] AS.AppSpec, [CompileWarning]) constructAppSpec waspDir options packageJson decls = do - externalServerCodeFiles <- - ExternalCode.readFiles (CompileOptions.externalServerCodeDirPath options) - - let externalClientCodeDirPath = CompileOptions.externalClientCodeDirPath options - externalClientCodeFiles <- ExternalCode.readFiles externalClientCodeDirPath + externalCodeFiles <- ExternalFiles.readCodeFiles waspDir + externalPublicFiles <- ExternalFiles.readPublicFiles waspDir + customViteConfigPath <- findCustomViteConfigPath waspDir - externalSharedCodeFiles <- - ExternalCode.readFiles (CompileOptions.externalSharedCodeDirPath options) maybeMigrationsDir <- findMigrationsDir waspDir maybeUserDockerfileContents <- loadUserDockerfileContents waspDir configFiles <- CF.discoverConfigFiles waspDir G.CF.configFileRelocationMap @@ -82,15 +76,13 @@ constructAppSpec waspDir options packageJson decls = do serverEnvVars <- readDotEnvServer waspDir clientEnvVars <- readDotEnvClient waspDir - let customViteConfigPath = findCustomViteConfigPath externalClientCodeFiles let appSpec = AS.AppSpec { AS.decls = decls, AS.packageJson = packageJson, AS.waspProjectDir = waspDir, - AS.externalClientFiles = externalClientCodeFiles, - AS.externalServerFiles = externalServerCodeFiles, - AS.externalSharedFiles = externalSharedCodeFiles, + AS.externalCodeFiles = externalCodeFiles, + AS.externalPublicFiles = externalPublicFiles, AS.migrationsDir = maybeMigrationsDir, AS.devEnvVarsServer = serverEnvVars, AS.devEnvVarsClient = clientEnvVars, diff --git a/waspc/src/Wasp/Project/Common.hs b/waspc/src/Wasp/Project/Common.hs index d0cb63c38f..1f25691d49 100644 --- a/waspc/src/Wasp/Project/Common.hs +++ b/waspc/src/Wasp/Project/Common.hs @@ -2,6 +2,8 @@ module Wasp.Project.Common ( WaspProjectDir, DotWaspDir, NodeModulesDir, + CompileError, + CompileWarning, findFileInWaspProjectDir, dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir, @@ -9,34 +11,37 @@ module Wasp.Project.Common waspProjectDirFromProjectRootDir, dotWaspRootFileInWaspProjectDir, dotWaspInfoFileInGeneratedCodeDir, - srcDirInWaspProjectDir, - extServerCodeDirInWaspProjectDir, - extClientCodeDirInWaspProjectDir, - extSharedCodeDirInWaspProjectDir, packageJsonInWaspProjectDir, nodeModulesDirInWaspProjectDir, - CompileError, - CompileWarning, + srcDirInWaspProjectDir, + extPublicDirInWaspProjectDir, tsconfigInWaspProjectDir, ) where import StrongPath (Abs, Dir, File', Path', Rel, reldir, relfile, toFilePath, ()) import System.Directory (doesFileExist) -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) +import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir, SourceExternalPublicDir) import qualified Wasp.Generator.Common -data WaspProjectDir -- Root dir of Wasp project, containing source files. +type CompileError = String -data DotWaspDir -- Here we put everything that wasp generates. +type CompileWarning = String + +data WaspProjectDir -- Root dir of Wasp project, containing source files. data NodeModulesDir +data DotWaspDir -- Here we put everything that wasp generates. + -- | NOTE: If you change the depth of this path, also update @waspProjectDirFromProjectRootDir@ below. -- TODO: SHould this be renamed to include word "root"? dotWaspDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir DotWaspDir) dotWaspDirInWaspProjectDir = [reldir|.wasp|] +nodeModulesDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir NodeModulesDir) +nodeModulesDirInWaspProjectDir = [reldir|node_modules|] + -- | NOTE: If you change the depth of this path, also update @waspProjectDirFromProjectRootDir@ below. -- TODO: Hm this has different name than it has in Generator. generatedCodeDirInDotWaspDir :: Path' (Rel DotWaspDir) (Dir Wasp.Generator.Common.ProjectRootDir) @@ -59,27 +64,18 @@ dotWaspRootFileInWaspProjectDir = [relfile|.wasproot|] dotWaspInfoFileInGeneratedCodeDir :: Path' (Rel Wasp.Generator.Common.ProjectRootDir) File' dotWaspInfoFileInGeneratedCodeDir = [relfile|.waspinfo|] +packageJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File' +packageJsonInWaspProjectDir = [relfile|package.json|] + srcDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir) srcDirInWaspProjectDir = [reldir|src|] -extServerCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir) -extServerCodeDirInWaspProjectDir = srcDirInWaspProjectDir - -extClientCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir) -extClientCodeDirInWaspProjectDir = srcDirInWaspProjectDir - -extSharedCodeDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalCodeDir) -extSharedCodeDirInWaspProjectDir = srcDirInWaspProjectDir - -packageJsonInWaspProjectDir :: Path' (Rel WaspProjectDir) File' -packageJsonInWaspProjectDir = [relfile|package.json|] +extPublicDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir SourceExternalPublicDir) +extPublicDirInWaspProjectDir = [reldir|public|] tsconfigInWaspProjectDir :: Path' (Rel WaspProjectDir) File' tsconfigInWaspProjectDir = [relfile|tsconfig.json|] -nodeModulesDirInWaspProjectDir :: Path' (Rel WaspProjectDir) (Dir NodeModulesDir) -nodeModulesDirInWaspProjectDir = [reldir|node_modules|] - findFileInWaspProjectDir :: Path' Abs (Dir WaspProjectDir) -> Path' (Rel WaspProjectDir) File' -> @@ -88,7 +84,3 @@ findFileInWaspProjectDir waspDir file = do let fileAbsFp = waspDir file fileExists <- doesFileExist $ toFilePath fileAbsFp return $ if fileExists then Just fileAbsFp else Nothing - -type CompileError = String - -type CompileWarning = String diff --git a/waspc/src/Wasp/ExternalCode.hs b/waspc/src/Wasp/Project/ExternalFiles.hs similarity index 57% rename from waspc/src/Wasp/ExternalCode.hs rename to waspc/src/Wasp/Project/ExternalFiles.hs index 40bf95fca0..f846b4f0b2 100644 --- a/waspc/src/Wasp/ExternalCode.hs +++ b/waspc/src/Wasp/Project/ExternalFiles.hs @@ -1,32 +1,37 @@ -module Wasp.ExternalCode - ( readFiles, +module Wasp.Project.ExternalFiles + ( readPublicFiles, + readCodeFiles, ) where import Data.Maybe (catMaybes) import qualified Data.Text.Lazy as TextL import qualified Data.Text.Lazy.IO as TextL.IO -import StrongPath (Abs, Dir, File', Path', Rel, relfile, ()) +import StrongPath (Abs, Dir, Path', ()) import qualified StrongPath as SP import System.IO.Error (isDoesNotExistError) import UnliftIO.Exception (catch, throwIO) -import Wasp.AppSpec.ExternalCode (File (..), SourceExternalCodeDir) -import qualified Wasp.Util.IO -import Wasp.WaspignoreFile (ignores, readWaspignoreFile) +import Wasp.AppSpec.ExternalFiles (CodeFile (CodeFile), PublicFile (PublicFile)) +import Wasp.Project.Common (WaspProjectDir, extPublicDirInWaspProjectDir, srcDirInWaspProjectDir) +import Wasp.Project.Waspignore (getNotIgnoredRelFilePaths, waspIgnorePathInWaspProjectDir) -waspignorePathInExtCodeDir :: Path' (Rel SourceExternalCodeDir) File' -waspignorePathInExtCodeDir = [relfile|.waspignore|] +-- | Returns all files contained in the specified ext public dir +-- except files ignores by the specified waspignore file. +readPublicFiles :: Path' Abs (Dir WaspProjectDir) -> IO [PublicFile] +readPublicFiles waspProjectDir = do + let externalPublicDirPath = waspProjectDir extPublicDirInWaspProjectDir + let waspignoreFilePath = waspProjectDir waspIgnorePathInWaspProjectDir + relFilePaths <- getNotIgnoredRelFilePaths waspignoreFilePath externalPublicDirPath + return $ map (`PublicFile` externalPublicDirPath) relFilePaths --- | Returns all files contained in the specified external code dir, recursively, +-- | Returns all files contained in the specified ext code dir -- except files ignores by the specified waspignore file. -readFiles :: Path' Abs (Dir SourceExternalCodeDir) -> IO [File] -readFiles extCodeDirPath = do - let waspignoreFilePath = extCodeDirPath waspignorePathInExtCodeDir - waspignoreFile <- readWaspignoreFile waspignoreFilePath - relFilePaths <- - filter (not . ignores waspignoreFile . SP.toFilePath) - <$> Wasp.Util.IO.listDirectoryDeep extCodeDirPath - let absFilePaths = map (extCodeDirPath ) relFilePaths +readCodeFiles :: Path' Abs (Dir WaspProjectDir) -> IO [CodeFile] +readCodeFiles waspProjectDir = do + let externalCodeDirPath = waspProjectDir srcDirInWaspProjectDir + let waspignoreFilePath = waspProjectDir waspIgnorePathInWaspProjectDir + relFilePaths <- getNotIgnoredRelFilePaths waspignoreFilePath externalCodeDirPath + let absFiles = map (externalCodeDirPath ) relFilePaths -- NOTE: We read text from all the files, regardless if they are text files or not, because -- we don't know if they are a text file or not. -- Since we do lazy reading (Text.Lazy), this is not a problem as long as we don't try to use @@ -40,8 +45,8 @@ readFiles extCodeDirPath = do -- or create new file draft that will support that. -- In generator, when creating TextFileDraft, give it function/logic for text transformation, -- and it will be taken care of when draft will be written to the disk. - fileTexts <- catMaybes <$> mapM (tryReadFile . SP.toFilePath) absFilePaths - let files = zipWith (`File` extCodeDirPath) relFilePaths fileTexts + fileTexts <- catMaybes <$> mapM (tryReadFile . SP.toFilePath) absFiles + let files = zipWith (`CodeFile` externalCodeDirPath) relFilePaths fileTexts return files where -- NOTE(matija): we had cases (e.g. tmp Vim files) where a file initially existed diff --git a/waspc/src/Wasp/Project/Vite.hs b/waspc/src/Wasp/Project/Vite.hs index 6208f4fcfb..9153d5011f 100644 --- a/waspc/src/Wasp/Project/Vite.hs +++ b/waspc/src/Wasp/Project/Vite.hs @@ -1,23 +1,20 @@ module Wasp.Project.Vite where import Data.List (find) -import StrongPath (File', Path', Rel, relfile) -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) -import qualified Wasp.AppSpec.ExternalCode as ExternalCode +import StrongPath (Abs, Dir, File', Path', Rel, relfile) +import Wasp.Project.Common (WaspProjectDir) +import qualified Wasp.Util.IO as Util.IO -findCustomViteConfigPath :: [ExternalCode.File] -> Maybe (Path' (Rel SourceExternalCodeDir) File') -findCustomViteConfigPath externalClientCodeFiles = ExternalCode._pathInExtCodeDir <$> maybeCustomViteConfigPath - where - maybeCustomViteConfigPath = find isCustomViteConfig externalClientCodeFiles +findCustomViteConfigPath :: Path' Abs (Dir WaspProjectDir) -> IO (Maybe (Path' (Rel WaspProjectDir) File')) +findCustomViteConfigPath waspProjectDir = do + waspProjectDirFiles <- fst <$> Util.IO.listDirectory waspProjectDir - isCustomViteConfig :: ExternalCode.File -> Bool - isCustomViteConfig - ExternalCode.File - { _pathInExtCodeDir = path - } = path == pathToViteTsConfig || path == pathToViteJsConfig + return $ find isCustomViteConfig waspProjectDirFiles + where + isCustomViteConfig path = path == pathToViteTsConfig || path == pathToViteJsConfig - pathToViteTsConfig :: Path' (Rel SourceExternalCodeDir) File' + pathToViteTsConfig :: Path' (Rel WaspProjectDir) File' pathToViteTsConfig = [relfile|vite.config.ts|] - pathToViteJsConfig :: Path' (Rel SourceExternalCodeDir) File' + pathToViteJsConfig :: Path' (Rel WaspProjectDir) File' pathToViteJsConfig = [relfile|vite.config.js|] diff --git a/waspc/src/Wasp/WaspignoreFile.hs b/waspc/src/Wasp/Project/Waspignore.hs similarity index 71% rename from waspc/src/Wasp/WaspignoreFile.hs rename to waspc/src/Wasp/Project/Waspignore.hs index 7b45778408..3bcd3e5897 100644 --- a/waspc/src/Wasp/WaspignoreFile.hs +++ b/waspc/src/Wasp/Project/Waspignore.hs @@ -1,19 +1,43 @@ -module Wasp.WaspignoreFile +module Wasp.Project.Waspignore ( WaspignoreFile, parseWaspignoreFile, + getNotIgnoredRelFilePaths, readWaspignoreFile, + waspIgnorePathInWaspProjectDir, ignores, ) where -import StrongPath (Abs, File', Path') +import StrongPath (Abs, Dir, File', Path', Rel) +import qualified StrongPath as SP +import StrongPath.TH (relfile) import System.FilePath.Glob (Pattern, compile, match) import System.IO.Error (isDoesNotExistError) import UnliftIO.Exception (catch, throwIO) +import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir, SourceExternalPublicDir) +import Wasp.Project.Common import qualified Wasp.Util.IO as IOUtil +class AffectedByWaspignoreFile a + newtype WaspignoreFile = WaspignoreFile [Pattern] +instance AffectedByWaspignoreFile SourceExternalCodeDir + +instance AffectedByWaspignoreFile SourceExternalPublicDir + +getNotIgnoredRelFilePaths :: + AffectedByWaspignoreFile d => + Path' Abs File' -> + Path' Abs (Dir d) -> + IO [Path' (Rel d) File'] +getNotIgnoredRelFilePaths waspignoreFilePath externalDirPath = do + waspignoreFile <- readWaspignoreFile waspignoreFilePath + filter (not . ignores waspignoreFile . SP.toFilePath) <$> IOUtil.listDirectoryDeep externalDirPath + +waspIgnorePathInWaspProjectDir :: Path' (Rel WaspProjectDir) File' +waspIgnorePathInWaspProjectDir = [relfile|.waspignore|] + -- | These patterns are ignored by every 'WaspignoreFile' defaultIgnorePatterns :: [Pattern] defaultIgnorePatterns = map compile [".waspignore"] diff --git a/waspc/src/Wasp/Project/WebApp.hs b/waspc/src/Wasp/Project/WebApp.hs deleted file mode 100644 index aba8bf692a..0000000000 --- a/waspc/src/Wasp/Project/WebApp.hs +++ /dev/null @@ -1,9 +0,0 @@ -module Wasp.Project.WebApp where - -import StrongPath (Dir, Path', Rel, reldir) -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) - -data StaticAssetsDir - -staticAssetsDirInExtClientCodeDir :: Path' (Rel SourceExternalCodeDir) (Dir StaticAssetsDir) -staticAssetsDirInExtClientCodeDir = [reldir|public|] diff --git a/waspc/test/AnalyzerTest.hs b/waspc/test/AnalyzerTest.hs index 6cc92a399f..5f21fda2f3 100644 --- a/waspc/test/AnalyzerTest.hs +++ b/waspc/test/AnalyzerTest.hs @@ -9,6 +9,7 @@ import Data.List (intercalate) import Data.Maybe (fromJust) import qualified StrongPath as SP import Test.Tasty.Hspec +import qualified Wasp.AI.GenerateNewProject.Common as Auth import Wasp.Analyzer import Wasp.Analyzer.Parser (Ctx) import qualified Wasp.Analyzer.TypeChecker as TC diff --git a/waspc/test/AppSpec/ValidTest.hs b/waspc/test/AppSpec/ValidTest.hs index ba1bbdd372..4623bdf5c8 100644 --- a/waspc/test/AppSpec/ValidTest.hs +++ b/waspc/test/AppSpec/ValidTest.hs @@ -357,9 +357,8 @@ spec_AppSpecValid = do AS.AppSpec { AS.decls = [basicAppDecl], AS.waspProjectDir = systemSPRoot SP. [SP.reldir|test/|], - AS.externalClientFiles = [], - AS.externalServerFiles = [], - AS.externalSharedFiles = [], + AS.externalCodeFiles = [], + AS.externalPublicFiles = [], AS.packageJson = AS.PJS.PackageJson { AS.PJS.name = "testApp", diff --git a/waspc/test/Generator/ExternalCodeGenerator/JsTest.hs b/waspc/test/Generator/ExternalCodeGenerator/JsTest.hs deleted file mode 100644 index 00228bffd9..0000000000 --- a/waspc/test/Generator/ExternalCodeGenerator/JsTest.hs +++ /dev/null @@ -1,17 +0,0 @@ -module Generator.ExternalCodeGenerator.JsTest where - -import qualified StrongPath as SP -import Test.Tasty.Hspec -import Wasp.Generator.ExternalCodeGenerator.Common (asGenExtFile) -import Wasp.Generator.ExternalCodeGenerator.Js as Js - -spec_resolveJsFileWaspImportsForExtCodeDir :: Spec -spec_resolveJsFileWaspImportsForExtCodeDir = do - (asGenExtFile [SP.relfile|extFile.js|], "import foo from 'bar'") ~> "import foo from 'bar'" - (asGenExtFile [SP.relfile|extFile.js|], "import foo from '@wasp/bar'") ~> "import foo from '../bar'" - (asGenExtFile [SP.relfile|a/extFile.js|], "import foo from \"@wasp/bar/foo\"") - ~> "import foo from \"../../bar/foo\"" - where - (path, text) ~> expectedText = - it (SP.toFilePath path ++ " " ++ show text ++ " -> " ++ show expectedText) $ do - Js.resolveJsFileWaspImportsForExtCodeDir [SP.reldir|src|] path text `shouldBe` expectedText diff --git a/waspc/test/Generator/WebAppGeneratorTest.hs b/waspc/test/Generator/WebAppGeneratorTest.hs index 7a168e6208..d45c0891d8 100644 --- a/waspc/test/Generator/WebAppGeneratorTest.hs +++ b/waspc/test/Generator/WebAppGeneratorTest.hs @@ -47,9 +47,8 @@ spec_WebAppGenerator = do } ], AS.waspProjectDir = systemSPRoot SP. [SP.reldir|test/|], - AS.externalClientFiles = [], - AS.externalServerFiles = [], - AS.externalSharedFiles = [], + AS.externalCodeFiles = [], + AS.externalPublicFiles = [], AS.packageJson = AS.PJS.PackageJson { AS.PJS.name = "testApp", diff --git a/waspc/test/WaspignoreFileTest.hs b/waspc/test/Project/WaspignoreTest.hs similarity index 94% rename from waspc/test/WaspignoreFileTest.hs rename to waspc/test/Project/WaspignoreTest.hs index 3392aa1de1..eedd110100 100644 --- a/waspc/test/WaspignoreFileTest.hs +++ b/waspc/test/Project/WaspignoreTest.hs @@ -1,8 +1,8 @@ -module WaspignoreFileTest where +module Project.WaspignoreTest where import Test.Tasty.Hspec import Test.Tasty.QuickCheck (arbitraryPrintableChar, forAll, listOf, property) -import Wasp.WaspignoreFile (ignores, parseWaspignoreFile) +import Wasp.Project.Waspignore (ignores, parseWaspignoreFile) spec_IgnoreFile :: Spec spec_IgnoreFile = do diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 6feca9acf5..6a8d58b139 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -238,7 +238,7 @@ library Wasp.AppSpec.Core.Ref Wasp.AppSpec.Entity Wasp.AppSpec.Entity.Field - Wasp.AppSpec.ExternalCode + Wasp.AppSpec.ExternalFiles Wasp.AppSpec.ExtImport Wasp.AppSpec.Job Wasp.AppSpec.JSON @@ -255,7 +255,6 @@ library Wasp.Db.Postgres Wasp.Error Wasp.Env - Wasp.ExternalCode Wasp.Generator Wasp.Generator.AuthProviders Wasp.Generator.AuthProviders.Common @@ -273,9 +272,7 @@ library Wasp.Generator.DbGenerator.Jobs Wasp.Generator.DbGenerator.Operations Wasp.Generator.DockerGenerator - Wasp.Generator.ExternalCodeGenerator Wasp.Generator.ExternalCodeGenerator.Common - Wasp.Generator.ExternalCodeGenerator.Js Wasp.Generator.FileDraft Wasp.Generator.FileDraft.CopyDirFileDraft Wasp.Generator.FileDraft.CopyFileDraft @@ -296,6 +293,21 @@ library Wasp.Generator.NpmInstall.Common Wasp.Generator.NpmInstall.InstalledNpmDepsLog Wasp.Generator.SdkGenerator + Wasp.Generator.SdkGenerator.ApiRoutesG + Wasp.Generator.SdkGenerator.Auth.AuthFormsG + Wasp.Generator.SdkGenerator.Auth.EmailAuthG + Wasp.Generator.SdkGenerator.CrudG + Wasp.Generator.SdkGenerator.Auth.LocalAuthG + Wasp.Generator.SdkGenerator.Auth.OAuthAuthG + Wasp.Generator.SdkGenerator.AuthG + Wasp.Generator.SdkGenerator.Common + Wasp.Generator.SdkGenerator.RpcGenerator + Wasp.Generator.SdkGenerator.JobGenerator + Wasp.Generator.SdkGenerator.ServerOpsGenerator + Wasp.Generator.SdkGenerator.EmailSenderG + Wasp.Generator.SdkGenerator.EmailSender.Providers + Wasp.Generator.SdkGenerator.WebSocketGenerator + Wasp.Generator.SdkGenerator.RouterGenerator Wasp.Generator.ServerGenerator Wasp.Generator.ServerGenerator.JsImport Wasp.Generator.ServerGenerator.ApiRoutesG @@ -304,11 +316,7 @@ library Wasp.Generator.ServerGenerator.Auth.LocalAuthG Wasp.Generator.ServerGenerator.Auth.EmailAuthG Wasp.Generator.ServerGenerator.Db.Seed - Wasp.Generator.ServerGenerator.EmailSenderG - Wasp.Generator.ServerGenerator.EmailSender.Providers Wasp.Generator.ServerGenerator.Common - Wasp.Generator.ServerGenerator.ConfigG - Wasp.Generator.ServerGenerator.ExternalCodeGenerator Wasp.Generator.ServerGenerator.JobGenerator Wasp.Generator.ServerGenerator.OperationsG Wasp.Generator.ServerGenerator.OperationsRoutesG @@ -323,21 +331,13 @@ library Wasp.Generator.WebAppGenerator Wasp.Generator.WebAppGenerator.JsImport Wasp.Generator.WebAppGenerator.AuthG - Wasp.Generator.WebAppGenerator.Auth.AuthFormsG - Wasp.Generator.WebAppGenerator.Auth.OAuthAuthG - Wasp.Generator.WebAppGenerator.Auth.LocalAuthG - Wasp.Generator.WebAppGenerator.Auth.EmailAuthG Wasp.Generator.WebAppGenerator.Auth.Common Wasp.Generator.WebAppGenerator.Common - Wasp.Generator.WebAppGenerator.ExternalCodeGenerator - Wasp.Generator.WebAppGenerator.OperationsGenerator - Wasp.Generator.WebAppGenerator.OperationsGenerator.ResourcesG Wasp.Generator.WebAppGenerator.RouterGenerator Wasp.Generator.WebAppGenerator.Setup Wasp.Generator.WebAppGenerator.Start Wasp.Generator.WebAppGenerator.Test Wasp.Generator.WebSocket - Wasp.Generator.WebAppGenerator.CrudG Wasp.Generator.WriteFileDrafts Wasp.JsImport Wasp.Message @@ -353,7 +353,7 @@ library Wasp.Project.Db.Dev.Postgres Wasp.Project.Deployment Wasp.Project.Env - Wasp.Project.WebApp + Wasp.Project.ExternalFiles Wasp.Project.Studio Wasp.Project.Vite Wasp.Psl.Ast.Model @@ -382,7 +382,7 @@ library Wasp.Util.StrongPath Wasp.Util.WebRouterPath Wasp.Version - Wasp.WaspignoreFile + Wasp.Project.Waspignore library waspls import: common-all @@ -589,7 +589,6 @@ test-suite waspc-test FilePath.ExtraTest Fixtures Generator.DbGeneratorTest - Generator.ExternalCodeGenerator.JsTest Generator.FileDraft.CopyFileDraftTest Generator.FileDraft.CopyAndModifyTextFileDraftTest Generator.FileDraft.TemplateFileDraftTest @@ -613,7 +612,7 @@ test-suite waspc-test SemanticVersionTest SemanticVersion.VersionBoundTest SemanticVersion.VersionTest - WaspignoreFileTest + Project.WaspignoreTest Paths_waspc Generator.NpmDependenciesTest JsImportTest diff --git a/waspc/waspls/src/Wasp/LSP/ExtImport/Path.hs b/waspc/waspls/src/Wasp/LSP/ExtImport/Path.hs index f32f986f0c..83625f99d5 100644 --- a/waspc/waspls/src/Wasp/LSP/ExtImport/Path.hs +++ b/waspc/waspls/src/Wasp/LSP/ExtImport/Path.hs @@ -22,7 +22,7 @@ import GHC.Generics (Generic) import qualified Path as P import qualified StrongPath as SP import qualified StrongPath.Path as SP -import Wasp.AppSpec.ExternalCode (SourceExternalCodeDir) +import Wasp.AppSpec.ExternalFiles (SourceExternalCodeDir) import Wasp.LSP.ServerMonads.HasProjectRootDir (HasProjectRootDir (getProjectRootDir)) import Wasp.Project.Common (WaspProjectDir, srcDirInWaspProjectDir) import Wasp.Util.IO (doesFileExist)