webstudio 0.168.0 → 0.173.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webstudio",
3
- "version": "0.168.0",
3
+ "version": "0.173.0",
4
4
  "description": "Webstudio CLI",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -35,45 +35,44 @@
35
35
  "parse5": "7.1.2",
36
36
  "picocolors": "^1.0.1",
37
37
  "strip-indent": "^4.0.0",
38
- "title-case": "^4.1.0",
39
38
  "yargs": "^17.7.2",
40
39
  "zod": "^3.22.4",
41
- "@webstudio-is/image": "0.168.0",
42
- "@webstudio-is/react-sdk": "0.168.0",
43
- "@webstudio-is/sdk": "0.168.0",
44
- "@webstudio-is/http-client": "0.168.0",
45
- "@webstudio-is/sdk-components-react": "0.168.0",
46
- "@webstudio-is/sdk-components-react-radix": "0.168.0",
47
- "@webstudio-is/sdk-components-react-remix": "0.168.0"
40
+ "@webstudio-is/http-client": "0.173.0",
41
+ "@webstudio-is/sdk": "0.173.0",
42
+ "@webstudio-is/sdk-components-react": "0.173.0",
43
+ "@webstudio-is/image": "0.173.0",
44
+ "@webstudio-is/react-sdk": "0.173.0",
45
+ "@webstudio-is/sdk-components-react-radix": "0.173.0",
46
+ "@webstudio-is/sdk-components-react-remix": "0.173.0"
48
47
  },
49
48
  "devDependencies": {
50
49
  "@jest/globals": "^29.7.0",
51
50
  "@netlify/remix-adapter": "^2.4.0",
52
51
  "@netlify/remix-edge-adapter": "3.3.0",
53
- "@remix-run/cloudflare": "^2.9.2",
54
- "@remix-run/cloudflare-pages": "^2.9.2",
55
- "@remix-run/dev": "^2.9.2",
56
- "@remix-run/node": "^2.9.2",
57
- "@remix-run/react": "^2.9.2",
58
- "@remix-run/server-runtime": "^2.9.2",
52
+ "@remix-run/cloudflare": "^2.10.3",
53
+ "@remix-run/cloudflare-pages": "^2.10.3",
54
+ "@remix-run/dev": "^2.10.3",
55
+ "@remix-run/node": "^2.10.3",
56
+ "@remix-run/react": "^2.10.3",
57
+ "@remix-run/server-runtime": "^2.10.3",
59
58
  "@types/node": "^20.12.7",
60
59
  "@types/react": "^18.2.70",
61
60
  "@types/react-dom": "^18.2.25",
62
61
  "@types/yargs": "^17.0.32",
62
+ "@vitejs/plugin-react": "^4.3.1",
63
63
  "react": "18.3.0-canary-14898b6a9-20240318",
64
64
  "react-dom": "18.3.0-canary-14898b6a9-20240318",
65
- "typescript": "5.4.5",
66
- "vite": "^5.2.13",
67
- "wrangler": "^3.48.0",
65
+ "typescript": "5.5.2",
66
+ "vike": "^0.4.180",
67
+ "vite": "^5.3.4",
68
+ "wrangler": "^3.63.2",
69
+ "@webstudio-is/form-handlers": "0.173.0",
68
70
  "@webstudio-is/jest-config": "1.0.7",
69
- "@webstudio-is/form-handlers": "0.168.0",
70
71
  "@webstudio-is/tsconfig": "1.0.7"
71
72
  },
72
73
  "scripts": {
73
74
  "typecheck": "tsc",
74
- "checks": "pnpm typecheck",
75
75
  "build": "rm -rf lib && esbuild src/cli.ts --outdir=lib --bundle --format=esm --packages=external",
76
- "dev": "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib",
77
76
  "test": "NODE_OPTIONS=--experimental-vm-modules jest"
78
77
  }
79
78
  }
@@ -4,4 +4,6 @@ import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
4
4
  // @ts-ignore - the server build file is generated by `remix vite:build`
