@wasp.sh/wasp-cli-darwin-arm64-unknown 0.21.1 → 0.22.0-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/data/Cli/starters/skeleton/gitignore +11 -0
  2. package/data/Cli/starters/ts-minimal/main.wasp.ts +15 -0
  3. package/data/Cli/starters/ts-minimal/package.json +18 -0
  4. package/data/Cli/starters/ts-minimal/schema.prisma +10 -0
  5. package/data/Cli/starters/ts-minimal/src/Main.css +103 -0
  6. package/data/Cli/starters/ts-minimal/src/MainPage.tsx +37 -0
  7. package/data/Cli/starters/ts-minimal/src/assets/logo.svg +1 -0
  8. package/data/Cli/starters/ts-minimal/tsconfig.json +7 -0
  9. package/data/Cli/starters/ts-minimal/tsconfig.src.json +28 -0
  10. package/data/Cli/starters/ts-minimal/tsconfig.wasp.json +15 -0
  11. package/data/Cli/starters/ts-minimal/vite.config.ts +9 -0
  12. package/data/Generator/libs/auth/wasp.sh-lib-auth-0.22.0.tgz +0 -0
  13. package/data/Generator/libs/vite-ssr/wasp.sh-lib-vite-ssr-0.22.0.tgz +0 -0
  14. package/data/Generator/templates/Dockerfile +5 -3
  15. package/data/Generator/templates/sdk/wasp/api/index.ts +18 -14
  16. package/data/Generator/templates/sdk/wasp/client/app/components/WaspApp.tsx +5 -24
  17. package/data/Generator/templates/sdk/wasp/client/app/hooks/useIsClient.ts +22 -0
  18. package/data/Generator/templates/sdk/wasp/client/app/index.tsx +1 -19
  19. package/data/Generator/templates/sdk/wasp/client/app/layout.tsx +92 -0
  20. package/data/Generator/templates/sdk/wasp/client/app/router.tsx +64 -0
  21. package/data/Generator/templates/sdk/wasp/client/env/schema.ts +28 -11
  22. package/data/Generator/templates/sdk/wasp/client/env.ts +6 -3
  23. package/data/Generator/templates/sdk/wasp/client/vite/plugins/validateEnv.ts +25 -26
  24. package/data/Generator/templates/sdk/wasp/client/vite/plugins/virtualModules.ts +4 -2
  25. package/data/Generator/templates/sdk/wasp/client/vite/plugins/wasp.ts +8 -4
  26. package/data/Generator/templates/sdk/wasp/client/vite/plugins/waspConfig.ts +57 -11
  27. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/client-entry.tsx +33 -0
  28. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/routes.tsx +57 -8
  29. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/ssr-entry.tsx +64 -0
  30. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/index.ts +4 -4
  31. package/data/Generator/templates/sdk/wasp/client/webSocket/WebSocketProvider.tsx +1 -1
  32. package/data/Generator/templates/sdk/wasp/core/storage.ts +49 -26
  33. package/data/Generator/templates/sdk/wasp/env/index.ts +1 -1
  34. package/data/Generator/templates/sdk/wasp/env/validation.ts +28 -22
  35. package/data/Generator/templates/sdk/wasp/package.json +2 -0
  36. package/data/Generator/templates/sdk/wasp/server/email/core/providers/dummy.ts +23 -25
  37. package/data/Generator/templates/sdk/wasp/server/env.ts +72 -67
  38. package/data/Generator/templates/sdk/wasp/universal/ansiColors.ts +55 -11
  39. package/data/packages/deploy/dist/providers/fly/flyCli.js +1 -4
  40. package/data/packages/deploy/dist/providers/fly/jsonOutputSchemas.js +6 -2
  41. package/data/packages/deploy/dist/providers/fly/tomlFile.js +8 -2
  42. package/data/packages/deploy/dist/providers/railway/commands/setup/setup.js +1 -12
  43. package/data/packages/deploy/dist/providers/railway/env.js +13 -0
  44. package/data/packages/deploy/dist/providers/railway/railwayService/url.js +1 -1
  45. package/data/packages/deploy/package-lock.json +2268 -15
  46. package/data/packages/deploy/package.json +6 -3
  47. package/data/packages/ts-inspect/dist/exports.js +1 -1
  48. package/data/packages/ts-inspect/package-lock.json +8 -7
  49. package/data/packages/ts-inspect/package.json +1 -1
  50. package/data/packages/wasp-config/dist/__tests__/appAnalyzer.unit.test.js +13 -0
  51. package/data/packages/wasp-config/dist/__tests__/mapTsAppSpecToAppSpecDecls.unit.test.js +1 -0
  52. package/data/packages/wasp-config/dist/__tests__/testFixtures.d.ts.map +1 -1
  53. package/data/packages/wasp-config/dist/__tests__/testFixtures.js +1 -0
  54. package/data/packages/wasp-config/dist/src/appAnalyzer.js +1 -1
  55. package/data/packages/wasp-config/dist/src/appSpec.d.ts +1 -0
  56. package/data/packages/wasp-config/dist/src/appSpec.d.ts.map +1 -1
  57. package/data/packages/wasp-config/dist/src/mapTsAppSpecToAppSpecDecls.d.ts.map +1 -1
  58. package/data/packages/wasp-config/dist/src/mapTsAppSpecToAppSpecDecls.js +2 -1
  59. package/data/packages/wasp-config/dist/src/publicApi/tsAppSpec.d.ts +1 -0
  60. package/data/packages/wasp-config/dist/src/publicApi/tsAppSpec.d.ts.map +1 -1
  61. package/data/packages/wasp-config/package.json +5 -4
  62. package/package.json +1 -1
  63. package/wasp-bin +0 -0
  64. package/data/Generator/libs/auth/wasp.sh-lib-auth-0.21.1.tgz +0 -0
  65. package/data/Generator/templates/sdk/wasp/client/app/router/router.tsx +0 -47
  66. package/data/Generator/templates/sdk/wasp/client/vite/plugins/html/build.ts +0 -38
  67. package/data/Generator/templates/sdk/wasp/client/vite/plugins/html/dev.ts +0 -35
  68. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/index.html +0 -21
  69. package/data/Generator/templates/sdk/wasp/client/vite/virtual-files/files/index.tsx +0 -34
