@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
@@ -1,19 +1,17 @@
1
- import { type Plugin } from 'vite'
1
+ import { type Plugin } from "vite";
2
2
 
3
- import { loadEnvVars } from './envFile.js'
4
3
  import {
4
+ formatZodEnvError,
5
5
  getValidatedEnvOrError,
6
- formatZodEnvErrors,
7
- } from '../../../env/validation.js'
8
- import { clientEnvSchema } from '../../env/schema.js'
9
- import { getColorizedConsoleFormatString } from '../../../universal/ansiColors.js'
10
-
11
- const redColorFormatString = getColorizedConsoleFormatString('red');
6
+ } from "../../../env/validation.js";
7
+ import { colorize } from "../../../universal/ansiColors.js";
8
+ import { getClientEnvSchema } from "../../env/schema.js";
9
+ import { loadEnvVars } from "./envFile.js";
12
10
 
13
11
  export function validateEnv(): Plugin {
14
- let validationResult: ReturnType<typeof getValidatedEnvOrError> | null = null
12
+ let validationResult: ReturnType<typeof getValidatedEnvOrError> | null = null;
15
13
  return {
16
- name: 'wasp:validate-env',
14
+ name: "wasp:validate-env",
17
15
  async configResolved(config) {
18
16
  const env = await loadEnvVars({
19
17
  rootDir: config.root,
@@ -23,33 +21,34 @@ export function validateEnv(): Plugin {
23
21
  // We load the env file variables only in development,
24
22
  // when building for production, users are expected to
25
23
  // provide the environment variables inline.
26
- loadDotEnvFile: config.command === 'serve',
27
- })
28
- validationResult = getValidatedEnvOrError(env, clientEnvSchema)
24
+ loadDotEnvFile: config.command === "serve",
25
+ });
26
+ const schema = getClientEnvSchema(config.mode);
27
+ validationResult = getValidatedEnvOrError(env, schema);
29
28
 
30
29
  // Exit if we are in build mode, because we can't show the error in the browser.
31
- if (config.command === 'build' && !validationResult.success) {
32
- const message = formatZodEnvErrors(validationResult.error.issues)
33
- console.error(`${redColorFormatString}${message}`)
34
- process.exit(1)
30
+ if (config.command === "build" && !validationResult.success) {
31
+ const validationErrorMessage = formatZodEnvError(validationResult.error);
32
+ console.error(colorize("red", validationErrorMessage));
33
+ process.exit(1);
35
34
  }
36
35
  },
37
36
  configureServer: (server) => {
38
37
  if (validationResult === null || validationResult.success) {
39
- return
38
+ return;
40
39
  }
41
40
 
42
41
  // Send the error to the browser.
43
- const message = formatZodEnvErrors(validationResult.error.issues)
44
- server.ws.on('connection', () => {
42
+ const validationErrorMessage = formatZodEnvError(validationResult.error);
43
+ server.ws.on("connection", () => {
45
44
  server.ws.send({
46
- type: 'error',
45
+ type: "error",
47
46
  err: {
48
- message,
49
- stack: '',
47
+ message: validationErrorMessage,
48
+ stack: "",
50
49
  },
51
- })
52
- })
50
+ });
51
+ });
53
52
  },
54
- }
53
+ };
55
54
  }
@@ -1,14 +1,16 @@
1
1
  {{={= =}=}}
2
2
  import { type Plugin } from "vite";
3
3
  import {
4
- getIndexTsxContent,
4
+ getClientEntryTsxContent,
5
5
  getRoutesTsxContent,
6
+ getSsrEntryTsxContent,
6
7
  } from "../virtual-files/index.js";
7
8
  import { makeVirtualFilesResolver, type VirtualFiles } from "../virtual-files/resolver.js";
8
9
 
9
10
  const resolveVirtualFiles = makeVirtualFilesResolver([
10
- { id: "{= clientEntryPointPath =}", load: getIndexTsxContent },
11
+ { id: "{= clientEntryPointPath =}", load: getClientEntryTsxContent },
11
12
  { id: "{= routesEntryPointPath =}", load: getRoutesTsxContent },
13
+ { id: "{= ssrEntryPointPath =}", load: getSsrEntryTsxContent },
12
14
  ]);
13
15
 
