webstudio 0.0.0-588fe22 → 0.0.0-73cd6ea

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 (59) hide show
  1. package/README.md +1 -7
  2. package/lib/cli.js +5131 -2582
  3. package/package.json +37 -34
  4. package/templates/cloudflare/package.json +2 -2
  5. package/templates/cloudflare/vite.config.ts +8 -0
  6. package/templates/defaults/app/root.tsx +6 -2
  7. package/templates/defaults/app/route-templates/html.tsx +12 -13
  8. package/templates/defaults/app/route-templates/xml.tsx +3 -5
  9. package/templates/defaults/package.json +14 -14
  10. package/templates/defaults/vite.config.ts +8 -0
  11. package/templates/{react-router-docker → react-router}/app/root.tsx +6 -2
  12. package/templates/{react-router-docker → react-router}/app/route-templates/html.tsx +12 -13
  13. package/templates/{react-router-docker → react-router}/app/route-templates/xml.tsx +3 -1
  14. package/templates/react-router/package.json +34 -0
  15. package/templates/react-router/vite.config.ts +14 -0
  16. package/templates/react-router-cloudflare/app/constants.mjs +21 -0
  17. package/templates/react-router-cloudflare/app/entry.server.tsx +43 -0
  18. package/templates/react-router-cloudflare/package.json +12 -0
  19. package/templates/react-router-cloudflare/react-router.config.ts +7 -0
  20. package/templates/react-router-cloudflare/tsconfig.json +23 -0
  21. package/templates/react-router-cloudflare/vite.config.ts +15 -0
  22. package/templates/react-router-cloudflare/workers/app.ts +26 -0
  23. package/templates/react-router-cloudflare/wrangler.jsonc +6 -0
  24. package/templates/react-router-docker/app/constants.mjs +14 -1
  25. package/templates/react-router-docker/package.json +5 -33
  26. package/templates/{netlify-edge-functions → react-router-netlify}/app/constants.mjs +1 -1
  27. package/templates/react-router-netlify/netlify.toml +6 -0
  28. package/templates/react-router-netlify/package.json +10 -0
  29. package/templates/react-router-netlify/vite.config.ts +15 -0
  30. package/templates/{vercel → react-router-vercel}/app/constants.mjs +1 -1
  31. package/templates/react-router-vercel/package.json +9 -0
  32. package/templates/react-router-vercel/react-router.config.ts +6 -0
  33. package/templates/{vercel → react-router-vercel}/vercel.json +1 -0
  34. package/templates/saas-helpers/package.json +4 -1
  35. package/templates/saas-helpers/vite.config.ts +48 -0
  36. package/templates/ssg/app/route-templates/html/+Head.tsx +14 -6
  37. package/templates/ssg/app/route-templates/html/+Page.tsx +3 -1
  38. package/templates/ssg/package.json +10 -10
  39. package/templates/ssg/renderer/+onRenderHtml.tsx +13 -5
  40. package/templates/ssg/vite.config.ts +8 -0
  41. package/templates/netlify-edge-functions/app/entry.server.tsx +0 -21
  42. package/templates/netlify-edge-functions/netlify.toml +0 -16
  43. package/templates/netlify-edge-functions/package.json +0 -9
  44. package/templates/netlify-edge-functions/vite.config.ts +0 -18
  45. package/templates/netlify-functions/app/constants.mjs +0 -29
  46. package/templates/netlify-functions/app/entry.server.tsx +0 -1
  47. package/templates/netlify-functions/netlify.toml +0 -16
  48. package/templates/netlify-functions/package.json +0 -9
  49. package/templates/netlify-functions/vite.config.ts +0 -18
  50. package/templates/react-router-docker/vite.config.ts +0 -6
  51. package/templates/vercel/.vercelignore +0 -8
  52. package/templates/vercel/package.json +0 -1
  53. /package/templates/{react-router-docker → react-router}/app/extension.ts +0 -0
  54. /package/templates/{react-router-docker → react-router}/app/route-templates/default-sitemap.tsx +0 -0
  55. /package/templates/{react-router-docker → react-router}/app/route-templates/redirect.tsx +0 -0
  56. /package/templates/{react-router-docker → react-router}/app/routes/[robots.txt].tsx +0 -0
  57. /package/templates/{react-router-docker → react-router}/app/routes.ts +0 -0
  58. /package/templates/{react-router-docker → react-router}/public/favicon.ico +0 -0
  59. /package/templates/{react-router-docker → react-router}/tsconfig.json +0 -0
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "webstudio-app",
4
+ "compatibility_date": "2025-04-28",
5
+ "main": "./workers/app.ts"
6
+ }
@@ -4,6 +4,19 @@
4
4
  */