@@ -0,0 +1,11 @@
1
+ .wasp/
2
+ node_modules/
3
+
4
+ # Ignore all dotenv files by default to prevent accidentally committing any secrets.
5
+ # To include specific dotenv files, use the `!` operator or adjust these rules.
6
+ .env
7
+ .env.*
8
+
9
+ # Don't ignore example dotenv files.
10
+ !.env.example
11
+ !.env.*.example
@@ -0,0 +1,15 @@
1
+ import { App } from "wasp-config";
2
+
3
+ const app = new App("__waspAppName__", {
4
+ title: "__waspProjectName__",
5
+ wasp: { version: "__waspVersion__" },
6
+ head: ["<link rel='icon' href='/favicon.ico' />"],
7
+ });
8
+
9
+ const mainPage = app.page("MainPage", {
10
+ component: { import: "MainPage", from: "@src/MainPage" },
11
+ });
12
+
13
+ app.route("RootRoute", { path: "/", to: mainPage });
14
+
15
+ export default app;
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__waspAppName__",
3
+ "type": "module",
4
+ "workspaces": [".wasp/out/*", ".wasp/out/sdk/wasp"],
5
+ "dependencies": {
6
+ "react": "^19.2.1",
7
+ "react-dom": "^19.2.1",
8
+ "react-router": "^7.12.0"
9
+ },
10
+ "devDependencies": {
11
+ "@types/react": "^19.2.7",
12
+ "@types/react-dom": "^19.2.3",
13
+ "prisma": "5.19.1",
14
+ "typescript": "5.8.2",
15
+ "vite": "^7.0.6",
16
+ "wasp-config": "__waspConfigPackageSpecifier__"
17
+ }
18
+ }
@@ -0,0 +1,10 @@
1
+ datasource db {
2
+ provider = "sqlite"
3
+ // Wasp requires that the url is set to the DATABASE_URL environment variable.
4
+ url = env("DATABASE_URL")
5
+ }
6
+
7
+ // Wasp requires the `prisma-client-js` generator to be present.
8
+ generator client {
9
+ provider = "prisma-client-js"
10
+ }
@@ -0,0 +1,103 @@
1
+ * {
2
+ -webkit-font-smoothing: antialiased;
3
+ -moz-osx-font-smoothing: grayscale;
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ body {
10
+ font-family:
11
+ -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
12
+ "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
13
+ }
14
+
15
+ #root {
16
+ min-height: 100vh;
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: center;
20
+ align-items: center;
21
+ }
22
+
23
+ .container {
24
+ margin: 3rem 3rem 10rem 3rem;
25
+ max-width: 726px;
26
+ display: flex;
27
+ flex-direction: column;
28
+ justify-content: center;
29
+ align-items: center;
30
+ text-align: center;
31
+ }
32
+
33
+ .logo {
34
+ max-height: 200px;
35
+ margin-bottom: 1rem;
36
+ }
37
+
38
+ .title {
39
+ font-size: 4rem;
40
+ font-weight: 700;
41
+ margin-bottom: 1rem;
42
+ }
43
+
44
+ .content {
45
+ font-size: 1.2rem;
46
+ font-weight: 400;
47
+ line-height: 2;
48
+ margin-bottom: 3rem;
49
+ }
50
+
51
+ .buttons {
52
+ display: flex;
53
+ flex-direction: row;
54
+ gap: 1rem;
55
+ }
56
+
57
+ .button {
58
+ font-size: 1.2rem;
59
+ font-weight: 700;
60
+ text-decoration: none;
61
+ padding: 1.2rem 1.5rem;
62
+ border-radius: 10px;
63
+ }
64
+
65
+ .button-filled {
66
+ color: black;
67
+ background-color: #ffcc00;
68
+ border: 2px solid #ffcc00;
69
+
70
+ transition: all 0.2s ease-in-out;
71
+ }
72
+
73
+ .button-filled:hover {
74
+ filter: brightness(0.95);
75
+ }
76
+
77
+ .button-outlined {
78
+ color: black;
79
+ background-color: transparent;
80
+ border: 2px solid #ffcc00;
81
+
82
+ transition: all 0.2s ease-in-out;
83
+ }
84
+
85
+ .button-outlined:hover {
86
+ filter: brightness(0.95);
87
+ }
88
+
89
+ code {
90
+ border-radius: 5px;
91
+ border: 1px solid #ffcc00;
92
+ padding: 0.2rem;
93
+ background: #ffcc0044;
94
+ font-family:
95
+ Menlo,
96
+ Monaco,
97
+ Lucida Console,
98
+ Liberation Mono,
99
+ DejaVu Sans Mono,
100
+ Bitstream Vera Sans Mono,
101
+ Courier New,
102
+ monospace;
103
+ }
@@ -0,0 +1,37 @@
1
+ import Logo from "./assets/logo.svg";
2
+ import "./Main.css";
3
+
4
+ export function MainPage() {
5
+ return (
6
+ <main className="container">
7
+ <img className="logo" src={Logo} alt="wasp" />
8
+
9
+ <h2 className="title">Welcome to Wasp!</h2>
10
+
11
+ <p className="content">
12
+ This is page <code>MainPage</code> located at route <code>/</code>.
13
+ <br />
14
+ Open <code>src/MainPage.tsx</code> to edit it.
15
+ </p>
16
+
17
+ <div className="buttons">
18
+ <a
19
+ className="button button-filled"
20
+ href="https://wasp.sh/docs/tutorial/create"
21
+ target="_blank"
22
+ rel="noreferrer noopener"
23
+ >
24
+ Take the Tutorial
25
+ </a>
26
+ <a
27
+ className="button button-outlined"
28
+ href="https://discord.com/invite/rzdnErX"
29
+ target="_blank"
30
+ rel="noreferrer noopener"
31
+ >
32
+ Chat on Discord
33
+ </a>
34
+ </div>
35
+ </main>
36
+ );
37
+ }
@@ -0,0 +1 @@
1
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 161 161"><defs><style>.cls-1{fill:#f5cc05;}.cls-2{fill-rule:evenodd;}</style></defs><g id="Page-1"><g id="Group-23"><circle id="Oval" class="cls-1" cx="80.5" cy="80.5" r="79"/><g id="Group-36"><g id="_" data-name="}"><path id="path-2" class="cls-2" d="M88.67,114.33h2.91q6,0,7.87-1.89c1.22-1.25,1.83-3.9,1.83-7.93V93.89c0-4.46.65-7.7,1.93-9.73s3.51-3.43,6.67-4.2q-4.69-1.08-6.65-4.12c-1.3-2-2-5.28-2-9.77V55.44q0-6-1.83-7.93t-7.87-1.88H88.67V39.5h2.65q10.65,0,14.24,3.15t3.59,12.62V65.56c0,4.28.77,7.24,2.29,8.87s4.3,2.44,8.32,2.44h2.74V83h-2.74q-6,0-8.32,2.49c-1.52,1.65-2.29,4.64-2.29,9v10.25q0,9.47-3.59,12.64T91.32,120.5H88.67Z"/><path id="path-2-2" data-name="path-2" class="cls-2" d="M88.67,114.33h2.91q6,0,7.87-1.89c1.22-1.25,1.83-3.9,1.83-7.93V93.89c0-4.46.65-7.7,1.93-9.73s3.51-3.43,6.67-4.2q-4.69-1.08-6.65-4.12c-1.3-2-2-5.28-2-9.77V55.44q0-6-1.83-7.93t-7.87-1.88H88.67V39.5h2.65q10.65,0,14.24,3.15t3.59,12.62V65.56c0,4.28.77,7.24,2.29,8.87s4.3,2.44,8.32,2.44h2.74V83h-2.74q-6,0-8.32,2.49c-1.52,1.65-2.29,4.64-2.29,9v10.25q0,9.47-3.59,12.64T91.32,120.5H88.67Z"/></g><g id="text831"><g id="_2" data-name="="><path id="path-3" class="cls-2" d="M38.5,85.15H75.83v7.58H38.5Zm0-17.88H75.83v7.49H38.5Z"/><path id="path-3-2" data-name="path-3" class="cls-2" d="M38.5,85.15H75.83v7.58H38.5Zm0-17.88H75.83v7.49H38.5Z"/></g></g></g></g></g></svg>
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.src.json" },
5
+ { "path": "./tsconfig.wasp.json" }
6
+ ]
7
+ }
@@ -0,0 +1,28 @@
1
+ // =============================== IMPORTANT =================================
2
+ // This file is mainly used for Wasp IDE support.
3
+ //
4
+ // Wasp will compile your code with slightly different (less strict) compilerOptions.
5
+ // You can increase the configuration's strictness (e.g., by adding
6
+ // "noUncheckedIndexedAccess": true), but you shouldn't reduce it (e.g., by
7
+ // adding "strict": false). Just keep in mind that this will only affect your
8
+ // IDE support, not the actual compilation.
9
+ //
10
+ // Full TypeScript configurability is coming very soon :)
11
+ {
12
+ "compilerOptions": {
13
+ "module": "esnext",
14
+ "composite": true,
15
+ "target": "esnext",
16
+ "moduleResolution": "bundler",
17
+ "jsx": "preserve",
18
+ "strict": true,
19
+ "esModuleInterop": true,
20
+ "isolatedModules": true,
21
+ "moduleDetection": "force",
22
+ "lib": ["dom", "dom.iterable", "esnext"],
23
+ "skipLibCheck": true,
24
+ "allowJs": true,
25
+ "outDir": ".wasp/out/user"
26
+ },
27
+ "include": ["src"]
28
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "skipLibCheck": true,
4
+ "target": "ES2022",
5
+ "isolatedModules": true,
6
+ "moduleDetection": "force",
7
+ "strict": true,
8
+ "noUnusedLocals": true,
9
+ "noUnusedParameters": true,
10
+ "module": "NodeNext",
11
+ "noEmit": true,
12
+ "lib": ["ES2023"]
13
+ },
14
+ "include": ["main.wasp.ts"]
15
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vite";
2
+ import { wasp } from "wasp/client/vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [wasp()],
6
+ server: {
7
+ open: true,
8
+ },
9
+ });
@@ -3,9 +3,7 @@
3
3
  # Because if not, we had situations where it would use the different version