14
16
  export function waspVirtualModules(): Plugin {
@@ -1,11 +1,11 @@
1
+ {{={= =}=}}
1
2
  import { type PluginOption } from "vite";
2
3
  import react, { type Options as ReactOptions } from "@vitejs/plugin-react";
4
+ import ssr from "@wasp.sh/lib-vite-ssr";
3
5
  import { validateEnv } from "./validateEnv.js";
4
6
  import { envFile } from "./envFile.js";
5
7
  import { detectServerImports } from "./detectServerImports.js";
6
8
  import { waspVirtualModules } from "./virtualModules.js";
7
- import { waspHtmlDev } from "./html/dev.js";
8
- import { waspHtmlBuild } from "./html/build.js";
9
9
  import { typescriptCheck } from "./typescriptCheck.js";
10
10
  import { waspConfig } from "./waspConfig.js";
11
11
 
@@ -28,9 +28,13 @@ export function wasp(options?: WaspPluginOptions): PluginOption {
28
28
  * Plugins running after core Vite plugins.
29
29
  */
30
30
  typescriptCheck(),
31
- waspHtmlDev(),
32
- waspHtmlBuild(),
33
31
  validateEnv(),
34
32
  react(options?.reactOptions),
33
+ ssr({
34
+ clientEntrySrc: "{= clientEntryPointPath =}",
35
+ ssrEntrySrc: "{= ssrEntryPointPath =}",
36
+ ssrPaths: [],
37
+ ssrFallbackFile: "{= ssrFallbackFile =}",
38
+ }),
35
39
  ];
36
40
  }
@@ -1,25 +1,46 @@
1
1
  {{={= =}=}}
2
- import { type PluginOption, mergeConfig } from "vite";
2
+ /// <reference types="vitest/config" />
3
+ import { type PluginOption } from "vite";
3
4
  import { defaultExclude } from "vitest/config"
4
5
 
6
+ // Vite merges `userConfig` and our `waspConfig` returned from the plugin.
7
+ // In that merge, primitive values from waspConfig take precedence, and
8
+ // arrays are concatenated.
9
+ //
10
+ // This allows us to treat config values differently:
11
+ // - Forced: hardcoded in the return object so they always win. If the
12
+ // user set one of these in their vite.config.ts, we throw an error.
13
+ // - Overridable: we read the user's value and use it or fall back to
14
+ // our default.
15
+ // - Additive (arrays): we only return Wasp's entries; Vite's merge
16
+ // appends them to whatever the user already has.
17
+
18
+ const forcedOptions = {
19
+ 'base': "{= baseDir =}",
20
+ 'envPrefix': "REACT_APP_",
21
+ 'build.outDir': "{= clientBuildDirPath =}",
22
+ } as const;
23
+
5
24
  export function waspConfig(): PluginOption {
6
25
  return {
7
26
  name: "wasp:config",
8
27
  enforce: 'pre',
9
28
  config(config) {
10
- return mergeConfig({
11
- base: "{= baseDir =}",
29
+ throwIfOverridingForcedOptions(config);
30
+
31
+ // Returned config is merged with the user's config by Vite (mergeConfig).
32
+ return {
33
+ base: forcedOptions['base'],
12
34
  optimizeDeps: {
13
35
  exclude: {=& depsExcludedFromOptimization =}
14
36
  },
15
37
  server: {
16
- port: {= defaultClientPort =},
17
- host: "0.0.0.0",
18
- open: true,
38
+ port: useUserValue(config.server?.port, {= defaultClientPort =}),
39
+ host: useUserValue(config.server?.host, "0.0.0.0"),
19
40
  },
20
- envPrefix: "REACT_APP_",
41
+ envPrefix: forcedOptions['envPrefix'],
21
42
  build: {
22
- outDir: "{= clientBuildDirPath =}",
43
+ outDir: forcedOptions['build.outDir'],
23
44
  },
24
45
  resolve: {
25
46
  // These packages rely on a single instance per page. Not deduping them
@@ -42,15 +63,40 @@ export function waspConfig(): PluginOption {
42
63
  ],
43
64
  },
44
65
  test: {
45
- globals: true,
46
- environment: "jsdom",
66
+ globals: useUserValue(config.test?.globals, true),
67
+ environment: useUserValue(config.test?.environment, "jsdom"),
47
68
  setupFiles: {=& vitest.setupFilesArray =},
48
69
  exclude: [
49
70
  ...defaultExclude,
50
71
  "{= vitest.excludeWaspArtefactsPattern =}",
51
72
  ]
52
73
  },
53
- }, config);
74
+ };
54
75
  }
55
76
  };
56
77
  }