5
5
  import * as build from "../build/server";
6
6
 
7
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
8
+ // @ts-ignore - the server build file is generated by `remix vite:build`
7
9
  export const onRequest = createPagesFunctionHandler({ build });
@@ -12,13 +12,11 @@
12
12
  "build-cf-types": "wrangler types"
13
13
  },
14
14
  "dependencies": {
15
- "@remix-run/cloudflare": "2.9.2",
16
- "@remix-run/cloudflare-pages": "2.9.2",
17
- "isbot": "^5.1.8"
15
+ "@remix-run/cloudflare": "2.10.3",
16
+ "@remix-run/cloudflare-pages": "2.10.3"
18
17
  },
19
18
  "devDependencies": {
20
- "@cloudflare/workers-types": "^4.20240405.0",
21
- "wrangler": "^3.48.0",
22
- "miniflare": "^3.20231030.4"
19
+ "@cloudflare/workers-types": "^4.20240620.0",
20
+ "wrangler": "^3.63.2"
23
21
  }
24
22
  }
@@ -8,26 +8,11 @@
8
8
  "**/.client/**/*.tsx"
9
9
  ],
10
10
  "compilerOptions": {
11
- "lib": ["DOM", "DOM.Iterable", "ES2023"],
12
11
  "types": [
13
12
  "@remix-run/cloudflare",
14
13
  "vite/client",
15
- "@cloudflare/workers-types/2023-07-01"
16
- ],
17
- "isolatedModules": true,
18
- "esModuleInterop": true,
19
- "jsx": "react-jsx",
20
- "module": "ESNext",
21
- "moduleResolution": "Bundler",
22
- "resolveJsonModule": true,
23
- "target": "ES2022",
24
- "strict": true,
25
- "allowJs": true,
26
- "skipLibCheck": true,
27
- "forceConsistentCasingInFileNames": true,
28
- "baseUrl": ".",
29
-
30
- // Vite takes care of building everything, not tsc.
31
- "noEmit": true
14
+ "@cloudflare/workers-types/2023-07-01",
15
+ "@webstudio-is/react-sdk/placeholder"
16
+ ]
32
17
  }
33
18
  }
@@ -1,5 +1,5 @@
1
1
  import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
2
- import { sitemap } from "../../../../__generated__/$resources.sitemap.xml";
2
+ import { sitemap } from "__SITEMAP__";
3
3
 
4
4
  export const loader = (arg: LoaderFunctionArgs) => {
5
5
  const host =
@@ -10,6 +10,11 @@ import {
10
10
  redirect,
11
11
  } from "@remix-run/server-runtime";
12
12
  import { useLoaderData } from "@remix-run/react";
13
+ import {
14
+ isLocalResource,
15
+ loadResource,
16
+ loadResources,
17
+ } from "@webstudio-is/sdk";
13
18
  import { ReactSdkContext } from "@webstudio-is/react-sdk";
14
19
  import {
15
20
  n8nHandler,
@@ -20,21 +25,34 @@ import {
20
25
  Page,
21
26
  siteName,
22
27
  favIconAsset,
23
- socialImageAsset,
24
28
  pageFontAssets,
25
29
  pageBackgroundImageAssets,
26
- } from "../../../../__generated__/_index";
30
+ } from "__CLIENT__";
27
31
  import {
28
- formsProperties,
29
- loadResources,
32
+ getResources,
30
33
  getPageMeta,
31
34
  getRemixParams,
32
35
  projectId,
33
36
  contactEmail,
34
- } from "../../../../__generated__/_index.server";
37
+ } from "__SERVER__";
38
+ import { assetBaseUrl, imageBaseUrl, imageLoader } from "__CONSTANTS__";
39
+ import css from "__CSS__?url";
40
+ import { sitemap } from "__SITEMAP__";
41
+
42
+ const customFetch: typeof fetch = (input, init) => {
43
+ if (typeof input !== "string") {
44
+ return fetch(input, init);
45
+ }
35
46
 
36
- import css from "../__generated__/index.css?url";
37
- import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs";
47
+ if (isLocalResource(input, "sitemap.xml")) {
48
+ // @todo: dynamic import sitemap ???
49
+ const response = new Response(JSON.stringify(sitemap));
50
+ response.headers.set("content-type", "application/json; charset=utf-8");
51
+ return Promise.resolve(response);
52
+ }
53
+
54
+ return fetch(input, init);
55
+ };
38
56
 