4
4
  # locally and on Github CI. This way we ensure exact version is used,
5
5
  # and also have control over updating it (instead of update surprising us).
6
- {=! TODO: Upgrade this to the latest Alpine version once we update Prisma #2504 =}
7
- {=! (Alpine 3.21 at time of writing) =}
8
- FROM node:{= nodeVersion =}-alpine3.20 AS node
6
+ FROM node:{= nodeVersion =}-alpine3.23 AS node
9
7
 
10
8
 
11
9
  # We split Dockerfile into base, server-builder and server-production.
@@ -18,6 +16,10 @@ FROM node:{= nodeVersion =}-alpine3.20 AS node
18
16
 
19
17
  FROM node AS base
20
18
  RUN apk --no-cache -U upgrade # To ensure any potential security patches are applied.
19
+ # Alpine 3.21+ no longer includes openssl as a transitive dependency of the
20
+ # Node.js image. Prisma's native engine needs libssl at both build and runtime.
21
+ # TODO: Revisit once we upgrade to Prisma 6 (https://github.com/wasp-lang/wasp/issues/2504).
22
+ RUN apk add --no-cache openssl
21
23
 
22
24
 
23
25
  # Todo: The 'server-builder' image stays on disk under <none>:<none> and is
@@ -1,4 +1,4 @@
1
- import axios, { type AxiosInstance, type AxiosError } from 'axios'
1
+ import axios, { type AxiosError, type AxiosInstance } from 'axios'
2
2
 
3
3
  import { config } from 'wasp/client'
4
4
  import { storage } from 'wasp/core/storage'
@@ -75,20 +75,24 @@ api.interceptors.response.use(undefined, (error) => {
75
75
  return Promise.reject(error)
76
76
  })
77
77
 
78
- // This handler will run on other tabs (not the active one calling API functions),
79
- // and will ensure they know about auth session ID changes.
80
- // Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
81
- // "Note: This won't work on the same page that is making the changes it is really a way
82
- // for other pages on the domain using the storage to sync any changes that are made."
83
- window.addEventListener('storage', (event) => {
84
- if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) {
85
- if (!!event.newValue) {
86
- apiEventsEmitter.emit('sessionId.set')
87
- } else {
88
- apiEventsEmitter.emit('sessionId.clear')
78
+ // This makes sure that the following handler won't try to run in a non-browser
79
+ // environment (e.g. during SSR), where `window` is not defined.
80
+ if (typeof window !== 'undefined') {
81
+ // This handler will run on other tabs (not the active one calling API functions),
82
+ // and will ensure they know about auth session ID changes.
83
+ // Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event
84
+ // "Note: This won't work on the same page that is making the changes — it is really a way
85
+ // for other pages on the domain using the storage to sync any changes that are made."
86
+ window.addEventListener('storage', (event) => {
87
+ if (event.key === storage.getPrefixedKey(WASP_APP_AUTH_SESSION_ID_NAME)) {
88
+ if (!!event.newValue) {
89
+ apiEventsEmitter.emit('sessionId.set')
90
+ } else {
91
+ apiEventsEmitter.emit('sessionId.clear')
92
+ }
89
93
  }
90
- }
91
- })
94
+ })
95
+ }
92
96
 
93
97
  // PRIVATE API (sdk)
94
98
  /**
@@ -1,44 +1,25 @@
1
1
  {{={= =}=}}
2
- import * as React from 'react'
2
+ import { use, type ReactNode } from 'react'
3
3
  import { QueryClientProvider } from '@tanstack/react-query'
4
4
 
5
- import { getRouter } from '../router/router'
6
5
  import { queryClientInitialized } from '../../operations/index'
7
6
 
8
7
  {=# areWebSocketsUsed =}
9
8
  import { WebSocketProvider } from '../../webSocket/WebSocketProvider'
10
9
  {=/ areWebSocketsUsed =}
11
10
 
12
- export type WaspAppProps = {
13
- rootElement?: React.ReactNode;
14
- routesMapping: Record<string, React.ComponentType>;
15
- }
16
-
17
- export function WaspApp({ rootElement, routesMapping }: Required<WaspAppProps>) {
18
- const [queryClient, setQueryClient] = React.useState<any>(null)
19
-
20
- React.useEffect(() => {
21
- queryClientInitialized.then(setQueryClient)
22
- }, [])
23
-
24
- if (!queryClient) {
25
- return null
26
- }
27
-
28
- const router = getRouter({
29
- rootElement,
30
- routesMapping,
31
- })
11
+ export function WaspApp({ children }: { children: ReactNode }) {
12
+ const queryClient = use(queryClientInitialized)
32
13
 
33
14
  return (
34
15
  <QueryClientProvider client={queryClient}>
35
16
  {=# areWebSocketsUsed =}
36
17
  <WebSocketProvider>
37
- {router}
18
+ {children}
38
19
  </WebSocketProvider>
39
20
  {=/ areWebSocketsUsed =}
40
21
  {=^ areWebSocketsUsed =}
41
- {router}
22
+ {children}
42
23
  {=/ areWebSocketsUsed =}
43
24
  </QueryClientProvider>
44
25
  )
@@ -0,0 +1,22 @@
1
+ import { useSyncExternalStore } from "react"
2
+
3
+ /**
4
+ * Returns `true` if the component is running on the client (browser) and
5
+ * `false` if on the server (SSR). Doesn't cause hydration mismatches, so it is
6
+ * safe to use for conditional rendering.
7
+ */
8
+ export function useIsClient() {
9
+ // We use `useSyncExternalStore` to get a value that is `true` on the client
10
+ // and `false` on the server, while avoiding hydration mismatches. It looks
11
+ // like a hack, but it conforms to the semantics of `useSyncExternalStore`,
12
+ // with *the environment* being the "external store" in this case. We just
13
+ // don't have any real subscription logic, since the value is static.
14
+ return useSyncExternalStore(emptySubscribe, getClientValue, getServerValue)
15
+ }
16
+
17
+ // These functions are just to satisfy the API of `useSyncExternalStore` in
18
+ // `useIsClient`, and defined outside the hook so they are stable references.
19
+ function emptySubscribe() { return emptyUnsubscribe }
20
+ function emptyUnsubscribe() {}
21
+ function getClientValue() { return true }
22
+ function getServerValue() { return false }
@@ -1,24 +1,6 @@
1
1
  {{={= =}=}}
2
- import { Outlet } from 'react-router'
3
- import { initializeQueryClient } from '../operations'
4
- import { WaspApp, type WaspAppProps } from './components/WaspApp'
5
-
6
- const DefaultRootComponent = () => <Outlet />
7
-
8
- let isAppInitialized = false
9
-
10
2
  // PRIVATE API (web-app)
11
- export function getWaspApp({
12
- rootElement = <DefaultRootComponent />,
13
- routesMapping,
14
- }: WaspAppProps): React.ReactNode {
15
- if (!isAppInitialized) {
16
- initializeQueryClient()
17
- isAppInitialized = true
18
- }
19
-
20
- return <WaspApp rootElement={rootElement} routesMapping={routesMapping} />
21
- }
3
+ export { WaspApp } from './components/WaspApp'
22
4
 
23
5
  {=# isAuthEnabled =}
24
6
  // PRIVATE API (web-app)
@@ -0,0 +1,92 @@
1
+ {{={= =}=}}
2
+ import { StrictMode, type ReactNode } from "react";
3
+ import { useIsClient } from "./hooks/useIsClient.js"
4
+
5
+ export function Layout({
6
+ children,
7
+ isFallbackPage = false,
8
+ clientEntrySrc,
9
+ }: {
10
+ children?: ReactNode;
11
+ isFallbackPage?: boolean;
12
+ clientEntrySrc?: string;
13
+ }) {
14
+ const isClient = useIsClient()
15
+
16
+ /*
17
+ From the Vite SSR plugin, we inherit the concept of a "prerendered page" vs.
18
+ a "fallback page".
19
+ - A prerendered page is a page that is rendered on the server, and then
20
+ hydrated on the client.
21
+ - A fallback page is a page which only prerenders the common HTML structure
22
+ on the server, and then renders the actual page content on the client.
23
+
24
+ To use an analogy, a fallback page is a pluripotent stem cell that can turn
25
+ into any page in the client; while a prerendered page is already specialized
26
+ and can only render its specific content.
27
+
28
+ So, if we are prerendering a fallback page, we want to avoid rendering the
29
+ actual page content, so that it can turn into anything. If we're
30
+ prerendering a non-fallback page, we'll give it its content.
31
+
32
+ But, if we're already in the client, we always want to render the page
33
+ content. Whether prerendered as a fallback or not, now it's showtime, so we
34
+ must show the user the content.
35
+
36
+ Thus, we end up with the line below:
37
+ */
38
+ const shouldRenderChildren = isClient || !isFallbackPage
39
+
40
+ return (
41
+ <StrictMode>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charSet="utf-8" />
45
+ <meta
46
+ name="viewport"
47
+ content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
48
+ />
49
+
50
+ {=& head =}
51
+
52
+ <title>{= title =}</title>
53
+ </head>
54
+ <body>
55
+ <noscript>You need to enable JavaScript to run this app.</noscript>
56
+
57
+ {
58
+ // We don't really need to wrap the app in a div nor name it "root",
59
+ // but we keep it for backwards compatibility with older Wasp
60
+ // versions.
61
+ }
62
+ <div id="root">
63
+ {shouldRenderChildren ? children : null}
64
+ </div>
65
+
66
+ {
67
+ // We pass that argument in SSR builds and not in client builds.
68
+ // This would usually cause a hydration mismatch, but React has an
69
+ // exception for `<script>` tags, for this specific usecase, so it
70
+ // will work fine.
71
+ clientEntrySrc ? (
72
+ // We'd usually use React prerender's `bootstrapModules` options for
73
+ // injecting this script, but it would also add a `<link
74
+ // rel="modulepreload">` tag that Vite doesn't handle correctly. So
75
+ // we just add the script ourselves in the regular way.
76
+ //
77
+ // https://react.dev/reference/react-dom/static/prerenderToNodeStream
78
+ <script
79
+ type="module"
80
+ src={clientEntrySrc}
81
+ // We make it `async` to decouple the tag's position from its
82
+ // execution phase. This way Vite can move it anywhere in the
83
+ // document to optimize loading performance.
84
+ async
85
+ />
86
+ ) : null
87
+ }
88
+ </body>
89
+ </html>
90
+ </StrictMode>
91
+ );
92
+ }
@@ -0,0 +1,64 @@
1
+ {{={= =}=}}
2
+ import type { ReactNode, ComponentType } from 'react'
3
+ import { createBrowserRouter, RouterProvider, type RouteObject } from 'react-router'
4
+
5
+ {=# isExternalAuthEnabled =}
6
+ import { OAuthCallbackPage } from "./pages/OAuthCallback"
7
+ {=/ isExternalAuthEnabled =}
8
+
9
+ import { DefaultRootErrorBoundary } from './components/DefaultRootErrorBoundary'
10
+
11
+ import { routes } from '../router/index'
12
+
13
+ type RouteMapping = Record<
14
+ string,
15
+ | { lazy: () => Promise<{ Component: ComponentType }> }
16
+ | { Component: ComponentType }
17
+ >;
18
+
19
+ export function getRouteObjects({
20
+ routesMapping,
21
+ rootElement,
22
+ }: {
23
+ routesMapping: RouteMapping,
24
+ rootElement: ReactNode,
25
+ }): RouteObject[] {
26
+ const waspDefinedRoutes = [
27
+ {=# isExternalAuthEnabled =}
28
+ {
29
+ path: "{= oAuthCallbackPath =}",
30
+ Component: OAuthCallbackPage,
31
+ },
32
+ {=/ isExternalAuthEnabled =}
33
+ ]
34
+ const userDefinedRoutes = Object.entries(routes).map(([routeKey, route]) => {
35
+ return {
36
+ path: route.to,
37
+ ...routesMapping[routeKey],
38
+ }
39
+ })
40
+
41
+ return [{
42
+ path: '/',
43
+ element: rootElement,
44
+ ErrorBoundary: DefaultRootErrorBoundary,
45
+ children: [
46
+ ...waspDefinedRoutes,
47
+ ...userDefinedRoutes,
48
+ ],
49
+ }]
50
+ }
51
+
52
+ export function getRouter({
53
+ routesMapping,
54
+ rootElement,
55
+ }: {
56
+ routesMapping: RouteMapping,
57
+ rootElement: ReactNode,
58
+ }) {
59
+ const routeObjects = getRouteObjects({ routesMapping, rootElement })
60
+ const browserRouter = createBrowserRouter(routeObjects, {
61
+ basename: '{= baseDir =}',
62
+ })
63
+ return <RouterProvider router={browserRouter} />;
64
+ }
@@ -1,22 +1,39 @@
1
1
  {{={= =}=}}
2
- import * as z from 'zod'
2
+ import * as z from "zod"
3
3
 
4
4
  {=# envValidationSchema.isDefined =}
5
5
  {=& envValidationSchema.importStatement =}
6
- const userClientEnvSchema = {= envValidationSchema.importIdentifier =}
6
+ const userClientEnvSchema = {= envValidationSchema.importIdentifier =};
7
7
  {=/ envValidationSchema.isDefined =}
8
8
  {=^ envValidationSchema.isDefined =}
9
- const userClientEnvSchema = z.object({})
9
+ const userClientEnvSchema = z.object({});
10
10
  {=/ envValidationSchema.isDefined =}
11
11
 
12
- const waspClientEnvSchema = z.object({
13
- "{= serverUrlEnvVarName =}": z
14
- .string()
15
- .url({
16
- message: '{= serverUrlEnvVarName =} must be a valid URL',
12
+ const serverUrlSchema =
13
+ z.string({
14
+ error: '{= serverUrlEnvVarName =} is required',
17
15
  })
18
- .default('{= defaultServerUrl =}'),
19
- })
16
+ .pipe(
17
+ z.url({
18
+ error: '{= serverUrlEnvVarName =} must be a valid URL',
19
+ })
20
+ )
21
+
22
+ const waspDevClientEnvSchema = z.object({
23
+ "{= serverUrlEnvVarName =}": serverUrlSchema
24
+ .default("{= defaultServerUrl =}"),
25
+ });
26
+
27
+ const waspProdClientEnvSchema = z.object({
28
+ "{= serverUrlEnvVarName =}": serverUrlSchema,
29
+ });
20
30
 
21
31
  // PRIVATE API (sdk, Vite config)
22
- export const clientEnvSchema = userClientEnvSchema.merge(waspClientEnvSchema)
32
+ // TODO(franjo): Remove passing mode as param when this is no longer a plugin.
33
+ // See: https://github.com/wasp-lang/wasp/issues/3875.
34
+ export function getClientEnvSchema(mode: string) {
35
+ const waspClientEnvSchema = mode === "production"
36
+ ? waspProdClientEnvSchema
37
+ : waspDevClientEnvSchema;
38
+ return z.object({ ...userClientEnvSchema.shape, ...waspClientEnvSchema.shape })
39
+ }
@@ -1,5 +1,8 @@
1
- import { clientEnvSchema } from './env/schema.js'
2
- import { ensureEnvSchema } from '../env/validation.js'
1
+ import { ensureEnvSchema } from "../env/validation.js";
2
+ import { getClientEnvSchema } from "./env/schema.js";
3
3
 
4
4
  // PUBLIC API
5
- export const env = ensureEnvSchema(import.meta.env, clientEnvSchema)
5
+ export const env = ensureEnvSchema(
6
+ import.meta.env,
7
+ getClientEnvSchema(import.meta.env.MODE),
8
+ );