78
+
79
+ function useUserValue<T>(userValue: T | undefined, defaultValue: T): T {
80
+ return userValue ?? defaultValue;
81
+ }
82
+
83
+ function throwIfOverridingForcedOptions(config: Record<string, any>): void {
84
+ const conflicts: string[] = [];
85
+ for (const [path, forcedValue] of Object.entries(forcedOptions)) {
86
+ const userValue = getByPath(config, path);
87
+ if (userValue !== undefined && userValue !== forcedValue) {
88
+ conflicts.push(
89
+ ` - "${path}" is set to ${JSON.stringify(userValue)}, but Wasp requires ${JSON.stringify(forcedValue)}`
90
+ );
91
+ }
92
+ }
93
+ if (conflicts.length > 0) {
94
+ throw new Error(
95
+ `Your vite.config.ts sets options that Wasp controls:\n${conflicts.join('\n')}\n\nRemove these from your Vite config, Wasp sets them automatically.`
96
+ );
97
+ }
98
+ }
99
+
100
+ function getByPath(obj: Record<string, any>, path: string): unknown {
101
+ return path.split('.').reduce<any>((node, segment) => node?.[segment], obj);
102
+ }
@@ -0,0 +1,33 @@
1
+ {{={= =}=}}
2
+ import { startTransition } from "react";
3
+ import { hydrateRoot } from "react-dom/client";
4
+ import { createBrowserRouter, type HydrationState } from "react-router";
5
+ import { RouterProvider } from "react-router/dom";
6
+ import { Layout } from "wasp/client/app/layout";
7
+ import { WaspApp } from "wasp/client/app";
8
+
9
+ {=& routeObjects.importStatement =}
10
+
11
+ // React Router will put hydration data on this property of the `window` object.
12
+ // https://reactrouter.com/7.13.1/start/data/custom#4-hydrate-in-the-browser
13
+ const hydrationData = (window as any).__staticRouterHydrationData as HydrationState | undefined;
14
+
15
+ const router = createBrowserRouter({= routeObjects.importIdentifier =}, {
16
+ basename: "{= baseDir =}",
17
+ hydrationData,
18
+ })
19
+
20
+ function App({ isFallbackPage }: { isFallbackPage: boolean }) {
21
+ return (
22
+ <Layout isFallbackPage={isFallbackPage}>
23
+ <WaspApp>
24
+ <RouterProvider router={router} />
25
+ </WaspApp>
26
+ </Layout>
27
+ );
28
+ }
29
+
30
+ startTransition(() => {
31
+ const isFallbackpage = hydrationData == null;
32
+ hydrateRoot(document, <App isFallbackPage={isFallbackpage} />);
33
+ });
@@ -1,17 +1,66 @@
1
1
  {{={= =}=}}
