Fix build for 0.12.0 (#1773)

This commit is contained in:
Filip Sodić 2024-02-16 18:25:26 +01:00 committed by GitHub
parent 87e576d66d
commit 5cd5130534
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 2061 additions and 992 deletions

View File

@ -7,11 +7,6 @@ import Control.Monad (when)
import Control.Monad.Except (throwError)
import Control.Monad.IO.Class (liftIO)
import StrongPath (Abs, Dir, Path', (</>))
import qualified StrongPath as SP
import System.Directory
( doesDirectoryExist,
removeDirectoryRecursive,
)
import Wasp.Cli.Command (Command, CommandError (..))
import Wasp.Cli.Command.Compile (compileIOWithOptions, printCompilationResult)
import Wasp.Cli.Command.Message (cliSendMessageC)
@ -20,9 +15,11 @@ import Wasp.Cli.Message (cliSendMessage)
import Wasp.CompileOptions (CompileOptions (..))
import qualified Wasp.Generator
import Wasp.Generator.Monad (GeneratorWarning (GeneratorNeedsMigrationWarning))
import Wasp.Generator.SdkGenerator.Common (sdkRootDirInProjectRootDir)
import qualified Wasp.Message as Msg
import Wasp.Project (CompileError, CompileWarning, WaspProjectDir)
import Wasp.Project.Common (buildDirInDotWaspDir, dotWaspDirInWaspProjectDir)
import Wasp.Project.Common (buildDirInDotWaspDir, dotWaspDirInWaspProjectDir, generatedCodeDirInDotWaspDir)
import Wasp.Util.IO (doesDirectoryExist, removeDirectory)
-- | 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
@ -37,14 +34,22 @@ build = do
let buildDir =
waspProjectDir </> dotWaspDirInWaspProjectDir
</> buildDirInDotWaspDir
buildDirFilePath = SP.fromAbsDir buildDir
doesBuildDirExist <- liftIO $ doesDirectoryExist buildDirFilePath
doesBuildDirExist <- liftIO $ doesDirectoryExist buildDir
when doesBuildDirExist $ do
cliSendMessageC $ Msg.Start "Clearing the content of the .wasp/build directory..."
liftIO $ removeDirectoryRecursive buildDirFilePath
liftIO $ removeDirectory buildDir
cliSendMessageC $ Msg.Success "Successfully cleared the contents of the .wasp/build directory."
-- We are using the same SDK location for both build and start. Read this issue
-- for the full story: https://github.com/wasp-lang/wasp/issues/1769
let sdkDir = waspProjectDir </> dotWaspDirInWaspProjectDir </> generatedCodeDirInDotWaspDir </> sdkRootDirInProjectRootDir
doesSdkDirExist <- liftIO $ doesDirectoryExist sdkDir
when doesSdkDirExist $ do
cliSendMessageC $ Msg.Start "Clearing the content of the .wasp/out/sdk directory..."
liftIO $ removeDirectory sdkDir
cliSendMessageC $ Msg.Success "Successfully cleared the contents of the .wasp/out/sdk directory."
cliSendMessageC $ Msg.Start "Building wasp project..."
(warnings, errors) <- liftIO $ buildIO waspProjectDir buildDir

View File

@ -23,8 +23,8 @@ import Wasp.Project.Common (dotWaspDirInWaspProjectDir, generatedCodeDirInDotWas
-- It also listens for any file changes and recompiles and restarts generated project accordingly.
start :: Command ()
start = do
InWaspProject waspRoot <- require
let outDir = waspRoot </> dotWaspDirInWaspProjectDir </> generatedCodeDirInDotWaspDir
InWaspProject waspProjectDir <- require
let outDir = waspProjectDir </> dotWaspDirInWaspProjectDir </> generatedCodeDirInDotWaspDir
cliSendMessageC $ Msg.Start "Starting compilation and setup phase. Hold tight..."
@ -42,7 +42,7 @@ start = do
-- This way we can show newest Wasp compile warnings and errors (produced by recompilation from
-- 'watch') once jobs from 'start' quiet down a bit.
ongoingCompilationResultMVar <- newMVar (warnings, [])
let watchWaspProjectSource = watch waspRoot outDir ongoingCompilationResultMVar
let watchWaspProjectSource = watch waspProjectDir outDir ongoingCompilationResultMVar
let startGeneratedWebApp = Wasp.Generator.start outDir (onJobsQuietDown ongoingCompilationResultMVar)
-- In parallel:
-- 1. watch for any changes in the Wasp project, be it users wasp code or users JS/HTML/...

View File

@ -3,7 +3,7 @@
# Because if not, we had situations where it would use the different version
# locally and on Github CI. This way we ensure exact version is used,
# and also have control over updating it (instead of update surprising us).
FROM node:{= nodeMajorVersion =}-alpine3.17 AS node
FROM node:{= nodeVersion =}-alpine3.17 AS node
# We split Dockerfile into base, server-builder and server-production.
@ -18,18 +18,31 @@ FROM node AS base
RUN apk --no-cache -U upgrade # To ensure any potential security patches are applied.
# Todo: The 'server-builder' image stays on disk under <none>:<none> and is
# relatively large (~900 MB), should we remove it? Or is it useful for future
# builds?
FROM base AS server-builder
RUN apk add --no-cache build-base libtool autoconf automake
# Building the Docker image on Apple's Silicon Mac fails without python3 (the build
# throws `node-gyp` errors when it tries to compile native deps. Installing
# `python3` fixes the issue.
RUN apk add --no-cache python3 build-base libtool autoconf automake
WORKDIR /app
COPY server/ ./server/
# Since the framwork code in /.wasp/build/server imports the user code in /src
# using relative imports, we must mirror the same directory structure in the
# Docker image.
COPY src ./src
COPY package.json .
COPY package-lock.json .
COPY .wasp/build/server .wasp/build/server
COPY .wasp/out/sdk .wasp/out/sdk
# Install npm packages, resulting in node_modules/.
RUN cd server && npm install
RUN npm install && cd .wasp/build/server && npm install
{=# usingPrisma =}
COPY db/schema.prisma ./db/
RUN cd server && npx prisma generate --schema='{= dbSchemaFileFromServerDir =}'
COPY .wasp/build/db/schema.prisma .wasp/build/db/
RUN cd .wasp/build/server && npx prisma generate --schema='{= dbSchemaFileFromServerDir =}'
{=/ usingPrisma =}
# Building the server should come after Prisma generation.
RUN cd server && npm run bundle
RUN cd .wasp/build/server && npm run bundle
# TODO: Use pm2?
@ -39,13 +52,21 @@ FROM base AS server-production
RUN apk add --no-cache python3
ENV NODE_ENV production
WORKDIR /app
COPY --from=server-builder /app/server/node_modules ./server/node_modules
COPY --from=server-builder /app/server/dist ./server/dist
COPY --from=server-builder /app/server/package*.json ./server/
COPY --from=server-builder /app/server/scripts ./server/scripts
COPY db/ ./db/
# Copying the top level 'node_modules' because it contains the Prisma packages
# necessary for migrating the database.
COPY --from=server-builder /app/node_modules ./node_modules
# Copying the SDK because 'validate-env.mjs' executes independent of the bundle
# and references the 'wasp' package.
COPY --from=server-builder /app/.wasp/out/sdk .wasp/out/sdk
# Copying 'server/node_modules' because 'validate-env.mjs' executes independent
# of the bundle and references the dotenv package.
COPY --from=server-builder /app/.wasp/build/server/node_modules .wasp/build/server/node_modules
COPY --from=server-builder /app/.wasp/build/server/bundle .wasp/build/server/bundle
COPY --from=server-builder /app/.wasp/build/server/package*.json .wasp/build/server/
COPY --from=server-builder /app/.wasp/build/server/scripts .wasp/build/server/scripts
COPY .wasp/build/db/ .wasp/build/db/
EXPOSE ${PORT}
WORKDIR /app/server
WORKDIR /app/.wasp/build/server
ENTRYPOINT ["npm", "run", "start-production"]

View File

@ -29,6 +29,12 @@ const defaultViteConfig = {
build: {
outDir: "build",
},
resolve: {
// These packages rely on a single instance per page. Not dedpuing them
// causes runtime errors (e.g., hook rule violation in react, QueryClient
// instance error in react-query, Invariant Error in react-router-dom).
dedupe: ["react", "react-dom", "@tanstack/react-query", "react-router-dom"]
},
test: {
globals: true,
environment: "jsdom",

View File

@ -1,5 +1,5 @@
{{={= =}=}}
import { createContext, useState, useEffect } from 'react'
import { createContext, useState, useEffect, Context } from 'react'
import { io, Socket } from 'socket.io-client'
import { getSessionId } from 'wasp/client/api'
@ -8,6 +8,11 @@ import config from 'wasp/core/config'
import type { ClientToServerEvents, ServerToClientEvents } from 'wasp/server/webSocket';
export type WebSocketContextValue = {
socket: typeof socket
isConnected: boolean
}
// PRIVATE API
// TODO: In the future, it would be nice if users could pass more
// options to `io`, likely via some `configFn`.
@ -31,7 +36,7 @@ apiEventsEmitter.on('sessionId.set', refreshAuthToken)
apiEventsEmitter.on('sessionId.clear', refreshAuthToken)
// PRIVATE API
export const WebSocketContext = createContext({
export const WebSocketContext: Context<WebSocketContextValue> = createContext({
socket,
isConnected: false,
});

View File

@ -97,6 +97,7 @@
{=! Public: { type WebSocketDefinition, type WaspSocketData } =}
{=! Private: [server, sdk] =}
"./server/webSocket": "./dist/server/webSocket/index.js",
"./entities": "./dist/entities/index.js",
"./auth": "./dist/auth/index.js",
"./client/auth": "./dist/client/auth/index.js",
"./client/operations": "./dist/client/operations/index.js",

View File

@ -18,7 +18,7 @@ export interface JSONObject {
type PrimitiveJSONValue = string | number | boolean | undefined | null
interface JSONArray extends Array<JSONValue> {}
export interface JSONArray extends Array<JSONValue> {}
type SerializableJSONValue =
| Symbol

View File

@ -8,6 +8,7 @@
"dom",
"DOM.Iterable"
],
"declaration": true,
"strict": false,
// Overriding this because we want to use top-level await
"module": "esnext",

View File

@ -7,33 +7,33 @@ app TodoTypescript {
auth: {
userEntity: User,
methods: {
// usernameAndPassword: {
// userSignupFields: import { userSignupFields } from "@src/user/userSignupFields.js"
// },
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
// 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
},
// emailSender: {
// provider: Dummy
// },
db: {
seeds: [
import { seedMyDb } from "@src/db/seeds.js"
@ -100,10 +100,10 @@ page SignupPage {
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 EmailVerificationRoute { path: "/email-verify", to: EmailVerification }
// page EmailVerification {
// component: import { EmailVerificationPage } from "@src/user/auth.tsx"
// }
route ChatRoute { path: "/chat", to: Chat }
page Chat {
@ -115,16 +115,16 @@ page Chat {
// 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 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"
}
// route PasswordResetRoute { path: "/password-reset", to: PasswordReset }
// page PasswordReset {
// component: import { PasswordResetPage } from "@src/user/auth.tsx"
// }
query getTasks {
fn: import { getTasks } from "@src/task/queries",
@ -175,7 +175,7 @@ apiNamespace fooBarNamespace {
path: "/foo/bar"
}
action customEmailSending {
fn: import { send } from "@src/user/customEmailSending",
entities: [User]
}
// action customEmailSending {
// fn: import { send } from "@src/user/customEmailSending",
// entities: [User]
// }

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ import './Main.css'
import React, { useEffect, FormEventHandler, FormEvent } from 'react'
import {
createTask,
customEmailSending,
// customEmailSending,
deleteTasks,
getTasks,
useQuery,
@ -34,9 +34,9 @@ export const MainPage = ({ user }: { user: AuthUser }) => {
return (
<main>
<img src={waspLogo} alt="wasp logo" />
<button onClick={() => customEmailSending(undefined)}>
{/* <button onClick={() => customEmailSending(undefined)}>
customEmailSending
</button>
</button> */}
<Link to="/chat">Wonna chat?</Link>
{user && (
<h1>

View File

@ -5,7 +5,7 @@ import type {
DeleteTasks,
} from 'wasp/server/operations'
import type { Task } from 'wasp/entities'
import { emailSender } from 'wasp/server/email'
// import { emailSender } from 'wasp/server/email'
import { printTimeAndNumberOfTasks } from 'wasp/server/jobs'
type CreateArgs = Pick<Task, 'description'>
@ -21,16 +21,16 @@ export const createTask: CreateTask<CreateArgs, Task> = async (
console.log("Executing 'printTimeAndNumberOfTasks' task.")
await printTimeAndNumberOfTasks.submit({})
emailSender.send({
to: 'test@example.com',
from: {
name: 'Test',
email: 'test@example.com',
},
subject: 'Test email',
text: 'Thank you for using our app!',
html: '<h1>Thank you for using our app!</h1>',
})
// emailSender.send({
// to: 'test@example.com',
// from: {
// name: 'Test',
// email: 'test@example.com',
// },
// subject: 'Test email',
// text: 'Thank you for using our app!',
// html: '<h1>Thank you for using our app!</h1>',
// })
return context.entities.Task.create({
data: {

View File

@ -1,7 +1,7 @@
import { HttpError } from 'wasp/server'
import type { GetTasks, GetTotalNumberOfTasks } from 'wasp/server/operations'
import type { Task } from 'wasp/entities'
import { ensureValidEmail, createProviderId } from 'wasp/server/auth'
// import { ensureValidEmail, createProviderId } from 'wasp/server/auth'
//Using TypeScript's new 'satisfies' keyword, it will infer the types of the arguments and return value
export const getTasks = ((_args, context) => {
@ -9,8 +9,8 @@ export const getTasks = ((_args, context) => {
throw new HttpError(401)
}
console.log(createProviderId)
ensureValidEmail({ email: 'wasp@gmail.com' })
// console.log(createProviderId)
// ensureValidEmail({ email: 'wasp@gmail.com' })
return context.entities.Task.findMany({
where: { user: { id: context.user.id } },

View File

@ -1,9 +1,9 @@
import {
LoginForm,
SignupForm,
VerifyEmailForm,
ResetPasswordForm,
ForgotPasswordForm,
// VerifyEmailForm,
// ResetPasswordForm,
// ForgotPasswordForm,
FormError,
FormInput,
FormItemGroup,
@ -74,14 +74,14 @@ export function LoginPage() {
)
}
export function RequestPasswordResetPage() {
return <ForgotPasswordForm />
}
// export function RequestPasswordResetPage() {
// return <ForgotPasswordForm />
// }
export function PasswordResetPage() {
return <ResetPasswordForm />
}
// export function PasswordResetPage() {
// return <ResetPasswordForm />
// }
export function EmailVerificationPage() {
return <VerifyEmailForm />
}
// export function EmailVerificationPage() {
// return <VerifyEmailForm />
// }

View File

@ -1,43 +1,43 @@
import {
createPasswordResetLink,
createEmailVerificationLink,
sendEmailVerificationEmail,
sendPasswordResetEmail,
} from 'wasp/server/auth'
// import {
// createPasswordResetLink,
// createEmailVerificationLink,
// sendEmailVerificationEmail,
// sendPasswordResetEmail,
// } from 'wasp/server/auth'
export async function send() {
const userEmail = 'mihovil@ilakovac.com'
// export async function send() {
// const userEmail = 'mihovil@ilakovac.com'
const link = await createPasswordResetLink(
userEmail,
'/password-reset'
)
const secondLink = await createEmailVerificationLink(
userEmail,
'/email-verify'
)
// const link = await createPasswordResetLink(
// userEmail,
// '/password-reset'
// )
// const secondLink = await createEmailVerificationLink(
// userEmail,
// '/email-verify'
// )
// Send email verification email.
await sendEmailVerificationEmail(userEmail, {
from: {
name: 'Wasp',
email: userEmail,
},
to: userEmail,
subject: 'Email verification',
text: 'Click on the link to verify your email. ' + secondLink,
html: `<a href="${secondLink}">Click here to verify your email.</a>`,
})
// // Send email verification email.
// await sendEmailVerificationEmail(userEmail, {
// from: {
// name: 'Wasp',
// email: userEmail,
// },
// to: userEmail,
// subject: 'Email verification',
// text: 'Click on the link to verify your email. ' + secondLink,
// html: `<a href="${secondLink}">Click here to verify your email.</a>`,
// })
// Send password reset email.
await sendPasswordResetEmail(userEmail, {
from: {
name: 'Wasp',
email: userEmail,
},
to: userEmail,
subject: 'Password reset',
text: 'Click on the link to reset your password.' + link,
html: `<a href="${link}">Click here to reset your password.</a>`,
})
}
// // Send password reset email.
// await sendPasswordResetEmail(userEmail, {
// from: {
// name: 'Wasp',
// email: userEmail,
// },
// to: userEmail,
// subject: 'Password reset',
// text: 'Click on the link to reset your password.' + link,
// html: `<a href="${link}">Click here to reset your password.</a>`,
// })
// }

View File

@ -165,6 +165,10 @@ runPrismaCommandAsJobFromWaspServerDir :: Path' Abs (Dir ProjectRootDir) -> [Str
runPrismaCommandAsJobFromWaspServerDir projectRootDir cmdArgs =
runPrismaCommandAsJobWithExtraEnv serverDir [] projectRootDir cmdArgs
where
-- We must run our Prisma commands from the server dir for Prisma
-- to pick up our .env file there like before. In the future, we might want
-- to reconsider how Prisma and the server env vars interact and change
-- this. Text copied from: https://github.com/wasp-lang/wasp/pull/1662
serverDir = projectRootDir </> serverRootDirInProjectRootDir
runPrismaCommandAsJobWithExtraEnv ::

View File

@ -29,7 +29,6 @@ import Wasp.Generator.FileDraft (FileDraft (..), createTemplateFileDraft)
import qualified Wasp.Generator.FileDraft.TemplateFileDraft as TmplFD
import Wasp.Generator.Monad (Generator, GeneratorError, runGenerator)
import Wasp.Generator.Templates (TemplatesDir, compileAndRenderTemplate)
import qualified Wasp.SemanticVersion as SV
genDockerFiles :: AppSpec -> Generator [FileDraft]
genDockerFiles spec = sequence [genDockerfile spec, genDockerignore spec]
@ -46,7 +45,7 @@ genDockerfile spec = do
object
[ "usingPrisma" .= not (null $ AS.getDecls @AS.Entity.Entity spec),
"dbSchemaFileFromServerDir" .= SP.fromRelFile dbSchemaFileFromServerDir,
"nodeMajorVersion" .= show (SV.major $ getLowestNodeVersionUserAllows spec),
"nodeVersion" .= show (getLowestNodeVersionUserAllows spec),
"userDockerfile" .= fromMaybe "" (AS.userDockerfileContents spec)
]
)

View File

@ -212,6 +212,20 @@ npmDepsForSdk spec =
("react-router-dom", "^5.3.3"),
("react-hook-form", "^7.45.4"),
("secure-password", "^4.0.0"),
-- Secure-password 4.0.0. defaults to using sodium-native 3.4.1.,
-- which segfaults on on Alpine:
-- https://github.com/sodium-friends/sodium-native/issues/160
--
-- Before 0.12.0 (i.e., restructuring), we made Wasp use
-- sodium-native 3.3.0. with package.json overrides in our web-app/package.json:
-- https://github.com/wasp-lang/wasp/pull/729
--
-- Because our code that uses secure-password now lives in the SDK,
-- and NPM apparently ignores package.json overrides for
-- dependencies (no reference, found out by trying it out), the only
-- way to force NPM to install sodium-native 3.3.0 is by listing it
-- as a direct dependency.
("sodium-native", "3.3.0"),
("superjson", "^1.12.2"),
("@types/express-serve-static-core", "^4.17.13")
]

View File

@ -8,6 +8,7 @@ import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.ExternalCodeGenerator.Common (GeneratedExternalCodeDir)
import Wasp.Generator.FileDraft (FileDraft, createTemplateFileDraft)
import Wasp.Generator.Templates (TemplatesDir)
import Wasp.Project.Common (generatedCodeDirInDotWaspDir)
data SdkRootDir
@ -45,8 +46,13 @@ mkTmplFdWithData relSrcPath tmplData = mkTmplFdWithDstAndData relSrcPath relDstP
mkTmplFd :: Path' (Rel SdkTemplatesDir) File' -> FileDraft
mkTmplFd path = mkTmplFdWithDst path (SP.castRel path)
-- To understand what's going on here, read this issue:
-- https://github.com/wasp-lang/wasp/issues/1769
sdkRootDirInProjectRootDir :: Path' (Rel ProjectRootDir) (Dir SdkRootDir)
sdkRootDirInProjectRootDir = [reldir|sdk/wasp|]
sdkRootDirInProjectRootDir =
[reldir|../|]
</> basename generatedCodeDirInDotWaspDir
</> [reldir|sdk/wasp|]
sdkTemplatesDirInTemplatesDir :: Path' (Rel TemplatesDir) (Dir SdkTemplatesDir)
sdkTemplatesDirInTemplatesDir = [reldir|sdk|]

View File

@ -128,7 +128,6 @@ genPackageJson spec waspDependencies = do
.= ( (if hasEntities then "npm run db-migrate-prod && " else "")
++ "NODE_ENV=production npm run start"
),
"overrides" .= getPackageJsonOverrides,
"prisma" .= ByteStringLazyUTF8.toString (Aeson.encode $ getPackageJsonPrismaField spec)
]
)
@ -256,35 +255,6 @@ genRoutesIndex spec =
operationsRouteInRootRouter :: String
operationsRouteInRootRouter = "operations"
-- Allows us to make specific changes to dependencies of our dependencies.
-- This is helpful if something broke in later versions, etc.
-- Ref: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides
getPackageJsonOverrides :: [Aeson.Value]
getPackageJsonOverrides = map buildOverrideData (designateLastElement overrides)
where
overrides :: [(String, String, String)]
overrides =
[ -- sodium-native > 3.3.0 broke deploying on Heroku.
-- Ref: https://github.com/sodium-friends/sodium-native/issues/160
("secure-password", "sodium-native", "3.3.0")
]
-- NOTE: We must designate the last element so the JSON template can omit the final comma.
buildOverrideData :: (String, String, String, Bool) -> Aeson.Value
buildOverrideData (packageName, dependencyName, dependencyVersion, lastElement) =
object
[ "packageName" .= packageName,
"dependencyName" .= dependencyName,
"dependencyVersion" .= dependencyVersion,
"last" .= lastElement
]
designateLastElement :: [(String, String, String)] -> [(String, String, String, Bool)]
designateLastElement [] = []
designateLastElement l =
map (\(x1, x2, x3) -> (x1, x2, x3, False)) (init l)
++ map (\(x1, x2, x3) -> (x1, x2, x3, True)) [last l]
genEnvValidationScript :: Generator [FileDraft]
genEnvValidationScript =
return

View File

@ -25,7 +25,7 @@ import Wasp.Util (indent)
-- - /waspc/.nvmrc
-- - /web/docs/introduction/getting-started.md -> "Requirements" section.
oldestWaspSupportedNodeVersion :: SV.Version
oldestWaspSupportedNodeVersion = SV.Version 18 0 0
oldestWaspSupportedNodeVersion = SV.Version 18 18 0
isRangeInWaspSupportedRange :: SV.Range -> Bool
isRangeInWaspSupportedRange range =