39
57
  export const loader = async (arg: LoaderFunctionArgs) => {
40
58
  const url = new URL(arg.request.url);
@@ -52,7 +70,10 @@ export const loader = async (arg: LoaderFunctionArgs) => {
52
70
  origin: url.origin,
53
71
  };
54
72
 
55
- const resources = await loadResources({ system });
73
+ const resources = await loadResources(
74
+ customFetch,
75
+ getResources({ system }).data
76
+ );
56
77
  const pageMeta = getPageMeta({ system, resources });
57
78
 
58
79
  if (pageMeta.redirect) {
@@ -66,11 +87,14 @@ export const loader = async (arg: LoaderFunctionArgs) => {
66
87
  // typecheck
67
88
  arg.context.EXCLUDE_FROM_SEARCH satisfies boolean;
68
89
 
90
+ if (arg.context.EXCLUDE_FROM_SEARCH) {
91
+ pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH;
92
+ }
93
+
69
94
  return json(
70
95
  {
71
96
  host,
72
97
  url: url.href,
73
- excludeFromSearch: arg.context.EXCLUDE_FROM_SEARCH,
74
98
  system,
75
99
  resources,
76
100
  pageMeta,
@@ -86,7 +110,7 @@ export const loader = async (arg: LoaderFunctionArgs) => {
86
110
  );
87
111
  };
88
112
 
89
- export const headers: HeadersFunction = ({ loaderHeaders }) => {
113
+ export const headers: HeadersFunction = () => {
90
114
  return {
91
115
  "Cache-Control": "public, max-age=0, must-revalidate",
92
116
  };
@@ -134,7 +158,7 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
134
158
  });
135
159
  }
136
160
 
137
- if (pageMeta.excludePageFromSearch || data.excludeFromSearch) {
161
+ if (pageMeta.excludePageFromSearch) {
138
162
  metas.push({
139
163
  name: "robots",
140
164
  content: "noindex, nofollow",
@@ -152,11 +176,11 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
152
176
  });
153
177
  }
154
178
 
155
- if (socialImageAsset) {
179
+ if (pageMeta.socialImageAssetName) {
156
180
  metas.push({
157
181
  property: "og:image",
158
182
  content: `https://${data.host}${imageLoader({
159
- src: socialImageAsset.name,
183
+ src: pageMeta.socialImageAssetName,
160
184
  // Do not transform social image (not enough information do we need to do this)
161
185
  format: "raw",
162
186
  })}`,
@@ -220,19 +244,6 @@ export const links: LinksFunction = () => {
220
244
  const getRequestHost = (request: Request): string =>
221
245
  request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
222
246
 
223
- const getMethod = (value: string | undefined) => {
224
- if (value === undefined) {
225
- return "post";
226
- }
227
-
228
- switch (value.toLowerCase()) {
229
- case "get":
230
- return "get";
231
- default:
232
- return "post";
233
- }
234
- };
235
-
236
247
  export const action = async ({
237
248
  request,
238
249
  context,
@@ -240,14 +251,25 @@ export const action = async ({
240
251
  { success: true } | { success: false; errors: string[] }
241
252
  > => {
242
253
  try {
254
+ const pageUrl = new URL(request.url);
255
+ pageUrl.host = getRequestHost(request);
256
+
243
257
  const formData = await request.formData();
244
258
 
245
- const formId = formData.get(formIdFieldName);
259
+ const system = {
260
+ params: {},
261
+ search: {},
262
+ origin: pageUrl.origin,
263
+ };
264
+
265
+ const resourceName = formData.get(formIdFieldName);
246
266
 
247
- if (formId == null || typeof formId !== "string") {
267
+ if (resourceName == null || typeof resourceName !== "string") {
248
268
  throw new Error("No form id in FormData");
249
269
  }
250
270
 
271
+ const resource = getResources({ system }).action.get(resourceName);
272
+
251
273
  const formBotValue = formData.get(formBotFieldName);
252
274
 
253
275
  if (formBotValue == null || typeof formBotValue !== "string") {
@@ -266,41 +288,32 @@ export const action = async ({
266
288
  throw new Error(`Form bot value invalid ${formBotValue}`);
267
289
  }
268
290
 
269
- const formProperties = formsProperties.get(formId);
291
+ formData.delete(formIdFieldName);
292
+ formData.delete(formBotFieldName);
270
293
 
271
- // form properties are not defined when defaults are used
272
- const { action, method } = formProperties ?? {};
294
+ if (resource) {
295
+ const { ok, statusText } = await loadResource(fetch, {
296
+ ...resource,
297
+ body: Object.fromEntries(formData),
298
+ });
299
+ if (ok) {
300
+ return { success: true };
301
+ }
302
+ return { success: false, errors: [statusText] };
303
+ }
273
304
 
274
305
  if (contactEmail === undefined) {
275
306
  throw new Error("Contact email not found");
276
307
  }
277
308
 
278
- const pageUrl = new URL(request.url);
279
- pageUrl.host = getRequestHost(request);
280
-
281
- if (action !== undefined) {
282
- try {
283
- // Test that action is full URL
284
- new URL(action);
285
- } catch {
286
- throw new Error(
287
- "Invalid action URL, must be valid http/https protocol"
288
- );
289
- }
290
- }
291
-
292
- const formInfo = {
293
- formData,
294
- projectId,
295
- action: action ?? null,
296
- method: getMethod(method),
297
- pageUrl: pageUrl.toString(),
298
- toEmail: contactEmail,
299
- fromEmail: pageUrl.hostname + "@webstudio.email",
300
- } as const;
301
-
302
309
  const result = await n8nHandler({
303
- formInfo,
310
+ formInfo: {
311
+ formId: [projectId, resourceName].join("--"),
312
+ formData,
313
+ pageUrl: pageUrl.toString(),
314
+ toEmail: contactEmail,
315
+ fromEmail: pageUrl.hostname + "@webstudio.email",
316
+ },
304
317
  hookUrl: context.N8N_FORM_EMAIL_HOOK,
305
318
  });
306
319
 
@@ -316,7 +329,7 @@ export const action = async ({
316
329
  };
317
330
 
318
331
  const Outlet = () => {
319
- const { system, resources } = useLoaderData<typeof loader>();
332
+ const { system, resources, url } = useLoaderData<typeof loader>();
320
333
  return (
321
334
  <ReactSdkContext.Provider
322
335
  value={{
@@ -326,7 +339,8 @@ const Outlet = () => {
326
339
  resources,
327
340
  }}
328
341
  >
329
- <Page system={system} />
342
+ {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
343
+ <Page key={url} system={system} />
330
344
  </ReactSdkContext.Provider>
331
345
  );
332
346
  };
@@ -0,0 +1,6 @@
1
+ import { redirect } from "@remix-run/server-runtime";
2
+ import { url, status } from "__REDIRECT__";
3
+
4
+ export const loader = () => {
5
+ return redirect(url, status);
6
+ };
@@ -1,15 +1,27 @@
1
1
  /* eslint-disable camelcase */
2
+ import { renderToString } from "react-dom/server";
2
3
  import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
4
+ import { isLocalResource, loadResources } from "@webstudio-is/sdk";
3
5
  import { ReactSdkContext } from "@webstudio-is/react-sdk";
4
- import { Page } from "../../../../__generated__/_index";
5
- import {
6
- loadResources,
7
- getPageMeta,
8
- getRemixParams,
9
- } from "../../../../__generated__/_index.server";
10
-
11
- import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs";
12
- import { renderToString } from "react-dom/server";
6
+ import { Page } from "__CLIENT__";
7
+ import { getPageMeta, getRemixParams, getResources } from "__SERVER__";
8
+ import { assetBaseUrl, imageBaseUrl, imageLoader } from "__CONSTANTS__";
9
+ import { sitemap } from "__SITEMAP__";
10
+
11
+ const customFetch: typeof fetch = (input, init) => {
12
+ if (typeof input !== "string") {
13
+ return fetch(input, init);
14
+ }
15
+
16
+ if (isLocalResource(input, "sitemap.xml")) {
17
+ // @todo: dynamic import sitemap ???
18
+ const response = new Response(JSON.stringify(sitemap));
19
+ response.headers.set("content-type", "application/json; charset=utf-8");
20
+ return Promise.resolve(response);
21
+ }
22
+
23
+ return fetch(input, init);
24
+ };
13
25
 
14
26
  export const loader = async (arg: LoaderFunctionArgs) => {
15
27
  const url = new URL(arg.request.url);
@@ -28,7 +40,10 @@ export const loader = async (arg: LoaderFunctionArgs) => {
28
40
  origin: url.origin,
29
41
  };
30
42
 
31
- const resources = await loadResources({ system });
43
+ const resources = await loadResources(
44
+ customFetch,
45
+ getResources({ system }).data
46
+ );
32
47
  const pageMeta = getPageMeta({ system, resources });
33
48
 
34
49
  if (pageMeta.redirect) {
@@ -8,26 +8,26 @@
8
8
  "typecheck": "tsc"
9
9
  },
10
10
  "dependencies": {
11
- "@remix-run/node": "2.9.2",
12
- "@remix-run/react": "2.9.2",
13
- "@remix-run/server-runtime": "2.9.2",
14
- "@webstudio-is/react-sdk": "0.168.0",
15
- "@webstudio-is/sdk-components-react-radix": "0.168.0",
16
- "@webstudio-is/sdk-components-react-remix": "0.168.0",
17
- "@webstudio-is/sdk-components-react": "0.168.0",
18
- "@webstudio-is/form-handlers": "0.168.0",
19
- "@webstudio-is/image": "0.168.0",
20
- "@webstudio-is/sdk": "0.168.0",
21
- "isbot": "^5.1.8",
11
+ "@remix-run/node": "2.10.3",
12
+ "@remix-run/react": "2.10.3",
13
+ "@remix-run/server-runtime": "2.10.3",
14
+ "@webstudio-is/react-sdk": "0.173.0",
15
+ "@webstudio-is/sdk-components-react-radix": "0.173.0",
16
+ "@webstudio-is/sdk-components-react-remix": "0.173.0",
17
+ "@webstudio-is/sdk-components-react": "0.173.0",
18
+ "@webstudio-is/form-handlers": "0.173.0",
19
+ "@webstudio-is/image": "0.173.0",
20
+ "@webstudio-is/sdk": "0.173.0",
21
+ "isbot": "^5.1.13",
22
22
  "react": "18.3.0-canary-14898b6a9-20240318",
23
23
  "react-dom": "18.3.0-canary-14898b6a9-20240318"
24
24
  },
25
25
  "devDependencies": {
26
- "@remix-run/dev": "2.9.2",
26
+ "@remix-run/dev": "2.10.3",
27
27
  "@types/react": "^18.2.70",
28
28
  "@types/react-dom": "^18.2.25",
29
- "typescript": "5.4.5",
30
- "vite": "^5.2.13"
29
+ "typescript": "5.5.2",
30
+ "vite": "^5.3.4"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=20.0.0"
@@ -2,21 +2,20 @@
2
2
  "include": ["**/*.ts", "**/*.tsx", "**/*.mjs"],
3
3
  "compilerOptions": {
4
4
  "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
- "types": ["@remix-run/node", "vite/client"],
5
+ "types": [
6
+ "@remix-run/node",
7
+ "vite/client",
8
+ "@webstudio-is/react-sdk/placeholder"
9
+ ],
6
10
  "isolatedModules": true,
7
11
  "esModuleInterop": true,
8
12
  "jsx": "react-jsx",
9
13
  "module": "ESNext",
10
14
  "moduleResolution": "bundler",
11
- "resolveJsonModule": true,
12
15
  "target": "ES2022",
13
16
  "strict": true,
14
17
  "allowJs": true,
15
- "checkJs": true,
16
18
  "forceConsistentCasingInFileNames": true,
17
- "allowImportingTsExtensions": true,
18
- "baseUrl": ".",
19
- // Remix takes care of building everything in `remix build`.
20
19
  "noEmit": true,
21
20
  "skipLibCheck": true
22
21
  }
@@ -1,25 +1,5 @@
1
1
  {
2
- "include": ["**/*.ts", "**/*.tsx", "**/*.mjs"],
3
2
  "compilerOptions": {
4
- "lib": ["DOM", "DOM.Iterable", "ES2023"],
5
- "types": ["@remix-run/node", "vite/client"],
6
- "isolatedModules": true,
7
- "esModuleInterop": true,
8
- "jsx": "react-jsx",
9
- "module": "ESNext",
10
- "moduleResolution": "bundler",
11
- "resolveJsonModule": true,
12
- "target": "ES2022",
13
- "strict": true,
14
- "allowJs": true,
15
- "checkJs": true,
16
- "forceConsistentCasingInFileNames": true,
17
- "allowImportingTsExtensions": true,
18
- "baseUrl": ".",
19
- "customConditions": ["webstudio"],
20
-
21
- // Remix takes care of building everything in `remix build`.
22
- "noEmit": true,
23
- "skipLibCheck": true
3
+ "customConditions": ["webstudio"]
24
4
  }
25
5
  }
@@ -3,7 +3,7 @@
3
3
  "start": "netlify serve"
4
4
  },
5
5
  "dependencies": {
6
- "@netlify/edge-functions": "^2.8.1",
6
+ "@netlify/edge-functions": "^2.10.0",
7
7
  "@netlify/remix-edge-adapter": "^3.3.0"
8
8
  }
9
9
  }
@@ -3,7 +3,7 @@
3
3
  "start": "netlify serve"
4
4
  },
5
5
  "dependencies": {
6
- "@netlify/functions": "^2.7.0",
6
+ "@netlify/functions": "^2.8.1",
7
7
  "@netlify/remix-adapter": "^2.4.0"
8
8
  }
9
9
  }
@@ -8,27 +8,11 @@
8
8
  "**/.client/**/*.tsx"
9
9
  ],
10
10
  "compilerOptions": {
11
- "lib": ["DOM", "DOM.Iterable", "ES2023"],
12
11
  "types": [
13
12
  "@remix-run/cloudflare",
14
13
  "vite/client",
15
- "@cloudflare/workers-types/2023-07-01"
16
- ],
17
- "isolatedModules": true,
18
- "esModuleInterop": true,
19
- "jsx": "react-jsx",
20
- "module": "ESNext",
21
- "moduleResolution": "Bundler",
22
- "resolveJsonModule": true,
23
- "target": "ES2022",
24
- "strict": true,
25
- "allowJs": true,
26
- "skipLibCheck": true,
27
- "forceConsistentCasingInFileNames": true,
28
- "baseUrl": ".",
29
- "customConditions": ["webstudio"],
30
-
31
- // Vite takes care of building everything, not tsc.
32
- "noEmit": true
14
+ "@cloudflare/workers-types/2023-07-01",
15
+ "@webstudio-is/react-sdk/placeholder"
16
+ ]
33
17
  }
34
18
  }
@@ -0,0 +1,13 @@
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
+ export const imageBaseUrl = "/assets/";
7
+
8
+ /**
9
+ * @type {import("@webstudio-is/image").ImageLoader}
10
+ */
11
+ export const imageLoader = ({ src }) => {
12
+ return imageBaseUrl + src;
13
+ };