2
- // @ts-nocheck
2
+ import { getRouteObjects } from "wasp/client/app/router";
3
+ import { initializeQueryClient } from "wasp/client/operations";
4
+
3
5
  {=# isAuthEnabled =}
4
6
  import { createAuthRequiredPage } from "wasp/client/app"
5
7
  {=/ isAuthEnabled =}
6
8
 
7
- // These files are used from user-land and the import paths below are relative to the
8
- // user's project dir, and not the SDK:
9
- {=# pagesToImport =}
10
- {=& importStatement =}
11
- {=/ pagesToImport =}
9
+ {=# rootComponent.isDefined =}
10
+ {=& rootComponent.importStatement =}
11
+ {=/ rootComponent.isDefined =}
12
+
13
+ {=# setupFn.isDefined =}
14
+ {=& setupFn.importStatement =}
15
+ {=/ setupFn.isDefined =}
12
16
 
13
- export const routesMapping = {
17
+ {=# routes =}
18
+ {=^ isLazy =}
19
+ {=& import.importStatement =}
20
+ {=/ isLazy =}
21
+ {=/ routes =}
22
+
23
+ const routesMapping = {
14
24
  {=# routes =}
15
- {= name =}: {= targetComponent =},
25
+ {=# isLazy =}
26
+ {= name =}: { lazy: async () => {
27
+ const Component = await {=& import.dynamicImportExpression =}
28
+ {=# isAuthRequired =}
29
+ return { Component: createAuthRequiredPage(Component) }
30
+ {=/ isAuthRequired =}
31
+ {=^ isAuthRequired =}
32
+ return { Component }
33
+ {=/ isAuthRequired =}
34
+ }},
35
+ {=/ isLazy =}
36
+ {=^ isLazy =}
37
+ {= name =}: {
38
+ {=# isAuthRequired =}
39
+ Component: createAuthRequiredPage({= import.importIdentifier =}),
40
+ {=/ isAuthRequired =}
41
+ {=^ isAuthRequired =}
42
+ Component: {= import.importIdentifier =},
43
+ {=/ isAuthRequired =}
44
+ },
45
+ {=/ isLazy =}
16
46
  {=/ routes =}
17
47
  } as const;
48
+
49
+ {=# setupFn.isDefined =}
50
+ await {= setupFn.importIdentifier =}()
51
+ {=/ setupFn.isDefined =}
52
+
53
+ initializeQueryClient()
54
+
55
+ const rootElement =
56
+ {=# rootComponent.isDefined =}
57
+ <{= rootComponent.importIdentifier =} />
58
+ {=/ rootComponent.isDefined =}
59
+ {=^ rootComponent.isDefined =}
60
+ undefined
61
+ {=/ rootComponent.isDefined =}
62
+
63
+ export const routeObjects = getRouteObjects({
64
+ routesMapping,
65
+ rootElement,
66
+ })
@@ -0,0 +1,64 @@
1
+ {{={= =}=}}
2
+ import type { PrerenderContext, PrerenderFn } from "@wasp.sh/lib-vite-ssr/types";
3
+ import * as streamConsumers from "node:stream/consumers";
4
+ import assert from "node:assert/strict";
5
+ import type { ReactNode } from "react";
6
+ import { prerenderToNodeStream as reactPrerender } from "react-dom/static";
7
+ import {
8
+ createStaticHandler,
9
+ createStaticRouter,
10
+ RouterProvider,
11
+ } from "react-router";
12
+ import { Layout } from "wasp/client/app/layout";
13
+ import { WaspApp } from "wasp/client/app";
14
+
15
+ {=& routeObjects.importStatement =}
16
+
17
+ const FALLBACK_FILE = "{= ssrFallbackFile =}";
18
+
19
+ const prerenderApp: PrerenderFn = async (route, ctx) => {
20
+ const isFallbackPage = route === FALLBACK_FILE;
21
+
22
+ if (isFallbackPage) {
23
+ return await appToHtml({ isFallbackPage: true, children: null }, ctx);
24
+ } else {
25
+ const { query, dataRoutes } = createStaticHandler({= routeObjects.importIdentifier =}, {
26
+ basename: "{= baseDir =}",
27
+ });
28
+
29
+ const req = new Request(new URL(route, "http://localhost"));
30
+
31
+ const context = await query(req);
32
+ assert (!(context instanceof Response), "Expected no redirect responses from static handler");
33
+
34
+ const router = createStaticRouter(dataRoutes, context);
35
+
36
+ return await appToHtml(
37
+ { isFallbackPage: false, children: <RouterProvider router={router} /> },
38
+ ctx,
39
+ );
40
+ }
41
+ }
42
+
43
+ export default prerenderApp;
44
+
45
+ async function appToHtml(
46
+ {
47
+ isFallbackPage,
48
+ children,
49
+ }: { isFallbackPage: boolean; children?: ReactNode },
50
+ { clientEntrySrc }: PrerenderContext,
51
+ ) {
52
+ const app = (
53
+ <Layout isFallbackPage={isFallbackPage} clientEntrySrc={clientEntrySrc}>
54
+ <WaspApp>
55
+ {children}
56
+ </WaspApp>
57
+ </Layout>
58
+ );
59
+
60
+ const html = await reactPrerender(app)
61
+ .then((result) => streamConsumers.text(result.prelude))
62
+
63
+ return html;
64
+ };
@@ -1,15 +1,15 @@
1
1
  import fs from "node:fs";
2
2
 
3
- export function getIndexTsxContent(): string {
4
- return getFileContentFromRelativePath("./files/index.tsx");
3
+ export function getClientEntryTsxContent(): string {
4
+ return getFileContentFromRelativePath("./files/client-entry.tsx");
5
5
  }
6
6
 
7
7
  export function getRoutesTsxContent(): string {
8
8
  return getFileContentFromRelativePath("./files/routes.tsx");
9
9
  }
10
10
 
11
- export function getIndexHtmlContent(): string {
12
- return getFileContentFromRelativePath("./files/index.html");
11
+ export function getSsrEntryTsxContent(): string {
12
+ return getFileContentFromRelativePath("./files/ssr-entry.tsx");
13
13
  }
14
14
 
15
15
  function getFileContentFromRelativePath(relativePath: string): string {
@@ -20,7 +20,7 @@ export const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
20
20
  config.apiUrl,
21
21
  {
22
22
  transports: ['websocket'],
23
- autoConnect: {= autoConnect =},
23
+ autoConnect: {= autoConnect =} && !import.meta.env.SSR,
24
24
  }
25
25
  )
26
26
 
@@ -1,50 +1,73 @@
1
1
  export type DataStore = {
2
- getPrefixedKey(key: string): string
3
- set(key: string, value: unknown): void
4
- get(key: string): unknown
5
- remove(key: string): void
6
- clear(): void
2
+ getPrefixedKey(key: string): string;
3
+ set(key: string, value: unknown): void;
4
+ get(key: string): unknown;
5
+ remove(key: string): void;
6
+ clear(): void;
7
+ };
8
+
9
+ const createStorage =
10
+ typeof window === "undefined" || !window.localStorage
11
+ ? createMemoryDataStore
12
+ : createLocalStorageDataStore
13
+
14
+ export const storage = createStorage("wasp");
15
+
16
+ function createMemoryDataStore(prefix: string): DataStore {
17
+ const store: Map<string, unknown> = new Map();
18
+
19
+ function getPrefixedKey(key: string): string {
20
+ return `${prefix}:${key}`;
21
+ }
22
+
23
+ return {
24
+ getPrefixedKey,
25
+ set(key, value) {
26
+ store.set(getPrefixedKey(key), value);
27
+ },
28
+ get(key) {
29
+ return store.get(getPrefixedKey(key));
30
+ },
31
+ remove(key) {
32
+ store.delete(getPrefixedKey(key));
33
+ },
34
+ clear() {
35
+ store.clear();
36
+ },
37
+ };
7
38
  }
8
39
 
9
40
  function createLocalStorageDataStore(prefix: string): DataStore {
41
+ if (!window.localStorage) {
42
+ throw new Error("Local storage is not available.");
43
+ }
44
+
10
45
  function getPrefixedKey(key: string): string {
11
- return `${prefix}:${key}`
46
+ return `${prefix}:${key}`;
12
47
  }
13
48
 
14
49
  return {
15
50
  getPrefixedKey,
16
51
  set(key, value) {
17
- ensureLocalStorageIsAvailable()
18
- localStorage.setItem(getPrefixedKey(key), JSON.stringify(value))
52
+ localStorage.setItem(getPrefixedKey(key), JSON.stringify(value));
19
53
  },
20
54
  get(key) {
21
- ensureLocalStorageIsAvailable()
22
- const value = localStorage.getItem(getPrefixedKey(key))
55
+ const value = localStorage.getItem(getPrefixedKey(key));
23
56
  try {
24
- return value ? JSON.parse(value) : undefined
57
+ return value ? JSON.parse(value) : undefined;
25
58
  } catch (e: any) {
26
- return undefined
59
+ return undefined;
27
60
  }
28
61
  },
29
62
  remove(key) {
30
- ensureLocalStorageIsAvailable()
31
- localStorage.removeItem(getPrefixedKey(key))
63
+ localStorage.removeItem(getPrefixedKey(key));
32
64
  },
33
65
  clear() {
34
- ensureLocalStorageIsAvailable()
35
66
  Object.keys(localStorage).forEach((key) => {
36
67
  if (key.startsWith(prefix)) {
37
- localStorage.removeItem(key)
68
+ localStorage.removeItem(key);
38
69
  }
39
- })
70
+ });
40
71
  },
41
- }
42
- }
43
-
44
- export const storage = createLocalStorageDataStore('wasp')
45
-
46
- function ensureLocalStorageIsAvailable(): void {
47
- if (!window.localStorage) {
48
- throw new Error('Local storage is not available.')
49
- }
72
+ };
50
73
  }
@@ -1,7 +1,7 @@
1
1
  import * as z from 'zod'
2
2
 
3
3
  // PUBLIC API
4
- export function defineEnvValidationSchema<Schema extends z.ZodObject<any>>(
4
+ export function defineEnvValidationSchema<Schema extends z.ZodObject>(
5
5
  schema: Schema,
6
6
  ): Schema {
7
7
  return schema
@@ -1,38 +1,44 @@
1
- import * as z from 'zod'
1
+ import * as z from "zod";
2
2
 
3
- import { getColorizedConsoleFormatString } from 'wasp/universal/ansiColors'
4
-
5
- const redColorFormatString = getColorizedConsoleFormatString('red');
3
+ import { colorize } from "wasp/universal/ansiColors";
6
4
 
7
5
  // PRIVATE API (SDK)
8
- export function ensureEnvSchema<Schema extends z.ZodTypeAny>(
6
+ export function ensureEnvSchema<Schema extends z.ZodType>(
9
7
  data: unknown,
10
- schema: Schema
8
+ schema: Schema,
11
9
  ): z.infer<Schema> {
12
- const result = getValidatedEnvOrError(data, schema)
10
+ const result = getValidatedEnvOrError(data, schema);
13
11
  if (result.success) {
14
- return result.data
12
+ return result.data;
15
13
  } else {
16
- console.error(`${redColorFormatString}${formatZodEnvErrors(result.error.issues)}`)
17
- throw new Error('Error parsing environment variables')
14
+ console.error(colorize("red", formatZodEnvError(result.error)));
15
+ throw new Error("Error parsing environment variables");
18
16
  }
19
17
  }
20
18
 
21
19
  // PRIVATE API (SDK, Vite config)
22
- export function getValidatedEnvOrError<Schema extends z.ZodTypeAny>(
20
+ export function getValidatedEnvOrError<Schema extends z.ZodType>(
23
21
  env: unknown,
24
- schema: Schema
25
- ): z.SafeParseReturnType<unknown, z.infer<Schema>> {
26
- return schema.safeParse(env)
22
+ schema: Schema,
23
+ ): z.ZodSafeParseResult<z.infer<Schema>> {
24
+ return schema.safeParse(env);
27
25
  }
28
26
 
29
27
  // PRIVATE API (SDK, Vite config)
30
- export function formatZodEnvErrors(issues: z.ZodIssue[]): string {
31
- const errorOutput = ['', '══ Env vars validation failed ══', '']
32
- for (const error of issues) {
33
- errorOutput.push(` - ${error.message}`)
34
- }
35
- errorOutput.push('')
36
- errorOutput.push('════════════════════════════════')
37
- return errorOutput.join('\n')
28
+ export function formatZodEnvError(error: z.ZodError): string {
29
+ const flattenedIssues = z.flattenError(error);
30
+
31
+ return [
32
+ "══ Env vars validation failed ══",
33
+ "",
34
+ // Top-level errors
35
+ ...flattenedIssues.formErrors,
36
+ "",
37
+ // Errors per field
38
+ ...Object.entries(flattenedIssues.fieldErrors).map(
39
+ ([prop, error]) => `${prop} - ${error}`,
40
+ ),
41
+ "",
42
+ "════════════════════════════════",
43
+ ].join("\n");
38
44
  }
@@ -121,6 +121,8 @@
121
121
  "./client/env/schema": "./dist/client/env/schema.js",
122
122
  {=! Private: [client] =}
123
123
  "./client/app": "./dist/client/app/index.jsx",
124
+ "./client/app/layout": "./dist/client/app/layout.jsx",
125
+ "./client/app/router": "./dist/client/app/router.jsx",
124
126
  {=! Private: [client] =}
125
127
  "./client/vite": "./dist/client/vite/index.js",
126
128