5
5
  export const assetBaseUrl = "/assets/";
6
6
 
7
+ /**
8
+ * URL.canParse(props.src)
9
+ * @type {(url: string) => boolean}
10
+ */
11
+ const UrlCanParse = (url) => {
12
+ try {
13
+ new URL(url);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
19
+
7
20
  /**
8
21
  * @type {import("@webstudio-is/image").ImageLoader}
9
22
  */
@@ -12,7 +25,7 @@ export const imageLoader = (props) => {
12
25
  return props.src;
13
26
  }
14
27
  // handle absolute urls
15
- const path = URL.canParse(props.src) ? `/${props.src}` : props.src;
28
+ const path = UrlCanParse(props.src) ? `/${props.src}` : props.src;
16
29
  // https://github.com/unjs/ipx?tab=readme-ov-file#modifiers
17
30
  return `/_image/w_${props.width},q_${props.quality}${path}`;
18
31
  };
@@ -1,39 +1,11 @@
1
1
  {
2
- "type": "module",
3
- "private": true,
4
- "sideEffects": false,
5
2
  "scripts": {
6
- "build": "react-router build",
7
- "dev": "react-router dev",
8
- "start": "react-router-serve ./build/server/index.js",
9
- "typecheck": "tsc"
3
+ "start": "react-router-serve ./build/server/index.js"
10
4
  },
11
5
  "dependencies": {
12
- "@react-router/dev": "^7.1.3",
13
- "@react-router/fs-routes": "^7.1.3",
14
- "@react-router/node": "^7.1.3",
15
- "@react-router/serve": "^7.1.3",
16
- "@webstudio-is/image": "0.0.0-588fe22",
17
- "@webstudio-is/react-sdk": "0.0.0-588fe22",
18
- "@webstudio-is/sdk": "0.0.0-588fe22",
19
- "@webstudio-is/sdk-components-animation": "0.0.0-588fe22",
20
- "@webstudio-is/sdk-components-react-radix": "0.0.0-588fe22",
21
- "@webstudio-is/sdk-components-react-router": "0.0.0-588fe22",
22
- "@webstudio-is/sdk-components-react": "0.0.0-588fe22",
23
- "h3": "^1.14.0",
24
- "ipx": "^3.0.1",
25
- "isbot": "^5.1.21",
26
- "react": "18.3.0-canary-14898b6a9-20240318",
27
- "react-dom": "18.3.0-canary-14898b6a9-20240318",
28
- "react-router": "^7.1.3",
29
- "vite": "^5.4.11"
30
- },
31
- "devDependencies": {
32
- "@types/react": "^18.2.70",
33
- "@types/react-dom": "^18.2.25",
34
- "typescript": "5.7.3"
35
- },
36
- "engines": {
37
- "node": ">=20.0.0"
6
+ "@react-router/node": "^7.5.3",
7
+ "@react-router/serve": "^7.5.3",
8
+ "h3": "^1.15.1",
9
+ "ipx": "^3.0.3"
38
10
  }
39
11
  }
@@ -8,7 +8,7 @@ export const assetBaseUrl = "/assets/";
8
8
  * @type {import("@webstudio-is/image").ImageLoader}
9
9
  */
10
10
  export const imageLoader = (props) => {
11
- if (process.env.NODE_ENV !== "production") {
11
+ if (import.meta.env.DEV) {
12
12
  return props.src;
13
13
  }
14
14
 
@@ -0,0 +1,6 @@
1
+ [build]
2
+ command = "react-router build"
3
+ publish = "build/client"
4
+
5
+ [dev]
6
+ command = "react-router dev"
@@ -0,0 +1,10 @@
1
+ {
2
+ "scripts": {
3
+ "start": "npx netlify-cli serve",
4
+ "deploy": "npx netlify-cli deploy --build --prod"
5
+ },
6
+ "dependencies": {
7
+ "@netlify/vite-plugin-react-router": "^1.0.1",
8
+ "@react-router/node": "^7.5.3"
9
+ }
10
+ }
@@ -0,0 +1,15 @@
1
+ import { reactRouter } from "@react-router/dev/vite";
2
+ import { defineConfig } from "vite";
3
+ import netlifyPlugin from "@netlify/vite-plugin-react-router";
4
+
5
+ export default defineConfig({
6
+ plugins: [reactRouter(), netlifyPlugin()],
7
+ resolve: {
8
+ conditions: ["browser", "development|production"],
9
+ },
10
+ ssr: {
11
+ resolve: {
12
+ conditions: ["node", "development|production"],
13
+ },
14
+ },
15
+ });
@@ -8,7 +8,7 @@ export const assetBaseUrl = "/assets/";
8
8
  * @type {import("@webstudio-is/image").ImageLoader}
9
9
  */
10
10
  export const imageLoader = (props) => {
11
- if (process.env.NODE_ENV !== "production") {
11
+ if (import.meta.env.DEV) {
12
12
  return props.src;
13
13
  }
14
14
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "scripts": {
3
+ "deploy": "npx vercel deploy"
4
+ },
5
+ "dependencies": {
6
+ "@react-router/node": "^7.5.3",
7
+ "@vercel/react-router": "^1.1.0"
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ import type { Config } from "@react-router/dev/config";
2
+ import { vercelPreset } from "@vercel/react-router/vite";
3
+
4
+ export default {
5
+ presets: [vercelPreset()],
6
+ } satisfies Config;
@@ -1,4 +1,5 @@
1
1
  {
2
+ "framework": "react-router",
2
3
  "images": {
3
4
  "domains": [],
4
5
  "sizes": [
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "worktop": "0.8.0-next.18",
4
- "zod": "^3.22.4"
4
+ "zod": "^3.24.2"
5
+ },
6
+ "devDependencies": {
7
+ "fast-glob": "^3.3.2"
5
8
  }
6
9
  }
@@ -0,0 +1,48 @@
1
+ import {
2
+ vitePlugin as remix,
3
+ cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
4
+ } from "@remix-run/dev";
5
+ import { defineConfig } from "vite";
6
+
7
+ import { existsSync } from "node:fs";
8
+ // @ts-ignore
9
+ import path from "node:path";
10
+ // @ts-ignore
11
+ import fg from "fast-glob";
12
+
13
+ const rootDir = ["..", "../..", "../../.."]
14
+ .map((dir) => path.join(__dirname, dir))
15
+ .find((dir) => existsSync(path.join(dir, ".git")));
16
+
17
+ const hasPrivateFolders =
18
+ fg.sync([path.join(rootDir ?? "", "packages/*/private-src/*")], {
19
+ ignore: ["**/node_modules/**"],
20
+ }).length > 0;
21
+
22
+ const conditions = hasPrivateFolders
23
+ ? ["webstudio-private", "webstudio"]
24
+ : ["webstudio"];
25
+
26
+ export default defineConfig(({ mode }) => ({
27
+ resolve: {
28
+ conditions: [...conditions, "browser", "development|production"],
29
+ },
30
+ ssr: {
31
+ resolve: {
32
+ conditions: [...conditions, "node", "development|production"],
33
+ },
34
+ },
35
+ plugins: [
36
+ // without this, remixCloudflareDevProxy trying to load workerd even for production (it's not needed for production)
37
+ mode === "production" ? undefined : remixCloudflareDevProxy(),
38
+ remix({
39
+ future: {
40
+ v3_lazyRouteDiscovery: false,
41
+ v3_relativeSplatPath: false,
42
+ v3_singleFetch: false,
43
+ v3_fetcherPersist: false,
44
+ v3_throwAbortReason: false,
45
+ },
46
+ }),
47
+ ].filter(Boolean),
48
+ }));
@@ -20,11 +20,14 @@ export const Head = ({ data }: { data: PageContext["data"] }) => {
20
20
  let socialImageUrl = pageMeta.socialImageUrl;
21
21
  if (pageMeta.socialImageAssetName) {
22
22
  socialImageUrl = `${origin}${imageLoader({
23
- src: pageMeta.socialImageAssetName,
23
+ src: `${assetBaseUrl}/${pageMeta.socialImageAssetName}`,
24
24
  // Do not transform social image (not enough information do we need to do this)
25
25
  format: "raw",
26
26
  })}`;
27
27
  }
28
+ const isTwitterCardSizeDefined = pageMeta.custom.some(
29
+ (meta) => meta.property === "twitter:card"
30
+ );
28
31
  return (
29
32
  <>
30
33
  {data.url && <meta property="og:url" content={data.url} />}
@@ -55,12 +58,17 @@ export const Head = ({ data }: { data: PageContext["data"] }) => {
55
58
  {pageMeta.custom.map(({ property, content }) => (
56
59
  <meta key={property} property={property} content={content} />
57
60
  ))}
61
+ {(pageMeta.socialImageAssetName !== undefined ||
62
+ pageMeta.socialImageUrl !== undefined) &&
63
+ isTwitterCardSizeDefined === false && (
64
+ <meta property="twitter:card" content="summary_large_image" />
65
+ )}
58
66
 
59
67
  {favIconAsset && (
60
68
  <link
61
69
  rel="icon"
62
70
  href={imageLoader({
63
- src: `${assetBaseUrl}${favIconAsset.name}`,
71
+ src: `${assetBaseUrl}${favIconAsset}`,
64
72
  // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search
65
73
  width: 144,
66
74
  height: 144,
@@ -72,18 +80,18 @@ export const Head = ({ data }: { data: PageContext["data"] }) => {
72
80
  )}
73
81
  {pageFontAssets.map((asset) => (
74
82
  <link
75
- key={asset.id}
83
+ key={asset}
76
84
  rel="preload"
77
- href={`${assetBaseUrl}${asset.name}`}
85
+ href={`${assetBaseUrl}${asset}`}
78
86
  as="font"
79
87
  crossOrigin="anonymous"
80
88
  />
81
89
  ))}
82
90
  {pageBackgroundImageAssets.map((asset) => (
83
91
  <link
84
- key={asset.id}
92
+ key={asset}
85
93
  rel="preload"
86
- href={`${assetBaseUrl}${asset.name}`}
94
+ href={`${assetBaseUrl}${asset}`}
87
95
  as="image"
88
96
  />
89
97
  ))}
@@ -1,7 +1,7 @@
1
1
  import type { PageContext } from "vike/types";
2
2
  import { ReactSdkContext } from "@webstudio-is/react-sdk/runtime";
3
3
  import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
4
- import { Page } from "__CLIENT__";
4
+ import { Page, breakpoints } from "__CLIENT__";
5
5
 
6
6
  const PageComponent = ({ data }: { data: PageContext["data"] }) => {
7
7
  const { system, resources, url } = data;
@@ -11,6 +11,8 @@ const PageComponent = ({ data }: { data: PageContext["data"] }) => {
11
11
  imageLoader,
12
12
  assetBaseUrl,
13
13
  resources,
14
+ breakpoints,
15
+ onError: console.error,
14
16
  }}
15
17
  >
16
18
  {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
@@ -8,22 +8,22 @@
8
8
  "typecheck": "tsc"
9
9
  },
10
10
  "dependencies": {
11
- "@webstudio-is/image": "0.0.0-588fe22",
12
- "@webstudio-is/react-sdk": "0.0.0-588fe22",
13
- "@webstudio-is/sdk": "0.0.0-588fe22",
14
- "@webstudio-is/sdk-components-react": "0.0.0-588fe22",
15
- "@webstudio-is/sdk-components-animation": "0.0.0-588fe22",
16
- "@webstudio-is/sdk-components-react-radix": "0.0.0-588fe22",
11
+ "@webstudio-is/image": "0.0.0-73cd6ea",
12
+ "@webstudio-is/react-sdk": "0.0.0-73cd6ea",
13
+ "@webstudio-is/sdk": "0.0.0-73cd6ea",
14
+ "@webstudio-is/sdk-components-react": "0.0.0-73cd6ea",
15
+ "@webstudio-is/sdk-components-animation": "0.0.0-73cd6ea",
16
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-73cd6ea",
17
17
  "react": "18.3.0-canary-14898b6a9-20240318",
18
18
  "react-dom": "18.3.0-canary-14898b6a9-20240318",
19
- "vike": "^0.4.219"
19
+ "vike": "^0.4.229"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/react": "^18.2.70",
23
23
  "@types/react-dom": "^18.2.25",
24
- "@vitejs/plugin-react": "^4.3.4",
25
- "typescript": "5.7.3",
26
- "vite": "^5.4.11"
24
+ "@vitejs/plugin-react": "^4.4.1",
25
+ "typescript": "5.8.2",
26
+ "vite": "^6.3.4"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=20.0.0"
@@ -1,10 +1,14 @@
1
1
  import { renderToString } from "react-dom/server";
2
2
  import { dangerouslySkipEscape, escapeInject } from "vike/server";
3
3
  import type { OnRenderHtmlSync } from "vike/types";
4
- // @todo think about how to make __generated__ typeable
5
- /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
6
- // @ts-ignore
7
- import { CustomCode } from "../app/__generated__/_index";
4
+ import {
5
+ CustomCode,
6
+ projectId,
7
+ lastPublished,
8
+ // @todo think about how to make __generated__ typeable
9
+ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
10
+ // @ts-ignore
11
+ } from "../app/__generated__/_index";
8
12
 
9
13
  export const onRenderHtml: OnRenderHtmlSync = (pageContext) => {
10
14
  const lang = pageContext.data.pageMeta.language || "en";
@@ -12,7 +16,11 @@ export const onRenderHtml: OnRenderHtmlSync = (pageContext) => {
12
16
  const Page = pageContext.Page ?? (() => <></>);
13
17
  const html = dangerouslySkipEscape(
14
18
  renderToString(
15
- <html lang={lang}>
19
+ <html
20
+ lang={lang}
21
+ data-ws-project={projectId}
22
+ data-ws-last-published={lastPublished}
23
+ >
16
24
  <head>
17
25
  <meta charSet="UTF-8" />
18
26
  <meta name="viewport" content="width=device-width,initial-scale=1" />
@@ -4,4 +4,12 @@ import vike from "vike/plugin";
4
4
 
5
5
  export default defineConfig({
6
6
  plugins: [react(), vike({ prerender: true })],
7
+ resolve: {
8
+ conditions: ["browser", "development|production"],
9
+ },
10
+ ssr: {
11
+ resolve: {
12
+ conditions: ["node", "development|production"],
13
+ },
14
+ },
7
15
  });
@@ -1,21 +0,0 @@
1
- import type { EntryContext } from "@remix-run/node";
2
- import { RemixServer } from "@remix-run/react";
3
- import { renderToString } from "react-dom/server";
4
-
5
- export default function handleRequest(
6
- request: Request,
7
- responseStatusCode: number,
8
- responseHeaders: Headers,
9
- remixContext: EntryContext
10
- ) {
11
- const markup = renderToString(
12
- <RemixServer context={remixContext} url={request.url} />
13
- );
14
-
15
- responseHeaders.set("Content-Type", "text/html");
16
-
17
- return new Response("<!DOCTYPE html>" + markup, {
18
- headers: responseHeaders,
19
- status: responseStatusCode,
20
- });
21
- }
@@ -1,16 +0,0 @@
1
- [build]
2
- command = "npm run build"
3
- publish = "build/client"
4
-
5
- [dev]
6
- command = "npm run dev"
7
- framework = "vite"
8
-
9
- # Set immutable caching for static files, because they have fingerprinted filenames
10
-
11
- [[headers]]
12
- for = "/build/*"
13
-
14
- [headers.values]
15
-
16
- "Cache-Control" = "public, max-age=31560000, immutable"
@@ -1,9 +0,0 @@
1
- {
2
- "scripts": {
3
- "start": "netlify serve"
4
- },
5
- "dependencies": {
6
- "@netlify/edge-functions": "^2.11.1",
7
- "@netlify/remix-edge-adapter": "^3.4.2"
8
- }
9
- }
@@ -1,18 +0,0 @@
1
- import { vitePlugin as remix } from "@remix-run/dev";
2
- import { defineConfig } from "vite";
3
- import { netlifyPlugin } from "@netlify/remix-edge-adapter/plugin";
4
-
5
- export default defineConfig({
6
- plugins: [
7
- remix({
8
- future: {
9
- v3_lazyRouteDiscovery: false,
10
- v3_relativeSplatPath: false,
11
- v3_singleFetch: false,
12
- v3_fetcherPersist: false,
13
- v3_throwAbortReason: false,
14
- },
15
- }),
16
- netlifyPlugin(),
17
- ],
18
- });
@@ -1,29 +0,0 @@
1
- /**
2
- * We use mjs extension as constants in this file is shared with the build script
3
- * and we use `node --eval` to extract the constants.
4
- */
5
- export const assetBaseUrl = "/assets/";
6
-
7
- /**
8
- * @type {import("@webstudio-is/image").ImageLoader}
9
- */
10
- export const imageLoader = (props) => {
11
- if (process.env.NODE_ENV !== "production") {
12
- return props.src;
13
- }
14
-
15
- if (props.format === "raw") {
16
- return props.src;
17
- }
18
-
19
- // https://docs.netlify.com/image-cdn/overview/
20
- const searchParams = new URLSearchParams();
21
- searchParams.set("url", props.src);
22
- searchParams.set("w", props.width.toString());
23
- if (props.height) {
24
- searchParams.set("h", props.height.toString());
25
- }
26
- searchParams.set("q", props.quality.toString());
27
- // fit=contain by default
28
- return `/.netlify/images?${searchParams}`;
29
- };
@@ -1 +0,0 @@
1
- export { handleRequest as default } from "@netlify/remix-adapter";
@@ -1,16 +0,0 @@
1
- [build]
2
- command = "npm run build"
3
- publish = "build/client"
4
-
5
- [dev]
6
- command = "npm run dev"
7
- framework = "vite"
8
-
9
- # Set immutable caching for static files, because they have fingerprinted filenames
10
-
11
- [[headers]]
12
- for = "/build/*"
13
-
14
- [headers.values]
15
-
16
- "Cache-Control" = "public, max-age=31560000, immutable"
@@ -1,9 +0,0 @@
1
- {
2
- "scripts": {
3
- "start": "npx netlify-cli serve"
4
- },
5
- "dependencies": {
6
- "@netlify/functions": "^2.8.2",
7
- "@netlify/remix-adapter": "^2.5.1"
8
- }
9
- }
@@ -1,18 +0,0 @@
1
- import { vitePlugin as remix } from "@remix-run/dev";
2
- import { defineConfig } from "vite";
3
- import { netlifyPlugin } from "@netlify/remix-adapter/plugin";
4
-
5
- export default defineConfig({
6
- plugins: [
7
- remix({
8
- future: {
9
- v3_lazyRouteDiscovery: false,
10
- v3_relativeSplatPath: false,
11
- v3_singleFetch: false,
12
- v3_fetcherPersist: false,
13
- v3_throwAbortReason: false,
14
- },
15
- }),
16
- netlifyPlugin(),
17
- ],
18
- });
@@ -1,6 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import { reactRouter } from "@react-router/dev/vite";
3
-
4
- export default defineConfig({
5
- plugins: [reactRouter()],
6
- });
@@ -1,8 +0,0 @@
1
- node_modules
2
-
3
- .cache
4
- .env
5
- .vercel
6
- .webstudio
7
- build
8
- public/build
@@ -1 +0,0 @@
1
- {}